访问树中节点
树中节点的访问不限制在往下一级,你可以访问当前节点的兄弟节点,父节点,甚至任何一个可能被访问到的节点。
访问子节点
子节点是当前节点的直接后代。简而言之,所有直接处于当前节点的下一级别的节点都是当前节点的子节点。例如,一个元素节点可能有包含(并不局限于此)注释节点,文本节点和一些其它的元素节点。属性节点只有一个子节点,这个子节点是一个包含属性节点的值的文本节点。文档节点可以包含注释节点,PIs金额一个单独的元素节点来作为子节点。子节点的类型很多时候是由当前节点决定的。可以使用hasChildNodes()方法来检查一个节点是否包含子节点,这个方法将返回一个布尔值来表明该节点是否有子节点。
可以用childNodes属性返回一个包含所有子节点的DOMNoteList对象。DOMNoteList是一个可以循环的对象。可以使用item属性来取得一个DOMNoteList中的一个特定的节点或者利用PHP中的循环函数来访问。比如
- if ($root->hasChildNodes()) {
- $children = $root->childNodes;
- foreach($children as $node) {
- print $node->nodeName."/n";
- }
- }
这段代码先返回了文档节点$root的子节点,然后利用foreach来遍历DOMNodeList对象。输出结果如下
#text
bookinfo
#text
preface
#text
chapter
#text
book元素包含了三个子原属,而且也包含了一些换行符或制表符之类的空白,这些空白在加载XML的时候没有去掉,因此输出结果中有#text。如何在没有去掉XML空白的情况下在结果中去掉#text呢,nodeType属性派上用场了。例如:
- foreach($children as $node) {
- if ($node->nodeType != XML_TEXT_NODE) {
- print $node->nodeName."/n";
- }
- }
这样就可以去掉那些文本节点(text node),输出结果也没有#text字符了。
———————————————–
接下来不翻译了,改成笔记,翻译的废话太多了 >.
读者可自己运行代码后思考一下,这样更容易上手 ^^
———————————————–
访问特定节点
如果要访问特点节点可以使用节点的标签名。可使用的方法有getElementsByTagName()和getElementsByTagNameNS(),被访问的节点只能包含在文档节点(document node)和元素节点(element node)中,也就是说,要访问的节点必须是基于DOMDocument或DOMElement类。比如文档节点$dom,可以用getElementsByTagName()方法来取得文档中的所有title节点:
- $elements = $dom->getElementsByTagName("title");
- $length = $elements->length;
- for ($x=0;$x < $length;$x++) {
- print "Element Value: ".$elements->item($x)->nodeValue."/n";
- }
getElementsByTagName()返回了一个DOMNodeList对象$elements,包含了$dom领域内的所有title元素。length属性返回了DOMNodeList对象中元素的个数。
可以使用通配符*作为参数来取得所有的元素:
- $preface = $root->getElementsByTagName("preface");
- $elements = $preface->item(0)->getElementsByTagName("*");
- $length = $elements->length;
- for ($x=0;$x < $length;$x++) {
- print "Element Name: ".$elements->item($x)->nodeName."/n";
- print "Element Value: ".$elements->item($x)->nodeValue."/n";
- }
这段代码通过文档元素$root以一个DOMNodeList对象返回了$root之内的所有preface元素并赋值给$preface。接着,返回取得的DOMNodeList对象中的第一个值,然后直接执行getElementsByTagName(”*”)。所有$preface内的元素都以DOMNodeList对象返回到$elements中。最后利用for循环输出所有的元素名称及元素值。输出结果如下:
Element Name: title
Element Value: The DOM Tree
Element Name: para
Element Value: An example DOM Tree using DocBook.
在处理有命名空间的文档时,可以使用getElementsByTagNameNS()方法来返回指定的命名空间的元素。这个方法有两个参数,第一个是命名空间URI,第二个是元素标签的名称。命名空间URI参数也支持通配符*。可以用通配符来取得任一个命名空间的所有元素:
- $result = $dom->getElementsByTagNameNS("*", "*");
这行代码返回了一个DOMNodeList对象$result,返回了文档中有命名空间的所有元素。
访问属性(Accessing Attributes)
跟其它节点类型一样,节点的属性(attribute)继承了DOMNode类中的方法和属性(property),但是不能像其它节点一样访问它。
可以用hasAttributes()方法来检查一个节点是否有属性,用attributes属性(property)来取得节点的所有属性, hasAttributes()和attributes都是在DOMNode类中定义的,因此可以在所有的节点中使用,但是只有在DOMElement对象中是由才会返回有意义的值。
- if ($root->hasAttributes()) {
- $attributes = $root->attributes;
- foreach($attributes as $attr) {
- print "Attribute Name: ".$attr->nodeName."/n";
- print "Attribute Value: ".$attr->nodeValue."/n";
- }
- }
在这段代码中,如果$root所指代的节点存在属性的话hasAttributes()返回true,接着利用attributes属性返回一个DOMNamedNodeMap对象给$attributes。DOMNamedNodeMap跟DOMNodeList一样,对是可以循环的。输出结果如下:
Attribute Name: lang
Attribute Value: en
与DOMNodeList不同的是DOMNamedNodeMap可以使用名称来访问,而不只是使用位置。比如:
- $attr = $attributes->getNamedItem("lang");
- print "Attribute Name: ".$attr->nodeName."/n";
- print "Attribute Value: ".$attr->nodeValue."/n";
- if ($attributes->length > 0) {
- $attr = $attributes->item(0);
- print "Attribute Name: ".$attr->nodeName."/n";
- print "Attribute Value: ".$attr->nodeValue."/n";
- }
这两段代码输出结果是一样的。
访问特定属性
用DOMNameNodeMap对象来访问属性只是访问属性的方法之一。DOMElement类提供了一些用来访问指定属性的方法,getAttribute(), getAttributeNode(), getAttributeNS(), and getAttributeNodeNS()
- /* Access lang attribute value directly */
- print "Attribute Value: ".$root->getAttribute("lang")."/n";
- /* Return the lang attribute node and access the returned attribute node */
- $attr = $root->getAttributeNode("lang");
- print "Attribute Value: ".$attr->nodeValue."/n";
这两段代码的输出结果是一样的。第一段代码返回了lang属性后直接输出;第二段代码先返回lang属性节点,接着输出节点值。
尽管Listing 6-1 中的文档没有使用命名空间,但是还是可以使用命名空间的方法来访问。
- print "Attribute Value: ".$root->getAttributeNS(NULL, "lang")."/n";
- $attr = $root->getAttributeNodeNS(NULL, "lang");
- print "Attribute Value: ".$attr->nodeValue."/n";
第一个参数是要访问的命名空间URI,因为Listing 6-1没有命名空间,因此指定为NULL。
创建和编辑树
文档节点(Document Nodes)
本章早些时候介绍了几种创建DOMDocument对象的方法。创建的对象包含一个文档类型声明,你可以使用DOMImplementation累来创建一个DOMDocument对象,首先创建一个DOMDocType对象来作为DOMImplementation的参数。 DOMImplementation对象中的方法可以使用静态方式访问。比如:
- $doctype = DOMImplementation:: createDocumentType("book",
- "-//OASIS//DTD DocBook XML V4.1.2//EN",
- "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd");
- $dom = DOMImplementation:: createDocument(NULL, "book", $doctype);
指定编码
- $dom->encoding = "UTF-8";
指定编码只影响输出的文档,不影响创建文档过程中的文档编码。
元素节点
创建元素
有两种方法可以用来创建元素,一是使用DOMDocument对象中createElement()和createElementNS()方法,二是利用DOMElement直接实例化一个DOMElement对象。
创建一个bookinfo元素
- $bookinfo = $dom->createElement("bookinfo");
这段代码返回了一个名为”bookinfo”的DOMElement对象类型元素给$bookinfo变量。
也可以创建一个包含内容的元素
- $bititle = $dom->createElement("title", "DOM in PHP 5");
虽然已经创建了两个元素,但是它们跟文档没有直接关系,是独立的两个元素。
创建一个带有命名空间的元素
- $biauthor = $dom->createElementNS(NULL, "author");
- $trash = $dom->createElementNS("http://www.example.com/trash", "tr:trash");
直接实例化DOMElement对象来创建元素
- $firstname = new DOMElement("firstname", "Rob");
- $surname = new DOMElement("surname", "Richards");
- $nsElement = new DOMElement("nse:myelement", NULL, "http://www.example.com/ns");
第三行代码的NULL说明不指定值给所创建的元素。
插入元素节点
插入元素节点的方法是从DOMNode类继承的,它们不仅可以用于元素节点,还可以用于其它类型的节点。
- $dom->documentElement->appendChild($bookinfo);
appendChild()方法有一个被用来指定为子节点的对象作参数,并返回所创建的节点。新插入的节点作为当前节点的最后一个子节点插入。
在插入author元素前可以先在author元素中插入firstname和surname节点。当将author节点插入文档树中时,firstname和surname也被插入文档中:
- $biauthor->appendChild($surname);
- $biauthor->insertBefore($firstname, $surname);
上面的代码也可以这么写
- $biauthor->appendChild($firstname);
- $biauthor->appendChild($surname);
然后将author插入bookinfo节点:
- $bookinfo->appendChild($biauthor);
属性节点
可以使用DOMElement中的方法操作属性节点,使用DOMDocument中的方法来直接实例化一个属性节点或使用DOMElement中的方法来创建,也可以使用DOMNode和DOMElement中的方法来插入和移动属性节点。
使用DOMDocument中的createAttribute方法创建属性节点
- /* Equivalent methods for creation of lang attribute */
- $lang = $dom->createAttribute("lang");
- $lang = $dom->createAttributeNS(NULL, "lang");
使用这种方法创建必须另外再给属性赋值,可以使用DOMElement中的nodeValue属性或DOMAttr中的value属性来指定一个值,如:
- /* Equivalent calls to set the value for the lang attribute to "en" */
- $lang->nodeValue = "en";
- $lang->value = "en";
使用DOMAttr创建一个属性节点对象,并直接赋值:
- $lang = new DOMAttr("lang", "en");
创建好的属性节点可以想插入元素节点一样插入到一个节点中,但插入的结果不是插入一个子节点,而是一个属性:
- /* Equivalent methods for inserting an attribute */
- $bookinfo->appendChild($lang);
- $bookinfo->insertBefore($lang, NULL);
也可以使用DOMElement中的setAttributeNode()和setAttributeNodeNS()方法在一个节点中插入一个属性。这两个方法必须有一个DOMAtrr对象类型的属性节点作为参数。
- /* Equivalent calls for this document as no namespaces are being used */
- $oldlang = $bookinfo->setAttributeNode($lang);
- $oldlang = $bookinfo->setAttributeNodeNS($lang);
DOMElement类有两个可以直接给一个节点创建属性的方法,setAttribute()和setAttributeNS()。利用这两个方法可以在不创建DOMAttribute对象的情况下就创建一个属性:
- /* Equivalent calls to create the lang attribute with value "en" */
- $bookinfo->setAttribute("lang", "en");
- $bookinfo->setAttributeNS(NULL, "lang", "en");
文本节点
文本节点非常简单,因为它们没有子节点和属性,也就是说它们只包含文本。
创建和插入文本节点
可以利用DOMDocment对象的createTextNode()方法来创建一个文本节点:
- /* Equivalent creation of DOMText objects */
- $yeartxt = $dom->createTextNode("2005");
- $yeartxt = new DOMText("2005");
可以在一行代码中创建节点并将节点插入元素,最后返回创建的节点:
- /* Create and Append a copyright element */
- $copyright = $bookinfo->appendChild(new DOMElement("copyright"));
下面的代码实现了跟上一段代码一样的功能:
- /* Create year element */
- $year = $dom->createElement("year");
- /* Append text node to set content */
- $year->appendChild($yeartxt);
- $copyright->appendChild($year);
另外一种创建文本节点的方法:
- /* Append a newly created holder element with content "Rob Richards" */
- $copyright->appendChild(new DOMElement("holder", "Rob Richards"));
处理文本
DOMText类是从DOMCharacterData类派生出来的,因此,这两个类中的方法都可以用来处理文本。
- /* If content is not whitespace then ... */
- if (! $yeartxt->isElementContentWhitespace()) {
- /* Print substring at offset 1 and length 2: 00 */
- print $yeartxt->substringData(1,2)."/n";
- /* Append the string -2006 to the content and print output: 2005-2006 */
- $yeartxt->appendData("-2006");
- print $yeartxt->nodeValue."/n";
- /* Delete content at offset 4 with length of 5 and print output: 2005 */
- $yeartxt->deleteData(4,5);
- print $yeartxt->nodeValue."/n";
- /* Insert string "ABC" at offset 1 and print output: 2ABC005 */
- $yeartxt->insertData(1, "ABC");
- print $yeartxt->nodeValue."/n";
- /* Replace content at ofset 1 with length of 3 with an empty string: 2005 */
- $yeartxt->replaceData(1, 3, "");
- print $yeartxt->nodeValue."/n";
- }
现在,上面所有对节点的操作创建了一个如下所示的XML文档:
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
- "http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd">
- <book>
- <bookinfo lang="en">
- <title>DOM in PHP 5</title>
- <author>
- <firstname>Rob</firstname>
- <surname>Richards</surname>
- </author>
- <copyright>
- <year>2005</year>
- <holder>Rob Richards</holder>
- </copyright>
- </bookinfo>
- </book>
其它类型的节点
- /* Create a DOMDocumentFragment */
- $frag = $dom->createDocumentFragment();
- $frag = new DOMDocumentFragment();
- /* Create DOMComment */
- $comment = $dom->createComment("this is a comment");
- $comment = new DOMComment("this is a comment");
- /* Results in <!-- this is a comment --> */
- /* Create DOMCDATASection */
- $cdata = $dom->createCDATASection("<html></html");
- $cdata = new DOMCDATASection("<html></html");
- /* Results in <![CDATA[<html></html]]> */
- /* Create DOMProcessingInstruction */
- $pi = $dom->createProcessingInstruction("php", "echo 'Hello World';");
- $pi = new DOMProcessingInstruction("php", "echo 'Hello World';");
- /* Results in <?php echo 'Hello World';?> */
- /* Create DOMEntityReference */
- $entityref = $dom->createEntityReference("lt");
- $entityref = new DOMEntityReference("lt");
- /* Results in < */
- $frag = $dom->createDocumentFragment();
- $frag->appendChild(new DOMElement("node1", "node1 value"));
- $frag->appendChild(new DOMElement("node2", "node2 value"));
- $frag = $dom->createDocumentFragment();
- $frag->appendXML("<node1>node1 value</node1><node2>node2 value</node2>");
移除和替代节点
之前已经有一个方法setAttributeNode()可以用来移除和替代节点,如果有一个旧的节点跟新的节点同名的话, setAttributeNode()将移除旧的节点并以新的节点替代它,这只在属性类型的节点上奏效。对于其它节点可以使用replaceChild ()和removeChild()方法来实现。
首先创建一个文档:
- $doc = DOMDocument::loadXML('<?xml version="1.0"?>
- <root>
- <child1>child1 content</child1>
- <child2>child2 content</child2>
- <child3>child3 content</child3>
- </root>');
接着要把child2元素删除,并把child3元素用newchild元素替代。第一步是取得这些元素。
- $root = $doc->documentElement;
- $child2 = $root->getElementsByTagName("child2")->item(0);
- $child3 = $root->getElementsByTagName("child3")->item(0);
接着删除$child2对象:
- $root->removeChild($child2);
这时候XML文档将会变成如下的结构:
- <?xml version="1.0"?>
- <root>
- <child1>child1 content</child1>
- <child3>child3 content</child3>
- </root>
在文档中会留出原来元素所占用的行。接着创建一个newchild元素来替代child3。
- $oldchild = $root->replaceChild(new DOMElement("newchild", "new content"), $child3);
这样,XML文档变成如下结构:
- <?xml version="1.0"?>
- <root>
- <child1>child1 content</child1>
- <newchild>new content</newchild>
- </root>
那一行空白也是一个节点,可以用下面的代码删除:
- $children = $root->childNodes;
- for ($x=$children->length; $x--; $x>=0) {
- $node = $children->item($x);
- if ($node->nodeType == XML_TEXT_NODE && $node-> isElementContentWhitespace()) {
- $root->removeChild($node);
- }
- }
使用XPath
从PHP5.0开始,可以使用DOMXPath对象的query()方法返回一个包含节点的DOMNodeList对象。PHP5.1开始提供了一个evaluate()方法,它可以返回更多类型的节点对象。
实例化DOMXPath
使用new关键字创建一个DOMXPath对象,并传入一个DOMDocument对象类型的参数。
- $domxpath = new DOMXPath($dom);
使用query()方法
PHP5的所有版本都有query()方法,可以利用这个方法和XPath表达式从树形结构获取一些节点,以DOMNodeList对象的形式返回,如果没有获取到任何一个节点就返回一个空的DOMNodeList。可以将一个XPath表达式作为一个参数传给query()方法,比如:
- $list = $domxpath->query("/book/bookinfo/author");
- $author = $list->item(0);
- $list = $domxpath->query("surname", $author);
- $surname = $list->item(0);
如果你试图利用XPath表达式返回一个字符串,你会发现返回的是一个空的DOMNodeList对象:
- $list = $domxpath->query("string('/book/bookinfo/author/surname')");
- var_dump($list);
- print "Number of Nodes Returned: ".$list->length."/n";
length返回0。
使用evaluate()方法
PHP5.1开始增加了evaluate()方法,可以像query()方法一样使用它。
- $list = $domxpath->evaluate("string(/book/bookinfo/author/surname)");
- var_dump($list);
var_dump()函数输出string(8) “Richards”。说明evaluate()方法实现了query()所不能实现的功能。
evaluate()方法返回值的类型很多,有Boolean,Integer,String,Null和DOMNodeList,返回什么类型的值取决于使用你所使用的XPath表达式,如:
- $newyear = $domxpath->evaluate("number(/book/bookinfo/copyright/year) + 1");
- var_dump($newyear);
像query()方法一样,evaluate()可以返回一个包含多个节点的DOMNodeList对象,如:
- $list = $domxpath->evaluate("/book/bookinfo/author");
- $author = $list->item(0);
- print $author->nodeName."/n";