X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/27b0c286444b5f2404f29fb2bc233bfcfaaa6bba..db71eb0640221199170ac9ea83371c2d467fa456:/src/xml/xml.cpp diff --git a/src/xml/xml.cpp b/src/xml/xml.cpp index 3b59fcf271..6a76beb7c6 100644 --- a/src/xml/xml.cpp +++ b/src/xml/xml.cpp @@ -1,5 +1,5 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: xml.cpp +// Name: src/xml/xml.cpp // Purpose: wxXmlDocument - XML parser & data holder class // Author: Vaclav Slavik // Created: 2000/03/05 @@ -8,10 +8,6 @@ // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// -#ifdef __GNUG__ -#pragma implementation "xml.h" -#endif - // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" @@ -19,19 +15,34 @@ #pragma hdrstop #endif +#if wxUSE_XML + #include "wx/xml/xml.h" -#if wxUSE_XML +#ifndef WX_PRECOMP + #include "wx/intl.h" + #include "wx/log.h" + #include "wx/app.h" +#endif #include "wx/wfstream.h" #include "wx/datstrm.h" #include "wx/zstream.h" -#include "wx/log.h" -#include "wx/intl.h" #include "wx/strconv.h" #include "expat.h" // from Expat +// DLL options compatibility check: +WX_CHECK_BUILD_OPTIONS("wxXML") + + +IMPLEMENT_CLASS(wxXmlDocument, wxObject) + + +// a private utility used by wxXML +static bool wxIsWhiteOnly(const wxChar *buf); + + //----------------------------------------------------------------------------- // wxXmlNode //----------------------------------------------------------------------------- @@ -123,11 +134,11 @@ bool wxXmlNode::HasProp(const wxString& propName) const while (prop) { - if (prop->GetName() == propName) return TRUE; + if (prop->GetName() == propName) return true; prop = prop->GetNext(); } - return FALSE; + return false; } bool wxXmlNode::GetPropVal(const wxString& propName, wxString *value) const @@ -139,12 +150,12 @@ bool wxXmlNode::GetPropVal(const wxString& propName, wxString *value) const if (prop->GetName() == propName) { *value = prop->GetValue(); - return TRUE; + return true; } prop = prop->GetNext(); } - return FALSE; + return false; } wxString wxXmlNode::GetPropVal(const wxString& propName, const wxString& defaultVal) const @@ -152,8 +163,8 @@ wxString wxXmlNode::GetPropVal(const wxString& propName, const wxString& default wxString tmp; if (GetPropVal(propName, &tmp)) return tmp; - else - return defaultVal; + + return defaultVal; } void wxXmlNode::AddChild(wxXmlNode *child) @@ -170,33 +181,52 @@ void wxXmlNode::AddChild(wxXmlNode *child) child->m_parent = this; } -void wxXmlNode::InsertChild(wxXmlNode *child, wxXmlNode *before_node) +bool wxXmlNode::InsertChild(wxXmlNode *child, wxXmlNode *before_node) { - wxASSERT_MSG(before_node->GetParent() == this, wxT("wxXmlNode::InsertChild - the node has incorrect parent")); + wxCHECK_MSG(before_node == NULL || before_node->GetParent() == this, false, + wxT("wxXmlNode::InsertChild - the node has incorrect parent")); + wxCHECK_MSG(child, false, wxT("Cannot insert a NULL pointer!")); if (m_children == before_node) m_children = child; + else if (m_children == NULL) + { + if (before_node != NULL) + return false; // we have no children so we don't need to search + m_children = child; + } + else if (before_node == NULL) + { + // prepend child + child->m_parent = this; + child->m_next = m_children; + m_children = child; + return true; + } else { wxXmlNode *ch = m_children; - while (ch->m_next != before_node) ch = ch->m_next; + while (ch && ch->m_next != before_node) ch = ch->m_next; + if (!ch) + return false; // before_node not found ch->m_next = child; } child->m_parent = this; child->m_next = before_node; + return true; } bool wxXmlNode::RemoveChild(wxXmlNode *child) { if (m_children == NULL) - return FALSE; + return false; else if (m_children == child) { m_children = child->m_next; child->m_parent = NULL; child->m_next = NULL; - return TRUE; + return true; } else { @@ -208,11 +238,11 @@ bool wxXmlNode::RemoveChild(wxXmlNode *child) ch->m_next = child->m_next; child->m_parent = NULL; child->m_next = NULL; - return TRUE; + return true; } ch = ch->m_next; } - return FALSE; + return false; } } @@ -238,7 +268,7 @@ bool wxXmlNode::DeleteProperty(const wxString& name) wxXmlProperty *prop; if (m_properties == NULL) - return FALSE; + return false; else if (m_properties->GetName() == name) { @@ -246,7 +276,7 @@ bool wxXmlNode::DeleteProperty(const wxString& name) m_properties = prop->GetNext(); prop->SetNext(NULL); delete prop; - return TRUE; + return true; } else @@ -260,14 +290,50 @@ bool wxXmlNode::DeleteProperty(const wxString& name) p->SetNext(prop->GetNext()); prop->SetNext(NULL); delete prop; - return TRUE; + return true; } p = p->GetNext(); } - return FALSE; + return false; } } +wxString wxXmlNode::GetNodeContent() const +{ + wxXmlNode *n = GetChildren(); + + while (n) + { + if (n->GetType() == wxXML_TEXT_NODE || + n->GetType() == wxXML_CDATA_SECTION_NODE) + return n->GetContent(); + n = n->GetNext(); + } + return wxEmptyString; +} + +int wxXmlNode::GetDepth(wxXmlNode *grandparent) const +{ + const wxXmlNode *n = this; + int ret = -1; + + do + { + ret++; + n = n->GetParent(); + if (n == grandparent) + return ret; + + } while (n); + + return wxNOT_FOUND; +} + +bool wxXmlNode::IsWhitespaceOnly() const +{ + return wxIsWhiteOnly(m_content); +} + //----------------------------------------------------------------------------- @@ -283,7 +349,7 @@ wxXmlDocument::wxXmlDocument() } wxXmlDocument::wxXmlDocument(const wxString& filename, const wxString& encoding) - : wxObject(), m_root(NULL) + :wxObject(), m_root(NULL) { if ( !Load(filename, encoding) ) { @@ -292,7 +358,7 @@ wxXmlDocument::wxXmlDocument(const wxString& filename, const wxString& encoding) } wxXmlDocument::wxXmlDocument(wxInputStream& stream, const wxString& encoding) - : wxObject(), m_root(NULL) + :wxObject(), m_root(NULL) { if ( !Load(stream, encoding) ) { @@ -301,6 +367,7 @@ wxXmlDocument::wxXmlDocument(wxInputStream& stream, const wxString& encoding) } wxXmlDocument::wxXmlDocument(const wxXmlDocument& doc) + :wxObject() { DoCopy(doc); } @@ -322,16 +389,20 @@ void wxXmlDocument::DoCopy(const wxXmlDocument& doc) m_root = new wxXmlNode(*doc.m_root); } -bool wxXmlDocument::Load(const wxString& filename, const wxString& encoding) +bool wxXmlDocument::Load(const wxString& filename, const wxString& encoding, int flags) { wxFileInputStream stream(filename); - return Load(stream, encoding); + if (!stream.Ok()) + return false; + return Load(stream, encoding, flags); } -bool wxXmlDocument::Save(const wxString& filename) const +bool wxXmlDocument::Save(const wxString& filename, int indentstep) const { wxFileOutputStream stream(filename); - return Save(stream); + if (!stream.Ok()) + return false; + return Save(stream, indentstep); } @@ -340,36 +411,42 @@ bool wxXmlDocument::Save(const wxString& filename) const // wxXmlDocument loading routines //----------------------------------------------------------------------------- -/* - FIXME: - - process all elements, including CDATA - */ - -// converts Expat-produced string in UTF-8 into wxString. -inline static wxString CharToString(wxMBConv *conv, +// converts Expat-produced string in UTF-8 into wxString using the specified +// conv or keep in UTF-8 if conv is NULL +static wxString CharToString(wxMBConv *conv, const char *s, size_t len = wxSTRING_MAXLEN) { #if wxUSE_UNICODE - (void)conv; + wxUnusedVar(conv); + return wxString(s, wxConvUTF8, len); -#else +#else // !wxUSE_UNICODE if ( conv ) { - size_t nLen = (len != wxSTRING_MAXLEN) ? len : - nLen = wxConvUTF8.MB2WC((wchar_t*) NULL, s, 0); + // there can be no embedded NULs in this string so we don't need the + // output length, it will be NUL-terminated + const wxWCharBuffer wbuf( + wxConvUTF8.cMB2WC(s, len == wxSTRING_MAXLEN ? wxNO_LEN : len, NULL)); - wchar_t *buf = new wchar_t[nLen+1]; - wxConvUTF8.MB2WC(buf, s, nLen); - buf[nLen] = 0; - wxString str(buf, *conv, len); - delete[] buf; - return str; + return wxString(wbuf, *conv); } - else - return wxString(s, len); -#endif + else // already in UTF-8, no conversion needed + { + return wxString(s, len != wxSTRING_MAXLEN ? len : strlen(s)); + } +#endif // wxUSE_UNICODE/!wxUSE_UNICODE +} + +// returns true if the given string contains only whitespaces +bool wxIsWhiteOnly(const wxChar *buf) +{ + for (const wxChar *c = buf; *c != wxT('\0'); c++) + if (*c != wxT(' ') && *c != wxT('\t') && *c != wxT('\n') && *c != wxT('\r')) + return false; + return true; } + struct wxXmlParsingContext { wxMBConv *conv; @@ -378,8 +455,11 @@ struct wxXmlParsingContext wxXmlNode *lastAsText; wxString encoding; wxString version; + bool bLastCdata; + bool removeWhiteOnlyNodes; }; +extern "C" { static void StartElementHnd(void *userData, const char *name, const char **atts) { wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; @@ -397,7 +477,9 @@ static void StartElementHnd(void *userData, const char *name, const char **atts) ctx->node = node; ctx->lastAsText = NULL; } +} +extern "C" { static void EndElementHnd(void *userData, const char* WXUNUSED(name)) { wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; @@ -405,40 +487,63 @@ static void EndElementHnd(void *userData, const char* WXUNUSED(name)) ctx->node = ctx->node->GetParent(); ctx->lastAsText = NULL; } +} +extern "C" { static void TextHnd(void *userData, const char *s, int len) { wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; - char *buf = new char[len + 1]; - - buf[len] = '\0'; - memcpy(buf, s, (size_t)len); + wxString str = CharToString(ctx->conv, s, len); if (ctx->lastAsText) { - ctx->lastAsText->SetContent(ctx->lastAsText->GetContent() + - CharToString(ctx->conv, buf)); + if ( ctx->bLastCdata ) + { + ctx->lastAsText->SetContent(ctx->lastAsText->GetContent() + + CharToString(NULL, s, len)); + } + else + { + ctx->lastAsText->SetContent(ctx->lastAsText->GetContent() + str); + } } else { - bool whiteOnly = TRUE; - for (char *c = buf; *c != '\0'; c++) - if (*c != ' ' && *c != '\t' && *c != '\n' && *c != '\r') - { - whiteOnly = FALSE; - break; - } + bool whiteOnly = false; + if (ctx->removeWhiteOnlyNodes) + whiteOnly = wxIsWhiteOnly(str); + if (!whiteOnly) { - ctx->lastAsText = new wxXmlNode(wxXML_TEXT_NODE, wxT("text"), - CharToString(ctx->conv, buf)); + ctx->lastAsText = new wxXmlNode(wxXML_TEXT_NODE, wxT("text"), str); ctx->node->AddChild(ctx->lastAsText); } } +} +} + +extern "C" { +static void StartCdataHnd(void *userData) +{ + wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; + + ctx->bLastCdata = true; + + ctx->lastAsText = new wxXmlNode(wxXML_CDATA_SECTION_NODE, wxT("cdata"),wxT("")); + ctx->node->AddChild(ctx->lastAsText); +} +} + +extern "C" { +static void EndCdataHnd(void *userData) +{ + wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; - delete[] buf; + ctx->bLastCdata = false; +} } +extern "C" { static void CommentHnd(void *userData, const char *data) { wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; @@ -453,7 +558,9 @@ static void CommentHnd(void *userData, const char *data) } ctx->lastAsText = NULL; } +} +extern "C" { static void DefaultHnd(void *userData, const char *s, int len) { // XML header: @@ -471,14 +578,17 @@ static void DefaultHnd(void *userData, const char *s, int len) ctx->version = buf.Mid(pos + 9).BeforeFirst(buf[(size_t)pos+8]); } } +} +extern "C" { static int UnknownEncodingHnd(void * WXUNUSED(encodingHandlerData), const XML_Char *name, XML_Encoding *info) { // We must build conversion table for expat. The easiest way to do so // is to let wxCSConv convert as string containing all characters to // wide character representation: - wxCSConv conv(wxString(name, wxConvLibc)); + wxString str(name, wxConvLibc); + wxCSConv conv(str); char mbBuf[2]; wchar_t wcBuf[10]; size_t i; @@ -495,15 +605,16 @@ static int UnknownEncodingHnd(void * WXUNUSED(encodingHandlerData), } info->map[i+1] = (int)wcBuf[0]; } - + info->data = NULL; info->convert = NULL; info->release = NULL; return 1; } +} -bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding) +bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding, int flags) { #if wxUSE_UNICODE (void)encoding; @@ -524,10 +635,13 @@ bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding) if ( encoding != wxT("UTF-8") && encoding != wxT("utf-8") ) ctx.conv = new wxCSConv(encoding); #endif + ctx.removeWhiteOnlyNodes = (flags & wxXMLDOC_KEEP_WHITESPACE_NODES) == 0; + ctx.bLastCdata = false; XML_SetUserData(parser, (void*)&ctx); XML_SetElementHandler(parser, StartElementHnd, EndElementHnd); XML_SetCharacterDataHandler(parser, TextHnd); + XML_SetCdataSectionHandler(parser, StartCdataHnd, EndCdataHnd ); XML_SetCommentHandler(parser, CommentHnd); XML_SetDefaultHandler(parser, DefaultHnd); XML_SetUnknownEncodingHandler(parser, UnknownEncodingHnd, NULL); @@ -539,8 +653,10 @@ bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding) done = (len < BUFSIZE); if (!XML_Parse(parser, buf, len, done)) { + wxString error(XML_ErrorString(XML_GetErrorCode(parser)), + *wxConvCurrent); wxLogError(_("XML parsing error: '%s' at line %d"), - XML_ErrorString(XML_GetErrorCode(parser)), + error.c_str(), XML_GetCurrentLineNumber(parser)); ok = false; break; @@ -549,10 +665,16 @@ bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding) if (ok) { - SetVersion(ctx.version); - SetFileEncoding(ctx.encoding); + if (!ctx.version.empty()) + SetVersion(ctx.version); + if (!ctx.encoding.empty()) + SetFileEncoding(ctx.encoding); SetRoot(ctx.root); } + else + { + delete ctx.root; + } XML_ParserFree(parser); #if !wxUSE_UNICODE @@ -572,27 +694,42 @@ bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding) // write string to output: inline static void OutputString(wxOutputStream& stream, const wxString& str, - wxMBConv *convMem, wxMBConv *convFile) + wxMBConv *convMem = NULL, + wxMBConv *convFile = NULL) { - if (str.IsEmpty()) return; + if (str.empty()) + return; + #if wxUSE_UNICODE - const wxWX2MBbuf buf(str.mb_str(convFile ? *convFile : wxConvUTF8)); + wxUnusedVar(convMem); + + const wxWX2MBbuf buf(str.mb_str(*(convFile ? convFile : &wxConvUTF8))); stream.Write((const char*)buf, strlen((const char*)buf)); -#else - if ( convFile == NULL ) - stream.Write(str.mb_str(), str.Len()); - else +#else // !wxUSE_UNICODE + if ( convFile && convMem ) { wxString str2(str.wc_str(*convMem), *convFile); stream.Write(str2.mb_str(), str2.Len()); } -#endif + else // no conversions to do + { + stream.Write(str.mb_str(), str.Len()); + } +#endif // wxUSE_UNICODE/!wxUSE_UNICODE } +// flags for OutputStringEnt() +enum +{ + XML_ESCAPE_QUOTES = 1 +}; + // Same as above, but create entities first. // Translates '<' to "<", '>' to ">" and '&' to "&" static void OutputStringEnt(wxOutputStream& stream, const wxString& str, - wxMBConv *convMem, wxMBConv *convFile) + wxMBConv *convMem = NULL, + wxMBConv *convFile = NULL, + int flags = 0) { wxString buf; size_t i, last, len; @@ -604,21 +741,26 @@ static void OutputStringEnt(wxOutputStream& stream, const wxString& str, { c = str.GetChar(i); if (c == wxT('<') || c == wxT('>') || - (c == wxT('&') && str.Mid(i+1, 4) != wxT("amp;"))) + (c == wxT('&') && str.Mid(i+1, 4) != wxT("amp;")) || + ((flags & XML_ESCAPE_QUOTES) && c == wxT('"'))) { OutputString(stream, str.Mid(last, i - last), convMem, convFile); switch (c) { case wxT('<'): - OutputString(stream, wxT("<"), NULL, NULL); + OutputString(stream, wxT("<")); break; case wxT('>'): - OutputString(stream, wxT(">"), NULL, NULL); + OutputString(stream, wxT(">")); break; case wxT('&'): - OutputString(stream, wxT("&"), NULL, NULL); + OutputString(stream, wxT("&")); + break; + case wxT('"'): + OutputString(stream, wxT(""")); + break; + default: break; - default: break; } last = i + 1; } @@ -631,62 +773,68 @@ inline static void OutputIndentation(wxOutputStream& stream, int indent) wxString str = wxT("\n"); for (int i = 0; i < indent; i++) str << wxT(' ') << wxT(' '); - OutputString(stream, str, NULL, NULL); + OutputString(stream, str); } static void OutputNode(wxOutputStream& stream, wxXmlNode *node, int indent, - wxMBConv *convMem, wxMBConv *convFile) + wxMBConv *convMem, wxMBConv *convFile, int indentstep) { wxXmlNode *n, *prev; wxXmlProperty *prop; switch (node->GetType()) { + case wxXML_CDATA_SECTION_NODE: + OutputString( stream, wxT("GetContent() ); + OutputString( stream, wxT("]]>") ); + break; + case wxXML_TEXT_NODE: OutputStringEnt(stream, node->GetContent(), convMem, convFile); break; case wxXML_ELEMENT_NODE: - OutputString(stream, wxT("<"), NULL, NULL); - OutputString(stream, node->GetName(), NULL, NULL); + OutputString(stream, wxT("<")); + OutputString(stream, node->GetName()); prop = node->GetProperties(); while (prop) { - OutputString(stream, wxT(" ") + prop->GetName() + - wxT("=\"") + prop->GetValue() + wxT("\""), - NULL, NULL); - // FIXME - what if prop contains '"'? + OutputString(stream, wxT(" ") + prop->GetName() + wxT("=\"")); + OutputStringEnt(stream, prop->GetValue(), convMem, convFile, + XML_ESCAPE_QUOTES); + OutputString(stream, wxT("\"")); prop = prop->GetNext(); } if (node->GetChildren()) { - OutputString(stream, wxT(">"), NULL, NULL); + OutputString(stream, wxT(">")); prev = NULL; n = node->GetChildren(); while (n) { - if (n && n->GetType() != wxXML_TEXT_NODE) - OutputIndentation(stream, indent + 1); - OutputNode(stream, n, indent + 1, convMem, convFile); + if (indentstep >= 0 && n && n->GetType() != wxXML_TEXT_NODE) + OutputIndentation(stream, indent + indentstep); + OutputNode(stream, n, indent + indentstep, convMem, convFile, indentstep); prev = n; n = n->GetNext(); } - if (prev && prev->GetType() != wxXML_TEXT_NODE) + if (indentstep >= 0 && prev && prev->GetType() != wxXML_TEXT_NODE) OutputIndentation(stream, indent); - OutputString(stream, wxT("GetName(), NULL, NULL); - OutputString(stream, wxT(">"), NULL, NULL); + OutputString(stream, wxT("GetName()); + OutputString(stream, wxT(">")); } else - OutputString(stream, wxT("/>"), NULL, NULL); + OutputString(stream, wxT("/>")); break; case wxXML_COMMENT_NODE: - OutputString(stream, wxT(""), NULL, NULL); + OutputString(stream, wxT("-->")); break; default: @@ -694,17 +842,19 @@ static void OutputNode(wxOutputStream& stream, wxXmlNode *node, int indent, } } -bool wxXmlDocument::Save(wxOutputStream& stream) const +bool wxXmlDocument::Save(wxOutputStream& stream, int indentstep) const { if ( !IsOk() ) - return FALSE; + return false; wxString s; - wxMBConv *convMem = NULL, *convFile = NULL; + wxMBConv *convMem = NULL; + #if wxUSE_UNICODE - convFile = new wxCSConv(GetFileEncoding()); + wxMBConv *convFile = new wxCSConv(GetFileEncoding()); #else + wxMBConv *convFile = NULL; if ( GetFileEncoding() != GetEncoding() ) { convFile = new wxCSConv(GetFileEncoding()); @@ -714,17 +864,17 @@ bool wxXmlDocument::Save(wxOutputStream& stream) const s.Printf(wxT("\n"), GetVersion().c_str(), GetFileEncoding().c_str()); - OutputString(stream, s, NULL, NULL); + OutputString(stream, s); - OutputNode(stream, GetRoot(), 0, convMem, convFile); - OutputString(stream, wxT("\n"), NULL, NULL); + OutputNode(stream, GetRoot(), 0, convMem, convFile, indentstep); + OutputString(stream, wxT("\n")); if ( convFile ) delete convFile; if ( convMem ) delete convMem; - return TRUE; + return true; } #endif // wxUSE_XML