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 14 #include <sstream> 15 #include <string> 16 #include <vector> 17 #include <iomanip> 18 19 namespace Catch { 20 21 class XmlEncode { 22 public: 23 enum ForWhat { ForTextNodes, ForAttributes }; 24 XmlEncode(std::string const & str,ForWhat forWhat=ForTextNodes)25 XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ) 26 : m_str( str ), 27 m_forWhat( forWhat ) 28 {} 29 encodeTo(std::ostream & os) const30 void encodeTo( std::ostream& os ) const { 31 32 // Apostrophe escaping not necessary if we always use " to write attributes 33 // (see: http://www.w3.org/TR/xml/#syntax) 34 35 for( std::size_t i = 0; i < m_str.size(); ++ i ) { 36 char c = m_str[i]; 37 switch( c ) { 38 case '<': os << "<"; break; 39 case '&': os << "&"; break; 40 41 case '>': 42 // See: http://www.w3.org/TR/xml/#syntax 43 if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' ) 44 os << ">"; 45 else 46 os << c; 47 break; 48 49 case '\"': 50 if( m_forWhat == ForAttributes ) 51 os << """; 52 else 53 os << c; 54 break; 55 56 default: 57 // Escape control chars - based on contribution by @espenalb in PR #465 and 58 // by @mrpi PR #588 59 if ( ( c >= 0 && c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) { 60 // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 61 os << "\\x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) 62 << static_cast<int>( c ); 63 } 64 else 65 os << c; 66 } 67 } 68 } 69 operator <<(std::ostream & os,XmlEncode const & xmlEncode)70 friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { 71 xmlEncode.encodeTo( os ); 72 return os; 73 } 74 75 private: 76 std::string m_str; 77 ForWhat m_forWhat; 78 }; 79 80 class XmlWriter { 81 public: 82 83 class ScopedElement { 84 public: ScopedElement(XmlWriter * writer)85 ScopedElement( XmlWriter* writer ) 86 : m_writer( writer ) 87 {} 88 ScopedElement(ScopedElement const & other)89 ScopedElement( ScopedElement const& other ) 90 : m_writer( other.m_writer ){ 91 other.m_writer = CATCH_NULL; 92 } 93 ~ScopedElement()94 ~ScopedElement() { 95 if( m_writer ) 96 m_writer->endElement(); 97 } 98 writeText(std::string const & text,bool indent=true)99 ScopedElement& writeText( std::string const& text, bool indent = true ) { 100 m_writer->writeText( text, indent ); 101 return *this; 102 } 103 104 template<typename T> writeAttribute(std::string const & name,T const & attribute)105 ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { 106 m_writer->writeAttribute( name, attribute ); 107 return *this; 108 } 109 110 private: 111 mutable XmlWriter* m_writer; 112 }; 113 XmlWriter()114 XmlWriter() 115 : m_tagIsOpen( false ), 116 m_needsNewline( false ), 117 m_os( Catch::cout() ) 118 { 119 writeDeclaration(); 120 } 121 XmlWriter(std::ostream & os)122 XmlWriter( std::ostream& os ) 123 : m_tagIsOpen( false ), 124 m_needsNewline( false ), 125 m_os( os ) 126 { 127 writeDeclaration(); 128 } 129 ~XmlWriter()130 ~XmlWriter() { 131 while( !m_tags.empty() ) 132 endElement(); 133 } 134 startElement(std::string const & name)135 XmlWriter& startElement( std::string const& name ) { 136 ensureTagClosed(); 137 newlineIfNecessary(); 138 m_os << m_indent << '<' << name; 139 m_tags.push_back( name ); 140 m_indent += " "; 141 m_tagIsOpen = true; 142 return *this; 143 } 144 scopedElement(std::string const & name)145 ScopedElement scopedElement( std::string const& name ) { 146 ScopedElement scoped( this ); 147 startElement( name ); 148 return scoped; 149 } 150 endElement()151 XmlWriter& endElement() { 152 newlineIfNecessary(); 153 m_indent = m_indent.substr( 0, m_indent.size()-2 ); 154 if( m_tagIsOpen ) { 155 m_os << "/>"; 156 m_tagIsOpen = false; 157 } 158 else { 159 m_os << m_indent << "</" << m_tags.back() << ">"; 160 } 161 m_os << std::endl; 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 m_os << ' ' << 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 m_os << ' ' << 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 m_os << m_indent; 190 m_os << 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 m_os << m_indent << "<!--" << text << "-->"; 199 m_needsNewline = true; 200 return *this; 201 } 202 writeStylesheetRef(std::string const & url)203 void writeStylesheetRef( std::string const& url ) { 204 m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n"; 205 } 206 writeBlankLine()207 XmlWriter& writeBlankLine() { 208 ensureTagClosed(); 209 m_os << '\n'; 210 return *this; 211 } 212 ensureTagClosed()213 void ensureTagClosed() { 214 if( m_tagIsOpen ) { 215 m_os << ">" << std::endl; 216 m_tagIsOpen = false; 217 } 218 } 219 220 private: 221 XmlWriter( XmlWriter const& ); 222 void operator=( XmlWriter const& ); 223 writeDeclaration()224 void writeDeclaration() { 225 m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; 226 } 227 newlineIfNecessary()228 void newlineIfNecessary() { 229 if( m_needsNewline ) { 230 m_os << std::endl; 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 244 #endif // TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED 245