diff --git a/docs/changes.txt b/docs/changes.txt index 50377642f3..b1aac62be2 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -454,6 +454,7 @@ All: - Added precision parameter to wxString::From[C]Double(). - Added wxThread::Wait() and Delete() "wait mode" parameter (Catalin Raceanu). - Allow showing milliseconds in wxLog time stamps (tan). +- Added support for processing instructions in wxXmlDocument (Nick Matthews). Unix: diff --git a/include/wx/xml/xml.h b/include/wx/xml/xml.h index 4a40d32506..fbd6faa4ac 100644 --- a/include/wx/xml/xml.h +++ b/include/wx/xml/xml.h @@ -257,7 +257,7 @@ public: const wxString& encoding = wxT("UTF-8")); wxXmlDocument(wxInputStream& stream, const wxString& encoding = wxT("UTF-8")); - virtual ~wxXmlDocument() { wxDELETE(m_root); } + virtual ~wxXmlDocument() { wxDELETE(m_docNode); } wxXmlDocument(const wxXmlDocument& doc); wxXmlDocument& operator=(const wxXmlDocument& doc); @@ -273,10 +273,13 @@ public: virtual bool Save(const wxString& filename, int indentstep = 2) const; virtual bool Save(wxOutputStream& stream, int indentstep = 2) const; - bool IsOk() const { return m_root != NULL; } + bool IsOk() const { return GetRoot() != NULL; } // Returns root node of the document. - wxXmlNode *GetRoot() const { return m_root; } + wxXmlNode *GetRoot() const; + // Returns the document node. + wxXmlNode *GetDocumentNode() const { return m_docNode; } + // Returns version of document (may be empty). const wxString& GetVersion() const { return m_version; } @@ -286,10 +289,13 @@ public: const wxString& GetFileEncoding() const { return m_fileEncoding; } // Write-access methods: - wxXmlNode *DetachRoot() { wxXmlNode *old=m_root; m_root=NULL; return old; } - void SetRoot(wxXmlNode *node) { wxDELETE(m_root); m_root = node; } + wxXmlNode *DetachDocumentNode() { wxXmlNode *old=m_docNode; m_docNode=NULL; return old; } + void SetDocumentNode(wxXmlNode *node) { wxDELETE(m_docNode); m_docNode = node; } + wxXmlNode *DetachRoot(); + void SetRoot(wxXmlNode *node); void SetVersion(const wxString& version) { m_version = version; } void SetFileEncoding(const wxString& encoding) { m_fileEncoding = encoding; } + void AppendToProlog(wxXmlNode *node); #if !wxUSE_UNICODE // Returns encoding of in-memory representation of the document @@ -307,7 +313,7 @@ private: #if !wxUSE_UNICODE wxString m_encoding; #endif - wxXmlNode *m_root; + wxXmlNode *m_docNode; void DoCopy(const wxXmlDocument& doc); diff --git a/interface/wx/xml/xml.h b/interface/wx/xml/xml.h index bdd3344bb8..8605491dd1 100644 --- a/interface/wx/xml/xml.h +++ b/interface/wx/xml/xml.h @@ -40,6 +40,11 @@ enum wxXmlNodeType @c title and irrelevant content and one child of type @c wxXML_TEXT_NODE with @c hi as content. + The @c wxXML_PI_NODE type sets the name to the PI target and the contents to + the instructions. Note that whilst the PI instructions are often in the form + of pseudo-attributes these do not use the nodes attribute system. It is the users + responsibility to code and decode the instruction text. + If @c wxUSE_UNICODE is 0, all strings are encoded in the encoding given to wxXmlDocument::Load (default is UTF-8). @@ -179,7 +184,7 @@ public: This function searches only the parents of this node until it finds @a grandparent or the @NULL node (which is the parent of non-linked - nodes or the parent of a wxXmlDocument's root node). + nodes or the parent of a wxXmlDocument's root element node). */ int GetDepth(wxXmlNode* grandparent = NULL) const; @@ -215,14 +220,14 @@ public: is represented by expat with the following tag tree: @code - wxXML_ENTITY_NODE name="tagname", content="" + wxXML_ELEMENT_NODE name="tagname", content="" |-- wxXML_TEXT_NODE name="", content="tagcontent" @endcode or eventually: @code - wxXML_ENTITY_NODE name="tagname", content="" + wxXML_ELEMENT_NODE name="tagname", content="" |-- wxXML_CDATA_SECTION_NODE name="", content="tagcontent" @endcode @@ -449,6 +454,20 @@ public: if (doc.GetRoot()->GetName() != "myroot-node") return false; + // examine prologue + wxXmlNode *prolog = doc.GetDocumentNode()->GetChildren(); + while (prolog) { + + if (prolog->GetType() == wxXML_PI_NODE && prolog->GetName() == "target") { + + // process Process Instruction contents + wxString pi = prolog->GetContent(); + + ... + + } + } + wxXmlNode *child = doc.GetRoot()->GetChildren(); while (child) { @@ -533,13 +552,37 @@ public: virtual ~wxXmlDocument(); /** - Detaches the document root node and returns it. + Appends a Process Instruction or Comment node to the document prologue. - The document root node will be set to @NULL and thus IsOk() will + Calling this function will create a prologue or attach the node to the + end of an existing prologue. + + @since 2.9.2 + */ + void AppendToProlog(wxXmlNode* node); + + /** + Detaches the document node and returns it. + + The document node will be set to @NULL and thus IsOk() will return @false after calling this function. Note that the caller is responsible for deleting the returned node in order to avoid memory leaks. + + @since 2.9.2 + */ + wxXmlNode* DetachDocumentNode(); + + /** + Detaches the root entity node and returns it. + + After calling this function, the document node will remain together with + any prologue nodes, but IsOk() will return @false since the root entity + will be missing. + + Note that the caller is reponsible for deleting the returned node in order + to avoid memory leaks. */ wxXmlNode* DetachRoot(); @@ -560,7 +603,14 @@ public: const wxString& GetFileEncoding() const; /** - Returns the root node of the document. + Returns the document node of the document. + + @since 2.9.2 + */ + wxXmlNode* GetDocumentNode() const; + + /** + Returns the root element node of the document. */ wxXmlNode* GetRoot() const; @@ -620,7 +670,18 @@ public: virtual bool Save(wxOutputStream& stream, int indentstep = 2) const; /** - Sets the enconding of the document. + Sets the document node of this document. + + Deletes any previous document node. + Use DetachDocumentNode() and then SetDocumentNode() if you want to + replace the document node without deleting the old document tree. + + @since 2.9.2 + */ + void SetDocumentNode(wxXmlNode* node); + + /** + Sets the encoding of the document. */ void SetEncoding(const wxString& enc); @@ -630,9 +691,10 @@ public: void SetFileEncoding(const wxString& encoding); /** - Sets the root node of this document. Deletes previous root node. - Use DetachRoot() and then SetRoot() if you want to replace the root - node without deleting the old document tree. + Sets the root element node of this document. + + Will create the document node if necessary. Any previous + root element node is deleted. */ void SetRoot(wxXmlNode* node); diff --git a/src/xml/xml.cpp b/src/xml/xml.cpp index 922332b1d6..0e82fffd01 100644 --- a/src/xml/xml.cpp +++ b/src/xml/xml.cpp @@ -396,7 +396,7 @@ bool wxXmlNode::IsWhitespaceOnly() const //----------------------------------------------------------------------------- wxXmlDocument::wxXmlDocument() - : m_version(wxS("1.0")), m_fileEncoding(wxS("utf-8")), m_root(NULL) + : m_version(wxS("1.0")), m_fileEncoding(wxS("UTF-8")), m_docNode(NULL) { #if !wxUSE_UNICODE m_encoding = wxS("UTF-8"); @@ -404,20 +404,20 @@ wxXmlDocument::wxXmlDocument() } wxXmlDocument::wxXmlDocument(const wxString& filename, const wxString& encoding) - :wxObject(), m_root(NULL) + :wxObject(), m_docNode(NULL) { if ( !Load(filename, encoding) ) { - wxDELETE(m_root); + wxDELETE(m_docNode); } } wxXmlDocument::wxXmlDocument(wxInputStream& stream, const wxString& encoding) - :wxObject(), m_root(NULL) + :wxObject(), m_docNode(NULL) { if ( !Load(stream, encoding) ) { - wxDELETE(m_root); + wxDELETE(m_docNode); } } @@ -429,7 +429,7 @@ wxXmlDocument::wxXmlDocument(const wxXmlDocument& doc) wxXmlDocument& wxXmlDocument::operator=(const wxXmlDocument& doc) { - wxDELETE(m_root); + wxDELETE(m_docNode); DoCopy(doc); return *this; } @@ -442,10 +442,10 @@ void wxXmlDocument::DoCopy(const wxXmlDocument& doc) #endif m_fileEncoding = doc.m_fileEncoding; - if (doc.m_root) - m_root = new wxXmlNode(*doc.m_root); + if (doc.m_docNode) + m_docNode = new wxXmlNode(*doc.m_docNode); else - m_root = NULL; + m_docNode = NULL; } bool wxXmlDocument::Load(const wxString& filename, const wxString& encoding, int flags) @@ -464,7 +464,83 @@ bool wxXmlDocument::Save(const wxString& filename, int indentstep) const return Save(stream, indentstep); } +wxXmlNode *wxXmlDocument::GetRoot() const +{ + wxXmlNode *node = m_docNode; + if (node) + { + node = m_docNode->GetChildren(); + while (node != NULL && node->GetType() != wxXML_ELEMENT_NODE) + node = node->GetNext(); + } + return node; +} +wxXmlNode *wxXmlDocument::DetachRoot() +{ + wxXmlNode *node = m_docNode; + if (node) + { + node = m_docNode->GetChildren(); + wxXmlNode *prev = NULL; + while (node != NULL && node->GetType() != wxXML_ELEMENT_NODE) + { + prev = node; + node = node->GetNext(); + } + if (node) + { + if (node == m_docNode->GetChildren()) + m_docNode->SetChildren(node->GetNext()); + + if (prev) + prev->SetNext(node->GetNext()); + + node->SetParent(NULL); + node->SetNext(NULL); + } + } + return node; +} + +void wxXmlDocument::SetRoot(wxXmlNode *root) +{ + wxXmlNode *node = m_docNode; + if (node) + { + node = m_docNode->GetChildren(); + wxXmlNode *prev = NULL; + while (node != NULL && node->GetType() != wxXML_ELEMENT_NODE) + { + prev = node; + node = node->GetNext(); + } + if (node) + { + root->SetNext( node->GetNext() ); + wxDELETE(node); + } + if (prev) + prev->SetNext(root); + else + m_docNode->SetChildren(root); + } + else + { + m_docNode = new wxXmlNode(wxXML_DOCUMENT_NODE, wxEmptyString); + } + root->SetParent(m_docNode); +} + +void wxXmlDocument::AppendToProlog(wxXmlNode *node) +{ + if (!m_docNode) + m_docNode = new wxXmlNode(wxXML_DOCUMENT_NODE, wxEmptyString); + if (IsOk()) + m_docNode->InsertChild( node, GetRoot() ); + else + m_docNode->AddChild( node ); +} //----------------------------------------------------------------------------- // wxXmlDocument loading routines @@ -509,7 +585,6 @@ struct wxXmlParsingContext { wxXmlParsingContext() : conv(NULL), - root(NULL), node(NULL), lastChild(NULL), lastAsText(NULL), @@ -518,7 +593,6 @@ struct wxXmlParsingContext XML_Parser parser; wxMBConv *conv; - wxXmlNode *root; wxXmlNode *node; // the node being parsed wxXmlNode *lastChild; // the last child of "node" wxXmlNode *lastAsText; // the last _text_ child of "node" @@ -551,16 +625,8 @@ static void StartElementHnd(void *userData, const char *name, const char **atts) a += 2; } - if (ctx->root == NULL) - { - ctx->root = node; - } - else - { - ASSERT_LAST_CHILD_OK(ctx); - ctx->node->InsertChildAfter(node, ctx->lastChild); - } - + ASSERT_LAST_CHILD_OK(ctx); + ctx->node->InsertChildAfter(node, ctx->lastChild); ctx->lastAsText = NULL; ctx->lastChild = NULL; // our new node "node" has no children yet @@ -636,21 +702,29 @@ static void CommentHnd(void *userData, const char *data) { wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; - if (ctx->node) - { - wxXmlNode *commentnode = - new wxXmlNode(wxXML_COMMENT_NODE, - wxS("comment"), CharToString(ctx->conv, data), - XML_GetCurrentLineNumber(ctx->parser)); + wxXmlNode *commentnode = + new wxXmlNode(wxXML_COMMENT_NODE, + wxS("comment"), CharToString(ctx->conv, data), + XML_GetCurrentLineNumber(ctx->parser)); - ASSERT_LAST_CHILD_OK(ctx); - ctx->node->InsertChildAfter(commentnode, ctx->lastChild); - ctx->lastChild = commentnode; - } - //else: ctx->node == NULL happens if there is a comment before - // the root element. We current don't have a way to represent - // these in wxXmlDocument (FIXME). + ASSERT_LAST_CHILD_OK(ctx); + ctx->node->InsertChildAfter(commentnode, ctx->lastChild); + ctx->lastChild = commentnode; + ctx->lastAsText = NULL; +} +static void PIHnd(void *userData, const char *target, const char *data) +{ + wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; + + wxXmlNode *pinode = + new wxXmlNode(wxXML_PI_NODE, CharToString(ctx->conv, target), + CharToString(ctx->conv, data), + XML_GetCurrentLineNumber(ctx->parser)); + + ASSERT_LAST_CHILD_OK(ctx); + ctx->node->InsertChildAfter(pinode, ctx->lastChild); + ctx->lastChild = pinode; ctx->lastAsText = NULL; } @@ -718,6 +792,7 @@ bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding, int fl wxXmlParsingContext ctx; bool done; XML_Parser parser = XML_ParserCreate(NULL); + wxXmlNode *root = new wxXmlNode(wxXML_DOCUMENT_NODE, wxEmptyString); ctx.encoding = wxS("UTF-8"); // default in absence of encoding="" ctx.conv = NULL; @@ -727,12 +802,14 @@ bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding, int fl #endif ctx.removeWhiteOnlyNodes = (flags & wxXMLDOC_KEEP_WHITESPACE_NODES) == 0; ctx.parser = parser; + ctx.node = root; XML_SetUserData(parser, (void*)&ctx); XML_SetElementHandler(parser, StartElementHnd, EndElementHnd); XML_SetCharacterDataHandler(parser, TextHnd); XML_SetCdataSectionHandler(parser, StartCdataHnd, EndCdataHnd);; XML_SetCommentHandler(parser, CommentHnd); + XML_SetProcessingInstructionHandler(parser, PIHnd); XML_SetDefaultHandler(parser, DefaultHnd); XML_SetUnknownEncodingHandler(parser, UnknownEncodingHnd, NULL); @@ -759,11 +836,11 @@ bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding, int fl SetVersion(ctx.version); if (!ctx.encoding.empty()) SetFileEncoding(ctx.encoding); - SetRoot(ctx.root); + SetDocumentNode(root); } else { - delete ctx.root; + delete root; } XML_ParserFree(parser); @@ -995,6 +1072,14 @@ bool OutputNode(wxOutputStream& stream, OutputString(stream, wxS("-->"), convMem, convFile); break; + case wxXML_PI_NODE: + rc = OutputString(stream, wxT("GetName(), convMem, convFile) && + OutputString(stream, wxT(" "), convMem, convFile) && + OutputString(stream, node->GetContent(), convMem, convFile) && + OutputString(stream, wxT("?>"), convMem, convFile); + break; + default: wxFAIL_MSG("unsupported node type"); rc = false; @@ -1023,17 +1108,24 @@ bool wxXmlDocument::Save(wxOutputStream& stream, int indentstep) const //else: file and in-memory encodings are the same, no conversion needed #endif - return OutputString(stream, - wxString::Format - ( - wxS("\n"), - GetVersion(), GetFileEncoding() - ), - convMem.get(), - convFile.get()) && - OutputNode(stream, GetRoot(), 0, - convMem.get(), convFile.get(), indentstep) && - OutputString(stream, wxS("\n"), convMem.get(), convFile.get()); + wxString dec = wxString::Format( + wxS("\n"), + GetVersion(), GetFileEncoding() + ); + bool rc = OutputString(stream, dec, convMem.get(), convFile.get()); + + wxXmlNode *node = GetDocumentNode(); + if ( node ) + node = node->GetChildren(); + + while( rc && node ) + { + rc = OutputNode(stream, node, 0, convMem.get(), + convFile.get(), indentstep) && + OutputString(stream, wxS("\n"), convMem.get(), convFile.get()); + node = node->GetNext(); + } + return rc; } /*static*/ wxVersionInfo wxXmlDocument::GetLibraryVersionInfo() diff --git a/tests/xml/xmltest.cpp b/tests/xml/xmltest.cpp index f04b10365f..153b9bcc4c 100644 --- a/tests/xml/xmltest.cpp +++ b/tests/xml/xmltest.cpp @@ -77,14 +77,20 @@ private: CPPUNIT_TEST( InsertChildAfter ); CPPUNIT_TEST( LoadSave ); CPPUNIT_TEST( CDATA ); + CPPUNIT_TEST( PI ); CPPUNIT_TEST( Escaping ); + CPPUNIT_TEST( DetachRoot ); + CPPUNIT_TEST( AppendToProlog ); CPPUNIT_TEST_SUITE_END(); void InsertChild(); void InsertChildAfter(); void LoadSave(); void CDATA(); + void PI(); void Escaping(); + void DetachRoot(); + void AppendToProlog(); DECLARE_NO_COPY_CLASS(XmlTestCase) }; @@ -150,6 +156,7 @@ void XmlTestCase::LoadSave() const char *xmlText = "\n" "\n" +" \n" " \n" " \n" " \n" @@ -194,6 +201,30 @@ void XmlTestCase::LoadSave() CPPUNIT_ASSERT_EQUAL( wxString(utf8xmlText), wxString(sos8.GetString().ToUTF8()) ); #endif // wxUSE_UNICODE + + const char *xmlTextProlog = +"\n" +"\n" +"\n" +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n" +"\n" + ; + + wxStringInputStream sisp(xmlTextProlog); + CPPUNIT_ASSERT( doc.Load(sisp, "UTF-8") ); + + wxStringOutputStream sosp; + CPPUNIT_ASSERT( doc.Save(sosp) ); + + CPPUNIT_ASSERT_EQUAL( xmlTextProlog, sosp.GetString() ); } void XmlTestCase::CDATA() @@ -221,6 +252,28 @@ void XmlTestCase::CDATA() CPPUNIT_ASSERT_EQUAL( "Giovanni Mittone", n->GetContent() ); } +void XmlTestCase::PI() +{ + const char *xmlText = + "\n" + "\n" + " \n" + "\n" + ; + + wxStringInputStream sis(xmlText); + wxXmlDocument doc; + CPPUNIT_ASSERT( doc.Load(sis) ); + + wxXmlNode *n = doc.GetRoot(); + CPPUNIT_ASSERT( n ); + + n = n->GetChildren(); + CPPUNIT_ASSERT( n ); + + CPPUNIT_ASSERT_EQUAL( "index=\"no\" follow=\"no\"", n->GetContent() ); +} + void XmlTestCase::Escaping() { // Verify that attribute values are escaped correctly, see @@ -243,3 +296,131 @@ void XmlTestCase::Escaping() CPPUNIT_ASSERT_EQUAL( xmlText, sos.GetString() ); } + +void XmlTestCase::DetachRoot() +{ + const char *xmlTextProlog = +"\n" +"\n" +"\n" +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n" +"\n" + ; + const char *xmlTextHtm = +"\n" +"\n" +" \n" +" Testing wxXml\n" +" \n" +" \n" +"

