X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/43a302f200f1e9f5f21380bbc7ba74ad8c22d6d6..7d6a4d96961eac84d05db8bb24c64d39003f6e54:/src/xml/xml.cpp?ds=inline diff --git a/src/xml/xml.cpp b/src/xml/xml.cpp index 4941e6104b..ee78a9aca4 100644 --- a/src/xml/xml.cpp +++ b/src/xml/xml.cpp @@ -29,6 +29,8 @@ #include "wx/datstrm.h" #include "wx/zstream.h" #include "wx/strconv.h" +#include "wx/scopedptr.h" +#include "wx/versioninfo.h" #include "expat.h" // from Expat @@ -53,8 +55,11 @@ wxXmlNode::wxXmlNode(wxXmlNode *parent,wxXmlNodeType type, : m_type(type), m_name(name), m_content(content), m_attrs(attrs), m_parent(parent), m_children(NULL), m_next(next), - m_lineNo(lineNo) + m_lineNo(lineNo), + m_noConversion(false) { + wxASSERT_MSG ( type != wxXML_ELEMENT_NODE || content.empty(), "element nodes can't have content" ); + if (m_parent) { if (m_parent->m_children) @@ -73,8 +78,10 @@ wxXmlNode::wxXmlNode(wxXmlNodeType type, const wxString& name, : m_type(type), m_name(name), m_content(content), m_attrs(NULL), m_parent(NULL), m_children(NULL), m_next(NULL), - m_lineNo(lineNo) -{} + m_lineNo(lineNo), m_noConversion(false) +{ + wxASSERT_MSG ( type != wxXML_ELEMENT_NODE || content.empty(), "element nodes can't have content" ); +} wxXmlNode::wxXmlNode(const wxXmlNode& node) { @@ -114,6 +121,7 @@ void wxXmlNode::DoCopy(const wxXmlNode& node) m_name = node.m_name; m_content = node.m_content; m_lineNo = node.m_lineNo; + m_noConversion = node.m_noConversion; m_children = NULL; wxXmlNode *n = node.m_children; @@ -392,28 +400,28 @@ bool wxXmlNode::IsWhitespaceOnly() const //----------------------------------------------------------------------------- wxXmlDocument::wxXmlDocument() - : m_version(wxT("1.0")), m_fileEncoding(wxT("utf-8")), m_root(NULL) + : m_version(wxS("1.0")), m_fileEncoding(wxS("UTF-8")), m_docNode(NULL) { #if !wxUSE_UNICODE - m_encoding = wxT("UTF-8"); + m_encoding = wxS("UTF-8"); #endif } 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); } } @@ -425,7 +433,7 @@ wxXmlDocument::wxXmlDocument(const wxXmlDocument& doc) wxXmlDocument& wxXmlDocument::operator=(const wxXmlDocument& doc) { - wxDELETE(m_root); + wxDELETE(m_docNode); DoCopy(doc); return *this; } @@ -438,16 +446,16 @@ 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) { wxFileInputStream stream(filename); - if (!stream.Ok()) + if (!stream.IsOk()) return false; return Load(stream, encoding, flags); } @@ -455,12 +463,96 @@ bool wxXmlDocument::Load(const wxString& filename, const wxString& encoding, int bool wxXmlDocument::Save(const wxString& filename, int indentstep) const { wxFileOutputStream stream(filename); - if (!stream.Ok()) + if (!stream.IsOk()) return false; 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) +{ + if (root) + { + wxASSERT_MSG( root->GetType() == wxXML_ELEMENT_NODE, + "Can only set an element type node as 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) + { + 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); + m_docNode->SetChildren(root); + } + if (root) + 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 @@ -485,7 +577,7 @@ static wxString CharToString(wxMBConv *conv, #endif // !wxUSE_UNICODE wxUnusedVar(conv); - return wxString::FromUTF8(s, len); + return wxString::FromUTF8Unchecked(s, len); } // returns true if the given string contains only whitespaces @@ -494,7 +586,7 @@ bool wxIsWhiteOnly(const wxString& buf) for ( wxString::const_iterator i = buf.begin(); i != buf.end(); ++i ) { wxChar c = *i; - if ( c != wxT(' ') && c != wxT('\t') && c != wxT('\n') && c != wxT('\r')) + if ( c != wxS(' ') && c != wxS('\t') && c != wxS('\n') && c != wxS('\r')) return false; } return true; @@ -505,7 +597,6 @@ struct wxXmlParsingContext { wxXmlParsingContext() : conv(NULL), - root(NULL), node(NULL), lastChild(NULL), lastAsText(NULL), @@ -514,7 +605,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" @@ -547,16 +637,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 @@ -594,7 +676,7 @@ static void TextHnd(void *userData, const char *s, int len) if (!whiteOnly) { wxXmlNode *textnode = - new wxXmlNode(wxXML_TEXT_NODE, wxT("text"), str, + new wxXmlNode(wxXML_TEXT_NODE, wxS("text"), str, XML_GetCurrentLineNumber(ctx->parser)); ASSERT_LAST_CHILD_OK(ctx); @@ -609,7 +691,7 @@ static void StartCdataHnd(void *userData) wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; wxXmlNode *textnode = - new wxXmlNode(wxXML_CDATA_SECTION_NODE, wxT("cdata"), wxT(""), + new wxXmlNode(wxXML_CDATA_SECTION_NODE, wxS("cdata"), wxS(""), XML_GetCurrentLineNumber(ctx->parser)); ASSERT_LAST_CHILD_OK(ctx); @@ -617,25 +699,44 @@ static void StartCdataHnd(void *userData) ctx->lastChild= ctx->lastAsText = textnode; } +static void EndCdataHnd(void *userData) +{ + wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; + + // we need to reset this pointer so that subsequent text nodes don't append + // their contents to this one but create new wxXML_TEXT_NODE objects (or + // not create anything at all if only white space follows the CDATA section + // and wxXMLDOC_KEEP_WHITESPACE_NODES is not used as is commonly the case) + ctx->lastAsText = NULL; +} + static void CommentHnd(void *userData, const char *data) { wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; - if (ctx->node) - { - wxXmlNode *commentnode = - new wxXmlNode(wxXML_COMMENT_NODE, - wxT("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; } @@ -648,10 +749,10 @@ static void DefaultHnd(void *userData, const char *s, int len) wxString buf = CharToString(ctx->conv, s, (size_t)len); int pos; - pos = buf.Find(wxT("encoding=")); + pos = buf.Find(wxS("encoding=")); if (pos != wxNOT_FOUND) ctx->encoding = buf.Mid(pos + 10).BeforeFirst(buf[(size_t)pos+9]); - pos = buf.Find(wxT("version=")); + pos = buf.Find(wxS("version=")); if (pos != wxNOT_FOUND) ctx->version = buf.Mid(pos + 9).BeforeFirst(buf[(size_t)pos+8]); } @@ -703,21 +804,24 @@ 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 = wxT("UTF-8"); // default in absence of encoding="" + ctx.encoding = wxS("UTF-8"); // default in absence of encoding="" ctx.conv = NULL; #if !wxUSE_UNICODE - if ( encoding.CmpNoCase(wxT("UTF-8")) != 0 ) + if ( encoding.CmpNoCase(wxS("UTF-8")) != 0 ) ctx.conv = new wxCSConv(encoding); #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_SetStartCdataSectionHandler(parser, StartCdataHnd); + XML_SetCdataSectionHandler(parser, StartCdataHnd, EndCdataHnd);; XML_SetCommentHandler(parser, CommentHnd); + XML_SetProcessingInstructionHandler(parser, PIHnd); XML_SetDefaultHandler(parser, DefaultHnd); XML_SetUnknownEncodingHandler(parser, UnknownEncodingHnd, NULL); @@ -732,7 +836,7 @@ bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding, int fl *wxConvCurrent); wxLogError(_("XML parsing error: '%s' at line %d"), error.c_str(), - XML_GetCurrentLineNumber(parser)); + (int)XML_GetCurrentLineNumber(parser)); ok = false; break; } @@ -744,11 +848,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); @@ -767,193 +871,281 @@ bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding, int fl // wxXmlDocument saving routines //----------------------------------------------------------------------------- +// helpers for XML generation +namespace +{ + // write string to output: -inline static void OutputString(wxOutputStream& stream, const wxString& str, - wxMBConv *convMem = NULL, - wxMBConv *convFile = NULL) +bool OutputString(wxOutputStream& stream, + const wxString& str, + wxMBConv *convMem, + wxMBConv *convFile) { if (str.empty()) - return; + return true; #if wxUSE_UNICODE wxUnusedVar(convMem); + if ( !convFile ) + convFile = &wxConvUTF8; - const wxWX2MBbuf buf(str.mb_str(*(convFile ? convFile : &wxConvUTF8))); - stream.Write((const char*)buf, strlen((const char*)buf)); + const wxScopedCharBuffer buf(str.mb_str(*convFile)); + if ( !buf.length() ) + { + // conversion failed, can't write this string in an XML file in this + // (presumably non-UTF-8) encoding + return false; + } + + stream.Write(buf, buf.length()); #else // !wxUSE_UNICODE if ( convFile && convMem ) { wxString str2(str.wc_str(*convMem), *convFile); - stream.Write(str2.mb_str(), str2.Len()); + stream.Write(str2.mb_str(), str2.length()); } else // no conversions to do { - stream.Write(str.mb_str(), str.Len()); + stream.Write(str.mb_str(), str.length()); } #endif // wxUSE_UNICODE/!wxUSE_UNICODE + + return stream.IsOk(); } -// flags for OutputStringEnt() -enum +enum EscapingMode { - XML_ESCAPE_QUOTES = 1 + Escape_Text, + Escape_Attribute }; // Same as above, but create entities first. -// Translates '<' to "<", '>' to ">" and '&' to "&" -static void OutputStringEnt(wxOutputStream& stream, const wxString& str, - wxMBConv *convMem = NULL, - wxMBConv *convFile = NULL, - int flags = 0) -{ - wxString buf; - size_t i, last, len; - wxChar c; - - len = str.Len(); - last = 0; - for (i = 0; i < len; i++) - { - c = str.GetChar(i); - if (c == wxT('<') || c == wxT('>') || - (c == wxT('&') && str.Mid(i+1, 4) != wxT("amp;")) || - ((flags & XML_ESCAPE_QUOTES) && c == wxT('"'))) +// Translates '<' to "<", '>' to ">" and so on, according to the spec: +// http://www.w3.org/TR/2000/WD-xml-c14n-20000119.html#charescaping +bool OutputEscapedString(wxOutputStream& stream, + const wxString& str, + wxMBConv *convMem, + wxMBConv *convFile, + EscapingMode mode) +{ + wxString escaped; + escaped.reserve(str.length()); + + for ( wxString::const_iterator i = str.begin(); i != str.end(); ++i ) + { + const wxChar c = *i; + + switch ( c ) { - OutputString(stream, str.Mid(last, i - last), convMem, convFile); - switch (c) - { - case wxT('<'): - OutputString(stream, wxT("<")); - break; - case wxT('>'): - OutputString(stream, wxT(">")); - break; - case wxT('&'): - OutputString(stream, wxT("&")); - break; - case wxT('"'): - OutputString(stream, wxT(""")); - break; - default: - break; - } - last = i + 1; + case wxS('<'): + escaped.append(wxS("<")); + break; + case wxS('>'): + escaped.append(wxS(">")); + break; + case wxS('&'): + escaped.append(wxS("&")); + break; + case wxS('\r'): + escaped.append(wxS(" ")); + break; + default: + if ( mode == Escape_Attribute ) + { + switch ( c ) + { + case wxS('"'): + escaped.append(wxS(""")); + break; + case wxS('\t'): + escaped.append(wxS(" ")); + break; + case wxS('\n'): + escaped.append(wxS(" ")); + break; + default: + escaped.append(c); + } + + } + else + { + escaped.append(c); + } } } - OutputString(stream, str.Mid(last, i - last), convMem, convFile); + + return OutputString(stream, escaped, convMem, convFile); } -inline static void OutputIndentation(wxOutputStream& stream, int indent) +bool OutputIndentation(wxOutputStream& stream, + int indent, + wxMBConv *convMem, + wxMBConv *convFile) { - wxString str = wxT("\n"); - for (int i = 0; i < indent; i++) - str << wxT(' ') << wxT(' '); - OutputString(stream, str); + wxString str(wxS("\n")); + str += wxString(indent, wxS(' ')); + return OutputString(stream, str, convMem, convFile); } -static void OutputNode(wxOutputStream& stream, wxXmlNode *node, int indent, - wxMBConv *convMem, wxMBConv *convFile, int indentstep) +bool OutputNode(wxOutputStream& stream, + wxXmlNode *node, + int indent, + wxMBConv *convMem, + wxMBConv *convFile, + int indentstep) { - wxXmlNode *n, *prev; - wxXmlAttribute *attr; - + bool rc; switch (node->GetType()) { case wxXML_CDATA_SECTION_NODE: - OutputString( stream, wxT("GetContent() ); - OutputString( stream, wxT("]]>") ); + rc = OutputString(stream, wxS("GetContent(), convMem, convFile) && + OutputString(stream, wxS("]]>"), convMem, convFile); break; case wxXML_TEXT_NODE: - OutputStringEnt(stream, node->GetContent(), convMem, convFile); + if (node->GetNoConversion()) + { + stream.Write(node->GetContent().c_str(), node->GetContent().Length()); + rc = true; + } + else + rc = OutputEscapedString(stream, node->GetContent(), + convMem, convFile, + Escape_Text); break; case wxXML_ELEMENT_NODE: - OutputString(stream, wxT("<")); - OutputString(stream, node->GetName()); + rc = OutputString(stream, wxS("<"), convMem, convFile) && + OutputString(stream, node->GetName(), convMem, convFile); - attr = node->GetAttributes(); - while (attr) + if ( rc ) { - OutputString(stream, wxT(" ") + attr->GetName() + wxT("=\"")); - OutputStringEnt(stream, attr->GetValue(), convMem, convFile, - XML_ESCAPE_QUOTES); - OutputString(stream, wxT("\"")); - attr = attr->GetNext(); + for ( wxXmlAttribute *attr = node->GetAttributes(); + attr && rc; + attr = attr->GetNext() ) + { + rc = OutputString(stream, + wxS(" ") + attr->GetName() + wxS("=\""), + convMem, convFile) && + OutputEscapedString(stream, attr->GetValue(), + convMem, convFile, + Escape_Attribute) && + OutputString(stream, wxS("\""), convMem, convFile); + } } - if (node->GetChildren()) + if ( node->GetChildren() ) { - OutputString(stream, wxT(">")); - prev = NULL; - n = node->GetChildren(); - while (n) + rc = OutputString(stream, wxS(">"), convMem, convFile); + + wxXmlNode *prev = NULL; + for ( wxXmlNode *n = node->GetChildren(); + n && rc; + n = n->GetNext() ) { - if (indentstep >= 0 && n && n->GetType() != wxXML_TEXT_NODE) - OutputIndentation(stream, indent + indentstep); - OutputNode(stream, n, indent + indentstep, convMem, convFile, indentstep); + if ( indentstep >= 0 && n->GetType() != wxXML_TEXT_NODE ) + { + rc = OutputIndentation(stream, indent + indentstep, + convMem, convFile); + } + + if ( rc ) + rc = OutputNode(stream, n, indent + indentstep, + convMem, convFile, indentstep); + prev = n; - n = n->GetNext(); } - if (indentstep >= 0 && prev && prev->GetType() != wxXML_TEXT_NODE) - OutputIndentation(stream, indent); - OutputString(stream, wxT("GetName()); - OutputString(stream, wxT(">")); + + if ( rc && indentstep >= 0 && + prev && prev->GetType() != wxXML_TEXT_NODE ) + { + rc = OutputIndentation(stream, indent, convMem, convFile); + } + + if ( rc ) + { + rc = OutputString(stream, wxS("GetName(), + convMem, convFile) && + OutputString(stream, wxS(">"), convMem, convFile); + } + } + else // no children, output "" + { + rc = OutputString(stream, wxS("/>"), convMem, convFile); } - else - OutputString(stream, wxT("/>")); break; case wxXML_COMMENT_NODE: - OutputString(stream, wxT("")); + rc = 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(wxT("unsupported node type")); + wxFAIL_MSG("unsupported node type"); + rc = false; } + + return rc; } +} // anonymous namespace + bool wxXmlDocument::Save(wxOutputStream& stream, int indentstep) const { if ( !IsOk() ) return false; - wxString s; - - wxMBConv *convMem = NULL, - *convFile; + wxScopedPtr convMem, convFile; #if wxUSE_UNICODE - convFile = new wxCSConv(GetFileEncoding()); - convMem = NULL; + convFile.reset(new wxCSConv(GetFileEncoding())); #else if ( GetFileEncoding().CmpNoCase(GetEncoding()) != 0 ) { - convFile = new wxCSConv(GetFileEncoding()); - convMem = new wxCSConv(GetEncoding()); - } - else // file and in-memory encodings are the same, no conversion needed - { - convFile = - convMem = NULL; + convFile.reset(new wxCSConv(GetFileEncoding())); + convMem.reset(new wxCSConv(GetEncoding())); } + //else: file and in-memory encodings are the same, no conversion needed #endif - s.Printf(wxT("\n"), - GetVersion().c_str(), GetFileEncoding().c_str()); - OutputString(stream, s); + wxString dec = wxString::Format( + wxS("\n"), + GetVersion(), GetFileEncoding() + ); + bool rc = OutputString(stream, dec, convMem.get(), convFile.get()); - OutputNode(stream, GetRoot(), 0, convMem, convFile, indentstep); - OutputString(stream, wxT("\n")); + wxXmlNode *node = GetDocumentNode(); + if ( node ) + node = node->GetChildren(); - delete convFile; - delete convMem; + 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; +} - return true; +/*static*/ wxVersionInfo wxXmlDocument::GetLibraryVersionInfo() +{ + return wxVersionInfo("expat", + XML_MAJOR_VERSION, + XML_MINOR_VERSION, + XML_MICRO_VERSION); } #endif // wxUSE_XML