1 /*
2  *  Created by Phil on 19/07/2017.
3  *
4  *  Distributed under the Boost Software License, Version 1.0. (See accompanying
5  *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6  */
7 
8 #include "catch_xmlwriter.h"
9 
10 #include <iomanip>
11 
12 namespace Catch {
13 
XmlEncode(std::string const & str,ForWhat forWhat)14     XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat )
15     :   m_str( str ),
16         m_forWhat( forWhat )
17     {}
18 
encodeTo(std::ostream & os) const19     void XmlEncode::encodeTo( std::ostream& os ) const {
20 
21         // Apostrophe escaping not necessary if we always use " to write attributes
22         // (see: http://www.w3.org/TR/xml/#syntax)
23 
24         for( std::size_t i = 0; i < m_str.size(); ++ i ) {
25             char c = m_str[i];
26             switch( c ) {
27                 case '<':   os << "&lt;"; break;
28                 case '&':   os << "&amp;"; break;
29 
30                 case '>':
31                     // See: http://www.w3.org/TR/xml/#syntax
32                     if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' )
33                         os << "&gt;";
34                     else
35                         os << c;
36                     break;
37 
38                 case '\"':
39                     if( m_forWhat == ForAttributes )
40                         os << "&quot;";
41                     else
42                         os << c;
43                     break;
44 
45                 default:
46                     // Escape control chars - based on contribution by @espenalb in PR #465 and
47                     // by @mrpi PR #588
48                     if ( ( c >= 0 && c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) {
49                         // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
50                         os << "\\x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2)
51                            << static_cast<int>( c );
52                     }
53                     else
54                         os << c;
55             }
56         }
57     }
58 
operator <<(std::ostream & os,XmlEncode const & xmlEncode)59     std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) {
60         xmlEncode.encodeTo( os );
61         return os;
62     }
63 
ScopedElement(XmlWriter * writer)64     XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer )
65     :   m_writer( writer )
66     {}
67 
ScopedElement(ScopedElement && other)68     XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept
69     :   m_writer( other.m_writer ){
70         other.m_writer = nullptr;
71     }
operator =(ScopedElement && other)72     XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept {
73         if ( m_writer ) {
74             m_writer->endElement();
75         }
76         m_writer = other.m_writer;
77         other.m_writer = nullptr;
78         return *this;
79     }
80 
81 
~ScopedElement()82     XmlWriter::ScopedElement::~ScopedElement() {
83         if( m_writer )
84             m_writer->endElement();
85     }
86 
writeText(std::string const & text,bool indent)87     XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) {
88         m_writer->writeText( text, indent );
89         return *this;
90     }
91 
XmlWriter(std::ostream & os)92     XmlWriter::XmlWriter( std::ostream& os ) : m_os( os )
93     {
94         writeDeclaration();
95     }
96 
~XmlWriter()97     XmlWriter::~XmlWriter() {
98         while( !m_tags.empty() )
99             endElement();
100     }
101 
startElement(std::string const & name)102     XmlWriter& XmlWriter::startElement( std::string const& name ) {
103         ensureTagClosed();
104         newlineIfNecessary();
105         m_os << m_indent << '<' << name;
106         m_tags.push_back( name );
107         m_indent += "  ";
108         m_tagIsOpen = true;
109         return *this;
110     }
111 
scopedElement(std::string const & name)112     XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) {
113         ScopedElement scoped( this );
114         startElement( name );
115         return scoped;
116     }
117 
endElement()118     XmlWriter& XmlWriter::endElement() {
119         newlineIfNecessary();
120         m_indent = m_indent.substr( 0, m_indent.size()-2 );
121         if( m_tagIsOpen ) {
122             m_os << "/>";
123             m_tagIsOpen = false;
124         }
125         else {
126             m_os << m_indent << "</" << m_tags.back() << ">";
127         }
128         m_os << std::endl;
129         m_tags.pop_back();
130         return *this;
131     }
132 
writeAttribute(std::string const & name,std::string const & attribute)133     XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) {
134         if( !name.empty() && !attribute.empty() )
135             m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"';
136         return *this;
137     }
138 
writeAttribute(std::string const & name,bool attribute)139     XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) {
140         m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"';
141         return *this;
142     }
143 
writeText(std::string const & text,bool indent)144     XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) {
145         if( !text.empty() ){
146             bool tagWasOpen = m_tagIsOpen;
147             ensureTagClosed();
148             if( tagWasOpen && indent )
149                 m_os << m_indent;
150             m_os << XmlEncode( text );
151             m_needsNewline = true;
152         }
153         return *this;
154     }
155 
writeComment(std::string const & text)156     XmlWriter& XmlWriter::writeComment( std::string const& text ) {
157         ensureTagClosed();
158         m_os << m_indent << "<!--" << text << "-->";
159         m_needsNewline = true;
160         return *this;
161     }
162 
writeStylesheetRef(std::string const & url)163     void XmlWriter::writeStylesheetRef( std::string const& url ) {
164         m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n";
165     }
166 
writeBlankLine()167     XmlWriter& XmlWriter::writeBlankLine() {
168         ensureTagClosed();
169         m_os << '\n';
170         return *this;
171     }
172 
ensureTagClosed()173     void XmlWriter::ensureTagClosed() {
174         if( m_tagIsOpen ) {
175             m_os << ">" << std::endl;
176             m_tagIsOpen = false;
177         }
178     }
179 
writeDeclaration()180     void XmlWriter::writeDeclaration() {
181         m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
182     }
183 
newlineIfNecessary()184     void XmlWriter::newlineIfNecessary() {
185         if( m_needsNewline ) {
186             m_os << std::endl;
187             m_needsNewline = false;
188         }
189     }
190 }
191