mirror of
				https://github.com/catchorg/Catch2.git
				synced 2025-11-04 05:59:32 +01:00 
			
		
		
		
	First draft of Junit reporter
This commit is contained in:
		@@ -33,7 +33,8 @@
 | 
			
		||||
/* Begin PBXFileReference section */
 | 
			
		||||
		4A3BFFB8128DCF06005609E3 /* TestMain.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TestMain.cpp; sourceTree = "<group>"; };
 | 
			
		||||
		4A3BFFF0128DD23C005609E3 /* catch_runnerconfig.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = catch_runnerconfig.hpp; path = ../internal/catch_runnerconfig.hpp; sourceTree = SOURCE_ROOT; };
 | 
			
		||||
		4AA7E968129FA1DF005A0B97 /* catch_reporter_junit.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = catch_reporter_junit.hpp; path = ../../../Lib/Catch/catch_reporter_junit.hpp; sourceTree = SOURCE_ROOT; };
 | 
			
		||||
		4A992A6512B2156C002B7B66 /* catch_xmlwriter.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = catch_xmlwriter.hpp; path = ../internal/catch_xmlwriter.hpp; sourceTree = SOURCE_ROOT; };
 | 
			
		||||
		4A992A6612B21582002B7B66 /* catch_reporter_junit.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = catch_reporter_junit.hpp; path = ../catch_reporter_junit.hpp; sourceTree = SOURCE_ROOT; };
 | 
			
		||||
		4AA7EA9112A438C7005A0B97 /* MiscTests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MiscTests.cpp; sourceTree = "<group>"; };
 | 
			
		||||
		4AFC341512809A36003A0C29 /* catch_capture.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = catch_capture.hpp; path = ../internal/catch_capture.hpp; sourceTree = SOURCE_ROOT; };
 | 
			
		||||
		4AFC341612809A36003A0C29 /* catch_common.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = catch_common.h; path = ../internal/catch_common.h; sourceTree = SOURCE_ROOT; };
 | 
			
		||||
@@ -101,9 +102,9 @@
 | 
			
		||||
		4AA7E96B129FA282005A0B97 /* Reporters */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				4A992A6612B21582002B7B66 /* catch_reporter_junit.hpp */,
 | 
			
		||||
				4AFC341D12809A45003A0C29 /* catch_reporter_basic.hpp */,
 | 
			
		||||
				4AFC341E12809A45003A0C29 /* catch_reporter_xml.hpp */,
 | 
			
		||||
				4AA7E968129FA1DF005A0B97 /* catch_reporter_junit.hpp */,
 | 
			
		||||
			);
 | 
			
		||||
			name = Reporters;
 | 
			
		||||
			sourceTree = "<group>";
 | 
			
		||||
