sa

来源:互联网 发布:ssam交通冲突软件 编辑:程序博客网 时间:2024/04/29 20:14

XML概述

1.什么是XML

XML是Extensible Markup Language的缩写,即“可扩展标记语言”。XML在形式上非常类似于HTML,但两者的不同之处在于,XML是被设计用来传输和存储数据的,其焦点是数据的内容,而HTML主要是用来显示数据的,其焦点是数据的外观。我们可以创建任何自己需要的内容,然后使用XML标准限定的标记标记它,从而使每个单词、短语或块成为可识别的信息。一个XML文件由元素(标记)和内容构成。元素的描述性越强,文档各部分越容易识别。XML是一种W3C的推荐标准,它具有自我描述性。XML的一个重要特性是,XML是没有任何行为的。它仅仅用于包装在XML标签中的纯粹的信息。我们需要编写软件或者程序,才能传送、接收和显示出这个文档。XML数据以纯文本格式进行存储,因此它提供了一种独立于软件和硬件的数据存储方法。下面分别给出了一个XML和一个HTML的示例。可以看出二者在形式上非常相似。

 

 

XML示例:

<?xml version="1.0"encoding="UTF-8"?>

<bookstore>

<book category="COOKING">

 <title lang="en">Everyday Italian</title>

 <author>Giada De Laurentiis</author>

 <year>2005</year>

 <price>30.00</price>

</book>

<book category="CHILDREN">

 <title lang="en">Harry Potter</title>

 <author>J K. Rowling</author>

 <year>2005</year>

 <price>29.99</price>

</book>

<book category="WEB">

 <title lang="en">Learning XML</title>

 <author>Erik T. Ray</author>

 <year>2003</year>

 <price>39.95</price>

</book>

</bookstore>

 

 

 

HTML示例:

<html>

<head> 

<title>简单的html示例</title>

<bgsoundsrc="../music/bendif.mid">

</head> 

<bodybackground="../img/bg.jpg"> <center> 

<h3>我的第一个网页</h3><hr/>

<font size=2>  这是我做的第一个网页,欢迎光临!谢谢!This is my first web. Welcome. </font> </center>

 </body>

</html>

 

2.一个XML的结构

通常情况下一个XML文件的结构如下图。XML 文档中的元素形成了一棵文档树。这棵树从根部开始,并扩展到树的最底端。所有元素都可以可拥有子元素。父、子以及兄弟等术语用于描述元素之间的关系。父元素拥有子元素。相同层级上的子元素互相是兄弟。所有元素都可以拥有文本内容和属性,这一点和html类似。

 

 

我们以上面给出的的XML为例来介绍XML的结构。给出的XML文件描述了一个书店的信息,包含了这个书店图书的种类、图书标题、作者、出版时间和价格等信息。

第一行的<?xml version="1.0" encoding="UTF-8"?>是一个XML声明。这是一个XML文件的可选部分,它将文件识别为 XML 文件,有助于工具和人类识别 XML(不会误认为是 SGML 或其他标记)。可以将这个声明简单地写成<?xml?>,或包含 XML 版本(<?xmlversion="1.0"?>),甚至包含字符编码,比如针对Unicode的<?xmlversion="1.0" encoding="utf-8"?>。<bookstore>和</bookstore>是XML文件的根元素。根元素用来包围 XML 文档的内容。一个文件只能有一个根元素,并且需要使用<>包含它。一条XML Message的所有内容必须放在根元素以内。在这里我们看到所有的书店信息都包含在了<bookstore>和</bookstore>之间。XML 文档必须包含根元素。该元素是所有其他元素的父元素。在这个例子中,<bookstore>根元素的子元素是<book>,<book>的子元素有<title>,<author>,<year>和<price>,他们互为兄弟。对于一个元素,它可以拥有多个属性,比如,category是book的属性,lang是title的属性。一个元素还可以拥有文本,去表示他的具体内容,比如  <year>2005</year> 表示这本书的出版年份是2005年;  <title lang="en">EverydayItalian</title> 表示这本书的题目叫做“Everyday Italian”。

 

 

 

3.XML的基本语法

1)在 XML 中,所有元素都必须有关闭标签,如上面例子的根元素<bookstore>、子元素<book>、<author>、<year>、<price>都是有对应的</book>、</author>、</year>、</price>作为关闭标签的。XML标签对大小写敏感。

2)XML 文档必须有根元素。这个跟元素是所有其他元素的父元素。上面例子中的<bookstore>就是根元素。

3)XML 的属性值须加引号。

