Refactored XMLWriter to provide finer-grained control over formatting

This commit is contained in:
Martin Hořeňovský 2019-06-22 14:39:34 +02:00
parent 74e0e737a6
commit 3136c4fb6a
No known key found for this signature in database
GPG Key ID: DE48307B8B0D381A
5 changed files with 104 additions and 43 deletions

View File

@ -10,6 +10,7 @@
#include "catch_enforce.h" #include "catch_enforce.h"
#include <iomanip> #include <iomanip>
#include <type_traits>
using uchar = unsigned char; using uchar = unsigned char;
@ -51,8 +52,31 @@ namespace {
os.flags(f); 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 } // 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 ) XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat )
: m_str( str ), : m_str( str ),
m_forWhat( forWhat ) m_forWhat( forWhat )
@ -157,13 +181,17 @@ namespace {
return os; return os;
} }
XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer, XmlFormatting fmt )
: m_writer( writer ) : m_writer( writer ),
m_fmt(fmt)
{} {}
XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept 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_writer = nullptr;
other.m_fmt = XmlFormatting::None;
} }
XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept {
if ( m_writer ) { if ( m_writer ) {
@ -171,17 +199,20 @@ namespace {
} }
m_writer = other.m_writer; m_writer = other.m_writer;
other.m_writer = nullptr; other.m_writer = nullptr;
m_fmt = other.m_fmt;
other.m_fmt = XmlFormatting::None;
return *this; return *this;
} }
XmlWriter::ScopedElement::~ScopedElement() { XmlWriter::ScopedElement::~ScopedElement() {
if( m_writer ) if (m_writer) {
m_writer->endElement(); m_writer->endElement(m_fmt);
}
} }
XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, XmlFormatting fmt ) {
m_writer->writeText( text, indent ); m_writer->writeText( text, fmt );
return *this; return *this;
} }
@ -191,37 +222,47 @@ namespace {
} }
XmlWriter::~XmlWriter() { XmlWriter::~XmlWriter() {
while( !m_tags.empty() ) while (!m_tags.empty()) {
endElement(); endElement();
}
newlineIfNecessary();
} }
XmlWriter& XmlWriter::startElement( std::string const& name ) { XmlWriter& XmlWriter::startElement( std::string const& name, XmlFormatting fmt ) {
ensureTagClosed(); ensureTagClosed();
newlineIfNecessary(); newlineIfNecessary();
m_os << m_indent << '<' << name; if (shouldIndent(fmt)) {
m_os << m_indent;
m_indent += " ";
}
m_os << '<' << name;
m_tags.push_back( name ); m_tags.push_back( name );
m_indent += " ";
m_tagIsOpen = true; m_tagIsOpen = true;
applyFormatting(fmt);
return *this; return *this;
} }
XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name, XmlFormatting fmt ) {
ScopedElement scoped( this ); ScopedElement scoped( this, fmt );
startElement( name ); startElement( name, fmt );
return scoped; return scoped;
} }
XmlWriter& XmlWriter::endElement() { XmlWriter& XmlWriter::endElement(XmlFormatting fmt) {
newlineIfNecessary(); m_indent = m_indent.substr(0, m_indent.size() - 2);
m_indent = m_indent.substr( 0, m_indent.size()-2 );
if( m_tagIsOpen ) { if( m_tagIsOpen ) {
m_os << "/>"; m_os << "/>";
m_tagIsOpen = false; m_tagIsOpen = false;
} else {
newlineIfNecessary();
if (shouldIndent(fmt)) {
m_os << m_indent;
}
m_os << "</" << m_tags.back() << ">";
} }
else { m_os << std::flush;
m_os << m_indent << "</" << m_tags.back() << ">"; applyFormatting(fmt);
}
m_os << std::endl;
m_tags.pop_back(); m_tags.pop_back();
return *this; return *this;
} }
@ -237,22 +278,26 @@ namespace {
return *this; return *this;
} }
XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { XmlWriter& XmlWriter::writeText( std::string const& text, XmlFormatting fmt) {
if( !text.empty() ){ if( !text.empty() ){
bool tagWasOpen = m_tagIsOpen; bool tagWasOpen = m_tagIsOpen;
ensureTagClosed(); ensureTagClosed();
if( tagWasOpen && indent ) if (tagWasOpen && shouldIndent(fmt)) {
m_os << m_indent; m_os << m_indent;
}
m_os << XmlEncode( text ); m_os << XmlEncode( text );
m_needsNewline = true; applyFormatting(fmt);
} }
return *this; return *this;
} }
XmlWriter& XmlWriter::writeComment( std::string const& text ) { XmlWriter& XmlWriter::writeComment( std::string const& text, XmlFormatting fmt) {
ensureTagClosed(); ensureTagClosed();
m_os << m_indent << "<!--" << text << "-->"; if (shouldIndent(fmt)) {
m_needsNewline = true; m_os << m_indent;
}
m_os << "<!--" << text << "-->";
applyFormatting(fmt);
return *this; return *this;
} }
@ -268,11 +313,16 @@ namespace {
void XmlWriter::ensureTagClosed() { void XmlWriter::ensureTagClosed() {
if( m_tagIsOpen ) { if( m_tagIsOpen ) {
m_os << ">" << std::endl; m_os << '>' << std::flush;
newlineIfNecessary();
m_tagIsOpen = false; m_tagIsOpen = false;
} }
} }
void XmlWriter::applyFormatting(XmlFormatting fmt) {
m_needsNewline = shouldNewline(fmt);
}
void XmlWriter::writeDeclaration() { void XmlWriter::writeDeclaration() {
m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
} }

View File

@ -14,6 +14,14 @@
#include <vector> #include <vector>
namespace Catch { 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 { class XmlEncode {
public: public:
@ -35,14 +43,14 @@ namespace Catch {
class ScopedElement { class ScopedElement {
public: public:
ScopedElement( XmlWriter* writer ); ScopedElement( XmlWriter* writer, XmlFormatting fmt );
ScopedElement( ScopedElement&& other ) noexcept; ScopedElement( ScopedElement&& other ) noexcept;
ScopedElement& operator=( ScopedElement&& other ) noexcept; ScopedElement& operator=( ScopedElement&& other ) noexcept;
~ScopedElement(); ~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> template<typename T>
ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { ScopedElement& writeAttribute( std::string const& name, T const& attribute ) {
@ -52,6 +60,7 @@ namespace Catch {
private: private:
mutable XmlWriter* m_writer = nullptr; mutable XmlWriter* m_writer = nullptr;
XmlFormatting m_fmt;
}; };
XmlWriter( std::ostream& os = Catch::cout() ); XmlWriter( std::ostream& os = Catch::cout() );
@ -60,11 +69,11 @@ namespace Catch {
XmlWriter( XmlWriter const& ) = delete; XmlWriter( XmlWriter const& ) = delete;
XmlWriter& operator=( 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 ); XmlWriter& writeAttribute( std::string const& name, std::string const& attribute );
@ -77,9 +86,9 @@ namespace Catch {
return writeAttribute( name, rss.str() ); 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 ); void writeStylesheetRef( std::string const& url );
@ -89,6 +98,8 @@ namespace Catch {
private: private:
void applyFormatting(XmlFormatting fmt);
void writeDeclaration(); void writeDeclaration();
void newlineIfNecessary(); void newlineIfNecessary();

View File

@ -147,8 +147,8 @@ namespace Catch {
for( auto const& child : groupNode.children ) for( auto const& child : groupNode.children )
writeTestCase( *child ); writeTestCase( *child );
xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), false ); xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite ), XmlFormatting::Newline );
xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), false ); xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite ), XmlFormatting::Newline );
} }
void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) { void JunitReporter::writeTestCase( TestCaseNode const& testCaseNode ) {
@ -197,9 +197,9 @@ namespace Catch {
writeAssertions( sectionNode ); writeAssertions( sectionNode );
if( !sectionNode.stdOut.empty() ) 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() ) 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 ) for( auto const& childNode : sectionNode.childSections )
if( className.empty() ) if( className.empty() )
@ -271,7 +271,7 @@ namespace Catch {
rss << msg.message << '\n'; rss << msg.message << '\n';
rss << "at " << result.getSourceInfo(); rss << "at " << result.getSourceInfo();
xml.writeText( rss.str(), false ); xml.writeText( rss.str(), XmlFormatting::Newline );
} }
} }

View File

@ -162,7 +162,7 @@ namespace Catch {
textRss << msg.message << "\n"; textRss << msg.message << "\n";
textRss << "at " << result.getSourceInfo(); textRss << "at " << result.getSourceInfo();
xml.writeText(textRss.str(), false); xml.writeText(textRss.str(), XmlFormatting::Newline);
} }
} }

View File

@ -193,9 +193,9 @@ namespace Catch {
e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() );
if( !testCaseStats.stdOut.empty() ) 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() ) 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(); m_xml.endElement();
} }