@@ -136,6 +137,7 @@
 | 
			
		||||
		4AFC341412809A1B003A0C29 /* Internal */ = {
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				4A992A6512B2156C002B7B66 /* catch_xmlwriter.hpp */,
 | 
			
		||||
				4A3BFFF0128DD23C005609E3 /* catch_runnerconfig.hpp */,
 | 
			
		||||
				4AFC341F12809A45003A0C29 /* catch_list.hpp */,
 | 
			
		||||
				4AFC359B1281F00B003A0C29 /* catch_section.hpp */,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										286
									
								
								catch_reporter_junit.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										286
									
								
								catch_reporter_junit.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,286 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  catch_reporter_junit.hpp
 | 
			
		||||
 *  Catch
 | 
			
		||||
 *
 | 
			
		||||
 *  Created by Phil on 26/11/2010.
 | 
			
		||||
 *  Copyright 2010 Two Blue Cubes Ltd. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 *  Distributed under the Boost Software License, Version 1.0. (See accompanying
 | 
			
		||||
 *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
#ifndef TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED
 | 
			
		||||
#define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED
 | 
			
		||||
 | 
			
		||||
#include "internal/catch_capture.hpp"
 | 
			
		||||
#include "internal/catch_reporter_registry.hpp"
 | 
			
		||||
#include "internal/catch_xmlwriter.hpp"
 | 
			
		||||
#include <iostream>
 | 
			
		||||
 | 
			
		||||
namespace Catch
 | 
			
		||||
{
 | 
			
		||||
    class JunitReporter : public Catch::ITestReporter
 | 
			
		||||
    {
 | 
			
		||||
        struct Indenter
 | 
			
		||||
        {
 | 
			
		||||
            Indenter& operator ++()
 | 
			
		||||
            {
 | 
			
		||||
                m_indent += "\t";
 | 
			
		||||
                return *this;
 | 
			
		||||
            }
 | 
			
		||||
            Indenter& operator --()
 | 
			
		||||
            {
 | 
			
		||||
                m_indent = m_indent.substr( 0, m_indent.length()-1 );
 | 
			
		||||
                return *this;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            friend std::ostream& operator << ( std::ostream& os, const Indenter& indent )
 | 
			
		||||
            {
 | 
			
		||||
                os << indent.m_indent;
 | 
			
		||||
                return os;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            std::string m_indent;
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        struct TestStats
 | 
			
		||||
        {
 | 
			
		||||
            std::string element;
 | 
			
		||||
            std::string resultType;
 | 
			
		||||
            std::string message;
 | 
			
		||||
            std::string content;
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        struct TestCaseStats
 | 
			
		||||
        {
 | 
			
		||||
            TestCaseStats( const std::string& name = std::string() )
 | 
			
		||||
            :   name( name )
 | 
			
		||||
            {
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            double      timeInSeconds;
 | 
			
		||||
            std::string status;
 | 
			
		||||
            std::string className;
 | 
			
		||||
            std::string name;
 | 
			
		||||
            std::vector<TestStats> testStats;
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        struct Stats
 | 
			
		||||
        {
 | 
			
		||||
            Stats( const std::string& name = std::string() )
 | 
			
		||||
            :   testsCount( 0 ),
 | 
			
		||||
                failuresCount( 0 ),
 | 
			
		||||
                disabledCount( 0 ),
 | 
			
		||||
                errorsCount( 0 ),
 | 
			
		||||
                timeInSeconds( 0 ),
 | 
			
		||||
                name( name )
 | 
			
		||||
            {
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            std::size_t testsCount;
 | 
			
		||||
            std::size_t failuresCount;
 | 
			
		||||
            std::size_t disabledCount;
 | 
			
		||||
            std::size_t errorsCount;
 | 
			
		||||
            double      timeInSeconds;
 | 
			
		||||
            std::string name;
 | 
			
		||||
            
 | 
			
		||||
            std::vector<TestCaseStats> testCaseStats;
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
    public:
 | 
			
		||||
        ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
        JunitReporter( const ReporterConfig& config = ReporterConfig() )
 | 
			
		||||
        :   m_config( config ),
 | 
			
		||||
            m_testSuiteStats( "AllTests" ),
 | 
			
		||||
            m_currentStats( &m_testSuiteStats )
 | 
			
		||||
        {
 | 
			
		||||
        }        
 | 
			
		||||
        
 | 
			
		||||
        ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
        static std::string getDescription()
 | 
			
		||||
        {
 | 
			
		||||
            return "Reports test results in an XML format that looks like Ant's junitreport target";
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
    private: // ITestReporter
 | 
			
		||||
        
 | 
			
		||||
        ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
        virtual void StartTesting()
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
        virtual void StartGroup( const std::string& groupName )
 | 
			
		||||
        {
 | 
			
		||||
            
 | 
			
		||||
//            m_config.stream() << "\t<testsuite>\n";
 | 
			
		||||
//            if( !groupName.empty() )
 | 
			
		||||
            {
 | 
			
		||||
                m_statsForSuites.push_back( Stats( groupName ) );
 | 
			
		||||
                m_currentStats = &m_statsForSuites.back();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
        virtual void EndGroup( const std::string& groupName, std::size_t succeeded, std::size_t failed )
 | 
			
		||||
        {
 | 
			
		||||
//            m_config.stream() << "\t</testsuite>\n";
 | 
			
		||||
            (groupName, succeeded, failed);
 | 
			
		||||
            m_currentStats = &m_testSuiteStats;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        virtual void StartSection( const std::string& sectionName, const std::string description ){(sectionName,description);}
 | 
			
		||||
        virtual void EndSection( const std::string& sectionName, std::size_t succeeded, std::size_t failed ){(sectionName, succeeded, failed);}
 | 
			
		||||
        
 | 
			
		||||
        ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
        virtual void StartTestCase( const Catch::TestCaseInfo& testInfo )
 | 
			
		||||
        {
 | 
			
		||||
//            m_config.stream() << "\t\t<testcase name='" << testInfo.getName() << "'>\n";
 | 
			
		||||
  //          m_currentTestSuccess = true;
 | 
			
		||||
            m_currentStats->testCaseStats.push_back( TestCaseStats( testInfo.getName() ) );
 | 
			
		||||
            
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
        virtual void Result( const Catch::ResultInfo& resultInfo )
 | 
			
		||||
        {
 | 
			
		||||
            if( !resultInfo.ok() || m_config.includeSuccessfulResults() )
 | 
			
		||||
            {
 | 
			
		||||
                TestCaseStats& testCaseStats = m_currentStats->testCaseStats.back();
 | 
			
		||||
                TestStats stats;
 | 
			
		||||
                std::ostringstream oss;
 | 
			
		||||
                if( !resultInfo.getMessage().empty() )
 | 
			
		||||
                {
 | 
			
		||||
                    oss << resultInfo.getMessage() << " at ";
 | 
			
		||||
                }
 | 
			
		||||
                oss << resultInfo.getFilename() << ":" << resultInfo.getLine();
 | 
			
		||||
                stats.content = oss.str();
 | 
			
		||||
                stats.message = resultInfo.getExpandedExpression();
 | 
			
		||||
                stats.resultType = resultInfo.getTestMacroName();
 | 
			
		||||
                switch( resultInfo.getResultType() )
 | 
			
		||||
                {
 | 
			
		||||
                    case ResultWas::ThrewException:
 | 
			
		||||
                        stats.element = "error";
 | 
			
		||||
                        break;
 | 
			
		||||
                    case ResultWas::Info:
 | 
			
		||||
                        stats.element = "info"; // !TBD ?
 | 
			
		||||
                        break;
 | 
			
		||||
                    case ResultWas::Warning:
 | 
			
		||||
                        stats.element = "warning"; // !TBD ?
 | 
			
		||||
                        break;
 | 
			
		||||
                    case ResultWas::ExplicitFailure:
 | 
			
		||||
                        stats.element = "failure";
 | 
			
		||||
                        break;
 | 
			
		||||
                    case ResultWas::ExpressionFailed:
 | 
			
		||||
                        stats.element = "failure";
 | 
			
		||||
                        break;
 | 
			
		||||
                    case ResultWas::Ok:
 | 
			
		||||
                        stats.element = "success";
 | 
			
		||||
                        break;
 | 
			
		||||
                    default:
 | 
			
		||||
                        stats.element = "unknown";
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
                testCaseStats.testStats.push_back( stats );
 | 
			
		||||
                
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
        virtual void EndTestCase( const Catch::TestCaseInfo&, const std::string& stdOut, const std::string& stdErr )
 | 
			
		||||
        {
 | 
			
		||||
            if( !stdOut.empty() )
 | 
			
		||||
                m_stdOut << stdOut << "\n";
 | 
			
		||||
            if( !stdErr.empty() )
 | 
			
		||||
                m_stdErr << stdErr << "\n";
 | 
			
		||||
        }    
 | 
			
		||||
 | 
			
		||||
        static std::string trim( const std::string& str )
 | 
			
		||||
        {
 | 
			
		||||
            std::string::size_type start = str.find_first_not_of( "\n\r\t " );
 | 
			
		||||
            std::string::size_type end = str.find_last_not_of( "\n\r\t " );
 | 
			
		||||
            
 | 
			
		||||
            return start < end ? str.substr( start, 1+end-start ) : "";
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
        virtual void EndTesting( std::size_t /* succeeded */, std::size_t /* failed */ )
 | 
			
		||||
        {
 | 
			
		||||
            std::ostream& str = m_config.stream();
 | 
			
		||||
            {
 | 
			
		||||
                XmlWriter xml( str );
 | 
			
		||||
                
 | 
			
		||||
                if( m_statsForSuites.size() > 0 )
 | 
			
		||||
                    xml.startElement( "testsuites" );
 | 
			
		||||
                            
 | 
			
		||||
                std::vector<Stats>::const_iterator it = m_statsForSuites.begin();
 | 
			
		||||
                std::vector<Stats>::const_iterator itEnd = m_statsForSuites.end();
 | 
			
		||||
                
 | 
			
		||||
                for(; it != itEnd; ++it )
 | 
			
		||||
                {
 | 
			
		||||
                    XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" );
 | 
			
		||||
                    xml.writeAttribute( "name", it->name );
 | 
			
		||||
                    
 | 
			
		||||
                    OutputTestCases( xml, *it );
 | 
			
		||||
                }
 | 
			
		||||
      
 | 
			
		||||
                xml.scopedElement( "system-out" ).writeText( trim( m_stdOut.str() ) );                
 | 
			
		||||
                xml.scopedElement( "system-err" ).writeText( trim( m_stdOut.str() ) );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
        void OutputTestCases( XmlWriter& xml, const Stats& stats )
 | 
			
		||||
        {
 | 
			
		||||
            std::vector<TestCaseStats>::const_iterator it = stats.testCaseStats.begin();
 | 
			
		||||
            std::vector<TestCaseStats>::const_iterator itEnd = stats.testCaseStats.end();
 | 
			
		||||
            for(; it != itEnd; ++it )
 | 
			
		||||
            {
 | 
			
		||||
                xml.writeBlankLine();
 | 
			
		||||
                xml.writeComment( "Test case" );
 | 
			
		||||
                
 | 
			
		||||
                XmlWriter::ScopedElement e = xml.scopedElement( "testcase" );
 | 
			
		||||
                xml.writeAttribute( "classname", it->className );
 | 
			
		||||
                xml.writeAttribute( "name", it->name );
 | 
			
		||||
                xml.writeAttribute( "time", "tbd" );
 | 
			
		||||
 | 
			
		||||
                OutputTestResult( xml, *it );
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        
 | 
			
		||||
        ///////////////////////////////////////////////////////////////////////////
 | 
			
		||||
        void OutputTestResult( XmlWriter& xml, const TestCaseStats& stats )
 | 
			
		||||
        {
 | 
			
		||||
            std::vector<TestStats>::const_iterator it = stats.testStats.begin();
 | 
			
		||||
            std::vector<TestStats>::const_iterator itEnd = stats.testStats.end();
 | 
			
		||||
            for(; it != itEnd; ++it )
 | 
			
		||||
            {
 | 
			
		||||
                if( it->element != "success" )
 | 
			
		||||
                {
 | 
			
		||||
                    XmlWriter::ScopedElement e = xml.scopedElement( it->element );
 | 
			
		||||
                    
 | 
			
		||||
                    xml.writeAttribute( "message", it->message );
 | 
			
		||||
                    xml.writeAttribute( "type", it->resultType );
 | 
			
		||||
                    if( !it->content.empty() )
 | 
			
		||||
                        xml.writeText( it->content );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
    private:
 | 
			
		||||
        const ReporterConfig& m_config;
 | 
			
		||||
        bool m_currentTestSuccess;
 | 
			
		||||
        
 | 
			
		||||
        Stats m_testSuiteStats;
 | 
			
		||||
        Stats* m_currentStats;
 | 
			
		||||
        std::vector<Stats> m_statsForSuites;
 | 
			
		||||
        std::ostringstream m_stdOut;
 | 
			
		||||
        std::ostringstream m_stdErr;
 | 
			
		||||
 | 
			
		||||
        Indenter m_indent;
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
} // end namespace Catch
 | 
			
		||||
 | 
			
		||||
#endif // TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED
 | 
			
		||||
@@ -82,6 +82,9 @@ namespace Catch
 | 
			
		||||
        }
 | 
			
		||||
        std::string getExpandedExpression() const
 | 
			
		||||
        {
 | 
			
		||||
            if( !hasExpression() )
 | 
			
		||||
                return "";
 | 
			
		||||
            
 | 
			
		||||
            return m_expressionIncomplete
 | 
			
		||||
                ? getExpandedExpressionInternal() + " {can't expand the rest of the expression - consider rewriting it}"
 | 
			
		||||
                : getExpandedExpressionInternal();
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										191
									
								
								internal/catch_xmlwriter.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								internal/catch_xmlwriter.hpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,191 @@
 | 
			
		||||
/*
 | 
			
		||||
 *  catch_xmlwriter.hpp
 | 
			
		||||
 *  Catch
 | 
			
		||||
 *
 | 
			
		||||
 *  Created by Phil on 09/12/2010.
 | 
			
		||||
 *  Copyright 2010 Two Blue Cubes Ltd. All rights reserved.
 | 
			
		||||
 *
 | 
			
		||||
 *  Distributed under the Boost Software License, Version 1.0. (See accompanying
 | 
			
		||||
 *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
 | 
			
		||||
 */
 | 
			
		||||
#ifndef TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED
 | 
			
		||||
#define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED
 | 
			
		||||
 | 
			
		||||
namespace Catch
 | 
			
		||||
{
 | 
			
		||||
    class XmlWriter
 | 
			
		||||
    {
 | 
			
		||||
    public:
 | 
			
		||||
        
 | 
			
		||||
        class ScopedElement
 | 
			
		||||
        {
 | 
			
		||||
        public:
 | 
			
		||||
            ScopedElement( XmlWriter* writer )
 | 
			
		||||
            :   m_writer( writer )
 | 
			
		||||
            {
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            ScopedElement( const ScopedElement& other )
 | 
			
		||||
            :   m_writer( other.m_writer )
 | 
			
		||||
            {
 | 
			
		||||
                other.m_writer = NULL;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            ~ScopedElement()
 | 
			
		||||
            {
 | 
			
		||||
                if( m_writer )
 | 
			
		||||
                    m_writer->endElement();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ScopedElement& writeText( const std::string& text )
 | 
			
		||||
            {
 | 
			
		||||
                m_writer->writeText( text );
 | 
			
		||||
                return *this;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
        private:
 | 
			
		||||
            mutable XmlWriter* m_writer;
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        XmlWriter( std::ostream& os)
 | 
			
		||||
        :   m_tagIsOpen( false ),
 | 
			
		||||
            m_needsNewline( false ),
 | 
			
		||||
            m_os( os )
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        ~XmlWriter()
 | 
			
		||||
        {
 | 
			
		||||
            while( !m_tags.empty() )
 | 
			
		||||
            {
 | 
			
		||||
                endElement();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        XmlWriter& startElement( const std::string& name )
 | 
			
		||||
        {
 | 
			
		||||
            ensureTagClosed();
 | 
			
		||||
            newlineIfNecessary();
 | 
			
		||||
            m_os << m_indent << "<" << name;
 | 
			
		||||
            m_tags.push_back( name );
 | 
			
		||||
            m_indent += "  ";
 | 
			
		||||
            m_tagIsOpen = true;
 | 
			
		||||
            return *this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ScopedElement scopedElement( const std::string& name )
 | 
			
		||||
        {
 | 
			
		||||
            ScopedElement scoped( this );
 | 
			
		||||
            startElement( name );
 | 
			
		||||
            return scoped;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        XmlWriter& endElement()
 | 
			
		||||
        {
 | 
			
		||||
            newlineIfNecessary();
 | 
			
		||||
            m_indent = m_indent.substr( 0, m_indent.size()-2 );
 | 
			
		||||
            if( m_tagIsOpen )
 | 
			
		||||
            {
 | 
			
		||||
                m_os << "/>\n";
 | 
			
		||||
                m_tagIsOpen = false;
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                m_os << m_indent << "</" << m_tags.back() << ">\n";
 | 
			
		||||
            } 
 | 
			
		||||
            m_tags.pop_back();
 | 
			
		||||
            return *this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        XmlWriter& writeAttribute( const std::string& name, const std::string& attribute )
 | 
			
		||||
        {
 | 
			
		||||
            if( !name.empty() && !attribute.empty() )
 | 
			
		||||
            {
 | 
			
		||||
                m_os << " " << name << "=\"";
 | 
			
		||||
                writeEncodedText( attribute );
 | 
			
		||||
                m_os << "\"";
 | 
			
		||||
            }
 | 
			
		||||
            return *this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        XmlWriter& writeText( const std::string& text )
 | 
			
		||||
        {
 | 
			
		||||
            if( !text.empty() )
 | 
			
		||||
            {
 | 
			
		||||
                bool tagWasOpen = m_tagIsOpen;
 | 
			
		||||
                ensureTagClosed();
 | 
			
		||||
                if( tagWasOpen )
 | 
			
		||||
                    m_os << m_indent;
 | 
			
		||||
                writeEncodedText( text );
 | 
			
		||||
                m_needsNewline = true;
 | 
			
		||||
            }
 | 
			
		||||
            return *this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        XmlWriter& writeComment( const std::string& text )
 | 
			
		||||
        {
 | 
			
		||||
            ensureTagClosed();
 | 
			
		||||
            m_os << m_indent << "<!--" << text << "-->";
 | 
			
		||||
            m_needsNewline = true;
 | 
			
		||||
            return *this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        XmlWriter& writeBlankLine()
 | 
			
		||||
        {
 | 
			
		||||
            ensureTagClosed();
 | 
			
		||||
            m_os << "\n";
 | 
			
		||||
            return *this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
    private:
 | 
			
		||||
        
 | 
			
		||||
        void ensureTagClosed()
 | 
			
		||||
        {
 | 
			
		||||
            if( m_tagIsOpen )
 | 
			
		||||
            {
 | 
			
		||||
                m_os << ">\n";
 | 
			
		||||
                m_tagIsOpen = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        void newlineIfNecessary()
 | 
			
		||||
        {
 | 
			
		||||
            if( m_needsNewline )
 | 
			
		||||
            {
 | 
			
		||||
                m_os << "\n";
 | 
			
		||||
                m_needsNewline = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        void writeEncodedText( const std::string& text )
 | 
			
		||||
        {
 | 
			
		||||
            // !TBD finish this
 | 
			
		||||
            if( !findReplaceableString( text, "<", "<" ) &&
 | 
			
		||||
               !findReplaceableString( text, "&", "&" ) &&
 | 
			
		||||
               !findReplaceableString( text, "\"", ""e;" ) )
 | 
			
		||||
            {
 | 
			
		||||
                m_os << text;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        bool findReplaceableString( const std::string& text, const std::string& replaceWhat, const std::string& replaceWith )
 | 
			
		||||
        {
 | 
			
		||||
            std::string::size_type pos = text.find_first_of( replaceWhat );
 | 
			
		||||
            if( pos != std::string::npos )
 | 
			
		||||
            {
 | 
			
		||||
                m_os << text.substr( 0, pos ) << replaceWith;
 | 
			
		||||
                writeEncodedText( text.substr( pos+1 ) );
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        bool m_tagIsOpen;
 | 
			
		||||
        bool m_needsNewline;
 | 
			
		||||
        std::vector<std::string> m_tags;
 | 
			
		||||
        std::string m_indent;
 | 
			
		||||
        std::ostream& m_os;
 | 
			
		||||
    };
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
		Reference in New Issue
	
	Block a user