4)在XML中,空格是会被保留的。

 

 

 

 

 

Java本身已经有XML的解析方法,那么本课题为什么还要设计一XML种解析器?

Java已有的解析XML的几种方法

1)DOM(JAXP Crimson解析器)

DOM的全称是Document Object Model,即文档对象模型,它可以以一种独立于平台和语言的方式访问和修改一个文档的内容和结构。DOM以对象管理组织(OMG)的规约为基础设计而成,因此没有编程语言的限制,任何一种编程语言都可以使用DOM。

DOM实际上是一种以面向对象方式描述的文档模型,它定义了表示和修改文档所需的一系列对象、这些对象的行为和属性,以及这些对象之间的关系。换句话说,DOM用树形结构表示了一个XML文档的数据和结构关系。

DOM也是官方W3C推荐的用与平台和语言无关的方式表示XML文档的一个标准。它是以层次结构组织的节点的集合。允许开发人员可以利用这个层次结构,在树中寻找需要的特定信息。为了分析XML的结构,用户需要加载整个文档、构造层次结构。DOM被认为是基于树或基于对象的。DOM这种基于树的处理XML文档的方法具有一下几个优点:首先,由于树在内存中是持久的,我们可以修改树的内容和结构,以便应用程序能对数据和结构做出更改。

DOM 定义了所有文档元素的对象和属性,以及访问它们的方法(接口)。

 

DOM分为以下三类:

核心 DOM:用于任何结构化文档的标准模型

XML DOM:用于 XML 文档的标准模型。XML DOM 定义了所有 XML 元素的对象和属性,以及访问它们的方法(接口)。

HTML DOM:用于 HTML 文档的标准模型。HTML DOM定义了所有 HTML 元素的对象和属性,以及访问它们的方法(接口)。

 

因为本课题探讨XML解析,因此我们只看XML DOM。

在bookstore这个XML文档中,DOM结构如下:

在这个节点树中,顶端的节点是根节点,根节点之外的每个节点都有一个父节点,节点可以有任何数量的子节点。没有子节点的节点叫做叶子节点。拥有相同父节点的节点互相为同级节点(也称作同胞、兄弟)。下面的图片展示出节点树的一个部分,以及节点间的关系:

 

 

《这里可以扩充DOM是怎么解析一个XML的。。。。需不需要扩充。?》

 

 

2)SAX

 

   SAX处理XML文档的优点类似于流媒体。SAX处理数据不用等到所有的数据都被处理后才开始,它可以独立地立即开始分析。而且,由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中。对于大型文档来说是,这大大优化了分析效率。事实上,应用程序甚至可以不用对整个文档做完全的解,可以在任何想要满足的条件满足后就停止解析。所以,一般情况下SAX的速度是快于DOM的解析速度的。

 

    在这种情况下,为什么有时候我们会选择DOM来解析呢?我们知道,DOM是采用建立树形结构的方式访问XML文档的,而SAX采用的是事件模型。

 

    用DOM解析模型的优点是编程容易,DOM解析器把XML文档转化为一个包含其内容的树,从而可以对树进行遍历。开发人员只需要调用建树的指令,然后利用navigation APIs访问所需的树节点就可以完成解析任务。因此采用DOM解析器可以很容易的添加和修改树中的元素。然而由于使用DOM解析器的时候需要处理整个XML文档,所以对内存的要求比较高,当一个XML文档很大的时候,DOM解析方式面临巨大压力。由于它的遍历方式需要内存的支持,XML文档需要经常改变的服务中我们会比较多的考虑用DOM的解析方式。

 

   SAX解析器采用的是基于事件的模型,它在解析XML文档的时候可以触发一系列的事件,当发现给定的tag的时候,它可以激活一个回调方法,告诉该方法制定的标签已经找到。SAX对内存的要求通常会比较低,因为需要处理的tag都是开发人员自己决定的。如果一个XML文档中只有部分数据需要处理,SAX的不依赖内存的解析方式的好处就体现出来了。尽管如此,SAX解析还是有它的缺点,因为不是基于树形结构去解析,使用SAX方式解析时,编码工作会比较困难,而且SAX解析方式很难同时访问到一个XML文档中的多处不同数据。

 

 

 

3)JDOM

 

JDOM解析方式简化了与XML文档的交互,解析速度比使用DOM实现更快。JDOM与DOM有两个主要的不同点,首先,JDOM仅使用具体类而不使用接口。一方面来说,这种做法简化了API,但是从另一方面,它限制了解析的灵活性(我们知道接口的出现会增强编码的灵活性);第二,JDOM的API大量使用了Collections类,而DOM并没有。如果一个开发者非常熟悉这些类的内容,那么操作JDOM对他们来说会非常简单。