Some body text

\n" +" \n" +"\n" + ; + wxXmlDocument doc; + + wxStringInputStream sish(xmlTextHtm); + CPPUNIT_ASSERT( doc.Load(sish) ); + + wxXmlNode *root = doc.DetachRoot(); + + wxStringInputStream sisp(xmlTextProlog); + CPPUNIT_ASSERT( doc.Load(sisp) ); + + doc.SetRoot(root); + + wxStringOutputStream sos; + CPPUNIT_ASSERT( doc.Save(sos) ); + + const char *xmlTextResult1 = +"\n" +"\n" +"\n" +"\n" +" \n" +" Testing wxXml\n" +" \n" +" \n" +"

Some body text

\n" +" \n" +"\n" +"\n" + ; + CPPUNIT_ASSERT_EQUAL( xmlTextResult1, sos.GetString() ); + + wxStringInputStream sisp2(xmlTextProlog); + CPPUNIT_ASSERT( doc.Load(sisp2) ); + + root = doc.DetachRoot(); + + wxStringInputStream sish2(xmlTextHtm); + CPPUNIT_ASSERT( doc.Load(sish2) ); + + doc.SetRoot(root); + + wxStringOutputStream sos2; + CPPUNIT_ASSERT( doc.Save(sos2) ); + + const char *xmlTextResult2 = +"\n" +"\n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +"\n" + ; + CPPUNIT_ASSERT_EQUAL( xmlTextResult2, sos2.GetString() ); +} + +void XmlTestCase::AppendToProlog() +{ + const char *xmlText = +"\n" +"\n" +"

Some text

\n" +"
\n" + ; + wxXmlDocument rootdoc; + wxStringInputStream sis(xmlText); + CPPUNIT_ASSERT( rootdoc.Load(sis) ); + wxXmlNode *root = rootdoc.DetachRoot(); + + wxXmlNode *comment1 = new wxXmlNode(wxXML_COMMENT_NODE, "comment", + " 1st prolog entry "); + wxXmlNode *pi = new wxXmlNode(wxXML_PI_NODE, "xml-stylesheet", + "href=\"style.css\" type=\"text/css\""); + wxXmlNode *comment2 = new wxXmlNode(wxXML_COMMENT_NODE, "comment", + " 3rd prolog entry "); + + wxXmlDocument doc; + doc.AppendToProlog( comment1 ); + doc.AppendToProlog( pi ); + doc.SetRoot( root ); + doc.AppendToProlog( comment2 ); + + wxStringOutputStream sos; + CPPUNIT_ASSERT( doc.Save(sos) ); + + const char *xmlTextResult = +"\n" +"\n" +"\n" +"\n" +"\n" +"

Some text

\n" +"
\n" + ; + CPPUNIT_ASSERT_EQUAL( xmlTextResult, sos.GetString() ); +}