1 /*
2  *  Created by Phil on 09/12/2010.
3  *  Copyright 2010 Two Blue Cubes Ltd. All rights reserved.
4  *
5  *  Distributed under the Boost Software License, Version 1.0. (See accompanying
6  *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7  */
8 #ifndef TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED
9 #define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED
10 
11 #include "catch_stream.h"
12 #include "catch_compiler_capabilities.h"
13 #include "catch_suppress_warnings.h"
14 
15 #include <sstream>
16 #include <string>
17 #include <vector>
18 #include <iomanip>
19 
20 namespace Catch {
21 
22     class XmlEncode {
23     public:
24         enum ForWhat { ForTextNodes, ForAttributes };
25 
XmlEncode(std::string const & str,ForWhat forWhat=ForTextNodes)26         XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes )
27         :   m_str( str ),
28             m_forWhat( forWhat )
29         {}
30 
encodeTo(std::ostream & os) const31         void encodeTo( std::ostream& os ) const {
32 
33             // Apostrophe escaping not necessary if we always use " to write attributes
34             // (see: http://www.w3.org/TR/xml/#syntax)
35 
36             for( std::size_t i = 0; i < m_str.size(); ++ i ) {
37                 char c = m_str[i];
38                 switch( c ) {
39                     case '<':   os << "&lt;"; break;
40                     case '&':   os << "&amp;"; break;
41 
42                     case '>':
43                         // See: http://www.w3.org/TR/xml/#syntax
44                         if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' )
45                             os << "&gt;";
46                         else
47                             os << c;
48                         break;
49 
50                     case '\"':
51                         if( m_forWhat == ForAttributes )
52                             os << "&quot;";
53                         else
54                             os << c;
55                         break;
56 
57                     default:
58                         // Escape control chars - based on contribution by @espenalb in PR #465 and
59                         // by @mrpi PR #588
60                         if ( ( c >= 0 && c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' )
61                             os << "&#x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>( c ) << ';';
62                         else
63                             os << c;
64                 }
65             }
66         }
67 
operator <<(std::ostream & os,XmlEncode const & xmlEncode)68         friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) {
69             xmlEncode.encodeTo( os );
70             return os;
71         }
72 
73     private:
74         std::string m_str;
75         ForWhat m_forWhat;
76     };
77 
78     class XmlWriter {
79     public:
80 
81         class ScopedElement {
82         public:
ScopedElement(XmlWriter * writer)83             ScopedElement( XmlWriter* writer )
84             :   m_writer( writer )
85             {}
86 
ScopedElement(ScopedElement const & other)87             ScopedElement( ScopedElement const& other )
88             :   m_writer( other.m_writer ){
89                 other.m_writer = CATCH_NULL;
90             }
91 
~ScopedElement()92             ~ScopedElement() {
93                 if( m_writer )
94                     m_writer->endElement();
95             }
96 
writeText(std::string const & text,bool indent=true)97             ScopedElement& writeText( std::string const& text, bool indent = true ) {
98                 m_writer->writeText( text, indent );
99                 return *this;
100             }
101 
102             template<typename T>
writeAttribute(std::string const & name,T const & attribute)103             ScopedElement& writeAttribute( std::string const& name, T const& attribute ) {
104                 m_writer->writeAttribute( name, attribute );
105                 return *this;
106             }
107 
108         private:
109             mutable XmlWriter* m_writer;
110         };
111 
XmlWriter()112         XmlWriter()
113         :   m_tagIsOpen( false ),
114             m_needsNewline( false ),
115             m_os( &Catch::cout() )
116         {
117             // We encode control characters, which requires
118             // XML 1.1
119             // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0
120             *m_os << "<?xml version=\"1.1\" encoding=\"UTF-8\"?>\n";
121         }
122 
XmlWriter(std::ostream & os)123         XmlWriter( std::ostream& os )
124         :   m_tagIsOpen( false ),
125             m_needsNewline( false ),
126             m_os( &os )
127         {
128             *m_os << "<?xml version=\"1.1\" encoding=\"UTF-8\"?>\n";
129         }
130 
~XmlWriter()131         ~XmlWriter() {
132             while( !m_tags.empty() )
133                 endElement();
134         }
135 
startElement(std::string const & name)136         XmlWriter& startElement( std::string const& name ) {
137             ensureTagClosed();
138             newlineIfNecessary();
139             stream() << m_indent << '<' << name;
140             m_tags.push_back( name );
141             m_indent += "  ";
142             m_tagIsOpen = true;
143             return *this;
144         }
145 
scopedElement(std::string const & name)146         ScopedElement scopedElement( std::string const& name ) {
147             ScopedElement scoped( this );
148             startElement( name );
149             return scoped;
150         }
151 
endElement()152         XmlWriter& endElement() {
153             newlineIfNecessary();
154             m_indent = m_indent.substr( 0, m_indent.size()-2 );
155             if( m_tagIsOpen ) {
156                 stream() << "/>\n";
157                 m_tagIsOpen = false;
158             }
159             else {
160                 stream() << m_indent << "</" << m_tags.back() << ">\n";
161             }
162             m_tags.pop_back();
163             return *this;
164         }
165 
writeAttribute(std::string const & name,std::string const & attribute)166         XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) {
167             if( !name.empty() && !attribute.empty() )
168                 stream() << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"';
169             return *this;
170         }
171 
writeAttribute(std::string const & name,bool attribute)172         XmlWriter& writeAttribute( std::string const& name, bool attribute ) {
173             stream() << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"';
174             return *this;
175         }
176 
177         template<typename T>
writeAttribute(std::string const & name,T const & attribute)178         XmlWriter& writeAttribute( std::string const& name, T const& attribute ) {
179             std::ostringstream oss;
180             oss << attribute;
181             return writeAttribute( name, oss.str() );
182         }
183 
writeText(std::string const & text,bool indent=true)184         XmlWriter& writeText( std::string const& text, bool indent = true ) {
185             if( !text.empty() ){
186                 bool tagWasOpen = m_tagIsOpen;
187                 ensureTagClosed();
188                 if( tagWasOpen && indent )
189                     stream() << m_indent;
190                 stream() << XmlEncode( text );
191                 m_needsNewline = true;
192             }
193             return *this;
194         }
195 
writeComment(std::string const & text)196         XmlWriter& writeComment( std::string const& text ) {
197             ensureTagClosed();
198             stream() << m_indent << "<!--" << text << "-->";
199             m_needsNewline = true;
200             return *this;
201         }
202 
writeBlankLine()203         XmlWriter& writeBlankLine() {
204             ensureTagClosed();
205             stream() << '\n';
206             return *this;
207         }
208 
setStream(std::ostream & os)209         void setStream( std::ostream& os ) {
210             m_os = &os;
211         }
212 
213     private:
214         XmlWriter( XmlWriter const& );
215         void operator=( XmlWriter const& );
216 
stream()217         std::ostream& stream() {
218             return *m_os;
219         }
220 
ensureTagClosed()221         void ensureTagClosed() {
222             if( m_tagIsOpen ) {
223                 stream() << ">\n";
224                 m_tagIsOpen = false;
225             }
226         }
227 
newlineIfNecessary()228         void newlineIfNecessary() {
229             if( m_needsNewline ) {
230                 stream() << '\n';
231                 m_needsNewline = false;
232             }
233         }
234 
235         bool m_tagIsOpen;
236         bool m_needsNewline;
237         std::vector<std::string> m_tags;
238         std::string m_indent;
239         std::ostream* m_os;
240     };
241 
242 }
243 #include "catch_reenable_warnings.h"
244 
245 #endif // TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED
246