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"
33 #include "expat.h" // from Expat
35 // DLL options compatibility check:
36 WX_CHECK_BUILD_OPTIONS("wxXML")
39 IMPLEMENT_CLASS(wxXmlDocument
, wxObject
)
42 // a private utility used by wxXML
43 static bool wxIsWhiteOnly(const wxString
& buf
);
46 //-----------------------------------------------------------------------------
48 //-----------------------------------------------------------------------------
50 wxXmlNode::wxXmlNode(wxXmlNode
*parent
,wxXmlNodeType type
,
51 const wxString
& name
, const wxString
& content
,
52 wxXmlAttribute
*attrs
, wxXmlNode
*next
, int lineNo
)
53 : m_type(type
), m_name(name
), m_content(content
),
54 m_attrs(attrs
), m_parent(parent
),
55 m_children(NULL
), m_next(next
),
60 if (m_parent
->m_children
)
62 m_next
= m_parent
->m_children
;
63 m_parent
->m_children
= this;
66 m_parent
->m_children
= this;
70 wxXmlNode::wxXmlNode(wxXmlNodeType type
, const wxString
& name
,
71 const wxString
& content
,
73 : m_type(type
), m_name(name
), m_content(content
),
74 m_attrs(NULL
), m_parent(NULL
),
75 m_children(NULL
), m_next(NULL
),
79 wxXmlNode::wxXmlNode(const wxXmlNode
& node
)
86 wxXmlNode::~wxXmlNode()
89 for (c
= m_children
; c
; c
= c2
)
95 wxXmlAttribute
*p
, *p2
;
96 for (p
= m_attrs
; p
; p
= p2
)
103 wxXmlNode
& wxXmlNode::operator=(const wxXmlNode
& node
)
106 wxDELETE(m_children
);
111 void wxXmlNode::DoCopy(const wxXmlNode
& node
)
113 m_type
= node
.m_type
;
114 m_name
= node
.m_name
;
115 m_content
= node
.m_content
;
116 m_lineNo
= node
.m_lineNo
;
119 wxXmlNode
*n
= node
.m_children
;
122 AddChild(new wxXmlNode(*n
));
127 wxXmlAttribute
*p
= node
.m_attrs
;
130 AddAttribute(p
->GetName(), p
->GetValue());
135 bool wxXmlNode::HasAttribute(const wxString
& attrName
) const
137 wxXmlAttribute
*attr
= GetAttributes();
141 if (attr
->GetName() == attrName
) return true;
142 attr
= attr
->GetNext();
148 bool wxXmlNode::GetAttribute(const wxString
& attrName
, wxString
*value
) const
150 wxCHECK_MSG( value
, false, "value argument must not be NULL" );
152 wxXmlAttribute
*attr
= GetAttributes();
156 if (attr
->GetName() == attrName
)
158 *value
= attr
->GetValue();
161 attr
= attr
->GetNext();
167 wxString
wxXmlNode::GetAttribute(const wxString
& attrName
, const wxString
& defaultVal
) const
170 if (GetAttribute(attrName
, &tmp
))
176 void wxXmlNode::AddChild(wxXmlNode
*child
)
178 if (m_children
== NULL
)
182 wxXmlNode
*ch
= m_children
;
183 while (ch
->m_next
) ch
= ch
->m_next
;
186 child
->m_next
= NULL
;
187 child
->m_parent
= this;
190 // inserts a new node in front of 'followingNode'
191 bool wxXmlNode::InsertChild(wxXmlNode
*child
, wxXmlNode
*followingNode
)
193 wxCHECK_MSG( child
, false, "cannot insert a NULL node!" );
194 wxCHECK_MSG( child
->m_parent
== NULL
, false, "node already has a parent" );
195 wxCHECK_MSG( child
->m_next
== NULL
, false, "node already has m_next" );
196 wxCHECK_MSG( followingNode
== NULL
|| followingNode
->GetParent() == this,
198 "wxXmlNode::InsertChild - followingNode has incorrect parent" );
200 // this is for backward compatibility, NULL was allowed here thanks to
201 // the confusion about followingNode's meaning
202 if ( followingNode
== NULL
)
203 followingNode
= m_children
;
205 if ( m_children
== followingNode
)
207 child
->m_next
= m_children
;
212 wxXmlNode
*ch
= m_children
;
213 while ( ch
&& ch
->m_next
!= followingNode
)
217 wxFAIL_MSG( "followingNode has this node as parent, but couldn't be found among children" );
221 child
->m_next
= followingNode
;
225 child
->m_parent
= this;
229 // inserts a new node right after 'precedingNode'
230 bool wxXmlNode::InsertChildAfter(wxXmlNode
*child
, wxXmlNode
*precedingNode
)
232 wxCHECK_MSG( child
, false, "cannot insert a NULL node!" );
233 wxCHECK_MSG( child
->m_parent
== NULL
, false, "node already has a parent" );
234 wxCHECK_MSG( child
->m_next
== NULL
, false, "node already has m_next" );
235 wxCHECK_MSG( precedingNode
== NULL
|| precedingNode
->m_parent
== this, false,
236 "precedingNode has wrong parent" );
240 child
->m_next
= precedingNode
->m_next
;
241 precedingNode
->m_next
= child
;
243 else // precedingNode == NULL
245 wxCHECK_MSG( m_children
== NULL
, false,
246 "NULL precedingNode only makes sense when there are no children" );
248 child
->m_next
= m_children
;
252 child
->m_parent
= this;
256 bool wxXmlNode::RemoveChild(wxXmlNode
*child
)
258 if (m_children
== NULL
)
260 else if (m_children
== child
)
262 m_children
= child
->m_next
;
263 child
->m_parent
= NULL
;
264 child
->m_next
= NULL
;
269 wxXmlNode
*ch
= m_children
;
272 if (ch
->m_next
== child
)
274 ch
->m_next
= child
->m_next
;
275 child
->m_parent
= NULL
;
276 child
->m_next
= NULL
;
285 void wxXmlNode::AddAttribute(const wxString
& name
, const wxString
& value
)
287 AddProperty(name
, value
);
290 void wxXmlNode::AddAttribute(wxXmlAttribute
*attr
)
295 bool wxXmlNode::DeleteAttribute(const wxString
& name
)
297 return DeleteProperty(name
);
300 void wxXmlNode::AddProperty(const wxString
& name
, const wxString
& value
)
302 AddProperty(new wxXmlAttribute(name
, value
, NULL
));
305 void wxXmlNode::AddProperty(wxXmlAttribute
*attr
)
311 wxXmlAttribute
*p
= m_attrs
;
312 while (p
->GetNext()) p
= p
->GetNext();
317 bool wxXmlNode::DeleteProperty(const wxString
& name
)
319 wxXmlAttribute
*attr
;
324 else if (m_attrs
->GetName() == name
)
327 m_attrs
= attr
->GetNext();
335 wxXmlAttribute
*p
= m_attrs
;
338 if (p
->GetNext()->GetName() == name
)
341 p
->SetNext(attr
->GetNext());
352 wxString
wxXmlNode::GetNodeContent() const
354 wxXmlNode
*n
= GetChildren();
358 if (n
->GetType() == wxXML_TEXT_NODE
||
359 n
->GetType() == wxXML_CDATA_SECTION_NODE
)
360 return n
->GetContent();
363 return wxEmptyString
;
366 int wxXmlNode::GetDepth(wxXmlNode
*grandparent
) const
368 const wxXmlNode
*n
= this;
375 if (n
== grandparent
)
383 bool wxXmlNode::IsWhitespaceOnly() const
385 return wxIsWhiteOnly(m_content
);
390 //-----------------------------------------------------------------------------
392 //-----------------------------------------------------------------------------
394 wxXmlDocument::wxXmlDocument()
395 : m_version(wxT("1.0")), m_fileEncoding(wxT("utf-8")), m_root(NULL
)
398 m_encoding
= wxT("UTF-8");
402 wxXmlDocument::wxXmlDocument(const wxString
& filename
, const wxString
& encoding
)
403 :wxObject(), m_root(NULL
)
405 if ( !Load(filename
, encoding
) )
411 wxXmlDocument::wxXmlDocument(wxInputStream
& stream
, const wxString
& encoding
)
412 :wxObject(), m_root(NULL
)
414 if ( !Load(stream
, encoding
) )
420 wxXmlDocument::wxXmlDocument(const wxXmlDocument
& doc
)
426 wxXmlDocument
& wxXmlDocument::operator=(const wxXmlDocument
& doc
)
433 void wxXmlDocument::DoCopy(const wxXmlDocument
& doc
)
435 m_version
= doc
.m_version
;
437 m_encoding
= doc
.m_encoding
;
439 m_fileEncoding
= doc
.m_fileEncoding
;
442 m_root
= new wxXmlNode(*doc
.m_root
);
447 bool wxXmlDocument::Load(const wxString
& filename
, const wxString
& encoding
, int flags
)
449 wxFileInputStream
stream(filename
);
452 return Load(stream
, encoding
, flags
);
455 bool wxXmlDocument::Save(const wxString
& filename
, int indentstep
) const
457 wxFileOutputStream
stream(filename
);
460 return Save(stream
, indentstep
);
465 //-----------------------------------------------------------------------------
466 // wxXmlDocument loading routines
467 //-----------------------------------------------------------------------------
469 // converts Expat-produced string in UTF-8 into wxString using the specified
470 // conv or keep in UTF-8 if conv is NULL
471 static wxString
CharToString(wxMBConv
*conv
,
472 const char *s
, size_t len
= wxString::npos
)
477 // there can be no embedded NULs in this string so we don't need the
478 // output length, it will be NUL-terminated
479 const wxWCharBuffer
wbuf(
480 wxConvUTF8
.cMB2WC(s
, len
== wxString::npos
? wxNO_LEN
: len
, NULL
));
482 return wxString(wbuf
, *conv
);
484 // else: the string is wanted in UTF-8
485 #endif // !wxUSE_UNICODE
488 return wxString::FromUTF8(s
, len
);
491 // returns true if the given string contains only whitespaces
492 bool wxIsWhiteOnly(const wxString
& buf
)
494 for ( wxString::const_iterator i
= buf
.begin(); i
!= buf
.end(); ++i
)
497 if ( c
!= wxT(' ') && c
!= wxT('\t') && c
!= wxT('\n') && c
!= wxT('\r'))
504 struct wxXmlParsingContext
506 wxXmlParsingContext()
512 removeWhiteOnlyNodes(false)
518 wxXmlNode
*node
; // the node being parsed
519 wxXmlNode
*lastChild
; // the last child of "node"
520 wxXmlNode
*lastAsText
; // the last _text_ child of "node"
523 bool removeWhiteOnlyNodes
;
526 // checks that ctx->lastChild is in consistent state
527 #define ASSERT_LAST_CHILD_OK(ctx) \
528 wxASSERT( ctx->lastChild == NULL || \
529 ctx->lastChild->GetNext() == NULL ); \
530 wxASSERT( ctx->lastChild == NULL || \
531 ctx->lastChild->GetParent() == ctx->node )
534 static void StartElementHnd(void *userData
, const char *name
, const char **atts
)
536 wxXmlParsingContext
*ctx
= (wxXmlParsingContext
*)userData
;
537 wxXmlNode
*node
= new wxXmlNode(wxXML_ELEMENT_NODE
,
538 CharToString(ctx
->conv
, name
),
540 XML_GetCurrentLineNumber(ctx
->parser
));
541 const char **a
= atts
;
543 // add node attributes
546 node
->AddAttribute(CharToString(ctx
->conv
, a
[0]), CharToString(ctx
->conv
, a
[1]));
550 if (ctx
->root
== NULL
)
556 ASSERT_LAST_CHILD_OK(ctx
);
557 ctx
->node
->InsertChildAfter(node
, ctx
->lastChild
);
560 ctx
->lastAsText
= NULL
;
561 ctx
->lastChild
= NULL
; // our new node "node" has no children yet
566 static void EndElementHnd(void *userData
, const char* WXUNUSED(name
))
568 wxXmlParsingContext
*ctx
= (wxXmlParsingContext
*)userData
;
570 // we're exiting the last children of ctx->node->GetParent() and going
571 // back one level up, so current value of ctx->node points to the last
572 // child of ctx->node->GetParent()
573 ctx
->lastChild
= ctx
->node
;
575 ctx
->node
= ctx
->node
->GetParent();
576 ctx
->lastAsText
= NULL
;
579 static void TextHnd(void *userData
, const char *s
, int len
)
581 wxXmlParsingContext
*ctx
= (wxXmlParsingContext
*)userData
;
582 wxString str
= CharToString(ctx
->conv
, s
, len
);
586 ctx
->lastAsText
->SetContent(ctx
->lastAsText
->GetContent() + str
);
590 bool whiteOnly
= false;
591 if (ctx
->removeWhiteOnlyNodes
)
592 whiteOnly
= wxIsWhiteOnly(str
);
596 wxXmlNode
*textnode
=
597 new wxXmlNode(wxXML_TEXT_NODE
, wxT("text"), str
,
598 XML_GetCurrentLineNumber(ctx
->parser
));
600 ASSERT_LAST_CHILD_OK(ctx
);
601 ctx
->node
->InsertChildAfter(textnode
, ctx
->lastChild
);
602 ctx
->lastChild
= ctx
->lastAsText
= textnode
;
607 static void StartCdataHnd(void *userData
)
609 wxXmlParsingContext
*ctx
= (wxXmlParsingContext
*)userData
;
611 wxXmlNode
*textnode
=
612 new wxXmlNode(wxXML_CDATA_SECTION_NODE
, wxT("cdata"), wxT(""),
613 XML_GetCurrentLineNumber(ctx
->parser
));
615 ASSERT_LAST_CHILD_OK(ctx
);
616 ctx
->node
->InsertChildAfter(textnode
, ctx
->lastChild
);
617 ctx
->lastChild
= ctx
->lastAsText
= textnode
;
620 static void CommentHnd(void *userData
, const char *data
)
622 wxXmlParsingContext
*ctx
= (wxXmlParsingContext
*)userData
;
626 wxXmlNode
*commentnode
=
627 new wxXmlNode(wxXML_COMMENT_NODE
,
628 wxT("comment"), CharToString(ctx
->conv
, data
),
629 XML_GetCurrentLineNumber(ctx
->parser
));
631 ASSERT_LAST_CHILD_OK(ctx
);
632 ctx
->node
->InsertChildAfter(commentnode
, ctx
->lastChild
);
633 ctx
->lastChild
= commentnode
;
635 //else: ctx->node == NULL happens if there is a comment before
636 // the root element. We current don't have a way to represent
637 // these in wxXmlDocument (FIXME).
639 ctx
->lastAsText
= NULL
;
642 static void DefaultHnd(void *userData
, const char *s
, int len
)
645 if (len
> 6 && memcmp(s
, "<?xml ", 6) == 0)
647 wxXmlParsingContext
*ctx
= (wxXmlParsingContext
*)userData
;
649 wxString buf
= CharToString(ctx
->conv
, s
, (size_t)len
);
651 pos
= buf
.Find(wxT("encoding="));
652 if (pos
!= wxNOT_FOUND
)
653 ctx
->encoding
= buf
.Mid(pos
+ 10).BeforeFirst(buf
[(size_t)pos
+9]);
654 pos
= buf
.Find(wxT("version="));
655 if (pos
!= wxNOT_FOUND
)
656 ctx
->version
= buf
.Mid(pos
+ 9).BeforeFirst(buf
[(size_t)pos
+8]);
660 static int UnknownEncodingHnd(void * WXUNUSED(encodingHandlerData
),
661 const XML_Char
*name
, XML_Encoding
*info
)
663 // We must build conversion table for expat. The easiest way to do so
664 // is to let wxCSConv convert as string containing all characters to
665 // wide character representation:
673 for (i
= 0; i
< 255; i
++)
675 mbBuf
[0] = (char)(i
+1);
676 if (conv
.MB2WC(wcBuf
, mbBuf
, 2) == (size_t)-1)
678 // invalid/undefined byte in the encoding:
681 info
->map
[i
+1] = (int)wcBuf
[0];
685 info
->convert
= NULL
;
686 info
->release
= NULL
;
693 bool wxXmlDocument::Load(wxInputStream
& stream
, const wxString
& encoding
, int flags
)
698 m_encoding
= encoding
;
701 const size_t BUFSIZE
= 1024;
703 wxXmlParsingContext ctx
;
705 XML_Parser parser
= XML_ParserCreate(NULL
);
707 ctx
.encoding
= wxT("UTF-8"); // default in absence of encoding=""
710 if ( encoding
.CmpNoCase(wxT("UTF-8")) != 0 )
711 ctx
.conv
= new wxCSConv(encoding
);
713 ctx
.removeWhiteOnlyNodes
= (flags
& wxXMLDOC_KEEP_WHITESPACE_NODES
) == 0;
716 XML_SetUserData(parser
, (void*)&ctx
);
717 XML_SetElementHandler(parser
, StartElementHnd
, EndElementHnd
);
718 XML_SetCharacterDataHandler(parser
, TextHnd
);
719 XML_SetStartCdataSectionHandler(parser
, StartCdataHnd
);
720 XML_SetCommentHandler(parser
, CommentHnd
);
721 XML_SetDefaultHandler(parser
, DefaultHnd
);
722 XML_SetUnknownEncodingHandler(parser
, UnknownEncodingHnd
, NULL
);
727 size_t len
= stream
.Read(buf
, BUFSIZE
).LastRead();
728 done
= (len
< BUFSIZE
);
729 if (!XML_Parse(parser
, buf
, len
, done
))
731 wxString
error(XML_ErrorString(XML_GetErrorCode(parser
)),
733 wxLogError(_("XML parsing error: '%s' at line %d"),
735 XML_GetCurrentLineNumber(parser
));
743 if (!ctx
.version
.empty())
744 SetVersion(ctx
.version
);
745 if (!ctx
.encoding
.empty())
746 SetFileEncoding(ctx
.encoding
);
754 XML_ParserFree(parser
);
766 //-----------------------------------------------------------------------------
767 // wxXmlDocument saving routines
768 //-----------------------------------------------------------------------------
770 // write string to output:
771 inline static void OutputString(wxOutputStream
& stream
, const wxString
& str
,
772 wxMBConv
*convMem
= NULL
,
773 wxMBConv
*convFile
= NULL
)
779 wxUnusedVar(convMem
);
781 const wxWX2MBbuf
buf(str
.mb_str(*(convFile
? convFile
: &wxConvUTF8
)));
782 stream
.Write((const char*)buf
, strlen((const char*)buf
));
783 #else // !wxUSE_UNICODE
784 if ( convFile
&& convMem
)
786 wxString
str2(str
.wc_str(*convMem
), *convFile
);
787 stream
.Write(str2
.mb_str(), str2
.Len());
789 else // no conversions to do
791 stream
.Write(str
.mb_str(), str
.Len());
793 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
796 // flags for OutputStringEnt()
799 XML_ESCAPE_QUOTES
= 1
802 // Same as above, but create entities first.
803 // Translates '<' to "<", '>' to ">" and '&' to "&"
804 static void OutputStringEnt(wxOutputStream
& stream
, const wxString
& str
,
805 wxMBConv
*convMem
= NULL
,
806 wxMBConv
*convFile
= NULL
,
815 for (i
= 0; i
< len
; i
++)
818 if (c
== wxT('<') || c
== wxT('>') ||
819 (c
== wxT('&') && str
.Mid(i
+1, 4) != wxT("amp;")) ||
820 ((flags
& XML_ESCAPE_QUOTES
) && c
== wxT('"')))
822 OutputString(stream
, str
.Mid(last
, i
- last
), convMem
, convFile
);
826 OutputString(stream
, wxT("<"));
829 OutputString(stream
, wxT(">"));
832 OutputString(stream
, wxT("&"));
835 OutputString(stream
, wxT("""));
843 OutputString(stream
, str
.Mid(last
, i
- last
), convMem
, convFile
);
846 inline static void OutputIndentation(wxOutputStream
& stream
, int indent
)
848 wxString str
= wxT("\n");
849 for (int i
= 0; i
< indent
; i
++)
850 str
<< wxT(' ') << wxT(' ');
851 OutputString(stream
, str
);
854 static void OutputNode(wxOutputStream
& stream
, wxXmlNode
*node
, int indent
,
855 wxMBConv
*convMem
, wxMBConv
*convFile
, int indentstep
)
858 wxXmlAttribute
*attr
;
860 switch (node
->GetType())
862 case wxXML_CDATA_SECTION_NODE
:
863 OutputString( stream
, wxT("<![CDATA["));
864 OutputString( stream
, node
->GetContent() );
865 OutputString( stream
, wxT("]]>") );
868 case wxXML_TEXT_NODE
:
869 OutputStringEnt(stream
, node
->GetContent(), convMem
, convFile
);
872 case wxXML_ELEMENT_NODE
:
873 OutputString(stream
, wxT("<"));
874 OutputString(stream
, node
->GetName());
876 attr
= node
->GetAttributes();
879 OutputString(stream
, wxT(" ") + attr
->GetName() + wxT("=\""));
880 OutputStringEnt(stream
, attr
->GetValue(), convMem
, convFile
,
882 OutputString(stream
, wxT("\""));
883 attr
= attr
->GetNext();
886 if (node
->GetChildren())
888 OutputString(stream
, wxT(">"));
890 n
= node
->GetChildren();
893 if (indentstep
>= 0 && n
&& n
->GetType() != wxXML_TEXT_NODE
)
894 OutputIndentation(stream
, indent
+ indentstep
);
895 OutputNode(stream
, n
, indent
+ indentstep
, convMem
, convFile
, indentstep
);
899 if (indentstep
>= 0 && prev
&& prev
->GetType() != wxXML_TEXT_NODE
)
900 OutputIndentation(stream
, indent
);
901 OutputString(stream
, wxT("</"));
902 OutputString(stream
, node
->GetName());
903 OutputString(stream
, wxT(">"));
906 OutputString(stream
, wxT("/>"));
909 case wxXML_COMMENT_NODE
:
910 OutputString(stream
, wxT("<!--"));
911 OutputString(stream
, node
->GetContent(), convMem
, convFile
);
912 OutputString(stream
, wxT("-->"));
916 wxFAIL_MSG(wxT("unsupported node type"));
920 bool wxXmlDocument::Save(wxOutputStream
& stream
, int indentstep
) const
927 wxMBConv
*convMem
= NULL
,
931 convFile
= new wxCSConv(GetFileEncoding());
934 if ( GetFileEncoding().CmpNoCase(GetEncoding()) != 0 )
936 convFile
= new wxCSConv(GetFileEncoding());
937 convMem
= new wxCSConv(GetEncoding());
939 else // file and in-memory encodings are the same, no conversion needed
946 s
.Printf(wxT("<?xml version=\"%s\" encoding=\"%s\"?>\n"),
947 GetVersion().c_str(), GetFileEncoding().c_str());
948 OutputString(stream
, s
);
950 OutputNode(stream
, GetRoot(), 0, convMem
, convFile
, indentstep
);
951 OutputString(stream
, wxT("\n"));