]>
git.saurik.com Git - wxWidgets.git/blob - src/xml/xml.cpp
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
)
43 //-----------------------------------------------------------------------------
45 //-----------------------------------------------------------------------------
47 wxXmlNode::wxXmlNode(wxXmlNode
*parent
,wxXmlNodeType type
,
48 const wxString
& name
, const wxString
& content
,
49 wxXmlProperty
*props
, wxXmlNode
*next
)
50 : m_type(type
), m_name(name
), m_content(content
),
51 m_properties(props
), m_parent(parent
),
52 m_children(NULL
), m_next(next
)
56 if (m_parent
->m_children
)
58 m_next
= m_parent
->m_children
;
59 m_parent
->m_children
= this;
62 m_parent
->m_children
= this;
66 wxXmlNode::wxXmlNode(wxXmlNodeType type
, const wxString
& name
,
67 const wxString
& content
)
68 : m_type(type
), m_name(name
), m_content(content
),
69 m_properties(NULL
), m_parent(NULL
),
70 m_children(NULL
), m_next(NULL
)
73 wxXmlNode::wxXmlNode(const wxXmlNode
& node
)
80 wxXmlNode::~wxXmlNode()
83 for (c
= m_children
; c
; c
= c2
)
89 wxXmlProperty
*p
, *p2
;
90 for (p
= m_properties
; p
; p
= p2
)
97 wxXmlNode
& wxXmlNode::operator=(const wxXmlNode
& node
)
99 wxDELETE(m_properties
);
100 wxDELETE(m_children
);
105 void wxXmlNode::DoCopy(const wxXmlNode
& node
)
107 m_type
= node
.m_type
;
108 m_name
= node
.m_name
;
109 m_content
= node
.m_content
;
112 wxXmlNode
*n
= node
.m_children
;
115 AddChild(new wxXmlNode(*n
));
120 wxXmlProperty
*p
= node
.m_properties
;
123 AddProperty(p
->GetName(), p
->GetValue());
128 bool wxXmlNode::HasProp(const wxString
& propName
) const
130 wxXmlProperty
*prop
= GetProperties();
134 if (prop
->GetName() == propName
) return true;
135 prop
= prop
->GetNext();
141 bool wxXmlNode::GetPropVal(const wxString
& propName
, wxString
*value
) const
143 wxXmlProperty
*prop
= GetProperties();
147 if (prop
->GetName() == propName
)
149 *value
= prop
->GetValue();
152 prop
= prop
->GetNext();
158 wxString
wxXmlNode::GetPropVal(const wxString
& propName
, const wxString
& defaultVal
) const
161 if (GetPropVal(propName
, &tmp
))
167 void wxXmlNode::AddChild(wxXmlNode
*child
)
169 if (m_children
== NULL
)
173 wxXmlNode
*ch
= m_children
;
174 while (ch
->m_next
) ch
= ch
->m_next
;
177 child
->m_next
= NULL
;
178 child
->m_parent
= this;
181 bool wxXmlNode::InsertChild(wxXmlNode
*child
, wxXmlNode
*before_node
)
183 wxCHECK_MSG(before_node
== NULL
|| before_node
->GetParent() == this, false,
184 wxT("wxXmlNode::InsertChild - the node has incorrect parent"));
185 wxCHECK_MSG(child
, false, wxT("Cannot insert a NULL pointer!"));
187 if (m_children
== before_node
)
189 else if (m_children
== NULL
)
191 if (before_node
!= NULL
)
192 return false; // we have no children so we don't need to search
195 else if (before_node
== NULL
)
198 child
->m_parent
= this;
199 child
->m_next
= m_children
;
205 wxXmlNode
*ch
= m_children
;
206 while (ch
&& ch
->m_next
!= before_node
) ch
= ch
->m_next
;
208 return false; // before_node not found
212 child
->m_parent
= this;
213 child
->m_next
= before_node
;
217 bool wxXmlNode::RemoveChild(wxXmlNode
*child
)
219 if (m_children
== NULL
)
221 else if (m_children
== child
)
223 m_children
= child
->m_next
;
224 child
->m_parent
= NULL
;
225 child
->m_next
= NULL
;
230 wxXmlNode
*ch
= m_children
;
233 if (ch
->m_next
== child
)
235 ch
->m_next
= child
->m_next
;
236 child
->m_parent
= NULL
;
237 child
->m_next
= NULL
;
246 void wxXmlNode::AddProperty(const wxString
& name
, const wxString
& value
)
248 AddProperty(new wxXmlProperty(name
, value
, NULL
));
251 void wxXmlNode::AddProperty(wxXmlProperty
*prop
)
253 if (m_properties
== NULL
)
257 wxXmlProperty
*p
= m_properties
;
258 while (p
->GetNext()) p
= p
->GetNext();
263 bool wxXmlNode::DeleteProperty(const wxString
& name
)
267 if (m_properties
== NULL
)
270 else if (m_properties
->GetName() == name
)
273 m_properties
= prop
->GetNext();
281 wxXmlProperty
*p
= m_properties
;
284 if (p
->GetNext()->GetName() == name
)
287 p
->SetNext(prop
->GetNext());
298 wxString
wxXmlNode::GetNodeContent() const
300 wxXmlNode
*n
= GetChildren();
304 if (n
->GetType() == wxXML_TEXT_NODE
||
305 n
->GetType() == wxXML_CDATA_SECTION_NODE
)
306 return n
->GetContent();
309 return wxEmptyString
;
314 //-----------------------------------------------------------------------------
316 //-----------------------------------------------------------------------------
318 wxXmlDocument::wxXmlDocument()
319 : m_version(wxT("1.0")), m_fileEncoding(wxT("utf-8")), m_root(NULL
)
322 m_encoding
= wxT("UTF-8");
326 wxXmlDocument::wxXmlDocument(const wxString
& filename
, const wxString
& encoding
)
327 :wxObject(), m_root(NULL
)
329 if ( !Load(filename
, encoding
) )
335 wxXmlDocument::wxXmlDocument(wxInputStream
& stream
, const wxString
& encoding
)
336 :wxObject(), m_root(NULL
)
338 if ( !Load(stream
, encoding
) )
344 wxXmlDocument::wxXmlDocument(const wxXmlDocument
& doc
)
350 wxXmlDocument
& wxXmlDocument::operator=(const wxXmlDocument
& doc
)
357 void wxXmlDocument::DoCopy(const wxXmlDocument
& doc
)
359 m_version
= doc
.m_version
;
361 m_encoding
= doc
.m_encoding
;
363 m_fileEncoding
= doc
.m_fileEncoding
;
364 m_root
= new wxXmlNode(*doc
.m_root
);
367 bool wxXmlDocument::Load(const wxString
& filename
, const wxString
& encoding
)
369 wxFileInputStream
stream(filename
);
372 return Load(stream
, encoding
);
375 bool wxXmlDocument::Save(const wxString
& filename
) const
377 wxFileOutputStream
stream(filename
);
385 //-----------------------------------------------------------------------------
386 // wxXmlDocument loading routines
387 //-----------------------------------------------------------------------------
391 - process all elements, including CDATA
394 // converts Expat-produced string in UTF-8 into wxString using the specified
395 // conv or keep in UTF-8 if conv is NULL
396 static wxString
CharToString(wxMBConv
*conv
,
397 const char *s
, size_t len
= wxSTRING_MAXLEN
)
402 return wxString(s
, wxConvUTF8
, len
);
403 #else // !wxUSE_UNICODE
406 // there can be no embedded NULs in this string so we don't need the
407 // output length, it will be NUL-terminated
408 const wxWCharBuffer
wbuf(
409 wxConvUTF8
.cMB2WC(s
, len
== wxSTRING_MAXLEN
? wxNO_LEN
: len
, NULL
));
411 return wxString(wbuf
, *conv
);
413 else // already in UTF-8, no conversion needed
415 return wxString(s
, len
!= wxSTRING_MAXLEN
? len
: strlen(s
));
417 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
420 struct wxXmlParsingContext
425 wxXmlNode
*lastAsText
;
432 static void StartElementHnd(void *userData
, const char *name
, const char **atts
)
434 wxXmlParsingContext
*ctx
= (wxXmlParsingContext
*)userData
;
435 wxXmlNode
*node
= new wxXmlNode(wxXML_ELEMENT_NODE
, CharToString(ctx
->conv
, name
));
436 const char **a
= atts
;
439 node
->AddProperty(CharToString(ctx
->conv
, a
[0]), CharToString(ctx
->conv
, a
[1]));
442 if (ctx
->root
== NULL
)
445 ctx
->node
->AddChild(node
);
447 ctx
->lastAsText
= NULL
;
452 static void EndElementHnd(void *userData
, const char* WXUNUSED(name
))
454 wxXmlParsingContext
*ctx
= (wxXmlParsingContext
*)userData
;
456 ctx
->node
= ctx
->node
->GetParent();
457 ctx
->lastAsText
= NULL
;
462 static void TextHnd(void *userData
, const char *s
, int len
)
464 wxXmlParsingContext
*ctx
= (wxXmlParsingContext
*)userData
;
465 char *buf
= new char[len
+ 1];
468 memcpy(buf
, s
, (size_t)len
);
472 if ( ctx
->bLastCdata
)
474 ctx
->lastAsText
->SetContent(ctx
->lastAsText
->GetContent() +
475 CharToString(NULL
, buf
));
479 ctx
->lastAsText
->SetContent(ctx
->lastAsText
->GetContent() +
480 CharToString(ctx
->conv
, buf
));
485 bool whiteOnly
= true;
486 for (char *c
= buf
; *c
!= '\0'; c
++)
487 if (*c
!= ' ' && *c
!= '\t' && *c
!= '\n' && *c
!= '\r')
494 ctx
->lastAsText
= new wxXmlNode(wxXML_TEXT_NODE
, wxT("text"),
495 CharToString(ctx
->conv
, buf
));
496 ctx
->node
->AddChild(ctx
->lastAsText
);
505 static void StartCdataHnd(void *userData
)
507 wxXmlParsingContext
*ctx
= (wxXmlParsingContext
*)userData
;
509 ctx
->bLastCdata
= true;
511 ctx
->lastAsText
= new wxXmlNode(wxXML_CDATA_SECTION_NODE
, wxT("cdata"),wxT(""));
512 ctx
->node
->AddChild(ctx
->lastAsText
);
517 static void EndCdataHnd(void *userData
)
519 wxXmlParsingContext
*ctx
= (wxXmlParsingContext
*)userData
;
521 ctx
->bLastCdata
= false;
526 static void CommentHnd(void *userData
, const char *data
)
528 wxXmlParsingContext
*ctx
= (wxXmlParsingContext
*)userData
;
532 // VS: ctx->node == NULL happens if there is a comment before
533 // the root element (e.g. wxDesigner's output). We ignore such
534 // comments, no big deal...
535 ctx
->node
->AddChild(new wxXmlNode(wxXML_COMMENT_NODE
,
536 wxT("comment"), CharToString(ctx
->conv
, data
)));
538 ctx
->lastAsText
= NULL
;
543 static void DefaultHnd(void *userData
, const char *s
, int len
)
546 if (len
> 6 && memcmp(s
, "<?xml ", 6) == 0)
548 wxXmlParsingContext
*ctx
= (wxXmlParsingContext
*)userData
;
550 wxString buf
= CharToString(ctx
->conv
, s
, (size_t)len
);
552 pos
= buf
.Find(wxT("encoding="));
553 if (pos
!= wxNOT_FOUND
)
554 ctx
->encoding
= buf
.Mid(pos
+ 10).BeforeFirst(buf
[(size_t)pos
+9]);
555 pos
= buf
.Find(wxT("version="));
556 if (pos
!= wxNOT_FOUND
)
557 ctx
->version
= buf
.Mid(pos
+ 9).BeforeFirst(buf
[(size_t)pos
+8]);
563 static int UnknownEncodingHnd(void * WXUNUSED(encodingHandlerData
),
564 const XML_Char
*name
, XML_Encoding
*info
)
566 // We must build conversion table for expat. The easiest way to do so
567 // is to let wxCSConv convert as string containing all characters to
568 // wide character representation:
569 wxString
str(name
, wxConvLibc
);
577 for (i
= 0; i
< 255; i
++)
579 mbBuf
[0] = (char)(i
+1);
580 if (conv
.MB2WC(wcBuf
, mbBuf
, 2) == (size_t)-1)
582 // invalid/undefined byte in the encoding:
585 info
->map
[i
+1] = (int)wcBuf
[0];
589 info
->convert
= NULL
;
590 info
->release
= NULL
;
596 bool wxXmlDocument::Load(wxInputStream
& stream
, const wxString
& encoding
)
601 m_encoding
= encoding
;
604 const size_t BUFSIZE
= 1024;
606 wxXmlParsingContext ctx
;
608 XML_Parser parser
= XML_ParserCreate(NULL
);
610 ctx
.root
= ctx
.node
= NULL
;
611 ctx
.encoding
= wxT("UTF-8"); // default in absence of encoding=""
614 if ( encoding
!= wxT("UTF-8") && encoding
!= wxT("utf-8") )
615 ctx
.conv
= new wxCSConv(encoding
);
617 ctx
.bLastCdata
= false;
619 XML_SetUserData(parser
, (void*)&ctx
);
620 XML_SetElementHandler(parser
, StartElementHnd
, EndElementHnd
);
621 XML_SetCharacterDataHandler(parser
, TextHnd
);
622 XML_SetCdataSectionHandler(parser
, StartCdataHnd
, EndCdataHnd
);
623 XML_SetCommentHandler(parser
, CommentHnd
);
624 XML_SetDefaultHandler(parser
, DefaultHnd
);
625 XML_SetUnknownEncodingHandler(parser
, UnknownEncodingHnd
, NULL
);
630 size_t len
= stream
.Read(buf
, BUFSIZE
).LastRead();
631 done
= (len
< BUFSIZE
);
632 if (!XML_Parse(parser
, buf
, len
, done
))
634 wxString
error(XML_ErrorString(XML_GetErrorCode(parser
)),
636 wxLogError(_("XML parsing error: '%s' at line %d"),
638 XML_GetCurrentLineNumber(parser
));
646 if (!ctx
.version
.empty())
647 SetVersion(ctx
.version
);
648 if (!ctx
.encoding
.empty())
649 SetFileEncoding(ctx
.encoding
);
657 XML_ParserFree(parser
);
669 //-----------------------------------------------------------------------------
670 // wxXmlDocument saving routines
671 //-----------------------------------------------------------------------------
673 // write string to output:
674 inline static void OutputString(wxOutputStream
& stream
, const wxString
& str
,
675 wxMBConv
*convMem
= NULL
,
676 wxMBConv
*convFile
= NULL
)
682 wxUnusedVar(convMem
);
684 const wxWX2MBbuf
buf(str
.mb_str(*(convFile
? convFile
: &wxConvUTF8
)));
685 stream
.Write((const char*)buf
, strlen((const char*)buf
));
686 #else // !wxUSE_UNICODE
687 if ( convFile
&& convMem
)
689 wxString
str2(str
.wc_str(*convMem
), *convFile
);
690 stream
.Write(str2
.mb_str(), str2
.Len());
692 else // no conversions to do
694 stream
.Write(str
.mb_str(), str
.Len());
696 #endif // wxUSE_UNICODE/!wxUSE_UNICODE
699 // flags for OutputStringEnt()
702 XML_ESCAPE_QUOTES
= 1
705 // Same as above, but create entities first.
706 // Translates '<' to "<", '>' to ">" and '&' to "&"
707 static void OutputStringEnt(wxOutputStream
& stream
, const wxString
& str
,
708 wxMBConv
*convMem
= NULL
,
709 wxMBConv
*convFile
= NULL
,
718 for (i
= 0; i
< len
; i
++)
721 if (c
== wxT('<') || c
== wxT('>') ||
722 (c
== wxT('&') && str
.Mid(i
+1, 4) != wxT("amp;")) ||
723 ((flags
& XML_ESCAPE_QUOTES
) && c
== wxT('"')))
725 OutputString(stream
, str
.Mid(last
, i
- last
), convMem
, convFile
);
729 OutputString(stream
, wxT("<"));
732 OutputString(stream
, wxT(">"));
735 OutputString(stream
, wxT("&"));
738 OutputString(stream
, wxT("""));
746 OutputString(stream
, str
.Mid(last
, i
- last
), convMem
, convFile
);
749 inline static void OutputIndentation(wxOutputStream
& stream
, int indent
)
751 wxString str
= wxT("\n");
752 for (int i
= 0; i
< indent
; i
++)
753 str
<< wxT(' ') << wxT(' ');
754 OutputString(stream
, str
);
757 static void OutputNode(wxOutputStream
& stream
, wxXmlNode
*node
, int indent
,
758 wxMBConv
*convMem
, wxMBConv
*convFile
)
763 switch (node
->GetType())
765 case wxXML_CDATA_SECTION_NODE
:
766 OutputString( stream
, wxT("<![CDATA["));
767 OutputString( stream
, node
->GetContent() );
768 OutputString( stream
, wxT("]]>") );
771 case wxXML_TEXT_NODE
:
772 OutputStringEnt(stream
, node
->GetContent(), convMem
, convFile
);
775 case wxXML_ELEMENT_NODE
:
776 OutputString(stream
, wxT("<"));
777 OutputString(stream
, node
->GetName());
779 prop
= node
->GetProperties();
782 OutputString(stream
, wxT(" ") + prop
->GetName() + wxT("=\""));
783 OutputStringEnt(stream
, prop
->GetValue(), convMem
, convFile
,
785 OutputString(stream
, wxT("\""));
786 prop
= prop
->GetNext();
789 if (node
->GetChildren())
791 OutputString(stream
, wxT(">"));
793 n
= node
->GetChildren();
796 if (n
&& n
->GetType() != wxXML_TEXT_NODE
)
797 OutputIndentation(stream
, indent
+ 1);
798 OutputNode(stream
, n
, indent
+ 1, convMem
, convFile
);
802 if (prev
&& prev
->GetType() != wxXML_TEXT_NODE
)
803 OutputIndentation(stream
, indent
);
804 OutputString(stream
, wxT("</"));
805 OutputString(stream
, node
->GetName());
806 OutputString(stream
, wxT(">"));
809 OutputString(stream
, wxT("/>"));
812 case wxXML_COMMENT_NODE
:
813 OutputString(stream
, wxT("<!--"));
814 OutputString(stream
, node
->GetContent(), convMem
, convFile
);
815 OutputString(stream
, wxT("-->"));
819 wxFAIL_MSG(wxT("unsupported node type"));
823 bool wxXmlDocument::Save(wxOutputStream
& stream
) const
830 wxMBConv
*convMem
= NULL
;
833 wxMBConv
*convFile
= new wxCSConv(GetFileEncoding());
835 wxMBConv
*convFile
= NULL
;
836 if ( GetFileEncoding() != GetEncoding() )
838 convFile
= new wxCSConv(GetFileEncoding());
839 convMem
= new wxCSConv(GetEncoding());
843 s
.Printf(wxT("<?xml version=\"%s\" encoding=\"%s\"?>\n"),
844 GetVersion().c_str(), GetFileEncoding().c_str());
845 OutputString(stream
, s
);
847 OutputNode(stream
, GetRoot(), 0, convMem
, convFile
);
848 OutputString(stream
, wxT("\n"));