1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/xml/xml.cpp
3 // Purpose: wxXmlDocument - XML parser & data holder class
4 // Author: Vaclav Slavik
7 // Copyright: (c) 2000 Vaclav Slavik
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
11 // For compilers that support precompilation, includes "wx.h".
12 #include "wx/wxprec.h"
20 #include "wx/xml/xml.h"
28 #include "wx/wfstream.h"
29 #include "wx/datstrm.h"
30 #include "wx/zstream.h"
31 #include "wx/strconv.h"
32 #include "wx/scopedptr.h"
34 #include "expat.h" // from Expat
36 // DLL options compatibility check:
37 WX_CHECK_BUILD_OPTIONS("wxXML")
40 IMPLEMENT_CLASS(wxXmlDocument
, wxObject
)
43 // a private utility used by wxXML
44 static bool wxIsWhiteOnly(const wxString
& buf
);
47 //-----------------------------------------------------------------------------
49 //-----------------------------------------------------------------------------
51 wxXmlNode::wxXmlNode(wxXmlNode
*parent
,wxXmlNodeType type
,
52 const wxString
& name
, const wxString
& content
,
53 wxXmlAttribute
*attrs
, wxXmlNode
*next
, int lineNo
)
54 : m_type(type
), m_name(name
), m_content(content
),
55 m_attrs(attrs
), m_parent(parent
),
56 m_children(NULL
), m_next(next
),
61 if (m_parent
->m_children
)
63 m_next
= m_parent
->m_children
;
64 m_parent
->m_children
= this;
67 m_parent
->m_children
= this;
71 wxXmlNode::wxXmlNode(wxXmlNodeType type
, const wxString
& name
,
72 const wxString
& content
,
74 : m_type(type
), m_name(name
), m_content(content
),
75 m_attrs(NULL
), m_parent(NULL
),
76 m_children(NULL
), m_next(NULL
),
80 wxXmlNode::wxXmlNode(const wxXmlNode
& node
)
87 wxXmlNode::~wxXmlNode()
90 for (c
= m_children
; c
; c
= c2
)
96 wxXmlAttribute
*p
, *p2
;
97 for (p
= m_attrs
; p
; p
= p2
)
104 wxXmlNode
& wxXmlNode::operator=(const wxXmlNode
& node
)
107 wxDELETE(m_children
);
112 void wxXmlNode::DoCopy(const wxXmlNode
& node
)
114 m_type
= node
.m_type
;
115 m_name
= node
.m_name
;
116 m_content
= node
.m_content
;
117 m_lineNo
= node
.m_lineNo
;
120 wxXmlNode
*n
= node
.m_children
;
123 AddChild(new wxXmlNode(*n
));
128 wxXmlAttribute
*p
= node
.m_attrs
;
131 AddAttribute(p
->GetName(), p
->GetValue());
136 bool wxXmlNode::HasAttribute(const wxString
& attrName
) const
138 wxXmlAttribute
*attr
= GetAttributes();
142 if (attr
->GetName() == attrName
) return true;
143 attr
= attr
->GetNext();
149 bool wxXmlNode::GetAttribute(const wxString
& attrName
, wxString
*value
) const
151 wxCHECK_MSG( value
, false, "value argument must not be NULL" );
153 wxXmlAttribute
*attr
= GetAttributes();
157 if (attr
->GetName() == attrName
)
159 *value
= attr
->GetValue();
162 attr
= attr
->GetNext();
168 wxString
wxXmlNode::GetAttribute(const wxString
& attrName
, const wxString
& defaultVal
) const
171 if (GetAttribute(attrName
, &tmp
))
177 void wxXmlNode::AddChild(wxXmlNode
*child
)
179 if (m_children
== NULL
)
183 wxXmlNode
*ch
= m_children
;
184 while (ch
->m_next
) ch
= ch
->m_next
;
187 child
->m_next
= NULL
;
188 child
->m_parent
= this;
191 // inserts a new node in front of 'followingNode'
192 bool wxXmlNode::InsertChild(wxXmlNode
*child
, wxXmlNode
*followingNode
)
194 wxCHECK_MSG( child
, false, "cannot insert a NULL node!" );
195 wxCHECK_MSG( child
->m_parent
== NULL
, false, "node already has a parent" );
196 wxCHECK_MSG( child
->m_next
== NULL
, false, "node already has m_next" );
197 wxCHECK_MSG( followingNode
== NULL
|| followingNode
->GetParent() == this,
199 "wxXmlNode::InsertChild - followingNode has incorrect parent" );
201 // this is for backward compatibility, NULL was allowed here thanks to
202 // the confusion about followingNode's meaning
203 if ( followingNode
== NULL
)
204 followingNode
= m_children
;
206 if ( m_children
== followingNode
)
208 child
->m_next
= m_children
;
213 wxXmlNode
*ch
= m_children
;
214 while ( ch
&& ch
->m_next
!= followingNode
)
218 wxFAIL_MSG( "followingNode has this node as parent, but couldn't be found among children" );
222 child
->m_next
= followingNode
;
226 child
->m_parent
= this;
230 // inserts a new node right after 'precedingNode'
231 bool wxXmlNode::InsertChildAfter(wxXmlNode
*child
, wxXmlNode
*precedingNode
)
233 wxCHECK_MSG( child
, false, "cannot insert a NULL node!" );
234 wxCHECK_MSG( child
->m_parent
== NULL
, false, "node already has a parent" );
235 wxCHECK_MSG( child
->m_next
== NULL
, false, "node already has m_next" );
236 wxCHECK_MSG( precedingNode
== NULL
|| precedingNode
->m_parent
== this, false,
237 "precedingNode has wrong parent" );
241 child
->m_next
= precedingNode
->m_next
;
242 precedingNode
->m_next
= child
;
244 else // precedingNode == NULL
246 wxCHECK_MSG( m_children
== NULL
, false,
247 "NULL precedingNode only makes sense when there are no children" );
249 child
->m_next
= m_children
;
253 child
->m_parent
= this;
257 bool wxXmlNode::RemoveChild(wxXmlNode
*child
)
259 if (m_children
== NULL
)
261 else if (m_children
== child
)
263 m_children
= child
->m_next
;
264 child
->m_parent
= NULL
;
265 child
->m_next
= NULL
;
270 wxXmlNode
*ch
= m_children
;
273 if (ch
->m_next
== child
)
275 ch
->m_next
= child
->m_next
;
276 child
->m_parent
= NULL
;
277 child
->m_next
= NULL
;
286 void wxXmlNode::AddAttribute(const wxString
& name
, const wxString
& value
)
288 AddProperty(name
, value
);
291 void wxXmlNode::AddAttribute(wxXmlAttribute
*attr
)
296 bool wxXmlNode::DeleteAttribute(const wxString
& name
)
298 return DeleteProperty(name
);
301 void wxXmlNode::AddProperty(const wxString
& name
, const wxString
& value
)
303 AddProperty(new wxXmlAttribute(name
, value
, NULL
));
306 void wxXmlNode::AddProperty(wxXmlAttribute
*attr
)
312 wxXmlAttribute
*p
= m_attrs
;
313 while (p
->GetNext()) p
= p
->GetNext();
318 bool wxXmlNode::DeleteProperty(const wxString
& name
)
320 wxXmlAttribute
*attr
;
325 else if (m_attrs
->GetName() == name
)
328 m_attrs
= attr
->GetNext();
336 wxXmlAttribute
*p
= m_attrs
;
339 if (p
->GetNext()->GetName() == name
)
342 p
->SetNext(attr
->GetNext());
353 wxString
wxXmlNode::GetNodeContent() const
355 wxXmlNode
*n
= GetChildren();
359 if (n
->GetType() == wxXML_TEXT_NODE
||
360 n
->GetType() == wxXML_CDATA_SECTION_NODE
)
361 return n
->GetContent();
364 return wxEmptyString
;
367 int wxXmlNode::GetDepth(wxXmlNode
*grandparent
) const
369 const wxXmlNode
*n
= this;
376 if (n
== grandparent
)
384 bool wxXmlNode::IsWhitespaceOnly() const
386 return wxIsWhiteOnly(m_content
);
391 //-----------------------------------------------------------------------------
393 //-----------------------------------------------------------------------------
395 wxXmlDocument::wxXmlDocument()
396 : m_version(wxS("1.0")), m_fileEncoding(wxS("utf-8")), m_root(NULL
)
399 m_encoding
= wxS("UTF-8");
403 wxXmlDocument::wxXmlDocument(const wxString
& filename
, const wxString
& encoding
)
404 :wxObject(), m_root(NULL
)
406 if ( !Load(filename
, encoding
) )
412 wxXmlDocument::wxXmlDocument(wxInputStream
& stream
, const wxString
& encoding
)
413 :wxObject(), m_root(NULL
)
415 if ( !Load(stream
, encoding
) )
421 wxXmlDocument::wxXmlDocument(const wxXmlDocument
& doc
)
427 wxXmlDocument
& wxXmlDocument::operator=(const wxXmlDocument
& doc
)
434 void wxXmlDocument::DoCopy(const wxXmlDocument
& doc
)
436 m_version
= doc
.m_version
;
438 m_encoding
= doc
.m_encoding
;
440 m_fileEncoding
= doc
.m_fileEncoding
;
443 m_root
= new wxXmlNode(*doc
.m_root
);
448 bool wxXmlDocument::Load(const wxString
& filename
, const wxString
& encoding
, int flags
)
450 wxFileInputStream
stream(filename
);
453 return Load(stream
, encoding
, flags
);
456 bool wxXmlDocument::Save(const wxString
& filename
, int indentstep
) const
458 wxFileOutputStream
stream(filename
);
461 return Save(stream
, indentstep
);
466 //-----------------------------------------------------------------------------
467 // wxXmlDocument loading routines
468 //-----------------------------------------------------------------------------
470 // converts Expat-produced string in UTF-8 into wxString using the specified
471 // conv or keep in UTF-8 if conv is NULL
472 static wxString
CharToString(wxMBConv
*conv
,
473 const char *s
, size_t len
= wxString::npos
)
478 // there can be no embedded NULs in this string so we don't need the
479 // output length, it will be NUL-terminated
480 const wxWCharBuffer
wbuf(
481 wxConvUTF8
.cMB2WC(s
, len
== wxString::npos
? wxNO_LEN
: len
, NULL
));
483 return wxString(wbuf
, *conv
);
485 // else: the string is wanted in UTF-8
486 #endif // !wxUSE_UNICODE
489 return wxString::FromUTF8Unchecked(s
, len
);
492 // returns true if the given string contains only whitespaces
493 bool wxIsWhiteOnly(const wxString
& buf
)
495 for ( wxString::const_iterator i
= buf
.begin(); i
!= buf
.end(); ++i
)
498 if ( c
!= wxS(' ') && c
!= wxS('\t') && c
!= wxS('\n') && c
!= wxS('\r'))
505 struct wxXmlParsingContext
507 wxXmlParsingContext()
513 removeWhiteOnlyNodes(false)
519 wxXmlNode
*node
; // the node being parsed
520 wxXmlNode
*lastChild
; // the last child of "node"
521 wxXmlNode
*lastAsText
; // the last _text_ child of "node"
524 bool removeWhiteOnlyNodes
;
527 // checks that ctx->lastChild is in consistent state
528 #define ASSERT_LAST_CHILD_OK(ctx) \
529 wxASSERT( ctx->lastChild == NULL || \
530 ctx->lastChild->GetNext() == NULL ); \
531 wxASSERT( ctx->lastChild == NULL || \
532 ctx->lastChild->GetParent() == ctx->node )
535 static void StartElementHnd(void *userData
, const char *name
, const char **atts
)
537 wxXmlParsingContext
*ctx
= (wxXmlParsingContext
*)userData
;
538 wxXmlNode
*node
= new wxXmlNode(wxXML_ELEMENT_NODE
,
539 CharToString(ctx
->conv
, name
),
541 XML_GetCurrentLineNumber(ctx
->parser
));
542 const char **a
= atts
;
544 // add node attributes
547 node
->AddAttribute(CharToString(ctx
->conv
, a
[0]), CharToString(ctx
->conv
, a
[1]));
551 if (ctx
->root
== NULL
)
557 ASSERT_LAST_CHILD_OK(ctx
);
558 ctx
->node
->InsertChildAfter(node
, ctx
->lastChild
);
561 ctx
->lastAsText
= NULL
;
562 ctx
->lastChild
= NULL
; // our new node "node" has no children yet
567 static void EndElementHnd(void *userData
, const char* WXUNUSED(name
))
569 wxXmlParsingContext
*ctx
= (wxXmlParsingContext
*)userData
;
571 // we're exiting the last children of ctx->node->GetParent() and going
572 // back one level up, so current value of ctx->node points to the last
573 // child of ctx->node->GetParent()
574 ctx
->lastChild
= ctx
->node
;
576 ctx
->node
= ctx
->node
->GetParent();
577 ctx
->lastAsText
= NULL
;
580 static void TextHnd(void *userData
, const char *s
, int len
)
582 wxXmlParsingContext
*ctx
= (wxXmlParsingContext
*)userData
;
583 wxString str
= CharToString(ctx
->conv
, s
, len
);
587 ctx
->lastAsText
->SetContent(ctx
->lastAsText
->GetContent() + str
);
591 bool whiteOnly
= false;
592 if (ctx
->removeWhiteOnlyNodes
)
593 whiteOnly
= wxIsWhiteOnly(str
);
597 wxXmlNode
*textnode
=
598 new wxXmlNode(wxXML_TEXT_NODE
, wxS("text"), str
,
599 XML_GetCurrentLineNumber(ctx
->parser
));
601 ASSERT_LAST_CHILD_OK(ctx
);
602 ctx
->node
->InsertChildAfter(textnode
, ctx
->lastChild
);
603 ctx
->lastChild
= ctx
->lastAsText
= textnode
;
608 static void StartCdataHnd(void *userData
)
610 wxXmlParsingContext
*ctx
= (wxXmlParsingContext
*)userData
;
612 wxXmlNode
*textnode
=
613 new wxXmlNode(wxXML_CDATA_SECTION_NODE
, wxS("cdata"), wxS(""),
614 XML_GetCurrentLineNumber(ctx
->parser
));
616 ASSERT_LAST_CHILD_OK(ctx
);
617 ctx
->node
->InsertChildAfter(textnode
, ctx
->lastChild
);
618 ctx
->lastChild
= ctx
->lastAsText
= textnode
;
621 static void CommentHnd(void *userData
, const char *data
)
623 wxXmlParsingContext
*ctx
= (wxXmlParsingContext
*)userData
;
627 wxXmlNode
*commentnode
=
628 new wxXmlNode(wxXML_COMMENT_NODE
,
629 wxS("comment"), CharToString(ctx
->conv
, data
),
630 XML_GetCurrentLineNumber(ctx
->parser
));
632 ASSERT_LAST_CHILD_OK(ctx
);
633 ctx
->node
->InsertChildAfter(commentnode
, ctx
->lastChild
);
634 ctx
->lastChild
= commentnode
;
636 //else: ctx->node == NULL happens if there is a comment before
637 // the root element. We current don't have a way to represent
638 // these in wxXmlDocument (FIXME).
640 ctx
->lastAsText
= NULL
;
643 static void DefaultHnd(void *userData
, const char *s
, int len
)
646 if (len
> 6 && memcmp(s
, "<?xml ", 6) == 0)
648 wxXmlParsingContext
*ctx
= (wxXmlParsingContext
*)userData
;
650 wxString buf
= CharToString(ctx
->conv
, s
, (size_t)len
);
652 pos
= buf
.Find(wxS("encoding="));
653 if (pos
!= wxNOT_FOUND
)
654 ctx
->encoding
= buf
.Mid(pos
+ 10).BeforeFirst(buf
[(size_t)pos
+9]);
655 pos
= buf
.Find(wxS("version="));
656 if (pos
!= wxNOT_FOUND
)
657 ctx
->version
= buf
.Mid(pos
+ 9).BeforeFirst(buf
[(size_t)pos
+8]);
661 static int UnknownEncodingHnd(void * WXUNUSED(encodingHandlerData
),
662 const XML_Char
*name
, XML_Encoding
*info
)
664 // We must build conversion table for expat. The easiest way to do so
665 // is to let wxCSConv convert as string containing all characters to
666 // wide character representation:
674 for (i
= 0; i
< 255; i
++)
676 mbBuf
[0] = (char)(i
+1);
677 if (conv
.MB2WC(wcBuf
, mbBuf
, 2) == (size_t)-1)
679 // invalid/undefined byte in the encoding:
682 info
->map
[i
+1] = (int)wcBuf
[0];
686 info
->convert
= NULL
;
687 info
->release
= NULL
;
694 bool wxXmlDocument::Load(wxInputStream
& stream
, const wxString
& encoding
, int flags
)
699 m_encoding
= encoding
;
702 const size_t BUFSIZE
= 1024;
704 wxXmlParsingContext ctx
;
706 XML_Parser parser
= XML_ParserCreate(NULL
);
708 ctx
.encoding
= wxS("UTF-8"); // default in absence of encoding=""
711 if ( encoding
.CmpNoCase(wxS("UTF-8")) != 0 )
712 ctx
.conv
= new wxCSConv(encoding
);
714 ctx
.removeWhiteOnlyNodes
= (flags
& wxXMLDOC_KEEP_WHITESPACE_NODES
) == 0;
717 XML_SetUserData(parser
, (void*)&ctx
);
718 XML_SetElementHandler(parser
, StartElementHnd
, EndElementHnd
);
719 XML_SetCharacterDataHandler(parser
, TextHnd
);
720 XML_SetStartCdataSectionHandler(parser
, StartCdataHnd
);
721 XML_SetCommentHandler(parser
, CommentHnd
);
722 XML_SetDefaultHandler(parser
, DefaultHnd
);
723 XML_SetUnknownEncodingHandler(parser
, UnknownEncodingHnd
, NULL
);
728 size_t len
= stream
.Read(buf
, BUFSIZE
).LastRead();
729 done
= (len
< BUFSIZE
);
730 if (!XML_Parse(parser
, buf
, len
, done
))
732 wxString
error(XML_ErrorString(XML_GetErrorCode(parser
)),
734 wxLogError(_("XML parsing error: '%s' at line %d"),
736 XML_GetCurrentLineNumber(parser
));
744 if (!ctx
.version
.empty())
745 SetVersion(ctx
.version
);
746 if (!ctx
.encoding
.empty())
747 SetFileEncoding(ctx
.encoding
);
755 XML_ParserFree(parser
);
767 //-----------------------------------------------------------------------------
768 // wxXmlDocument saving routines
769 //-----------------------------------------------------------------------------
771 // helpers for XML generation
775 // write string to output:
776 bool OutputString(wxOutputStream
& stream
,
785 wxUnusedVar(convMem
);
787 convFile
= &wxConvUTF8
;
789 const wxScopedCharBuffer
buf(str
.mb_str(*convFile
));
792 // conversion failed, can't write this string in an XML file in this
793 // (presumably non-UTF-8) encoding
797 stream
.Write(buf
, buf
.length());
798 #else // !wxUSE_UNICODE
799 if ( convFile
&& convMem
)
801 wxString
str2(str
.wc_str(*convMem
), *convFile
);
802 stream
.Write(str2
.mb_str(), str2
.length());
804 else // no conversions to do
806 stream
.Write(str
.mb_str(), str
.length());
808 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
810 return stream
.IsOk();
813 // flags for OutputStringEnt()
816 XML_ESCAPE_QUOTES
= 1
819 // Same as above, but create entities first.
820 // Translates '<' to "<", '>' to ">" and '&' to "&"
821 bool OutputStringEnt(wxOutputStream
& stream
,
827 const size_t len
= str
.length();
830 for (i
= 0; i
< len
; i
++)
832 wxChar c
= str
.GetChar(i
);
833 if (c
== wxS('<') || c
== wxS('>') ||
834 (c
== wxS('&') && str
.substr(i
+1, 4) != wxS("amp;")) ||
835 ((flags
& XML_ESCAPE_QUOTES
) && c
== wxS('"')))
837 if ( !OutputString(stream
, str
.substr(last
, i
- last
),
857 wxFAIL_MSG( "logic error in the code" );
861 if ( !OutputString(stream
, escaped
, convMem
, convFile
) )
868 return OutputString(stream
, str
.substr(last
, i
- last
), convMem
, convFile
);
871 bool OutputIndentation(wxOutputStream
& stream
,
876 wxString
str(wxS("\n"));
877 str
+= wxString(2*indent
, wxS(' '));
878 return OutputString(stream
, str
, convMem
, convFile
);
881 bool OutputNode(wxOutputStream
& stream
,
889 switch (node
->GetType())
891 case wxXML_CDATA_SECTION_NODE
:
892 rc
= OutputString(stream
, wxS("<![CDATA["), convMem
, convFile
) &&
893 OutputString(stream
, node
->GetContent(), convMem
, convFile
) &&
894 OutputString(stream
, wxS("]]>"), convMem
, convFile
);
897 case wxXML_TEXT_NODE
:
898 rc
= OutputStringEnt(stream
, node
->GetContent(), convMem
, convFile
);
901 case wxXML_ELEMENT_NODE
:
902 rc
= OutputString(stream
, wxS("<"), convMem
, convFile
) &&
903 OutputString(stream
, node
->GetName(), convMem
, convFile
);
907 for ( wxXmlAttribute
*attr
= node
->GetAttributes();
909 attr
= attr
->GetNext() )
911 rc
= OutputString(stream
,
912 wxS(" ") + attr
->GetName() + wxS("=\""),
913 convMem
, convFile
) &&
914 OutputStringEnt(stream
, attr
->GetValue(),
916 XML_ESCAPE_QUOTES
) &&
917 OutputString(stream
, wxS("\""), convMem
, convFile
);
921 if ( node
->GetChildren() )
923 rc
= OutputString(stream
, wxS(">"), convMem
, convFile
);
925 wxXmlNode
*prev
= NULL
;
926 for ( wxXmlNode
*n
= node
->GetChildren();
930 if ( indentstep
>= 0 && n
->GetType() != wxXML_TEXT_NODE
)
932 rc
= OutputIndentation(stream
, indent
+ indentstep
,
937 rc
= OutputNode(stream
, n
, indent
+ indentstep
,
938 convMem
, convFile
, indentstep
);
943 if ( rc
&& indentstep
>= 0 &&
944 prev
&& prev
->GetType() != wxXML_TEXT_NODE
)
946 rc
= OutputIndentation(stream
, indent
, convMem
, convFile
);
951 rc
= OutputString(stream
, wxS("</"), convMem
, convFile
) &&
952 OutputString(stream
, node
->GetName(),
953 convMem
, convFile
) &&
954 OutputString(stream
, wxS(">"), convMem
, convFile
);
957 else // no children, output "<foo/>"
959 rc
= OutputString(stream
, wxS("/>"), convMem
, convFile
);
963 case wxXML_COMMENT_NODE
:
964 rc
= OutputString(stream
, wxS("<!--"), convMem
, convFile
) &&
965 OutputString(stream
, node
->GetContent(), convMem
, convFile
) &&
966 OutputString(stream
, wxS("-->"), convMem
, convFile
);
970 wxFAIL_MSG("unsupported node type");
977 } // anonymous namespace
979 bool wxXmlDocument::Save(wxOutputStream
& stream
, int indentstep
) const
984 wxScopedPtr
<wxMBConv
> convMem
, convFile
;
987 convFile
.reset(new wxCSConv(GetFileEncoding()));
989 if ( GetFileEncoding().CmpNoCase(GetEncoding()) != 0 )
991 convFile
.reset(new wxCSConv(GetFileEncoding()));
992 convMem
.reset(new wxCSConv(GetEncoding()));
994 //else: file and in-memory encodings are the same, no conversion needed
997 return OutputString(stream
,
1000 wxS("<?xml version=\"%s\" encoding=\"%s\"?>\n"),
1001 GetVersion(), GetFileEncoding()
1005 OutputNode(stream
, GetRoot(), 0,
1006 convMem
.get(), convFile
.get(), indentstep
) &&
1007 OutputString(stream
, wxS("\n"), convMem
.get(), convFile
.get());