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 << "<"; break; 28 case '&': os << "&"; 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 << ">"; 34 else 35 os << c; 36 break; 37 38 case '\"': 39 if( m_forWhat == ForAttributes ) 40 os << """; 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