mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-10-31 20:27:11 +01:00 
			
		
		
		
	Refactored XMLWriter to provide finer-grained control over formatting
This commit is contained in:
		| @@ -10,6 +10,7 @@ | ||||
| #include "catch_enforce.h" | ||||
|  | ||||
| #include <iomanip> | ||||
| #include <type_traits> | ||||
|  | ||||
| using uchar = unsigned char; | ||||
|  | ||||
| @@ -51,8 +52,31 @@ namespace { | ||||
|         os.flags(f); | ||||
|     } | ||||
|  | ||||
|     bool shouldNewline(XmlFormatting fmt) { | ||||
|         return !!(static_cast<std::underlying_type<XmlFormatting>::type>(fmt & XmlFormatting::Newline)); | ||||
|     } | ||||
|  | ||||
|     bool shouldIndent(XmlFormatting fmt) { | ||||
|         return !!(static_cast<std::underlying_type<XmlFormatting>::type>(fmt & XmlFormatting::Indent)); | ||||
|     } | ||||
|  | ||||
| } // anonymous namespace | ||||
|  | ||||
|     XmlFormatting operator | (XmlFormatting lhs, XmlFormatting rhs) { | ||||
|         return static_cast<XmlFormatting>( | ||||
|             static_cast<std::underlying_type<XmlFormatting>::type>(lhs) | | ||||
|             static_cast<std::underlying_type<XmlFormatting>::type>(rhs) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     XmlFormatting operator & (XmlFormatting lhs, XmlFormatting rhs) { | ||||
|         return static_cast<XmlFormatting>( | ||||
|             static_cast<std::underlying_type<XmlFormatting>::type>(lhs) & | ||||
|             static_cast<std::underlying_type<XmlFormatting>::type>(rhs) | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) | ||||
|     :   m_str( str ), | ||||
|         m_forWhat( forWhat ) | ||||
| @@ -157,13 +181,17 @@ namespace { | ||||
|         return os; | ||||
|     } | ||||
|  | ||||
|     XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) | ||||
|     :   m_writer( writer ) | ||||
|     XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer, XmlFormatting fmt ) | ||||
|     :   m_writer( writer ), | ||||
|         m_fmt(fmt) | ||||
|     {} | ||||
|  | ||||
|     XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept | ||||
|     :   m_writer( other.m_writer ){ | ||||
|     :   m_writer( other.m_writer ), | ||||
|         m_fmt(other.m_fmt) | ||||
|     { | ||||
|         other.m_writer = nullptr; | ||||
|         other.m_fmt = XmlFormatting::None; | ||||
|     } | ||||
|     XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { | ||||
|         if ( m_writer ) { | ||||
| @@ -171,17 +199,20 @@ namespace { | ||||
|         } | ||||
|         m_writer = other.m_writer; | ||||
|         other.m_writer = nullptr; | ||||
|         m_fmt = other.m_fmt; | ||||
|         other.m_fmt = XmlFormatting::None; | ||||
|         return *this; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     XmlWriter::ScopedElement::~ScopedElement() { | ||||
|         if( m_writer ) | ||||
|             m_writer->endElement(); | ||||
|         if (m_writer) { | ||||
|             m_writer->endElement(m_fmt); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { | ||||
|         m_writer->writeText( text, indent ); | ||||
|     XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, XmlFormatting fmt ) { | ||||
|         m_writer->writeText( text, fmt ); | ||||
|         return *this; | ||||
|     } | ||||
|  | ||||
| @@ -191,37 +222,47 @@ namespace { | ||||
|     } | ||||
|  | ||||
|     XmlWriter::~XmlWriter() { | ||||
|         while( !m_tags.empty() ) | ||||
|         while (!m_tags.empty()) { | ||||
|             endElement(); | ||||
|         } | ||||
|         newlineIfNecessary(); | ||||
|     } | ||||
|  | ||||
|     XmlWriter& XmlWriter::startElement( std::string const& name ) { | ||||
|     XmlWriter& XmlWriter::startElement( std::string const& name, XmlFormatting fmt ) { | ||||
|         ensureTagClosed(); | ||||
|         newlineIfNecessary(); | ||||
|         m_os << m_indent << '<' << name; | ||||
|         if (shouldIndent(fmt)) { | ||||
|             m_os << m_indent; | ||||
|             m_indent += "  "; | ||||
|         } | ||||
|         m_os << '<' << name; | ||||
|         m_tags.push_back( name ); | ||||
|         m_indent += "  "; | ||||
|         m_tagIsOpen = true; | ||||
|         applyFormatting(fmt); | ||||
|         return *this; | ||||
|     } | ||||
|  | ||||
|     XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { | ||||
|         ScopedElement scoped( this ); | ||||
|         startElement( name ); | ||||
|     XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name, XmlFormatting fmt ) { | ||||
|         ScopedElement scoped( this, fmt ); | ||||
|         startElement( name, fmt ); | ||||
|         return scoped; | ||||
|     } | ||||
|  | ||||
|     XmlWriter& XmlWriter::endElement() { | ||||
|         newlineIfNecessary(); | ||||
|         m_indent = m_indent.substr( 0, m_indent.size()-2 ); | ||||
|     XmlWriter& XmlWriter::endElement(XmlFormatting fmt) { | ||||
|         m_indent = m_indent.substr(0, m_indent.size() - 2); | ||||
|  | ||||
|         if( m_tagIsOpen ) { | ||||
|             m_os << "/>"; | ||||
|             m_tagIsOpen = false; | ||||
|         } else { | ||||
|             newlineIfNecessary(); | ||||
|             if (shouldIndent(fmt)) { | ||||
|                 m_os << m_indent; | ||||
|             } | ||||
|             m_os << "</" << m_tags.back() << ">"; | ||||
|         } | ||||
|         else { | ||||
|             m_os << m_indent << "</" << m_tags.back() << ">"; | ||||
|         } | ||||
|         m_os << std::endl; | ||||
|         m_os << std::flush; | ||||
|         applyFormatting(fmt); | ||||
|         m_tags.pop_back(); | ||||
|         return *this; | ||||
|     } | ||||
| @@ -237,22 +278,26 @@ namespace { | ||||
|         return *this; | ||||
|     } | ||||
|  | ||||
|     XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { | ||||
|     XmlWriter& XmlWriter::writeText( std::string const& text, XmlFormatting fmt) { | ||||
|         if( !text.empty() ){ | ||||
|             bool tagWasOpen = m_tagIsOpen; | ||||
|             ensureTagClosed(); | ||||
|             if( tagWasOpen && indent ) | ||||
|             if (tagWasOpen && shouldIndent(fmt)) { | ||||
|                 m_os << m_indent; | ||||
|             } | ||||
|             m_os << XmlEncode( text ); | ||||
|             m_needsNewline = true; | ||||
|             applyFormatting(fmt); | ||||
|         } | ||||
|         return *this; | ||||
|     } | ||||
|  | ||||
|     XmlWriter& XmlWriter::writeComment( std::string const& text ) { | ||||
|     XmlWriter& XmlWriter::writeComment( std::string const& text, XmlFormatting fmt) { | ||||
|         ensureTagClosed(); | ||||
|         m_os << m_indent << "<!--" << text << "-->"; | ||||
|         m_needsNewline = true; | ||||
|         if (shouldIndent(fmt)) { | ||||
|             m_os << m_indent; | ||||
|         } | ||||
|         m_os << "<!--" << text << "-->"; | ||||
|         applyFormatting(fmt); | ||||
|         return *this; | ||||
|     } | ||||
|  | ||||
| @@ -268,11 +313,16 @@ namespace { | ||||
|  | ||||
|     void XmlWriter::ensureTagClosed() { | ||||
|         if( m_tagIsOpen ) { | ||||
|             m_os << ">" << std::endl; | ||||
|             m_os << '>' << std::flush; | ||||
|             newlineIfNecessary(); | ||||
|             m_tagIsOpen = false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void XmlWriter::applyFormatting(XmlFormatting fmt) { | ||||
|         m_needsNewline = shouldNewline(fmt); | ||||
|     } | ||||
|  | ||||
|     void XmlWriter::writeDeclaration() { | ||||
|         m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; | ||||
|     } | ||||
|   | ||||
| @@ -14,6 +14,14 @@ | ||||
| #include <vector> | ||||
|  | ||||
| namespace Catch { | ||||
|     enum class XmlFormatting { | ||||
|         None = 0x00, | ||||
|         Indent = 0x01, | ||||
|         Newline = 0x02, | ||||
|     }; | ||||
|  | ||||
|     XmlFormatting operator | (XmlFormatting lhs, XmlFormatting rhs); | ||||
|     XmlFormatting operator & (XmlFormatting lhs, XmlFormatting rhs); | ||||
|  | ||||
|     class XmlEncode { | ||||
|     public: | ||||
| @@ -35,14 +43,14 @@ namespace Catch { | ||||
|  | ||||
|         class ScopedElement { | ||||
|         public: | ||||
|             ScopedElement( XmlWriter* writer ); | ||||
|             ScopedElement( XmlWriter* writer, XmlFormatting fmt ); | ||||
|  | ||||
|             ScopedElement( ScopedElement&& other ) noexcept; | ||||
|             ScopedElement& operator=( ScopedElement&& other ) noexcept; | ||||
|  | ||||
|             ~ScopedElement(); | ||||
|  | ||||
|             ScopedElement& writeText( std::string const& text, bool indent = true ); | ||||
|             ScopedElement& writeText( std::string const& text, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent ); | ||||
|  | ||||
|             template<typename T> | ||||
|             ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { | ||||
| @@ -52,6 +60,7 @@ namespace Catch { | ||||
|  | ||||
|         private: | ||||
|             mutable XmlWriter* m_writer = nullptr; | ||||
|             XmlFormatting m_fmt; | ||||
|         }; | ||||
|  | ||||
|         XmlWriter( std::ostream& os = Catch::cout() ); | ||||
| @@ -60,11 +69,11 @@ namespace Catch { | ||||
|         XmlWriter( XmlWriter const& ) = delete; | ||||
|         XmlWriter& operator=( XmlWriter const& ) = delete; | ||||
|  | ||||
|         XmlWriter& startElement( std::string const& name ); | ||||
|         XmlWriter& startElement( std::string const& name, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); | ||||
|  | ||||
|         ScopedElement scopedElement( std::string const& name ); | ||||
|         ScopedElement scopedElement( std::string const& name, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); | ||||
|  | ||||
|         XmlWriter& endElement(); | ||||
|         XmlWriter& endElement(XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); | ||||
|  | ||||
|         XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); | ||||
|  | ||||
| @@ -77,9 +86,9 @@ namespace Catch { | ||||
|             return writeAttribute( name, rss.str() ); | ||||
|         } | ||||
|  | ||||
|         XmlWriter& writeText( std::string const& text, bool indent = true ); | ||||
|         XmlWriter& writeText( std::string const& text, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); | ||||
|  | ||||
|         XmlWriter& writeComment( std::string const& text ); | ||||
|         XmlWriter& writeComment(std::string const& text, XmlFormatting fmt = XmlFormatting::Newline | XmlFormatting::Indent); | ||||
|  | ||||
|         void writeStylesheetRef( std::string const& url ); | ||||
|  | ||||
| @@ -89,6 +98,8 @@ namespace Catch { | ||||
|  | ||||
|     private: | ||||
|  | ||||
|         void applyFormatting(XmlFormatting fmt); | ||||
|  | ||||
|         void writeDeclaration(); | ||||
|  | ||||
|         void newlineIfNecessary(); | ||||
|   | ||||
| @@ -147,8 +147,8 @@ namespace Catch { | ||||
|         for( auto const& child : groupNode.children ) | ||||
|             writeTestCase( *child ); | ||||
|  | ||||
|         xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), false ); | ||||
|         xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), false ); | ||||
|         xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), XmlFormatting::Newline ); | ||||
|         xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), XmlFormatting::Newline ); | ||||
|     } | ||||
|  | ||||
|     void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) { | ||||
| @@ -197,9 +197,9 @@ namespace Catch { | ||||
|             writeAssertions( sectionNode ); | ||||
|  | ||||
|             if( !sectionNode.stdOut.empty() ) | ||||
|                 xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); | ||||
|                 xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), XmlFormatting::Newline ); | ||||
|             if( !sectionNode.stdErr.empty() ) | ||||
|                 xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); | ||||
|                 xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), XmlFormatting::Newline ); | ||||
|         } | ||||
|         for( auto const& childNode : sectionNode.childSections ) | ||||
|             if( className.empty() ) | ||||
| @@ -271,7 +271,7 @@ namespace Catch { | ||||
|                     rss << msg.message << '\n'; | ||||
|  | ||||
|             rss << "at " << result.getSourceInfo(); | ||||
|             xml.writeText( rss.str(), false ); | ||||
|             xml.writeText( rss.str(), XmlFormatting::Newline ); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -162,7 +162,7 @@ namespace Catch { | ||||
|                         textRss << msg.message << "\n"; | ||||
|  | ||||
|                 textRss << "at " << result.getSourceInfo(); | ||||
|                 xml.writeText(textRss.str(), false); | ||||
|                 xml.writeText(textRss.str(), XmlFormatting::Newline); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -193,9 +193,9 @@ namespace Catch { | ||||
|             e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); | ||||
|  | ||||
|         if( !testCaseStats.stdOut.empty() ) | ||||
|             m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false ); | ||||
|             m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), XmlFormatting::Newline ); | ||||
|         if( !testCaseStats.stdErr.empty() ) | ||||
|             m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false ); | ||||
|             m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), XmlFormatting::Newline ); | ||||
|  | ||||
|         m_xml.endElement(); | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Martin Hořeňovský
					Martin Hořeňovský