文章目录
XML基础(一)
XML简介
XML:可扩展标记语言
用途:XML用来传输和存储数据(或用来结构化数据)
XML的标签没有被预定义。需要自己手动定义标签。
XML实例:
<?xml version="1.0" encoding="UTF-8"?>
<test>
<item></item>
</test>
XML需要写头信息声明,其他包才能识别这是一个xml文件。
XML的树状结构以及语法
XML文档类似于HTML文档,都是使用标签进行描述。其中的标签是一种树形结构。
<root [prop=]>
<child [prop=]>
<child-msg [prop=]></child-msg>
</child>
</root>
-
对于HTML有些标签可以不是闭合标签,但是在xml中所有标签都必须是闭合标签!
-
xml标签对于大小写是敏感的。
<Letter></Letter> | <letter></letter>
这两者是不一样的! -
属性必须加引号,像HTML中有些属性是可以直接写值的,但是xml中不可以。
-
在xml中一些特殊的标签符号必须使用实体进行书写。例如
<p>1 < 2</p>
这样xml解析器会将小于号当成一个新的元素开始的标志。 所以必须使用实体表示,例如:<p>1 < 2</p>
。这样的实体并不多!&it ; < 小于 > ; > 大于 & ; & and符 &apos ; ’ 单引号 " ; " 双引号 -
XML中空格会保留,不像HTML多个空格只留一个。
XML元素的命名规则
- 名称可以包含数字,字母,其他字符
- 名称不能以数字或标签符号开始
- 名称不能以字母xml(XML、Xml、xML等等)开始
- 名称不能包含空格
最好使用下划线命名,驼峰命名也是可以的
XML的可扩展性
下面的xml:
<root>
<book>xxx</book>
<name>xxx</name>
</root>
xml解析程序是可以获取到元素dom中的text的。那么我们进行扩展,xml解析器仍可以进行提取,因为是基于dom的,dom没有删除,那么就不会出错。 例如扩展成下面的样子:
<root>
<book>xxx</book>
<name>xxx</name>
<id>xxx</id>
</root>
<root2>
<id>xxx</id>
</root2>
XML的属性
xml属性通常提供不属于数据的信息,是dom元素的一些信息。例如:
<file type="docx">xxx</file>
正如之前说的,xml的属性必须加双引号。不管是字符串还是数值。
<file num="3">xxx</file>
在xml中应该避免书写属性。如果有一个信息既可以写成标签的属性,又可以写成标签的子元素,那么你尽量选择写成标签的子元素。这样方便与xml解析。
为什么要避免属性呢?
- 属性不可以包含多个值
- 属性不能包含树状结构
- 属性不易扩展
- 属性难以阅读与维护
一个原则是:元数据应该存储为数据,数据本身应该存储为元素!
XML的DTD
前面讲的只是为了将你的xml写成一个 “形式良好” 的xml。
通过 DTD(文档类型定义) 验证的xml才是 “合法” 的xml!
DTD的使用,例如:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note SYSTEM "Note.dtd">
<root>
<father>
<name>xxx</name>
<chlid>
<name>xxx</name>
</chlid>
</father>
</root>
以上代码的 DOCTYPE 声明是对外部 DTD 文档的引用。
或直接引导文档里:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note
[
<!ELEMENT root (father)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT father (name, child)>
<!ELEMENT child (name)>
]>
<root>
<father>
<name>xxx</name>
<chlid>
<name>xxx</name>
</chlid>
</father>
</root>
DTD
<!DOCTYPE note
[
<!ELEMENT root (father)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT father (name, child)>
<!ELEMENT child (name)>
]>
DTD还有一个替代者:XML Schema
DTD
DTD定义XML的书写标准,也可以看做XML的格式标准。
例如上面给的DTD代码:
<!DOCTYPE note
[
<!ELEMENT root (father)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT father (name, child)>
<!ELEMENT child (name)>
]>
!DOCTYPE note
:定义此文档是note类型的文档!ELEMENT root
:root元素有一个子元素father!ELEMENT name
:name元素是**#PCDATA**类型的
PCDATA与CDATA
PCDATA
PCDATA:被解析的字符数据(parsed character data),字符数据就是XML元素开始标签与结束标签之间的文本。
PCDATA 是会被解析器解析的文本,这些文本将被解析器检查实体以及标签。(文本中的标签会被当做标记处理,实体会展开成相应的东西)
CDATA
CDATA:字符数据(character data)
CDATA 是不会被解析的文本,这些文本中不会被解析器检查实体以及标签
DTD声明元素
DTD文档中也是通过元素来进行声明的。它声明的是xml文档中的元素
<!ELEMENT element-name category>
<!ELEMENT element-name (element-content)>
在声明xml元素的时候,一个元素可以对应多个规则,例如:
<!ELEMENT father (#PCDATA | name | child)>
说明 father 标签中可以包含:PCDATA文本,name元素,child元素。但是只能出现一次。
声明空元素
空元素对应的XML元素就是单闭合标签,其中不能包含文本,所以叫空
<!ELEMENT element-name EMPTY>
<!--例如-->
<!ELEMENT xyz EMPTY>
<xyz/>
声明只有 PCDATA文本 的元素
PCDATA元素对应的XML元素就是包含文本内容的双闭合标签,其中包含PCDATA文本。
<!ELEMENT element-name (#PCDATA)>
<!--例如:-->
<!ELEMENT name (#PCDATA)>
<name>xxxx & xxxx</name>
声明带有子元素的元素
带有子元素的元素对应的XML就是包含子标签的双闭合标签。
<!ELEMENT element-name (child1, child2..., childN)>
<!--例如:-->
<!ELEMENT father (name, child)>
<father>
<name>xxx</name>
<chlid>
<name>xxx</name>
</chlid>
</father>
声明带有任何内容的元素
ANY元素对应的XML元素就是包含任意内容(标签,文本)的双闭合标签,可以包含任何可解析数据的组合。
<!ELEMENT element-name ANY>
<!--例如:-->
<!ELEMENT root ANY>
<root>
<father>
<name>xxx</name>
<chlid>
<name>xxx</name>
</chlid>
</father>
</root>
声明只出现一次的元素
声明只出现一次的元素对应的XML元素就是只在声明的元素内出现且只出现一次!
<!ELEMENT element-name (child-name)>
<!--例如:-->
<!ELEMENT root (father)>
<root>
<father>
<name>xxx</name>
<chlid>
<name>xxx</name>
</chlid>
</father>
</root>
father只在root中出现,且只出现一次。
声明至少出现一次的元素
声明至少出现一次的元素对应的XML元素就是只在声明的元素内出现,且至少出现一次。
<!ELEMENT element-name (child-name+)>
<!--例如:-->
<!ELEMENT root (father+)>
<root>
<father>
<name>xxx</name>
<chlid>
<name>xxx</name>
</chlid>
</father>
<father>
<name>xxx</name>
</father>
</root>
声明出现零次或多次的元素
<!ELEMENT element-name (child-name*)>
<!--例如:-->
<!ELEMENT root (father*)>
声明出现零次或一次的元素
<!ELEMENT element-name (child-name?)>
<!--例如:-->
<!ELEMENT root (father?)>
DTD声明属性
属性的声明使用以下的语法:
<!ATTLIST element-name arrtibute-name attribute-type arrtibute-value>
<!--attribute-value也可以叫做默认值-->
<!--例如:-->
<!ATTLIST payment type CDATA "check">
<payment type="check">
类型 | 描述 |
---|---|
CDATA | 值为字符数据 (character data) |
(en1|en2|…) | 此值是枚举列表中的一个值 |
ID | 值为唯一的 id |
IDREF | 值为另外一个元素的 id |
IDREFS | 值为其他 id 的列表 |
NMTOKEN | 值为合法的 XML 名称 |
NMTOKENS | 值为合法的 XML 名称的列表 |
ENTITY | 值是一个实体 |
ENTITIES | 值是一个实体列表 |
NOTATION | 此值是符号的名称 |
xml: | 值是一个预定义的 XML 值 |
默认属性值可使用下列值 :
值 | 解释 |
---|---|
值 | 属性的默认值 |
#REQUIRED | 属性值是必需的 |
#IMPLIED | 属性不是必需的 |
#FIXED value | 属性值是固定的 |
python解析XML
python有三种方式解析XML:SAX、DOM、ElementTree
-
SAX(Simple API for XML)
python标准库包含 SAX 解析器,SAX使用事件驱动,通过在解析XML过程中出发事件并调用回调函数处理XML
-
DOM(Document Object Model)
将XML数据在内存中解析成一个树,通过对树的操作来操作XML
-
ElementTree(元素树)
ElementTree是一个轻量级的DOM,速度快,内存消耗少
SAX速度 > ElementTree速度 > DOM速度。但是SAX需要用户自己写回调函数进行处理!
例题XML:
<collection shelf="New Arrivals">
<movie title="Enemy Behind">
<type>War, Thriller</type>
<format>DVD</format>
<year>2003</year>
<rating>PG</rating>
<stars>10</stars>
<description>Talk about a US-Japan war</description>
</movie>
<movie title="Transformers">
<type>Anime, Science Fiction</type>
<format>DVD</format>
<year>1989</year>
<rating>R</rating>
<stars>8</stars>
<description>A schientific fiction</description>
</movie>
<movie title="Trigun">
<type>Anime, Action</type>
<format>DVD</format>
<episodes>4</episodes>
<rating>PG</rating>
<stars>10</stars>
<description>Vash the Stampede!</description>
</movie>
<movie title="Ishtar">
<type>Comedy</type>
<format>VHS</format>
<rating>PG</rating>
<stars>2</stars>
<description>Viewable boredom</description>
</movie>
</collection>
SAX解析XML
SAX是事件驱动的,那么就涉及到了两部分:解析器(parser)和事件处理器(handler)
handler用于定义遇到某标签时的回调函数,然后将事件处理器放到parser中代理,解析器解析xml文件时内部自动调用事件处理器!
ContentHandler
事件处理器必须继承自:xml.sax.handler.ContentHandler
其中有几个方法(这几个方法是回调函数)
-
def characters(content)
方法调用时机:
- 从行开始,到遇到标签之前,存在字符,content就是这些字符
- 从一个标签开始,到下一个标签之前,存在字符,content就是这些字符
- 从一个标签开始,到遇到行结束符之前,存在字符,content就是这些字符
- 标签可以是开始标签也可以是结束标签。
-
def startDocument()
方法文档启动时调用
-
def endDocument()
方法文档结束时调用
-
def startElement(name, attrs)
方法遇到XML开始标签时调用,name是标签名,attrs是标签的属性
-
def endElement(name)
方法遇到XML结束标签时调用
parser
parser的一些方法来自xml.sax
其中有以下几个函数
-
xml.sax.make_parser([parser_list])
创建一个新的解析器
- parser_list:可选参数,解析器列表(选中参数的话,就是根据解析器创建一个新的解析器)
-
xml.sax.parse(xmlfile, contenthandler [, errorhandler])
解析一个xml文件
- xmlfile:xml文件
- contenthandler:事件处理器,必须是一个ContentHandler的实例
- errorhandler:如果指定该参数,它必须也是一个ContentHandler的实例
-
xml.sax.parserString(xmlstring, contenthandler [, errorhandler])
解析一个xml字符串
- xmlstring:xm字符串
- contenthandler:事件处理器,必须是一个ContentHandler的实例
- errorhandler:如果指定该参数,它必须也是一个ContentHandler的实例
例如:这样一个解析过程
在这里我使用了一个小算法,handler内部遍历节点的过程是后序遍历树。所以我使得每个节点的num+1,如果是叶子节点,则减下来是0,加入字典。如果不是则减下来不是0。
from xml.sax.handler import ContentHandler
from xml.sax import make_parser
from xml.sax import parse
class MyHandler(ContentHandler):
def __init__(self):
super(MyHandler, self).__init__()
self.current_dom = ''
self.content = ''
self.dic = {}
self.stack = []
self.num = 0
def startElement(self, tag, attributes):
self.current_dom = tag
self.num += 1
self.stack.append(self.num)
def characters(self, content):
self.content = content
def endElement(self, tag):
num = self.stack.pop()
if (self.num - num) == 0:
if tag in self.dic:
self.dic[tag].append(self.content)
else:
self.dic[tag] = [self.content]
print(self.content)
handler = MyHandler()
parser = make_parser()
parser.setContentHandler(handler)
parser.parse("test.xml")
print(handler.dic)
War, Thriller
DVD
2003
PG
10
Talk about a US-Japan war
Anime, Science Fiction
DVD
1989
R
8
A schientific fiction
Anime, Action
DVD
4
PG
10
Vash the Stampede!
Comedy
VHS
PG
2
Viewable boredom
{'type': ['War, Thriller', 'Anime, Science Fiction', 'Anime, Action', 'Comedy'], 'format': ['DVD', 'DVD', 'DVD', 'VHS'], 'year': ['2003', '1989'], 'rating': ['PG', 'R', 'PG', 'PG'], 'stars': ['10', '8', '10', '2'], 'description': ['Talk about a US-Japan war', 'A schientific fiction', 'Vash the Stampede!', 'Viewable boredom'], 'episodes': ['4']}
DOM解析XML
from xml.dom.minidom import parse
import xml.dom.minidom
DOMTree = xml.dom.minidom.parse("movies.xml")
collection = DOMTree.documentElement
if collection.hasAttribute("shelf"):
print "Root element : %s" % collection.getAttribute("shelf")
movies = collection.getElementsByTagName("movie")
可以看出,它向js中的获取DOM节点类似。