X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/78d14f80e9a72041ede52c30d912ec5cef21b1b3..a327b520909bea498517e512ddfdee0a052fe876:/src/xrc/xml.cpp diff --git a/src/xrc/xml.cpp b/src/xrc/xml.cpp index d6ba14d820..32ee02a04b 100644 --- a/src/xrc/xml.cpp +++ b/src/xrc/xml.cpp @@ -26,11 +26,15 @@ #include "wx/zstream.h" #include "wx/log.h" #include "wx/intl.h" +#include "wx/strconv.h" #include "wx/xrc/xml.h" -#include "wx/xrc/xmlio.h" +#include "xmlparse.h" // from Expat +//----------------------------------------------------------------------------- +// wxXmlNode +//----------------------------------------------------------------------------- wxXmlNode::wxXmlNode(wxXmlNode *parent,wxXmlNodeType type, const wxString& name, const wxString& content, @@ -51,8 +55,6 @@ wxXmlNode::wxXmlNode(wxXmlNode *parent,wxXmlNodeType type, } } - - wxXmlNode::wxXmlNode(wxXmlNodeType type, const wxString& name, const wxString& content) : m_type(type), m_name(name), m_content(content), @@ -60,8 +62,6 @@ wxXmlNode::wxXmlNode(wxXmlNodeType type, const wxString& name, m_children(NULL), m_next(NULL) {} - - wxXmlNode::wxXmlNode(const wxXmlNode& node) { m_next = NULL; @@ -69,18 +69,31 @@ wxXmlNode::wxXmlNode(const wxXmlNode& node) DoCopy(node); } +wxXmlNode::~wxXmlNode() +{ + wxXmlNode *c, *c2; + for (c = m_children; c; c = c2) + { + c2 = c->m_next; + delete c; + } + wxXmlProperty *p, *p2; + for (p = m_properties; p; p = p2) + { + p2 = p->GetNext(); + delete p; + } +} wxXmlNode& wxXmlNode::operator=(const wxXmlNode& node) { - delete m_properties; - delete m_children; + wxDELETE(m_properties); + wxDELETE(m_children); DoCopy(node); return *this; } - - void wxXmlNode::DoCopy(const wxXmlNode& node) { m_type = node.m_type; @@ -104,7 +117,6 @@ void wxXmlNode::DoCopy(const wxXmlNode& node) } } - bool wxXmlNode::HasProp(const wxString& propName) const { wxXmlProperty *prop = GetProperties(); @@ -118,8 +130,6 @@ bool wxXmlNode::HasProp(const wxString& propName) const return FALSE; } - - bool wxXmlNode::GetPropVal(const wxString& propName, wxString *value) const { wxXmlProperty *prop = GetProperties(); @@ -137,8 +147,6 @@ bool wxXmlNode::GetPropVal(const wxString& propName, wxString *value) const return FALSE; } - - wxString wxXmlNode::GetPropVal(const wxString& propName, const wxString& defaultVal) const { wxString tmp; @@ -148,8 +156,6 @@ wxString wxXmlNode::GetPropVal(const wxString& propName, const wxString& default return defaultVal; } - - void wxXmlNode::AddChild(wxXmlNode *child) { if (m_children == NULL) @@ -164,8 +170,6 @@ void wxXmlNode::AddChild(wxXmlNode *child) child->m_parent = this; } - - void wxXmlNode::InsertChild(wxXmlNode *child, wxXmlNode *before_node) { wxASSERT_MSG(before_node->GetParent() == this, wxT("wxXmlNode::InsertChild - the node has incorrect parent")); @@ -183,8 +187,6 @@ void wxXmlNode::InsertChild(wxXmlNode *child, wxXmlNode *before_node) child->m_next = before_node; } - - bool wxXmlNode::RemoveChild(wxXmlNode *child) { if (m_children == NULL) @@ -214,8 +216,6 @@ bool wxXmlNode::RemoveChild(wxXmlNode *child) } } - - void wxXmlNode::AddProperty(const wxString& name, const wxString& value) { AddProperty(new wxXmlProperty(name, value, NULL)); @@ -233,16 +233,16 @@ void wxXmlNode::AddProperty(wxXmlProperty *prop) } } - - bool wxXmlNode::DeleteProperty(const wxString& name) { + wxXmlProperty *prop; + if (m_properties == NULL) return FALSE; else if (m_properties->GetName() == name) { - wxXmlProperty *prop = m_properties; + prop = m_properties; m_properties = prop->GetNext(); prop->SetNext(NULL); delete prop; @@ -256,7 +256,7 @@ bool wxXmlNode::DeleteProperty(const wxString& name) { if (p->GetNext()->GetName() == name) { - wxXmlProperty *prop = p->GetNext(); + prop = p->GetNext(); p->SetNext(prop->GetNext()); prop->SetNext(NULL); delete prop; @@ -270,172 +270,442 @@ bool wxXmlNode::DeleteProperty(const wxString& name) +//----------------------------------------------------------------------------- +// wxXmlDocument +//----------------------------------------------------------------------------- - - - - -wxList *wxXmlDocument::sm_handlers = NULL; - - - -wxXmlDocument::wxXmlDocument(const wxString& filename, wxXmlIOType io_type) +wxXmlDocument::wxXmlDocument(const wxString& filename, const wxString& encoding) : wxObject(), m_root(NULL) { - if (!Load(filename, io_type)) + if ( !Load(filename, encoding) ) { - delete m_root; - m_root = NULL; + wxDELETE(m_root); } } - - -wxXmlDocument::wxXmlDocument(wxInputStream& stream, wxXmlIOType io_type) +wxXmlDocument::wxXmlDocument(wxInputStream& stream, const wxString& encoding) : wxObject(), m_root(NULL) { - if (!Load(stream, io_type)) + if ( !Load(stream, encoding) ) { - delete m_root; - m_root = NULL; + wxDELETE(m_root); } } - - wxXmlDocument::wxXmlDocument(const wxXmlDocument& doc) { DoCopy(doc); } - - wxXmlDocument& wxXmlDocument::operator=(const wxXmlDocument& doc) { - delete m_root; + wxDELETE(m_root); DoCopy(doc); return *this; } - - void wxXmlDocument::DoCopy(const wxXmlDocument& doc) { m_version = doc.m_version; +#if !wxUSE_UNICODE m_encoding = doc.m_encoding; +#endif + m_fileEncoding = doc.m_fileEncoding; m_root = new wxXmlNode(*doc.m_root); } - - -bool wxXmlDocument::Load(const wxString& filename, wxXmlIOType io_type) +bool wxXmlDocument::Load(const wxString& filename, const wxString& encoding) { wxFileInputStream stream(filename); - return Load(stream, io_type); + return Load(stream, encoding); +} + +bool wxXmlDocument::Save(const wxString& filename) const +{ + wxFileOutputStream stream(filename); + return Save(stream); } -bool wxXmlDocument::Load(wxInputStream& stream, wxXmlIOType io_type) +//----------------------------------------------------------------------------- +// 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) { - wxNode *n = sm_handlers->GetFirst(); - while (n) +#if wxUSE_UNICODE + (void)conv; + return wxString(s, wxConvUTF8, len); +#else + if ( conv ) { - wxXmlIOHandler *h = (wxXmlIOHandler*) n->GetData(); - - if ((io_type == wxXML_IO_AUTO || io_type == h->GetType()) && - h->CanLoad(stream)) - { - return h->Load(stream, *this); - } - n = n->GetNext(); + size_t nLen = (len != wxSTRING_MAXLEN) ? len : + nLen = wxConvUTF8.MB2WC((wchar_t*) NULL, s, 0); + + wchar_t *buf = new wchar_t[nLen+1]; + wxConvUTF8.MB2WC(buf, s, nLen); + buf[nLen] = 0; + delete[] buf; + return wxString(buf, *conv, len); } - wxLogError(_("Cannot find XML I/O handler capable of loading this format.")); - return FALSE; + else + return wxString(s, len); +#endif } +struct wxXmlParsingContext +{ + wxMBConv *conv; + wxXmlNode *root; + wxXmlNode *node; + wxXmlNode *lastAsText; + wxString encoding; + wxString version; +}; - -bool wxXmlDocument::Save(const wxString& filename, wxXmlIOType io_type) const +static void StartElementHnd(void *userData, const char *name, const char **atts) { - wxFileOutputStream stream(filename); - return Save(stream, io_type); + wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; + wxXmlNode *node = new wxXmlNode(wxXML_ELEMENT_NODE, CharToString(ctx->conv, name)); + const char **a = atts; + while (*a) + { + node->AddProperty(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; + ctx->lastAsText = NULL; } +static void EndElementHnd(void *userData, const char* WXUNUSED(name)) +{ + wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; + ctx->node = ctx->node->GetParent(); + ctx->lastAsText = NULL; +} -bool wxXmlDocument::Save(wxOutputStream& stream, wxXmlIOType io_type) const +static void TextHnd(void *userData, const char *s, int len) { - wxNode *n = sm_handlers->GetFirst(); - while (n) + wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; + char *buf = new char[len + 1]; + + buf[len] = '\0'; + memcpy(buf, s, (size_t)len); + + if (ctx->lastAsText) { - wxXmlIOHandler *h = (wxXmlIOHandler*) n->GetData(); - if (io_type == h->GetType() && h->CanSave()) + ctx->lastAsText->SetContent(ctx->lastAsText->GetContent() + + CharToString(ctx->conv, buf)); + } + else + { + bool whiteOnly = TRUE; + for (char *c = buf; *c != '\0'; c++) + if (*c != ' ' && *c != '\t' && *c != '\n' && *c != '\r') + { + whiteOnly = FALSE; + break; + } + if (!whiteOnly) { - return h->Save(stream, *this); + ctx->lastAsText = new wxXmlNode(wxXML_TEXT_NODE, wxT("text"), + CharToString(ctx->conv, buf)); + ctx->node->AddChild(ctx->lastAsText); } - n = n->GetNext(); } - wxLogError(_("Cannot find XML I/O handler capable of saving in this format.")); - return FALSE; -} - - + delete[] buf; +} +static void CommentHnd(void *userData, const char *data) +{ + wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; + 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))); + } + ctx->lastAsText = NULL; +} -void wxXmlDocument::AddHandler(wxXmlIOHandler *handler) +static void DefaultHnd(void *userData, const char *s, int len) { - if (sm_handlers == NULL) + // XML header: + if (len > 6 && memcmp(s, "DeleteContents(TRUE); + wxXmlParsingContext *ctx = (wxXmlParsingContext*)userData; + + wxString buf = CharToString(ctx->conv, s, (size_t)len); + int pos; + pos = buf.Find(wxT("encoding=")); + if (pos != wxNOT_FOUND) + ctx->encoding = buf.Mid(pos + 10).BeforeFirst(buf[(size_t)pos+9]); + pos = buf.Find(wxT("version=")); + if (pos != wxNOT_FOUND) + ctx->version = buf.Mid(pos + 9).BeforeFirst(buf[(size_t)pos+8]); } - sm_handlers->Append(handler); } +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)); + char mbBuf[255]; + wchar_t wcBuf[255]; + size_t i; + + for (i = 0; i < 255; i++) + mbBuf[i] = (char) (i+1); + mbBuf[255] = 0; + conv.MB2WC(wcBuf, mbBuf, 255); + wcBuf[255] = 0; + + info->map[0] = 0; + for (i = 0; i < 255; i++) + info->map[i+1] = (int)wcBuf[i]; + + info->data = NULL; + info->convert = NULL; + info->release = NULL; + + return 1; +} -void wxXmlDocument::CleanUpHandlers() +bool wxXmlDocument::Load(wxInputStream& stream, const wxString& encoding) { - delete sm_handlers; - sm_handlers = NULL; +#if wxUSE_UNICODE + (void)encoding; +#else + m_encoding = encoding; +#endif + + const size_t BUFSIZE = 1024; + char buf[BUFSIZE]; + wxXmlParsingContext ctx; + bool done; + XML_Parser parser = XML_ParserCreate(NULL); + + ctx.root = ctx.node = NULL; + ctx.encoding = wxT("UTF-8"); // default in absence of encoding="" + ctx.conv = NULL; +#if !wxUSE_UNICODE + if ( encoding != wxT("UTF-8") && encoding != wxT("utf-8") ) + ctx.conv = new wxCSConv(encoding); +#endif + + XML_SetUserData(parser, (void*)&ctx); + XML_SetElementHandler(parser, StartElementHnd, EndElementHnd); + XML_SetCharacterDataHandler(parser, TextHnd); + XML_SetCommentHandler(parser, CommentHnd); + XML_SetDefaultHandler(parser, DefaultHnd); + XML_SetUnknownEncodingHandler(parser, UnknownEncodingHnd, NULL); + + do + { + size_t len = stream.Read(buf, BUFSIZE).LastRead(); + done = (len < BUFSIZE); + if (!XML_Parse(parser, buf, len, done)) + { + wxLogError(_("XML parsing error: '%s' at line %d"), + XML_ErrorString(XML_GetErrorCode(parser)), + XML_GetCurrentLineNumber(parser)); + return FALSE; + } + } while (!done); + + SetVersion(ctx.version); + SetFileEncoding(ctx.encoding); + SetRoot(ctx.root); + + XML_ParserFree(parser); +#if !wxUSE_UNICODE + if ( ctx.conv ) + delete ctx.conv; +#endif + + return TRUE; + } -void wxXmlDocument::InitStandardHandlers() + +//----------------------------------------------------------------------------- +// wxXmlDocument saving routines +//----------------------------------------------------------------------------- + +// write string to output: +inline static void OutputString(wxOutputStream& stream, const wxString& str, + wxMBConv *convMem, wxMBConv *convFile) { - AddHandler(new wxXmlIOHandlerBin); -#if wxUSE_ZLIB - AddHandler(new wxXmlIOHandlerBinZ); + if (str.IsEmpty()) return; +#if wxUSE_UNICODE + 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 + { + wxString str2(str.wc_str(*convMem), *convFile); + stream.Write(str2.mb_str(), str2.Len()); + } #endif - AddHandler(new wxXmlIOHandlerExpat); - AddHandler(new wxXmlIOHandlerWriter); } +// Same as above, but create entities first. +// Translates '<' to "<", '>' to ">" and '&' to "&" +static void OutputStringEnt(wxOutputStream& stream, const wxString& str, + wxMBConv *convMem, wxMBConv *convFile) +{ + wxString buf; + size_t i, last, len; + wxChar c; -#include "wx/module.h" + 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;"))) + { + OutputString(stream, str.Mid(last, i - last), convMem, convFile); + switch (c) + { + case wxT('<'): + OutputString(stream, wxT("<"), NULL, NULL); + break; + case wxT('>'): + OutputString(stream, wxT(">"), NULL, NULL); + break; + case wxT('&'): + OutputString(stream, wxT("&"), NULL, NULL); + break; + default: break; + } + last = i + 1; + } + } + OutputString(stream, str.Mid(last, i - last), convMem, convFile); +} -class wxXmlModule: public wxModule +inline static void OutputIndentation(wxOutputStream& stream, int indent) { - DECLARE_DYNAMIC_CLASS(wxXmlModule) - public: - wxXmlModule() {} - bool OnInit() { wxXmlDocument::InitStandardHandlers(); return TRUE; }; - void OnExit() { wxXmlDocument::CleanUpHandlers(); }; -}; + wxString str = wxT("\n"); + for (int i = 0; i < indent; i++) + str << wxT(' ') << wxT(' '); + OutputString(stream, str, NULL, NULL); +} + +static void OutputNode(wxOutputStream& stream, wxXmlNode *node, int indent, + wxMBConv *convMem, wxMBConv *convFile) +{ + wxXmlNode *n, *prev; + wxXmlProperty *prop; -IMPLEMENT_DYNAMIC_CLASS(wxXmlModule, wxModule) + switch (node->GetType()) + { + 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); + prop = node->GetProperties(); + while (prop) + { + OutputString(stream, wxT(" ") + prop->GetName() + + wxT("=\"") + prop->GetValue() + wxT("\""), + NULL, NULL); + // FIXME - what if prop contains '"'? + prop = prop->GetNext(); + } + if (node->GetChildren()) + { + OutputString(stream, wxT(">"), NULL, NULL); + 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); + 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); + } + else + OutputString(stream, wxT("/>"), NULL, NULL); + break; + + case wxXML_COMMENT_NODE: + OutputString(stream, wxT(""), NULL, NULL); + break; + + default: + wxFAIL_MSG(wxT("unsupported node type")); + } +} -// When wxXml is loaded dynamically after the application is already running -// then the built-in module system won't pick this one up. Add it manually. -void wxXmlInitXmlModule() +bool wxXmlDocument::Save(wxOutputStream& stream) const { - wxModule* module = new wxXmlModule; - module->Init(); - wxModule::RegisterModule(module); -} + if ( !IsOk() ) + return FALSE; + + wxString s; + wxMBConv *convMem = NULL, *convFile = NULL; +#if wxUSE_UNICODE + convFile = new wxCSConv(GetFileEncoding()); +#else + if ( GetFileEncoding() != GetEncoding() ) + { + convFile = new wxCSConv(GetFileEncoding()); + convMem = new wxCSConv(GetEncoding()); + } +#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; +}