JDOM自身不包含解析器。它通常使用SAX2解析器来解析和验证输入XML文档(尽管它还可以将以前构造的DOM表示作为输入)。它包含一些转换器,用来将JDOM表示输出成SAX2事件流、DOM模型或XML文本文档。

 

4)DOM4J

   DOM4J是JDOM的一个智能分支。它合并了许多超出基本XML文档表示的功能,包括集成的XPath支持、XML Schema支持以及用于大文档或流化文档的基于事件的处理。DOM4J还提供了构建文档的选项,它通过DOM4J API和标准DOM接口具有并行访问功能。

和JDOM一样,DOM4J大量使用了JAVA API中的Collections类,但是在许多情况下,为了获得更好的性能,它提供了一些替代方法,使用者甚至可以直接编码。这样做直接的好处是,虽然DOM4J的API的更加复杂,但是它的灵活性要远远高于JDOM。

    DOM4J和JDOM同样具有开发灵活、集成XPATH和便于处理大XML文档的特点,在这个基础上,DOM4J的解决方案比JDOM更加完整,能够在本质上处理所有XML的解析问题。

DOM4J是一个非常好的Java XML API,它有性能优异、功能强大,易于使用,并且是开源的。

 

 

 

 

比较以上四种XML解析方法:DOM4J、JDOM、DOM和SAX:

1)SAX方式由于采用事件驱动的解析方式,解析速度比较快,但是它的缺点是编码工作比较困难。

2)JDOM和DOM易于编码,但在性能测试时表现不佳,在测试10M文档时就会存在内存溢出的问题。因此在XML文档比较小的情况下,DOM和JDOM方式是可以考虑使用的

3)DOM4J在这四种方式中性能表现最好。易于编码,且可以解析较大的XML文档,但是它也不是完美的,因为每次获取XPATH都会遍历DOM tree,比较消耗内存。

 

基于以上的比较,本课题利用开源的DOM4J,设计了一种只遍历一次DOM tree就把树形结构存储到内存的解析方式,解决了DOM4J消耗内存的问题,大大优化了解析XML的性能。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

XPATH介绍

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

本课题设计的XML解析器

 

 

为什么要设计新的XML解析器(Java本身有xpath解析器)

Java本身有一些自带的Factory工具可以实现对XML的的解析,这些工具包括:

·             Dom Parser- Parses the document byloading the complete contents of the document and creating its completehiearchical tree in memory.

·             SAX Parser- Parses the document onevent based triggers. Does not load the complete document into the memory.

·             JDOM Parser- Parses the document insimilar fashion to DOM parser but in more easier way.

·             StAX Parser- Parses the document insimilar fashion to SAX parser but in more efficient way.

·             XPath Parser- Parses the XML basedon expression and is used extensively in conjuction with XSLT.

·             DOM4J Parser- A java library toparse XML, XPath and XSLT using Java Collections Framework , provides supportfor DOM, SAX and JAXP.

 

 

我们的解析器使用了开源的DOM4J工具。DOM4JDOM4J.org出品的一个开源的XML解析包,在它的官方网站上是这样定义的:

DOM4J is an easy to use, open source library for working withXML, XPath and XSLT on the Java platform using the Java Collections Frameworkand with full support for DOM, SAX and JAXP.

也就是说,DOM4J是一个用于XMLXPathXSLT的易用的、开源的库。在Java平台上它有很多的应用。DOM4J采用了Java集合框架,也支持DOMSAXJAXP

关于以上列出的工具的资料可以在http://www.tutorialspoint.com/java_xml/java_xpath_parse_document.htm网站上查到。

 

 

这些解析工具的解析思路是:当传进一个XML文件,每一次取每个节点的Xpath值都需要遍历一次XML文件。显然,这样会导致程序消耗过多的内存,处理效率极其低下。因此,我们针对已有工具的这一不足之处设计了我们自己的XML解析器。我们的解析逻辑是:将XML文件所有节点的信息一次性保存到内存中,然后再将待匹配的配置信息的xpath与之前保存在内存中的节点信息进行匹配、查询。因此,程序只需要遍历一次XML文件,就能完成XML的解析,并且保存所有节点的属性和值,配置信息只需要和内存中的节点信息进行匹配即可,无需每次匹配xpath的时候都要重新解析XML一次。这种方式大大增强了程序的可扩展性,也提高了程序的性能。

         我们实现的XML解析器使用了一个开源的DOM4J工具,根据文件输入流把XML文件转换成一个Domtree,然后遍历整个Dom tree以获取所有节点信息,并将这些信息存储进自己设计的数据结构(XPathElement)中,供后续遍历使用。

