// TXmlWriter — minimal streaming XML writer for Harbour / xHarbour.
// Designed for the typical OpenTag / Element / CloseTag pattern. Writes
// directly to disk so it stays cheap on memory for large documents.
#include "FileIO.ch"
#define CRLF Chr(13)+Chr(10)
CLASS TXmlWriter
DATA cFile AS CHARACTER
DATA hFile AS NUMERIC INIT -1
DATA nIndent AS NUMERIC INIT 0
DATA cIndStr AS CHARACTER INIT " " // 2 spaces per level
DATA aStack AS ARRAY INIT {} // tag stack for nesting
DATA lOpen AS LOGICAL INIT .F.
METHOD New( cFile ) CONSTRUCTOR
METHOD Start( cVersion, cEncoding )
METHOD OpenTag( cTag, hAttribs )
METHOD CloseTag( cTag )
METHOD Element( cTag, cValue, hAttribs )
METHOD Comment( cText )
METHOD CData( cText )
METHOD End()
METHOD Destroy()
METHOD Write( cText ) PROTECTED
METHOD WriteIndent() PROTECTED
METHOD AttrStr( hAttribs ) PROTECTED
METHOD Escape( cText ) PROTECTED
ENDCLASS
//----------------------------------------------------------------------------//
METHOD New( cFile ) CLASS TXmlWriter
::cFile := cFile
::hFile := FCreate( cFile )
::lOpen := ( ::hFile != -1 )
::aStack := {}
::nIndent := 0
RETURN Self
//----------------------------------------------------------------------------//
METHOD Start( cVersion, cEncoding ) CLASS TXmlWriter
DEFAULT cVersion := "1.0"
DEFAULT cEncoding := "UTF-8"
::Write( '<?xml version="' + cVersion + '" encoding="' + cEncoding + '"?>' + CRLF )
RETURN Self
//----------------------------------------------------------------------------//
METHOD OpenTag( cTag, hAttribs ) CLASS TXmlWriter
::WriteIndent()
::Write( "<" + cTag + ::AttrStr( hAttribs ) + ">" + CRLF )
AAdd( ::aStack, cTag )
::nIndent++
RETURN Self
//----------------------------------------------------------------------------//
METHOD CloseTag( cTag ) CLASS TXmlWriter
LOCAL n
IF ::nIndent > 0
::nIndent--
ENDIF
IF Len( ::aStack ) > 0
n := Len( ::aStack )
DEFAULT cTag := ::aStack[ n ]
ASize( ::aStack, n - 1 )
ENDIF
::WriteIndent()
::Write( "</" + cTag + ">" + CRLF )
RETURN Self
//----------------------------------------------------------------------------//
METHOD Element( cTag, cValue, hAttribs ) CLASS TXmlWriter
DEFAULT cValue := ""
::WriteIndent()
IF Empty( cValue )
::Write( "<" + cTag + ::AttrStr( hAttribs ) + "/>" + CRLF )
ELSE
::Write( "<" + cTag + ::AttrStr( hAttribs ) + ">" + ;
::Escape( cValue ) + "</" + cTag + ">" + CRLF )
ENDIF
RETURN Self
//----------------------------------------------------------------------------//
METHOD Comment( cText ) CLASS TXmlWriter
::WriteIndent()
::Write( "<!-- " + cText + " -->" + CRLF )
RETURN Self
//----------------------------------------------------------------------------//
METHOD CData( cText ) CLASS TXmlWriter
::WriteIndent()
::Write( "<![CDATA[" + cText + "]]>" + CRLF )
RETURN Self
//----------------------------------------------------------------------------//
METHOD End() CLASS TXmlWriter
// Flush any remaining open tags so the file is well-formed even if the
// caller forgot to close them.
DO WHILE Len( ::aStack ) > 0
::CloseTag()
ENDDO
RETURN Self
//----------------------------------------------------------------------------//
METHOD Destroy() CLASS TXmlWriter
IF ::lOpen
FClose( ::hFile )
::lOpen := .F.
::hFile := -1
ENDIF
RETURN Self
//----------------------------------------------------------------------------//
METHOD Write( cText ) CLASS TXmlWriter
IF ::lOpen
FWrite( ::hFile, cText )
ENDIF
RETURN NIL
//----------------------------------------------------------------------------//
METHOD WriteIndent() CLASS TXmlWriter
LOCAL i
FOR i := 1 TO ::nIndent
::Write( ::cIndStr )
NEXT
RETURN NIL
//----------------------------------------------------------------------------//
METHOD AttrStr( hAttribs ) CLASS TXmlWriter
LOCAL cOut := ""
LOCAL cKey
IF HB_ISHASH( hAttribs )
FOR EACH cKey IN hb_HKeys( hAttribs )
cOut += " " + cKey + '="' + ::Escape( cValToChar( hAttribs[ cKey ] ) ) + '"'
NEXT
ENDIF
RETURN cOut
//----------------------------------------------------------------------------//
METHOD Escape( cText ) CLASS TXmlWriter
cText := StrTran( cText, "&", "&" )
cText := StrTran( cText, "<", "<" )
cText := StrTran( cText, ">", ">" )
cText := StrTran( cText, '"', """ )
cText := StrTran( cText, "'", "'" )
RETURN cText