一个简单的 XML 解析器






4.69/5 (12投票s)
一个用于读取和写入非验证 XML 文件的类
Viewer 示例
引言
本文介绍了一个以元素为中心(Element Centric)的类,它支持读取和写入未经验证的 XML 文件。尽管它非常接近以文档为中心(DOM centric)的类(它确实使用了 MSXML 4 DOM 解析器进行读取),但它的不同之处在于每个元素都是一个可以单独使用的实例。这为程序员提供了极大的灵活性和责任。该类创建于 1995 年,用于处理内网应用程序的数据传输。随着 XML 规范的发布,该结构被修改以符合未经验证的格式。XML 类包含两个列表来存储基本数据。一个用于子 XML 元素,另一个用于参数条目。基本类实际上是一个单独 XML 元素的根。一个正确的 XML 文档有一个描述它是 XML 的头部行,至少包含一个版本定义。紧随该初始行之后的是一个包装嵌入数据的单个 XML 元素。
该类确实有一个 Save/SaveAs 函数,可以使用该类作为根元素来创建 XML 文档。我一直遵循的通用做法是打开一个文件,写入头部行和边界元素标签。然后,我使用该文件指针和 XMLWrite 函数来写入子元素的序列。最后,您需要用边界元素的闭合标签来终止文件。
对我来说,代码的焦点是元素而不是文档,这使得该类在使用上具有了我在其他地方未曾见过的灵活性。
由于其通用性,一些人可以很容易地在任何我未曾预料到的路径中使用这段代码。如果您发现错误,请给我写信。
历史
2002 年 1 月 - 将 V6 MFC 字符串扩展迁移到 V7。通过这次移植,已删除非 MFC 代码。还修复了 TextFile
类中潜在的内存泄漏。
2001 年 3 月 - 此更新修复了一些错误(主要是内存泄漏),并引入了一个新的 MabString
类。这个新的字符串类不再是 MFC CString
的扩展。这样做是为了避免与 .Net beta 版发生冲突。对于大型字符串块(数千个字符),CString
更快(请参阅 Joseph M. Newcomer 的 CString
管理)。
2000 年 8 月 - 本次发布与前一次发布的主要区别在于:
- 演示应用程序已更改,使其更具用处。
- 参数类已重命名为“Attribute”类,以更符合通用的 XML 约定。
- 随着 VRML 到 XML 的转换,我的用法增加了具有大型属性字段的数据。以前,读取这些字段是逐个字符进行的。对于包含数千个字符的字段,这完全不可用。现在通过缓冲字符块进行读取。与这些大型属性块相关的第二个更改是,在
MABString
类中添加了代码,允许通过使用 “StepField
” 开始和 “NextField
” 函数来遍历属性的子字段,从而步进遍历属性数据。
XML 注释
首先,您应该了解“http://www.w3.org/TR/REC-xml”。您可以在那里找到有效(valid)和格式良好(well-formed)的定义。我发现很多人误用这些术语。一个有效文档要么与 DTD 或 Schema 关联。对于来自可信源的内部文档,验证是一个可能不需要的额外步骤。对于外部不可信源,情况则不同。信任的定义必须逐例进行评估。像本文这样的未经验证的解析器可以轻松地读取一个有效文档,但它不执行验证步骤。
我发现(许多之中)有用的其他网站:
- http://www.w3.org
- http://www.xml.org
- http://www.xml.com
- http://www.oasis-open.org
- http://wdvl.internet.com
SoftwareAG 在其网站上有几篇白皮书和演示文稿:http://www.softwareag.com。
Viewer 示例评论
此示例展示了如何使用该代码读取现有文件,并在树状视图中显示层次结构,在第二个窗格中显示属性。
通用文件处理
虽然提供了 Open 和 Save 函数,但通用用法将负责打开和关闭文件以进行读写,并通过 XMLRead
和 XMLWrite
函数传递指向文件的指针。此版本假定文件指针来自 MFC CFile
类。内部,此类只处理单个根元素。但是,不对文件指针进行重定位,因此托管程序可以定位文件指针,读取函数将从该点开始查找下一个有效块。如果需要多个根,只需使用(基于 XML 类的另一个变量)在上次读取离开指针的位置开始。
MFC 依赖项
附带的代码依赖于 MFC。读取使用 CFile
类,两个列表使用类型化的 CObList
类。XML 和 CParam
类从 CObject
派生仅是为了与列表兼容。每个列表可以从任意一端开始,并从当前位置双向遍历。当到达末尾时,返回 NULL,并将当前位置设置为 NULL。托管程序必须使用 get first 或 get last 函数调用重新启动。
请阅读 - 嵌入的文本数据 - 请阅读
在我的应用程序中,嵌入的文本不依赖于它相对于子元素的位置。根据您期望的用法,这可能不令人满意。如果您不确定,我强烈建议您尝试使用 MSXML 解析器或 Apache 的 Xerces,并感受标准 DOM 解析器存在的子节点。所有这些格式化回车符和子元素之间的空格都构成了一系列文本节点。在此代码中,您没有这些。此类有一个属性和子元素的列表,以及一个单独的文本块,而不是一个包含子节点列表的 DOM,其中一个子节点是根元素。该根元素又包含一个子节点列表,其中一些是文本、元素等。写入的文件所有文本都位于元素块的开头。在读取的文件中,此文本可能散布在整个块中。
公共函数
构造函数
CXML(); virtual ~CXML(); void ResetContents();
文件处理
BOOL Open(LPCTSTR InFilter="XML Files (*.xml)| *.xml| All Files (*.*)| *.*||", LPCTSTR TagName="XML"); BOOL Save(LPCTSTR InFilter="XML Files (*.xml)| *.xml| All Files (*.*)| *.*||"); BOOL SaveAs(LPCTSTR InFilter="XML Files (*.xml)| *.xml| All Files (*.*)| *.*||"); void XMLWrite(CFile *pFile); BOOL XMLRead(CFile *pFile, LPCTSTR TagName="XML");
元素函数
LPCTSTR GetTagName(); void SetTagName(LPCTSTR NewName); LPCTSTR GetText(); void SetText(LPCTSTR TextBlock);
属性访问函数
属性必须是唯一的。
如果属性名称存在,则更新其值。
如果请求的属性不存在,则返回 NULL 或零。
long GetAttrCount(); void SetAttr(LPCTSTR Name, LPCTSTR Value); void SetAttr(LPCTSTR Name, int Value); void SetAttr(LPCTSTR Name, long Value); void SetAttr(LPCTSTR Name, float Value); void SetAttr(LPCTSTR Name, double Value); void SetCurrentAttr(CAttribute *Attribute); CAttribute* GetCurrentAttribute(); CAttribute* GetLastAttribute(); CAttribute* GetPrevAttribute(); CAttribute* GetNextAttribute(); CAttribute* GetFirstAttribute(); CAttribute* GetAttributePointer(LPCTSTR Name); CMabString GetAttrText(LPCTSTR Name); CMabString GetAttrTextUS(LPCTSTR Name); CMabString GetTag(); CMabString GetAttr(LPCTSTR Name); int GetAttrInt(LPCTSTR Name); long GetAttrLong(LPCTSTR Name); float GetAttrFloat(LPCTSTR Name); double GetAttrDouble(LPCTSTR Name); void RemoveCurrentAttr(); void AddAttr(CAttribute *Attribute); void RemoveAttr(LPCSTR Name); void SetAttrUS(LPCSTR Name, double value);
子访问函数
子元素可以具有任意数量的重复类型。子元素的顺序由程序维护。
void AddChildById(CXML *newchild); void AddChildByName(CXML *newchild); void SortById(); void SortByName(); CXML* FindByName(CString name); CXML* FindByID(long id); void InsertChild(CXML *Child); long GetChildCount(); void AddChild(CXML *Child); void SetCurrentChild(CXML *Child); void RemoveCurrentChild(); CXML* GetCurrentChild(); CXML* GetLastChild(); CXML* GetPrevChild(); CXML* GetNextChild(); CXML* GetFirstChild(); void InsertChildBefore(int ZeroBasedPos, CXML *Child); CXML* RemoveAt(int position); CXML* GetChildAt(int index); -- and data --- CMabString GetField(char Delim, int FieldNum); CMabString FirstField(char Delim); CMabString NextField(char Delim);
示例输入文件
<?xml version=\"1.0\" encoding=\"utf-8\" ?> <&ROOT> <People> This is internal text. LIne 2 <VALIDITY VALUE="TRUE" SUPPERCED=""/> <Person NAME="LastName,First" ID="001.03"/> <Person APointless="Entry is this" AnyData="Can this be"/> <Members NAME="Section 1" CLASSID="MyClassId" VERSION="1,0,0,0"> <Person NAME="Michael" CLASSID="2.02" VERSION="0,0,0,1" DATE="12/02/1997"/> </Members> [...Some more lines] <Members NAME="Typical" CLASSID="3.03" VERSION="1,0,0,0"> <Person NAME="George2" CLASSID="4.44" VERSION="0,0,0,1" DATE="12/02/1997"/> </Members> </People> </&ROOT>
示例输出文件
<?xml version=\"1.0\" encoding=\"utf-8\" ?> <&ROOT> <People> This is internal text. LIne 2 So more lines <VALIDITY SUPPERCED="" VALUE="TRUE"/> <Person ID="001.03" NAME="LastName,First"/> <Person APointless="Entry is this" AnyData="Can this be" Gothis="Jack"/> <Members CLASSID="MyClassId" NAME="Section 1" VERSION="1,0,0,0"> <Person CLASSID="2.02" DATE="12/02/1997" NAME="Michael" VERSION="0,0,0,1"/> </Members> <Members CLASSID="3.03" NAME="Typical" VERSION="1,0,0,0"> <Person CLASSID="4.44" DATE="12/02/1997" NAME="George2" VERSION="0,0,0,1"/> </Members> </People> </&ROOT>