diff --git a/docs/changes.txt b/docs/changes.txt index 863f7bd6ac..31ffb62152 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -47,6 +47,7 @@ All: - Allow calling wxItemContainer::Add() and similar with std::vector<> argument. - Add "%z" support to printf()-like functions like wxString::Format() (RIVDSL). +- Add DOCTYPE support to wxXmlDocument (Nick Matthews). - Add wxPowerResourceBlocker (Tobias Taschner). - Add wxApp::StoreCurrentException() and RethrowStoredException() and implement their functionality by default when using C++11 compiler. diff --git a/include/wx/xml/xml.h b/include/wx/xml/xml.h index ee47962616..4701f6812b 100644 --- a/include/wx/xml/xml.h +++ b/include/wx/xml/xml.h @@ -236,6 +236,35 @@ inline void wxXmlNode::SetProperties(wxXmlAttribute *prop) +class WXDLLIMPEXP_XML wxXmlDoctype +{ +public: + explicit + wxXmlDoctype(const wxString& name = wxString(), + const wxString& sysid = wxString(), + const wxString& pubid = wxString()) + : m_rootName(name), m_systemId(sysid), m_publicId(pubid) + {} + + // Default copy ctor and assignment operators are ok. + + bool IsValid() const; + void Clear(); + + const wxString& GetRootName() const { return m_rootName; } + const wxString& GetSystemId() const { return m_systemId; } + const wxString& GetPublicId() const { return m_publicId; } + + wxString GetFullString() const; + +private: + wxString m_rootName; + wxString m_systemId; + wxString m_publicId; +}; + + + // special indentation value for wxXmlDocument::Save #define wxXML_NO_INDENTATION (-1) @@ -287,6 +316,7 @@ public: // Note: this is the encoding original file was saved in, *not* the // encoding of in-memory representation! const wxString& GetFileEncoding() const { return m_fileEncoding; } + const wxXmlDoctype& GetDoctype() const { return m_doctype; } // Write-access methods: wxXmlNode *DetachDocumentNode() { wxXmlNode *old=m_docNode; m_docNode=NULL; return old; } @@ -295,6 +325,7 @@ public: void SetRoot(wxXmlNode *node); void SetVersion(const wxString& version) { m_version = version; } void SetFileEncoding(const wxString& encoding) { m_fileEncoding = encoding; } + void SetDoctype(const wxXmlDoctype& doctype) { m_doctype = doctype; } void AppendToProlog(wxXmlNode *node); #if !wxUSE_UNICODE @@ -313,6 +344,7 @@ private: #if !wxUSE_UNICODE wxString m_encoding; #endif + wxXmlDoctype m_doctype; wxXmlNode *m_docNode; void DoCopy(const wxXmlDocument& doc); diff --git a/interface/wx/xml/xml.h b/interface/wx/xml/xml.h index 16d17c9ccf..114711e518 100644 --- a/interface/wx/xml/xml.h +++ b/interface/wx/xml/xml.h @@ -44,13 +44,16 @@ enum wxXmlNodeType of pseudo-attributes these do not use the nodes attribute system. It is the users responsibility to code and decode the instruction text. + The @c wxXML_DOCUMENT_TYPE_NODE is not implemented at this time. Instead, + get and set the DOCTYPE values using the wxXmlDocument class. + If @c wxUSE_UNICODE is 0, all strings are encoded in the encoding given to wxXmlDocument::Load (default is UTF-8). @library{wxxml} @category{xml} - @see wxXmlDocument, wxXmlAttribute + @see wxXmlDocument, wxXmlDoctype, wxXmlAttribute */ class wxXmlNode { @@ -433,6 +436,99 @@ public: }; + +/** + @class wxXmlDoctype + + Represents a DOCTYPE Declaration. + + Example DOCTYPE: \. + + In the above example, "plist" is the name of root element, + "-//Apple//DTD PLIST 1.0//EN" (without the quotes) is the public identifier and + "http://www.apple.com/DTDs/PropertyList-1.0.dtd" (again, without the quotes) is + the system identifier. + + A valid DOCTYPE exists in one of following forms: + + 1. A root element name. + 2. A root element name and a system identifier. + 3. A root element name, a system identifier and a public identifier. + 4. A root element name and a public identifier. Although this form is not + valid XML it is valid for SMGL. + + The DOCTYPE may also contain an internal subset of declarations + added between square brackets at the end. + These have not been implemented at this time. + + @since 3.1.0 + + @library{wxxml} + @category{xml} + + @see wxXmlDocument +*/ +class wxXmlDoctype +{ +public: + /** + Creates and possible initializes the DOCTYPE. + + @param name + The root name. + @param sysid + The system identifier. + @param pubid + The public identifier. + */ + wxXmlDoctype(const wxString& rootName = wxString(), + const wxString& systemId = wxString(), + const wxString& publicId = wxString()); + + /** + Removes all the DOCTYPE values. + */ + void Clear(); + + /** + Returns the root name of the document. + */ + const wxString& GetRootName() const; + + /** + Returns the system id of the document. + */ + const wxString& GetSystemId() const; + + /** + Returns the public id of the document. + */ + const wxString& GetPublicId() const; + + /** + Returns the formatted DOCTYPE contents. + + This consists of all the text shown between the opening + "" of a DOCTYPE declaration. + + If this object is empty or invalid, i.e. IsValid() returns false, this + method returns an empty string. + */ + wxString GetFullString() const; + + /** + Returns true if the contents can produce a valid DOCTYPE string. + + For an object to be valid, it must have a non-empty root name and a + valid system identifier (currently the validity checks of the latter + are limited to checking that it doesn't contain both single and double + quotes). + */ + bool IsValid() const; +}; + + + //* special indentation value for wxXmlDocument::Save #define wxXML_NO_INDENTATION (-1) @@ -526,10 +622,14 @@ enum wxXmlDocumentLoadFlag doc.Save("myfile2.xml"); // myfile2.xml != myfile.xml @endcode + If the root name value of the DOCTYPE is set, either by loading a file with a + DOCTYPE declaration or by setting it directly with the SetDoctype member, + then a DOCTYPE declaration will be added immediately after the XML declaration. + @library{wxxml} @category{xml} - @see wxXmlNode, wxXmlAttribute + @see wxXmlNode, wxXmlAttribute, wxXmlDoctype */ class wxXmlDocument : public wxObject { @@ -612,6 +712,13 @@ public: */ const wxString& GetFileEncoding() const; + /** + Returns the DOCTYPE declaration data for the document. + + @since 3.1.0 + */ + const wxXmlDoctype& GetDoctype() const; + /** Returns the document node of the document. @@ -700,6 +807,14 @@ public: */ void SetFileEncoding(const wxString& encoding); + /** + Sets the data which will appear in the DOCTYPE declaration when the + document is saved. + + @since 3.1.0 + */ + void SetDoctype(const wxXmlDoctype& doctype); + /** Sets the root element node of this document. diff --git a/src/xml/xml.cpp b/src/xml/xml.cpp index 01a6f09e2a..4ea6b80baa 100644 --- a/src/xml/xml.cpp +++ b/src/xml/xml.cpp @@ -402,6 +402,56 @@ bool wxXmlNode::IsWhitespaceOnly() const +//----------------------------------------------------------------------------- +// wxXmlDoctype +//----------------------------------------------------------------------------- + +void wxXmlDoctype::Clear() +{ + m_rootName.clear(); + m_systemId.clear(); + m_publicId.clear(); +} + +wxString wxXmlDoctype::GetFullString() const +{ + wxString content; + if ( !m_rootName.empty() ) + { + content = m_rootName; + + if ( !m_publicId.empty() ) + { + content << wxS(" PUBLIC \"") << m_publicId << wxS("\""); + } + + if ( !m_systemId.empty() ) + { + if ( m_publicId.empty() ) + content << wxS(" SYSTEM"); + + // Prefer to use double quotes, but switch to single ones if a + // double quote appears inside the string to be quoted. + wxString quote; + if ( m_systemId.find('\"') == wxString::npos ) + quote = wxS('"'); + else if ( m_systemId.find('\'') == wxString::npos ) + quote = wxS('\''); + else // It's an error if we can't use either kind of quotes. + return wxString(); + + content << wxS(' ') << quote << m_systemId << quote; + } + } + + return content; +} + +bool wxXmlDoctype::IsValid() const +{ + return !GetFullString().empty(); +} + //----------------------------------------------------------------------------- // wxXmlDocument //----------------------------------------------------------------------------- @@ -452,6 +502,7 @@ void wxXmlDocument::DoCopy(const wxXmlDocument& doc) m_encoding = doc.m_encoding; #endif m_fileEncoding = doc.m_fileEncoding; + m_doctype = doc.m_doctype; if (doc.m_docNode) m_docNode = new wxXmlNode(*doc.m_docNode); @@ -607,6 +658,7 @@ struct wxXmlParsingContext node(NULL), lastChild(NULL), lastAsText(NULL), + doctype(NULL), removeWhiteOnlyNodes(false) {} @@ -617,6 +669,7 @@ struct wxXmlParsingContext wxXmlNode *lastAsText; // the last _text_ child of "node" wxString encoding; wxString version; + wxXmlDoctype *doctype; bool removeWhiteOnlyNodes; }; @@ -747,6 +800,21 @@ static void PIHnd(void *userData, const char *target, const char *data) ctx->lastAsText = NULL; } +static void StartDoctypeHnd(void *userData, const char *doctypeName, + const char *sysid, const char *pubid, + int WXUNUSED(has_internal_subset)) +{ + wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; + + *ctx->doctype = wxXmlDoctype(CharToString(ctx->conv, doctypeName), + CharToString(ctx->conv, sysid), + CharToString(ctx->conv, pubid)); +} + +static void EndDoctypeHnd(void *WXUNUSED(userData)) +{ +} + static void DefaultHnd(void *userData, const char *s, int len) { // XML header: @@ -819,6 +887,7 @@ bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding, int fl if ( encoding.CmpNoCase(wxS("UTF-8")) != 0 ) ctx.conv = new wxCSConv(encoding); #endif + ctx.doctype = &m_doctype; ctx.removeWhiteOnlyNodes = (flags & wxXMLDOC_KEEP_WHITESPACE_NODES) == 0; ctx.parser = parser; ctx.node = root; @@ -829,6 +898,7 @@ bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding, int fl XML_SetCdataSectionHandler(parser, StartCdataHnd, EndCdataHnd); XML_SetCommentHandler(parser, CommentHnd); XML_SetProcessingInstructionHandler(parser, PIHnd); + XML_SetDoctypeDeclHandler(parser, StartDoctypeHnd, EndDoctypeHnd); XML_SetDefaultHandler(parser, DefaultHnd); XML_SetUnknownEncodingHandler(parser, UnknownEncodingHnd, NULL); @@ -1133,6 +1203,17 @@ bool wxXmlDocument::Save(wxOutputStream& stream, int indentstep) const ); bool rc = OutputString(stream, dec, convMem.get(), convFile.get()); + if ( rc ) + { + const wxString doctype = m_doctype.GetFullString(); + if ( !doctype.empty() ) + { + rc = OutputString(stream, + wxS("\n"), + convMem.get(), convFile.get()); + } + } + wxXmlNode *node = GetDocumentNode(); if ( node ) node = node->GetChildren(); diff --git a/tests/xml/xmltest.cpp b/tests/xml/xmltest.cpp index e7f35ae7c9..a9de4217be 100644 --- a/tests/xml/xmltest.cpp +++ b/tests/xml/xmltest.cpp @@ -82,6 +82,8 @@ private: CPPUNIT_TEST( AppendToProlog ); CPPUNIT_TEST( SetRoot ); CPPUNIT_TEST( CopyNode ); + CPPUNIT_TEST( CopyDocument ); + CPPUNIT_TEST( Doctype ); CPPUNIT_TEST_SUITE_END(); void InsertChild(); @@ -94,6 +96,8 @@ private: void AppendToProlog(); void SetRoot(); void CopyNode(); + void CopyDocument(); + void Doctype(); wxDECLARE_NO_COPY_CLASS(XmlTestCase); }; @@ -207,6 +211,7 @@ void XmlTestCase::LoadSave() const char *xmlTextProlog = "\n" +"\n" "\n" "\n" "\n" @@ -507,3 +512,103 @@ void XmlTestCase::CopyNode() ; CPPUNIT_ASSERT_EQUAL( xmlTextResult, sos.GetString() ); } + +void XmlTestCase::CopyDocument() +{ + const char *xmlText = +"\n" +"\n" +"\n" +"\n" +" Text\n" +" \n" +"\n" + ; + wxXmlDocument doc1; + wxStringInputStream sis(xmlText); + CPPUNIT_ASSERT( doc1.Load(sis) ); + + wxXmlDocument doc2 = doc1; + + wxStringOutputStream sos; + CPPUNIT_ASSERT(doc2.Save(sos)); + + CPPUNIT_ASSERT_EQUAL( xmlText, sos.GetString() ); +} + +void XmlTestCase::Doctype() +{ + const char *xmlText = + "\n" + "\n" + "\n" + " \n" + "\n" + ; + + wxStringInputStream sis(xmlText); + wxXmlDocument doc; + CPPUNIT_ASSERT( doc.Load(sis) ); + + wxXmlDoctype dt = doc.GetDoctype(); + + CPPUNIT_ASSERT_EQUAL( "root", dt.GetRootName() ); + CPPUNIT_ASSERT_EQUAL( "System\"ID\"", dt.GetSystemId() ); + CPPUNIT_ASSERT_EQUAL( "Public-ID", dt.GetPublicId() ); + + CPPUNIT_ASSERT( dt.IsValid() ); + CPPUNIT_ASSERT_EQUAL( "root PUBLIC \"Public-ID\" 'System\"ID\"'", dt.GetFullString() ); + dt = wxXmlDoctype( dt.GetRootName(), dt.GetSystemId() ); + CPPUNIT_ASSERT( dt.IsValid() ); + CPPUNIT_ASSERT_EQUAL( "root SYSTEM 'System\"ID\"'", dt.GetFullString() ); + dt = wxXmlDoctype( dt.GetRootName() ); + CPPUNIT_ASSERT( dt.IsValid() ); + CPPUNIT_ASSERT_EQUAL( "root", dt.GetFullString() ); + + doc.SetDoctype(dt); + wxStringOutputStream sos; + CPPUNIT_ASSERT(doc.Save(sos)); + const char *xmlText1 = + "\n" + "\n" + "\n" + " \n" + "\n" + ; + CPPUNIT_ASSERT_EQUAL( xmlText1, sos.GetString() ); + + doc.SetDoctype(wxXmlDoctype()); + wxStringOutputStream sos2; + CPPUNIT_ASSERT(doc.Save(sos2)); + const char *xmlText2 = + "\n" + "\n" + " \n" + "\n" + ; + CPPUNIT_ASSERT_EQUAL( xmlText2, sos2.GetString() ); + + doc.SetDoctype(wxXmlDoctype("root", "Sys'id")); + wxStringOutputStream sos3; + CPPUNIT_ASSERT(doc.Save(sos3)); + const char *xmlText3 = + "\n" + "\n" + "\n" + " \n" + "\n" + ; + CPPUNIT_ASSERT_EQUAL( xmlText3, sos3.GetString() ); + + dt = wxXmlDoctype( "", "System\"ID\"", "Public-ID" ); + CPPUNIT_ASSERT( !dt.IsValid() ); + CPPUNIT_ASSERT_EQUAL( "", dt.GetFullString() ); + // Strictly speaking, this is illegal for XML but is legal for SGML. + dt = wxXmlDoctype( "root", "", "Public-ID" ); + CPPUNIT_ASSERT( dt.IsValid() ); + CPPUNIT_ASSERT_EQUAL( "root PUBLIC \"Public-ID\"", dt.GetFullString() ); + + // Using both single and double quotes in system ID is not allowed. + dt = wxXmlDoctype( "root", "O'Reilly (\"editor\")", "Public-ID" ); + CPPUNIT_ASSERT( !dt.IsValid() ); +}