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 << "<"; break; 40 case '&': os << "&"; 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 << ">"; 46 else 47 os << c; 48 break; 49 50 case '\"': 51 if( m_forWhat == ForAttributes ) 52 os << """; 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