X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/e13ce4a3d01cb6f06e698cba51e61793eb3ba9c1..7f19ef40eb05ef30e63ce12d40071d35b1d5aaaf:/src/xml/xml.cpp diff --git a/src/xml/xml.cpp b/src/xml/xml.cpp index a68f828b40..534777558a 100644 --- a/src/xml/xml.cpp +++ b/src/xml/xml.cpp @@ -29,6 +29,7 @@ #include "wx/datstrm.h" #include "wx/zstream.h" #include "wx/strconv.h" +#include "wx/scopedptr.h" #include "expat.h" // from Expat @@ -187,39 +188,69 @@ void wxXmlNode::AddChild(wxXmlNode *child) child->m_parent = this; } -bool wxXmlNode::InsertChild(wxXmlNode *child, wxXmlNode *before_node) +// inserts a new node in front of 'followingNode' +bool wxXmlNode::InsertChild(wxXmlNode *child, wxXmlNode *followingNode) { - 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!")); + wxCHECK_MSG( child, false, "cannot insert a NULL node!" ); + wxCHECK_MSG( child->m_parent == NULL, false, "node already has a parent" ); + wxCHECK_MSG( child->m_next == NULL, false, "node already has m_next" ); + wxCHECK_MSG( followingNode == NULL || followingNode->GetParent() == this, + false, + "wxXmlNode::InsertChild - followingNode has incorrect parent" ); - 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) + // this is for backward compatibility, NULL was allowed here thanks to + // the confusion about followingNode's meaning + if ( followingNode == NULL ) + followingNode = m_children; + + if ( m_children == followingNode ) { - // prepend child - child->m_parent = this; child->m_next = m_children; m_children = child; - return true; } else { wxXmlNode *ch = m_children; - while (ch && ch->m_next != before_node) ch = ch->m_next; - if (!ch) - return false; // before_node not found + while ( ch && ch->m_next != followingNode ) + ch = ch->m_next; + if ( !ch ) + { + wxFAIL_MSG( "followingNode has this node as parent, but couldn't be found among children" ); + return false; + } + + child->m_next = followingNode; ch->m_next = child; } child->m_parent = this; - child->m_next = before_node; + return true; +} + +// inserts a new node right after 'precedingNode' +bool wxXmlNode::InsertChildAfter(wxXmlNode *child, wxXmlNode *precedingNode) +{ + wxCHECK_MSG( child, false, "cannot insert a NULL node!" ); + wxCHECK_MSG( child->m_parent == NULL, false, "node already has a parent" ); + wxCHECK_MSG( child->m_next == NULL, false, "node already has m_next" ); + wxCHECK_MSG( precedingNode == NULL || precedingNode->m_parent == this, false, + "precedingNode has wrong parent" ); + + if ( precedingNode ) + { + child->m_next = precedingNode->m_next; + precedingNode->m_next = child; + } + else // precedingNode == NULL + { + wxCHECK_MSG( m_children == NULL, false, + "NULL precedingNode only makes sense when there are no children" ); + + child->m_next = m_children; + m_children = child; + } + + child->m_parent = this; return true; } @@ -362,10 +393,10 @@ 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_root(NULL) { #if !wxUSE_UNICODE - m_encoding = wxT("UTF-8"); + m_encoding = wxS("UTF-8"); #endif } @@ -455,7 +486,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 @@ -464,7 +495,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; @@ -473,16 +504,33 @@ bool wxIsWhiteOnly(const wxString& buf) struct wxXmlParsingContext { + wxXmlParsingContext() + : conv(NULL), + root(NULL), + node(NULL), + lastChild(NULL), + lastAsText(NULL), + removeWhiteOnlyNodes(false) + {} + XML_Parser parser; wxMBConv *conv; wxXmlNode *root; - wxXmlNode *node; - wxXmlNode *lastAsText; + wxXmlNode *node; // the node being parsed + wxXmlNode *lastChild; // the last child of "node" + wxXmlNode *lastAsText; // the last _text_ child of "node" wxString encoding; wxString version; bool removeWhiteOnlyNodes; }; +// checks that ctx->lastChild is in consistent state +#define ASSERT_LAST_CHILD_OK(ctx) \ + wxASSERT( ctx->lastChild == NULL || \ + ctx->lastChild->GetNext() == NULL ); \ + wxASSERT( ctx->lastChild == NULL || \ + ctx->lastChild->GetParent() == ctx->node ) + extern "C" { static void StartElementHnd(void *userData, const char *name, const char **atts) { @@ -493,23 +541,38 @@ static void StartElementHnd(void *userData, const char *name, const char **atts) XML_GetCurrentLineNumber(ctx->parser)); const char **a = atts; + // add node attributes while (*a) { node->AddAttribute(CharToString(ctx->conv, a[0]), CharToString(ctx->conv, a[1])); a += 2; } + if (ctx->root == NULL) + { ctx->root = node; + } else - ctx->node->AddChild(node); - ctx->node = node; + { + 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 + + ctx->node = node; } static void EndElementHnd(void *userData, const char* WXUNUSED(name)) { wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; + // we're exiting the last children of ctx->node->GetParent() and going + // back one level up, so current value of ctx->node points to the last + // child of ctx->node->GetParent() + ctx->lastChild = ctx->node; + ctx->node = ctx->node->GetParent(); ctx->lastAsText = NULL; } @@ -531,10 +594,13 @@ static void TextHnd(void *userData, const char *s, int len) if (!whiteOnly) { - ctx->lastAsText = - new wxXmlNode(wxXML_TEXT_NODE, wxT("text"), str, + wxXmlNode *textnode = + new wxXmlNode(wxXML_TEXT_NODE, wxS("text"), str, XML_GetCurrentLineNumber(ctx->parser)); - ctx->node->AddChild(ctx->lastAsText); + + ASSERT_LAST_CHILD_OK(ctx); + ctx->node->InsertChildAfter(textnode, ctx->lastChild); + ctx->lastChild= ctx->lastAsText = textnode; } } } @@ -543,10 +609,24 @@ static void StartCdataHnd(void *userData) { wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; - ctx->lastAsText = - new wxXmlNode(wxXML_CDATA_SECTION_NODE, wxT("cdata"), wxT(""), + wxXmlNode *textnode = + new wxXmlNode(wxXML_CDATA_SECTION_NODE, wxS("cdata"), wxS(""), XML_GetCurrentLineNumber(ctx->parser)); - ctx->node->AddChild(ctx->lastAsText); + + ASSERT_LAST_CHILD_OK(ctx); + ctx->node->InsertChildAfter(textnode, ctx->lastChild); + 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) @@ -555,14 +635,19 @@ static void CommentHnd(void *userData, const char *data) if (ctx->node) { - // VS: ctx->node == NULL happens if there is a comment before - // the root element (e.g. wxDesigner's output). We ignore such - // comments, no big deal... - ctx->node->AddChild( + wxXmlNode *commentnode = new wxXmlNode(wxXML_COMMENT_NODE, - wxT("comment"), CharToString(ctx->conv, data), - XML_GetCurrentLineNumber(ctx->parser))); + 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). + ctx->lastAsText = NULL; } @@ -575,10 +660,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]); } @@ -631,11 +716,10 @@ bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding, int fl bool done; XML_Parser parser = XML_ParserCreate(NULL); - ctx.root = ctx.node = NULL; - 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; @@ -644,7 +728,7 @@ bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding, int fl 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_SetDefaultHandler(parser, DefaultHnd); XML_SetUnknownEncodingHandler(parser, UnknownEncodingHnd, NULL); @@ -695,30 +779,46 @@ 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() @@ -729,159 +829,193 @@ enum // 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; +bool OutputStringEnt(wxOutputStream& stream, + const wxString& str, + wxMBConv *convMem, + wxMBConv *convFile, + int flags = 0) +{ + const size_t len = str.length(); + size_t i, + 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('"'))) + wxChar c = str.GetChar(i); + if (c == wxS('<') || c == wxS('>') || + (c == wxS('&') && str.substr(i+1, 4) != wxS("amp;")) || + ((flags & XML_ESCAPE_QUOTES) && c == wxS('"'))) { - OutputString(stream, str.Mid(last, i - last), convMem, convFile); - switch (c) + if ( !OutputString(stream, str.substr(last, i - last), + convMem, convFile) ) + return false; + + const char *escaped; + switch ( c ) { - case wxT('<'): - OutputString(stream, wxT("<")); + case wxS('<'): + escaped = "<"; break; - case wxT('>'): - OutputString(stream, wxT(">")); + case wxS('>'): + escaped = ">"; break; - case wxT('&'): - OutputString(stream, wxT("&")); + case wxS('&'): + escaped = "&"; break; - case wxT('"'): - OutputString(stream, wxT(""")); + case wxS('"'): + escaped = """; break; default: - break; + wxFAIL_MSG( "logic error in the code" ); + return false; } + + if ( !OutputString(stream, escaped, convMem, convFile) ) + return false; + last = i + 1; } } - OutputString(stream, str.Mid(last, i - last), convMem, convFile); + + return OutputString(stream, str.substr(last, i - last), 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); + rc = OutputStringEnt(stream, node->GetContent(), convMem, convFile); 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) && + OutputStringEnt(stream, attr->GetValue(), + convMem, convFile, + XML_ESCAPE_QUOTES) && + 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; 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); - - OutputNode(stream, GetRoot(), 0, convMem, convFile, indentstep); - OutputString(stream, wxT("\n")); - - delete convFile; - delete convMem; - - return true; + 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()); } #endif // wxUSE_XML