上图是我们的ETL框架。本课题实现的功能需要完成一个XMLExplorer类,通过transform转型后,能够将具有结构化的XML Message解析成Map<String, List<Map<String, String>>>(为了便于理解,把Map<String,String>看成是一个Object,即一条记录即可)。以这样的Map作为返回值(如图,这些都属于生产者部分),放到桌子上,供消费者消费。

 

 

 

XML解析器部分概述:

对于XML文件解析成Map<String,List<Map<String, String>>>这个过程,我们拆分为两个步骤来进行:第一步,把XML文件进行初步拆解,提取出每个节点的信息,用我们自己实现的一个类XPathElement来对已经提取的信息进行存储,返回值的类型为List<XPathElement>;第二步:将第一步中解析后得到的返回值List<XPathElement>再进一步处理(公司已经将这部分功能实现),达到可以按自定义关键字表示的结果,因此本课题实现的XML解析器重点在实现第一步过程,即把XML文件解析成List<XPathElement>,供第二步进行处理。

1要实现这个功能,我们参照XPATH标准,自定义一个XPathElement实体类,并且将节点信息储存在我们定义的实体类中。首先,我们用DOM4J已有的方法将一条XML Message生成一个Dom Tree,便于后续的解析。在Dom Tree中,我们对Element进行归类,目前我们支持这四种节点形式,分别是Element, Attribute Node, TextNode和CDATA。也就是元素节点、属性节点、文本和CDATA。CDATA 指的是不由 XML 解析器进行解析的文本数据。可以简单理解为带转义功能的Text。每一个XML Message中的任意一个节点都可能是这四种形式中的一种,每一个节点的子节点都可以可能包含这些类型的部分(因为Element和TextNode/CDATA是互斥的)。我们采用递归的思想去做遍历解析,对每一个Element节点,只处理其本身的信息,包括可能的Attribute Nodes和可能的所有子节点或是TextNode文本节点/CDATA文本节点。即从根节点遍历到叶子节点,直到穷尽所有节点。对于当前的Element节点,先遍历取出所有的Attribuite Nodes,再分析其他的孩子节点,如果孩子节点是TextNode或者CDATA,那么记录文本的值,并认为已经到达了叶子节点,退回到父节点继续遍历;如果是Element,则继续向下进行递归。直到遍历完所有的叶子节点,取出所有的文本,遍历结束。

一个Dom Tree的结构如下图所示:

 

 

                                    

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


<info>

<gradeid=”1” name=”Computer Science”>

<classid=”101” name=”class 1”>

<studentname=”Alex” age=”19”/>

<studentname=”Ann” age=”19”/>

<studentname=”Amod” age=”21”/>

</class>

 

<classid=”102” name=”class 2”>

<studentname=”Bob” age=”20”/>

<studentname=”Bill” age=”22”/>

<studentname=”Brown” age=”21”/>

</class>

 

<classid=”103” name=”class 3”>

<studentname=”Celine” age=”19”/>

<studentname=”Carol” age=”22”/>

<studentname=”Cindy” age=”21”/>

</class>

</grade>

 

<gradeid=”2” name=”Software Engineering”>

<classid=”201” name=”class 1”>

<studentname=”Darren” age=”19”/>

<studentname=”Dan” age=”18”/>

<studentname=”David” age=”23”/>

</class>

 

<classid=”202” name=”class 2”>

<studentname=”Ellen” age=”20”/>

<studentname=”Eddie” age=”22”/>

</class>

 

<classid=”203” name=”class 3”>

<studentname=”Flora” age=”19”/>

<studentname=”Fayman” age=”22”/>

<studentname=”Frederica” age=”21”/>

</class>

</grade>

</info>

 

 

2在递归遍历中,有一个很大的问题,就是如何保留父子关系,即如何得知当前节点为哪一个父节点的子节点。如上述范例XML Message中,如何能够得知我们在遍历到student的Attribute Name=“Alex”这个节点的父节点是class id=”101”的节点,而不是class id=”102”的节点。为了解决这个问题,我们引入了child id(即为代码中的id)和parent id(即为代码中的pid)的概念。cid(child id)表示当前节点的id,id在整个XML处理中的值是唯一的;pid(parent id)表示当前节点的父节点的id,即当前节点pid = 父节点的cid。因此,在范例XML中,假设class id=”101”节点的cid=10,class id=”102”节点的cid=20,student name=”A”节点的cid=12,pid=10,则可以得知student name=”Alex”节点是class id=”101”的子节点,因为student的pid = class的cid。在这个例子中,我们期望通过第一步处理输出的List<XPathElement如下:

