X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/6a8fb6bdeaa474cf8d965b4dc211de47f71ce5c5..d169fa6aef264c5dfc300dd6666236d59f4e169a:/src/xml/xml.cpp diff --git a/src/xml/xml.cpp b/src/xml/xml.cpp index ab40593bed..1555d29b72 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 ///////////////////////////////////////////////////////////////////////////// -#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) -#pragma implementation "xml.h" -#endif - // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" @@ -19,33 +15,46 @@ #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 "wx/ptr_scpd.h" #include "expat.h" // from Expat // DLL options compatibility check: -#include "wx/app.h" WX_CHECK_BUILD_OPTIONS("wxXML") + +IMPLEMENT_CLASS(wxXmlDocument, wxObject) + + +// a private utility used by wxXML +static bool wxIsWhiteOnly(const wxString& buf); + + //----------------------------------------------------------------------------- // wxXmlNode //----------------------------------------------------------------------------- wxXmlNode::wxXmlNode(wxXmlNode *parent,wxXmlNodeType type, const wxString& name, const wxString& content, - wxXmlProperty *props, wxXmlNode *next) + wxXmlAttribute *attrs, wxXmlNode *next, int lineNo) : m_type(type), m_name(name), m_content(content), - m_properties(props), m_parent(parent), - m_children(NULL), m_next(next) + m_attrs(attrs), m_parent(parent), + m_children(NULL), m_next(next), + m_lineNo(lineNo) { if (m_parent) { @@ -60,10 +69,12 @@ wxXmlNode::wxXmlNode(wxXmlNode *parent,wxXmlNodeType type, } wxXmlNode::wxXmlNode(wxXmlNodeType type, const wxString& name, - const wxString& content) + const wxString& content, + int lineNo) : m_type(type), m_name(name), m_content(content), - m_properties(NULL), m_parent(NULL), - m_children(NULL), m_next(NULL) + m_attrs(NULL), m_parent(NULL), + m_children(NULL), m_next(NULL), + m_lineNo(lineNo) {} wxXmlNode::wxXmlNode(const wxXmlNode& node) @@ -82,8 +93,8 @@ wxXmlNode::~wxXmlNode() delete c; } - wxXmlProperty *p, *p2; - for (p = m_properties; p; p = p2) + wxXmlAttribute *p, *p2; + for (p = m_attrs; p; p = p2) { p2 = p->GetNext(); delete p; @@ -92,7 +103,7 @@ wxXmlNode::~wxXmlNode() wxXmlNode& wxXmlNode::operator=(const wxXmlNode& node) { - wxDELETE(m_properties); + wxDELETE(m_attrs); wxDELETE(m_children); DoCopy(node); return *this; @@ -103,6 +114,7 @@ void wxXmlNode::DoCopy(const wxXmlNode& node) m_type = node.m_type; m_name = node.m_name; m_content = node.m_content; + m_lineNo = node.m_lineNo; m_children = NULL; wxXmlNode *n = node.m_children; @@ -112,52 +124,54 @@ void wxXmlNode::DoCopy(const wxXmlNode& node) n = n->GetNext(); } - m_properties = NULL; - wxXmlProperty *p = node.m_properties; + m_attrs = NULL; + wxXmlAttribute *p = node.m_attrs; while (p) { - AddProperty(p->GetName(), p->GetValue()); + AddAttribute(p->GetName(), p->GetValue()); p = p->GetNext(); } } -bool wxXmlNode::HasProp(const wxString& propName) const +bool wxXmlNode::HasAttribute(const wxString& attrName) const { - wxXmlProperty *prop = GetProperties(); + wxXmlAttribute *attr = GetAttributes(); - while (prop) + while (attr) { - if (prop->GetName() == propName) return TRUE; - prop = prop->GetNext(); + if (attr->GetName() == attrName) return true; + attr = attr->GetNext(); } - return FALSE; + return false; } -bool wxXmlNode::GetPropVal(const wxString& propName, wxString *value) const +bool wxXmlNode::GetAttribute(const wxString& attrName, wxString *value) const { - wxXmlProperty *prop = GetProperties(); + wxCHECK_MSG( value, false, "value argument must not be NULL" ); + + wxXmlAttribute *attr = GetAttributes(); - while (prop) + while (attr) { - if (prop->GetName() == propName) + if (attr->GetName() == attrName) { - *value = prop->GetValue(); - return TRUE; + *value = attr->GetValue(); + return true; } - prop = prop->GetNext(); + attr = attr->GetNext(); } - return FALSE; + return false; } -wxString wxXmlNode::GetPropVal(const wxString& propName, const wxString& defaultVal) const +wxString wxXmlNode::GetAttribute(const wxString& attrName, const wxString& defaultVal) const { wxString tmp; - if (GetPropVal(propName, &tmp)) + if (GetAttribute(attrName, &tmp)) return tmp; - else - return defaultVal; + + return defaultVal; } void wxXmlNode::AddChild(wxXmlNode *child) @@ -174,33 +188,82 @@ void wxXmlNode::AddChild(wxXmlNode *child) child->m_parent = this; } -void wxXmlNode::InsertChild(wxXmlNode *child, wxXmlNode *before_node) +// inserts a new node in front of 'followingNode' +bool wxXmlNode::InsertChild(wxXmlNode *child, wxXmlNode *followingNode) { - wxASSERT_MSG(before_node->GetParent() == this, wxT("wxXmlNode::InsertChild - the node has incorrect parent")); + 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" ); + + // 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 == before_node) - m_children = child; + if ( m_children == followingNode ) + { + child->m_next = m_children; + m_children = child; + } else { wxXmlNode *ch = m_children; - while (ch->m_next != before_node) ch = ch->m_next; + 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; } 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 { @@ -212,64 +275,115 @@ 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; } } +void wxXmlNode::AddAttribute(const wxString& name, const wxString& value) +{ + AddProperty(name, value); +} + +void wxXmlNode::AddAttribute(wxXmlAttribute *attr) +{ + AddProperty(attr); +} + +bool wxXmlNode::DeleteAttribute(const wxString& name) +{ + return DeleteProperty(name); +} + void wxXmlNode::AddProperty(const wxString& name, const wxString& value) { - AddProperty(new wxXmlProperty(name, value, NULL)); + AddProperty(new wxXmlAttribute(name, value, NULL)); } -void wxXmlNode::AddProperty(wxXmlProperty *prop) +void wxXmlNode::AddProperty(wxXmlAttribute *attr) { - if (m_properties == NULL) - m_properties = prop; + if (m_attrs == NULL) + m_attrs = attr; else { - wxXmlProperty *p = m_properties; + wxXmlAttribute *p = m_attrs; while (p->GetNext()) p = p->GetNext(); - p->SetNext(prop); + p->SetNext(attr); } } bool wxXmlNode::DeleteProperty(const wxString& name) { - wxXmlProperty *prop; + wxXmlAttribute *attr; - if (m_properties == NULL) - return FALSE; + if (m_attrs == NULL) + return false; - else if (m_properties->GetName() == name) + else if (m_attrs->GetName() == name) { - prop = m_properties; - m_properties = prop->GetNext(); - prop->SetNext(NULL); - delete prop; - return TRUE; + attr = m_attrs; + m_attrs = attr->GetNext(); + attr->SetNext(NULL); + delete attr; + return true; } else { - wxXmlProperty *p = m_properties; + wxXmlAttribute *p = m_attrs; while (p->GetNext()) { if (p->GetNext()->GetName() == name) { - prop = p->GetNext(); - p->SetNext(prop->GetNext()); - prop->SetNext(NULL); - delete prop; - return TRUE; + attr = p->GetNext(); + p->SetNext(attr->GetNext()); + attr->SetNext(NULL); + delete attr; + 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); } @@ -279,15 +393,15 @@ bool wxXmlNode::DeleteProperty(const wxString& name) //----------------------------------------------------------------------------- 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 } wxXmlDocument::wxXmlDocument(const wxString& filename, const wxString& encoding) - : wxObject(), m_root(NULL) + :wxObject(), m_root(NULL) { if ( !Load(filename, encoding) ) { @@ -296,7 +410,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) ) { @@ -305,6 +419,7 @@ wxXmlDocument::wxXmlDocument(wxInputStream& stream, const wxString& encoding) } wxXmlDocument::wxXmlDocument(const wxXmlDocument& doc) + :wxObject() { DoCopy(doc); } @@ -323,19 +438,27 @@ void wxXmlDocument::DoCopy(const wxXmlDocument& doc) m_encoding = doc.m_encoding; #endif m_fileEncoding = doc.m_fileEncoding; - m_root = new wxXmlNode(*doc.m_root); + + if (doc.m_root) + m_root = new wxXmlNode(*doc.m_root); + else + m_root = NULL; } -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); } @@ -344,68 +467,112 @@ 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, - const char *s, size_t len = wxSTRING_MAXLEN) +// 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::npos) { -#if wxUSE_UNICODE - (void)conv; - return wxString(s, wxConvUTF8, len); -#else +#if !wxUSE_UNICODE if ( conv ) { - size_t nLen = (len != wxSTRING_MAXLEN) ? len : - 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::npos ? 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: the string is wanted in UTF-8 +#endif // !wxUSE_UNICODE + + wxUnusedVar(conv); + return wxString::FromUTF8Unchecked(s, len); } +// returns true if the given string contains only whitespaces +bool wxIsWhiteOnly(const wxString& buf) +{ + for ( wxString::const_iterator i = buf.begin(); i != buf.end(); ++i ) + { + wxChar c = *i; + if ( c != wxS(' ') && c != wxS('\t') && c != wxS('\n') && c != wxS('\r')) + return false; + } + return true; +} + + 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) { wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; - wxXmlNode *node = new wxXmlNode(wxXML_ELEMENT_NODE, CharToString(ctx->conv, name)); + wxXmlNode *node = new wxXmlNode(wxXML_ELEMENT_NODE, + CharToString(ctx->conv, name), + wxEmptyString, + XML_GetCurrentLineNumber(ctx->parser)); const char **a = atts; + + // add node attributes while (*a) { - node->AddProperty(CharToString(ctx->conv, a[0]), CharToString(ctx->conv, a[1])); + 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; } @@ -413,34 +580,42 @@ static void EndElementHnd(void *userData, const char* WXUNUSED(name)) 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)); + 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->node->AddChild(ctx->lastAsText); + wxXmlNode *textnode = + new wxXmlNode(wxXML_TEXT_NODE, wxS("text"), str, + XML_GetCurrentLineNumber(ctx->parser)); + + ASSERT_LAST_CHILD_OK(ctx); + ctx->node->InsertChildAfter(textnode, ctx->lastChild); + ctx->lastChild= ctx->lastAsText = textnode; } } +} + +static void StartCdataHnd(void *userData) +{ + wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; + + wxXmlNode *textnode = + new wxXmlNode(wxXML_CDATA_SECTION_NODE, wxS("cdata"), wxS(""), + XML_GetCurrentLineNumber(ctx->parser)); - delete[] buf; + ASSERT_LAST_CHILD_OK(ctx); + ctx->node->InsertChildAfter(textnode, ctx->lastChild); + ctx->lastChild= ctx->lastAsText = textnode; } static void CommentHnd(void *userData, const char *data) @@ -449,12 +624,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(new wxXmlNode(wxXML_COMMENT_NODE, - wxT("comment"), CharToString(ctx->conv, data))); + 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). + ctx->lastAsText = NULL; } @@ -467,10 +649,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]); } @@ -482,7 +664,7 @@ static int UnknownEncodingHnd(void * WXUNUSED(encodingHandlerData), // 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)); + wxCSConv conv(name); char mbBuf[2]; wchar_t wcBuf[10]; size_t i; @@ -499,7 +681,7 @@ static int UnknownEncodingHnd(void * WXUNUSED(encodingHandlerData), } info->map[i+1] = (int)wcBuf[0]; } - + info->data = NULL; info->convert = NULL; info->release = NULL; @@ -507,7 +689,9 @@ static int UnknownEncodingHnd(void * WXUNUSED(encodingHandlerData), return 1; } -bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding) +} // extern "C" + +bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding, int flags) { #if wxUSE_UNICODE (void)encoding; @@ -521,17 +705,19 @@ bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding) 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 != wxT("UTF-8") && encoding != wxT("utf-8") ) + if ( encoding.CmpNoCase(wxS("UTF-8")) != 0 ) ctx.conv = new wxCSConv(encoding); #endif + ctx.removeWhiteOnlyNodes = (flags & wxXMLDOC_KEEP_WHITESPACE_NODES) == 0; + ctx.parser = parser; XML_SetUserData(parser, (void*)&ctx); XML_SetElementHandler(parser, StartElementHnd, EndElementHnd); XML_SetCharacterDataHandler(parser, TextHnd); + XML_SetStartCdataSectionHandler(parser, StartCdataHnd); XML_SetCommentHandler(parser, CommentHnd); XML_SetDefaultHandler(parser, DefaultHnd); XML_SetUnknownEncodingHandler(parser, UnknownEncodingHnd, NULL); @@ -555,9 +741,9 @@ bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding) if (ok) { - if (!ctx.version.IsEmpty()) + if (!ctx.version.empty()) SetVersion(ctx.version); - if (!ctx.encoding.IsEmpty()) + if (!ctx.encoding.empty()) SetFileEncoding(ctx.encoding); SetRoot(ctx.root); } @@ -582,161 +768,237 @@ bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding) // wxXmlDocument saving routines //----------------------------------------------------------------------------- +// helpers for XML generation +namespace +{ + // write string to output: -inline static void OutputString(wxOutputStream& stream, const wxString& str, - wxMBConv *convMem, wxMBConv *convFile) +bool OutputString(wxOutputStream& stream, + const wxString& str, + wxMBConv *convMem, + wxMBConv *convFile) { - if (str.IsEmpty()) return; + if (str.empty()) + return true; + #if wxUSE_UNICODE + 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 + if ( !buf ) + return false; + + stream.Write(buf, strlen(buf)); +#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()); } -#endif + else // no conversions to do + { + stream.Write(str.mb_str(), str.length()); + } +#endif // wxUSE_UNICODE/!wxUSE_UNICODE + + return stream.IsOk(); } -// Same as above, but create entities first. -// Translates '<' to "<", '>' to ">" and '&' to "&" -static void OutputStringEnt(wxOutputStream& stream, const wxString& str, - wxMBConv *convMem, wxMBConv *convFile) +// flags for OutputStringEnt() +enum { - wxString buf; - size_t i, last, len; - wxChar c; + XML_ESCAPE_QUOTES = 1 +}; - len = str.Len(); - last = 0; +// Same as above, but create entities first. +// Translates '<' to "<", '>' to ">" and '&' to "&" +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;"))) + 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("<"), NULL, NULL); + case wxS('<'): + escaped = "<"; + break; + case wxS('>'): + escaped = ">"; break; - case wxT('>'): - OutputString(stream, wxT(">"), NULL, NULL); + case wxS('&'): + escaped = "&"; break; - case wxT('&'): - OutputString(stream, wxT("&"), NULL, NULL); + case wxS('"'): + escaped = """; break; - default: break; + default: + 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, NULL, NULL); + wxString str(wxS("\n")); + str += wxString(2*indent, wxS(' ')); + return OutputString(stream, str, convMem, convFile); } -static void OutputNode(wxOutputStream& stream, wxXmlNode *node, int indent, - wxMBConv *convMem, wxMBConv *convFile) +bool OutputNode(wxOutputStream& stream, + wxXmlNode *node, + int indent, + wxMBConv *convMem, + wxMBConv *convFile, + int indentstep) { - wxXmlNode *n, *prev; - wxXmlProperty *prop; - + bool rc; switch (node->GetType()) { + case wxXML_CDATA_SECTION_NODE: + 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("<"), NULL, NULL); - OutputString(stream, node->GetName(), NULL, NULL); + rc = OutputString(stream, wxS("<"), convMem, convFile) && + OutputString(stream, node->GetName(), convMem, convFile); - prop = node->GetProperties(); - while (prop) + if ( rc ) { - OutputString(stream, wxT(" ") + prop->GetName() + - wxT("=\"") + prop->GetValue() + wxT("\""), - NULL, NULL); - // FIXME - what if prop contains '"'? - prop = prop->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(">"), NULL, NULL); - 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 (n && n->GetType() != wxXML_TEXT_NODE) - OutputIndentation(stream, indent + 1); - OutputNode(stream, n, indent + 1, convMem, convFile); + 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 (prev && prev->GetType() != wxXML_TEXT_NODE) - OutputIndentation(stream, indent); - OutputString(stream, wxT("GetName(), NULL, NULL); - OutputString(stream, wxT(">"), NULL, NULL); + + 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("/>"), NULL, NULL); break; case wxXML_COMMENT_NODE: - OutputString(stream, wxT(""), NULL, NULL); + rc = OutputString(stream, wxS(""), convMem, convFile); break; default: - wxFAIL_MSG(wxT("unsupported node type")); + wxFAIL_MSG("unsupported node type"); + rc = false; } + + return rc; } -bool wxXmlDocument::Save(wxOutputStream& stream) const +} // anonymous namespace + +bool wxXmlDocument::Save(wxOutputStream& stream, int indentstep) const { if ( !IsOk() ) - return FALSE; + return false; - wxString s; + wxScopedPtr convMem, convFile; - wxMBConv *convMem = NULL, *convFile = NULL; #if wxUSE_UNICODE - convFile = new wxCSConv(GetFileEncoding()); + convFile.reset(new wxCSConv(GetFileEncoding())); #else - if ( GetFileEncoding() != GetEncoding() ) + if ( GetFileEncoding().CmpNoCase(GetEncoding()) != 0 ) { - convFile = new wxCSConv(GetFileEncoding()); - convMem = new wxCSConv(GetEncoding()); + 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, NULL, NULL); - - OutputNode(stream, GetRoot(), 0, convMem, convFile); - OutputString(stream, wxT("\n"), NULL, NULL); - - if ( convFile ) - delete convFile; - if ( convMem ) - 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