输出值的含义从左至右分别为cid(currentid), pid(parent id), currentPath, currentValue, (boolean)isElement,即:cid,pid,当前xpath,当前值(如果是元素节点就为null,是否是元素节点)

-1:1: /info:null: true

1:2: /info/grade:null: true

2:3: /info/grade/@id:101:false

2:4: /info/grade/@name:Computer Science: false

2:5: /info/grade/class:null: true

5:6 /info/grade/class/@id:101 :false

5:7: /info/grade/class/@name:Class 1: false

5:8: /info/grade/class/student:null: true

8:9: /info/grade/class/student/@age:19: false

8:10: /info/grade/class/student/@name:Alex: false

5:11: /info/grade/class/student:null: true

11:12: /info/grade/class/student/@age:19: false

11:13: /info/grade/class/student/@name:Ann: false

5:14: /info/grade/class/student:null: true

14:15: /info/grade/class/student/@age:21: false

14:16: /info/grade/class/student/@name:Amod: false

2:17: /info/grade/class:null: true

17:18 /info/grade/class/@id:102 :false

17:19: /info/grade/class/@name:Class 2: false

17:20: /info/grade/class/student:null: true

20:21: /info/grade/class/student/@age:20: false

20:22 /info/grade/class/student/@name:Bob: false

17:23: /info/grade/class/student:null: true

23:24: /info/grade/class/student/@age:22: false

23:25 /info/grade/class/student/@name:Bill: false

17:26: /info/grade/class/student:null: true

26:27: /info/grade/class/student/@age:21: false

26:28: /info/grade/class/student/@name:Brown: false

2:29: /info/grade/class:null: true

29:30 /info/grade/class/@id:103 :false

29:31: /info/grade/class/@name:Class 3: false

29:32: /info/grade/class/student:null: true

32:33: /info/grade/class/student/@age:19: false

32:34 /info/grade/class/student/@name:Celine: false

 

 

3在保留了父子关系后,接下来的问题就是,如何得到当前节点的xpath。为了在遍历过程中我们能时刻保留当前节点的xpath,我们又引入了参数rawContext。有了rawContext,我们就可以在遍历到child node的时候得到每个节点的xpath,这样我们把一个XpathElement放进list后,可以将rawContext保存的xpath传入下一个XpathElement的构造函数中进行下一个节点的遍历。

         4因为上游有一个需求,需要用特定的index去解析,相当于一个带索引的解析过程,不用遍历整个XML Message,凭借索引就能快速匹配到某个节点的信息。所以又引入了KeyPathElement这个类。为了在遍历中保存当前的KeyPathElement,我们新加入一个Context参数。

 

 

 

 

 

XML解析器部分详细设计

下面详细阐述XMLExplorer中的各个内部类和函数的设计:

XMLExplorer类中有KeyPathElement和XpathElement两个关键的内部类,用到的关键函数是transfer()、parse()、visitDOC()和genRecords()。

类的结构如下:(这里省略参数,只描述类和函数的结构)

XmlExplorer{

Public static class KeyPathElement{}

Public static class XPathElement{}

Public XmlExplorer(){}

Public Map<String, List<Map<String,String>>> transfer{

List<XPathElement> list = parse();

Map<String, List<Map<String, String>>>ret = genRecords(list)

}

Protected List<XPathElement>parse(){}(我实现的函数)

Private void visitDoc(){}

Private Map<String,List<Map<String, String>>>genRecords(){}

}

 

下面分别介绍这些关键内部类和关键函数。

1.      XpathElement类

设计XpathElement是为了对Xptah路径进行拆分,把XPath用String数组的形式存储下来,生成Xpath结构体,再加上我们自己需要的参数id和pid等,在后续遍历中使用。例如一个Xpath为/info/grade/class/student/@id,通过XPathElement我们会把它拆解成{“info”,”grade”, ”class”, ”student”, ”@id”}String数组的形式。

同时,XpathElement还提供了getLocalName(),isAttribute等函数,用来获取当前Xpath路径的LocalName和是否是属性等信息。

 

 

2.      KeyPathElement类

3.      Transfer()函数。包括parse()和genRecords()函数,分别。。。

4.      VisitDOC函数parse的关键函数。

0 0