Document Object Model
- Введение
- Установка и настройка
- Предопределенные константы
- DOMAttr — Класс DOMAttr
- DOMAttr::__construct — Создает экземпляр класса DOMAttr
- DOMAttr::isId — Проверяет, является ли атрибут описанным в DTD ID
- DOMCdataSection — Класс DOMCdataSection
- DOMCdataSection::__construct — Создает новый экземпляр класса DOMCdataSection
- DOMCharacterData — Класс DOMCharacterData
- DOMCharacterData::appendData — Добавляет строку в конец символьных данных узла
- DOMCharacterData::deleteData — Удаление диапазона символов из узла
- DOMCharacterData::insertData — Вставляет строку после заданного отступа из 16-битных блоков
- DOMCharacterData::replaceData — Заменяет подстроку в узле типа DOMCharacterData
- DOMCharacterData::substringData — Извлекает определенный диапазон данных из узла
- DOMComment — Класс DOMComment
- DOMComment::__construct — Создает новый экземпляр класса DOMComment
- DOMDocument — Класс DOMDocument
- DOMDocument::__construct — Создание нового DOMDocument объекта
- DOMDocument::createAttribute — Создает новый атрибут
- DOMDocument::createAttributeNS — Создает новый узел-атрибут с соответствующим ему пространством имен
- DOMDocument::createCDATASection — Создает новый cdata узел
- DOMDocument::createComment — Создает новый узел-комментарий
- DOMDocument::createDocumentFragment — Создание фрагмента докуента
- DOMDocument::createElement — Создает новый узел-элемент
- DOMDocument::createElementNS — Создание нового узла-элемента с соответствующим пространством имен
- DOMDocument::createEntityReference — Создание нового узла-ссылки на сущность
- DOMDocument::createProcessingInstruction — Создает новый PI-узел
- DOMDocument::createTextNode — Создает новый текстовый узел
- DOMDocument::getElementById — Ищет элемент с заданным id
- DOMDocument::getElementsByTagName — Ищет все элементы с заданным локальным именем
- DOMDocument::getElementsByTagNameNS — Ищет элементы с заданным именем в определенном пространстве имен
- DOMDocument::importNode — Импорт узла в текущий документ
- DOMDocument::load — Загрузка XML из файла
- DOMDocument::loadHTML — Загрузка HTML из строки
- DOMDocument::loadHTMLFile — Загрузка HTML из файла
- DOMDocument::loadXML — Загрузка XML из строки
- DOMDocument::normalizeDocument — Нормализует документ
- DOMDocument::registerNodeClass — Регистрация расширенного класса, используемого для создания базового типа узлов
- DOMDocument::relaxNGValidate — Производит проверку документа на правильность построения посредством relaxNG
- DOMDocument::relaxNGValidateSource — Проверяет документ посредством relaxNG
- DOMDocument::save — Сохраняет XML дерево из внутреннего представления в файл
- DOMDocument::saveHTML — Сохраняет документ из внутреннего представления в строку, используя HTML форматирование
- DOMDocument::saveHTMLFile — Сохраняет документ из внутреннего представления в файл, используя HTML форматирование
- DOMDocument::saveXML — Сохраняет XML дерево из внутреннего представления в виде строки
- DOMDocument::schemaValidate — Проверяет действительности документа, основываясь на заданной схеме
- DOMDocument::schemaValidateSource — Проверяет действительность документа, основываясь на схеме
- DOMDocument::validate — Проверяет документ на соответствие его DTD
- DOMDocument::xinclude — Проводит вставку XInclude разделов в объектах DOMDocument
- DOMDocumentFragment — Класс DOMDocumentFragment
- DOMDocumentFragment::appendXML — Добавление необработанных XML данных
- DOMDocumentType — Класс DOMDocumentType
- DOMElement — Класс DOMElement
- DOMElement::__construct — Создание нового объекта класса DOMElement
- DOMElement::getAttribute — Возвращает значение атрибута
- DOMElement::getAttributeNode — Возвращает узел атрибута
- DOMElement::getAttributeNodeNS — Возвращает узел атрибута
- DOMElement::getAttributeNS — Возвращает значение атрибута
- DOMElement::getElementsByTagName — Возвращает элементы по имени тэга
- DOMElement::getElementsByTagNameNS — Получение элементов по локальному имени в заданном пространстве имен
- DOMElement::hasAttribute — Проверяет наличие атрибута
- DOMElement::hasAttributeNS — Проверяет, существует ли заданный атрибут
- DOMElement::removeAttribute — Удаляет атрибут
- DOMElement::removeAttributeNode — Удаляет атрибут
- DOMElement::removeAttributeNS — Удаляет атрибут
- DOMElement::setAttribute — Устанавливает значение атрибута
- DOMElement::setAttributeNode — Добавляет новый узел атрибута к элементу
- DOMElement::setAttributeNodeNS — Добавляет новый атрибут к элементу
- DOMElement::setAttributeNS — Добавляет новый атрибут
- DOMElement::setIdAttribute — Объявляет атрибут с заданным именем ключевым атрибутом
- DOMElement::setIdAttributeNode — Объявляет заданный узал атрибута ключевым
- DOMElement::setIdAttributeNS — Объявляет атрибут с заданным локальным именем и URI пространства имен идентифицирующим
- DOMEntity — Класс DOMEntity
- DOMEntityReference — Класс DOMEntityReference
- DOMEntityReference::__construct — Создает новый объект класса DOMEntityReference
- DOMException — Класс DOMException
- DOMImplementation — Класс DOMImplementation
- DOMImplementation::__construct — Создает новый объект класса DOMImplementation
- DOMImplementation::createDocument — Создает объект класса DOMDocument заданного типа с элементом document
- DOMImplementation::createDocumentType — Создает пустой объект класса DOMDocumentType
- DOMImplementation::hasFeature — Тестирует реализацию специфичных возможностей объекта DOMImplementation
- DOMNamedNodeMap — Класс DOMNamedNodeMap
- DOMNamedNodeMap::getNamedItem — Извлекает узел с заданным именем
- DOMNamedNodeMap::getNamedItemNS — Извлекает узел с заданным локальным именем и URI пространства имен
- DOMNamedNodeMap::item — Извлекает узел с заданным индексом
- DOMNode — Класс DOMNode
- DOMNode::appendChild — Добавляет новый дочерний узел в конец списка потомков
- DOMNode::C14N — Canonicalize nodes to a string
- DOMNode::C14NFile — Canonicalize nodes to a file
- DOMNode::cloneNode — Клонирует узел
- DOMNode::getLineNo — Возвращает номер строки узла
- DOMNode::getNodePath — Получение XPath пути к узлу
- DOMNode::hasAttributes — Проверяет, содержит ли данный узел атрибуты
- DOMNode::hasChildNodes — Проверяет, содержит ли данный узел потомков
- DOMNode::insertBefore — Добавляет новый дочерний узел перед опорным узлом
- DOMNode::isDefaultNamespace — Проверяет, совпадает ли URI пространства имен узла с пространством имен по умолчанию
- DOMNode::isSameNode — Проверяет, являются ли два узла одним и тем же узлом
- DOMNode::isSupported — Проверяет, поддерживается ли заданное свойство в определенной версии
- DOMNode::lookupNamespaceURI — Получает URI пространства имен узла по префиксу
- DOMNode::lookupPrefix — Возвращает префикс пространства имен узла из URI пространства имен
- DOMNode::normalize — Нормализует узел
- DOMNode::removeChild — Удаляет дочерний узел из списка потомков
- DOMNode::replaceChild — Заменяет дочерний узел
- DOMNodeList — Класс DOMNodeList
- DOMNodelist::item — Извлекает узел с заданным индексом
- DOMNotation — Класс DOMNotation
- DOMProcessingInstruction — Класс DOMProcessingInstruction
- DOMProcessingInstruction::__construct — Создает новый объект классаDOMProcessingInstruction
- DOMText — The DOMText class
- DOMText::__construct — Создает объект класса DOMText
- DOMText::isWhitespaceInElementContent — Определяет, содержит ли текстовый узел пробел в содержимом
- DOMText::splitText — Разделяет узел на два, начиная с заданной позиции
- DOMXPath — Класс DOMXPath
- DOMXPath::__construct — Создает новый объект класса DOMXPath
- DOMXPath::evaluate — Вычисляет переданное XPath выражение и возвращает типизированный результат, если возможно
- DOMXPath::query — Выполняет заданное XPath выражение
- DOMXPath::registerNamespace — Ассоциирует пространство имен с объектом DOMXPath
- DOMXPath::registerPhpFunctions — Регистрация функций PHP как XPath функций
- DOM Функции
- dom_import_simplexml — Получает объект класса DOMElement из объекта класса SimpleXMLElement
Коментарии
I wrote a framework to implement the StyleSheet interfaces as specified on the W3C website. The code is written in PHP, and is NOT a complete implementation. Use it how ya like. I was planning on adding the CSSStyleSheet interfaces as well. Feel free to ask.
<?
class StyleSheetList {
public length;
private self;
function __construct ( ) {
$this->self = array();
}
function __get($property, $&ret) {
if($property == 'length')
$ret = count($this->self);
return true;
}
function __set($property, $val) {
if($property == 'length')
return true;
}
function item( $index ) {
return $this->self[$index];
}
}
class MediaList extends StyleSheetList {
function appendMedium ( $newMedium ) {
array_push($this->self, $newMedium);
}
function deleteMedium ( $oldMedium ) {
foreach($this->self as $item) {
if( $item == $oldMedium ) {
$item = $this->self[ $this->length-1 ];
array_pop($this->self);
break;
}
}
}
}
class DocumentStyle {
public styleSheets;
function __construct ( ) {
$this->styleSheets = new StyleSheetList();
}
function __set($property, $val) {
if($property == 'styleSheets')
return true;
}
}
class LinkStyle {
public sheet;
function __construct () {
$this->sheet = new StyleSheet();
}
function __set($property, $val) {
if($property == 'sheet')
return true;
}
}
class StyleSheet {
public type;
public disabled;
public ownerNode;
public parentStyleSheet;
public href;
public title;
public media;
function __construct( $type, $disabled, $ownerNode, $parentStyleSheet, $href, $title){
$this->type = $type;
$this->disabled = $disabled;
$this->media = new MediaList();
$this->ownerNode = $ownerNode;
$this->parentStyleSheet = $parentStyleSheet;
$this->href = $href;
$this->title = $title;
}
}
?>
Only contactable via http://murpsoft.com/contact.html
[Editor's Note: If you're using entities, then you have no choice. XML Catalogs can speed DTD resolution.]
Never use
$dom->resolveExternals=true;
when parsing XHTML document that has the DOCTYPE declaration with DTD URL specified in it.
Otherwise parsing the XHTML with DOCTYPE like this one:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
will result in PHP/DOM downloading the DTD file from W3C site when parsing your document. It will add extra delay to your script - I experienced that $dom->load()'s total time was from 1 to 16 seconds.
elixon
When dealing with validation or loading, the output errors can be quite annoying.
PHP 5.1 introduces libxml_get_errors().
http://php.net/libxml_get_errors
As of PHP 5.1, libxml options may be set using constants rather than the use of proprietary DomDocument properties.
DomDocument->resolveExternals is equivilant to setting
LIBXML_DTDLOAD
LIBXML_DTDATTR
DomDocument->validateOnParse is equivilant to setting
LIBXML_DTDLOAD
LIBXML_DTDVALID
PHP 5.1 users are encouraged to use the new constants.
Example:
DomDocument->load($file, LIBXML_DTDLOAD|LIBXML_DTDATTR);
DomDocument->load($file, LIBXML_DTDLOAD|LIBXML_DTDVALID);
This particular W3C page provides invaluable documentation for the DOM classes implemented in php5 (via libxml2). It fills in plenty of php.net's gaps:
http://www.w3.org/TR/DOM-Level-2-Core/core.html
Some key examples:
* concise summary of the class heirachy (1.1.1)
* clarification that DOM level 2 doesn't allow for population of internal DTDs
* explanation of DOMNode->normalize()
* explanation of the DOMImplementation class
The interfaces are described in OMG's Interface Definition Language
When I tried to parse my XHTML Strict files with DOM extension, it couldn't understand xhtml entities (like ©). I found post about it here (14-Jul-2005 09:05) which adviced to add resolveExternals = true, but it was very slow. There was some small note about xml catalogs but without any glue. Here it is:
XML catalogs is something like cache. Download all needed dtd's to /etc/xml, edit file /etc/xml/catalog and add this line: <public publicId="-//W3C//DTD XHTML 1.0 Strict//EN" uri="file:///etc/xml/xhtml1-strict.dtd" />
Thats all. Thanks to http://www.whump.com/moreLikeThis/link/03815
This module is not included by default either in the CentOS 4 "centosplus" repository. For those using PHP5 on CentOS 4, a simple "yum --enablerepo=centosplus install php-xml" will do the trick (this will install both the XML and DOM modules).
Note that these DOM functions expect (and presumably return) all their data in UTF-8 character encoding, regardless of what PHP's current encoding is. This means that text nodes, attribute values etc, should be in utf8.
This applies even if you're generating an XML document which is not ultimately in utf8.
Mark
Being an experienced ASP developer I was wondering how to replace textual content of a node (with msxml this is simply acheived by setting the 'text' property of a node). Out of frustration I started to play around with SimpleXml but I could not get it to work in combination with xPath.
I took me a lot of time to find out so I hope this helps others:
function replaceNodeText($objXml, $objNode, $strNewContent){
/*
This function replaces a node's string content with strNewContent
*/
$objNodeListNested = &$objNode->childNodes;
foreach ( $objNodeListNested as $objNodeNested ){
if ($objNodeNested->nodeType == XML_TEXT_NODE)$objNode->removeChild ($objNodeNested);
}
$objNode->appendChild($objXml->createTextNode($strNewContent));
}
$objXml= new DOMDocument();
$objXml->loadXML('<root><node id="1">bla</note></root>');
$objXpath = new domxpath($objXml);
$strXpath="/root/node[@id='1']";
$objNodeList = $objXpath ->query($strXpath);
foreach ($objNodeList as $objNode){
//pass the node by reference
replaceNodeText($objXml, &$objNode, $strImportedValue);
}
The project I'm currently working on uses XPaths to dynamically navigate through chunks of an XML file. I couldn't find any PHP code on the net that would build the XPath to a node for me, so I wrote my own function. Turns out it wasn't as hard as I thought it might be (yay recursion), though it does entail using some PHP shenanigans...
Hopefully it'll save someone else the trouble of reinventing this wheel.
<?php
function getNodeXPath( $node ) {
// REMEMBER THAT XPATHS USE BASE-1 INSTEAD OF BASE-0!!!
// Get the index for the current node by looping through the siblings.
$parentNode = $node->parentNode;
if( $parentNode != null ) {
$nodeIndex = 0;
do {
$testNode = $parentNode->childNodes->item( $nodeIndex );
$nodeName = $testNode->nodeName;
$nodeIndex++;
// PHP trickery! Here we create a counter based on the node
// name of the test node to use in the XPath.
if( !isset( $$nodeName ) ) $$nodeName = 1;
else $$nodeName++;
// Failsafe return value.
if( $nodeIndex > $parentNode->childNodes->length ) return( "/" );
} while( !$node->isSameNode( $testNode ) );
// Recursively get the XPath for the parent.
return( getNodeXPath( $parentNode ) . "/{$node->nodeName}[{$$nodeName}]" );
} else {
// Hit the root node! Note that the slash is added when
// building the XPath, so we return just an empty string.
return( "" );
}
}
?>
$xmlDoc=<<<XML
<?xml version="1.0"?>
<methodCall>
<methodName>examples.getStateName</methodName>
<params>
<param>
<value><i4>41</i4></value>
</param>
</params>
</methodCall>
XML;
$xml= new DOMDocument();
$xml->preserveWhiteSpace=false;
$xml->loadXML($xmlDoc);
print_r(xml2array($xml));
function xml2array($n)
{
$return=array();
foreach($n->childNodes as $nc)
($nc->hasChildNodes())
?($n->firstChild->nodeName== $n->lastChild->nodeName&&$n->childNodes->length>1)
?$return[$nc->nodeName][]=xml2array($item)
:$return[$nc->nodeName]=xml2array($nc)
:$return=$nc->nodeValue;
return $return;
}
If you are using not object-oriented functions and it takes too much time to change them all (or you'll be replacing them later) then as a temporary decision can be used this modules:
For DOM XML:
http://alexandre.alapetite.net/doc-alex/domxml-php4-php5/
For XSLT:
http://alexandre.alapetite.net/doc-alex/xslt-php4-php5/
I found the xml2array function below very useful, but there seems to be a bug in it. The $item variable was never getting set. I've expanded this out to be a bit more readable, and the corrected code is :
function xmlToArray($n)
{
$return=array();
foreach($n->childNodes as $nc){
if( $nc->hasChildNodes() ){
if( $n->firstChild->nodeName== $n->lastChild->nodeName&&$n->childNodes->length>1){
$item = $n->firstChild;
$return[$nc->nodeName][]=$this->xmlToArray($item);
}
else{
$return[$nc->nodeName]=$this->xmlToArray($nc);
}
}
else{
$return=$nc->nodeValue;
}
}
return $return;
}
I wrote a couple of functions to:
- create a DOMDocument from a file
- parse the namespaces in it
- create a XPath object with all the namespaces registered
- load the schemalocations
- validate the file on the main schema (the one without prefix)
It is useful for me, see if it is also for someone else!!
Giulio
function decodeNode($node)
{
$out = $node->ownerDocument->saveXML($node);
$re = "{^<((?:\\w*:)?\\w*)". //the tag name
"[\\s\n\r]*((?:[\\s\n\r]*".
"(?:\\w*:)?\\w+[\\s\n\r]*=[\\s\n\r]*". //possible attribute name
"(?:\"[^\"]*\"|\'[^\']*\'))*)". //attribute value
"[\\s\n\r]*>[\r\n]*".
"((?:.*[\r\n]*)*)". //content
"[\r\n]*</\\1>$}"; //closing tag
preg_match($re, $out, $mat);
return $mat;
}
function innerXml($node)
{
$mat = decodeNode($node);
return $mat[3];
}
function getnodeAttributes($node)
{
$mat = decodeNode($node);
$txt = $mat[2];
$re = "{((?:\\w*:)?\\w+)[\\s\n\r]*=[\\s\n\r]*(\"[^\"]*\"|\'[^\']*\')}";
preg_match_all($re, $txt, $mat);
$att = array();
for ($i=0; $i<count($mat[0]); $i++)
{
$value = $mat[2][$i];
if ($value[0] == "\'" || $value[0] == "\"")
{
$len = strlen($value);
$value = substr($value, 1, strlen($value)-2);
}
$att[ $mat[1][$i] ] = $value;
}
return $att;
}
function loadXml($file)
{
$doc = new DOMDocument();
$doc->load($file);
//cerca l'attributo xmlns
$xsi = false;
$doc->namespaces = array();
$doc->xpath = new DOMXPath($doc);
$attr = getnodeAttributes($doc->documentElement);
foreach ($attr as $name => $value)
{
if (substr($name,0,5) == "xmlns")
{
$uri = $value;
$pre = $doc->documentElement->lookupPrefix($uri);
if ($uri == "http://www.w3.org/2001/XMLSchema-instance")
$xsi = $pre;
$doc->namespaces[$pre] = $uri;
if ($pre == "")
$pre = "noname";
$doc->xpath->registerNamespace($pre, $uri);
}
}
if ($xsi)
{
$doc->schemaLocations = array();
$lst = $doc->xpath->query("//@$xsi:schemaLocation");
foreach($lst as $el)
{
$re = "{[\\s\n\r]*([^\\s\n\r]+)[\\s\n\r]*([^\\s\n\r]+)}";
preg_match_all($re, $el->nodeValue, $mat);
for ($i=0; $i<count($mat[0]); $i++)
{
$value = $mat[2][$i];
$doc->schemaLocations[ $mat[1][$i] ] = $value;
}
}
$olddir = getcwd();
chdir(dirname($file));
$schema = $doc->schemaLocations[$doc->namespaces[""]];
if (substr($schema,0,7) == "file://")
{
$schema = substr($value,7);
}
if (!$doc->schemaValidate($schema))
dbg()->err("Invalid file");
chdir($olddir);
}
return $doc;
}
I hate DOM model !
so I wrote dom2array simple function (simple for use):
function dom2array($node) {
$res = array();
print $node->nodeType.'<br/>';
if($node->nodeType == XML_TEXT_NODE){
$res = $node->nodeValue;
}
else{
if($node->hasAttributes()){
$attributes = $node->attributes;
if(!is_null($attributes)){
$res['@attributes'] = array();
foreach ($attributes as $index=>$attr) {
$res['@attributes'][$attr->name] = $attr->value;
}
}
}
if($node->hasChildNodes()){
$children = $node->childNodes;
for($i=0;$i<$children->length;$i++){
$child = $children->item($i);
$res[$child->nodeName] = dom2array($child);
}
}
}
return $res;
}
appended to
brian dot reynolds at risaris dot com
20-Feb-2007 10:09
when you got variable nodes at start you array fails and looses nodes beneath.
solution that counts occurance though eats up performance:
function xmlToArray($n)
{
$xml_array = array();
$occurance = array();
foreach($n->childNodes as $nc)
{
$occurance[$nc->nodeName]++;
}
foreach($n->childNodes as $nc){
if( $nc->hasChildNodes() )
{
if($occurance[$nc->nodeName] > 1)
{
$xml_array[$nc->nodeName][] = xmlToArray($nc);
}
else
{
$xml_array[$nc->nodeName] = xmlToArray($nc);
}
}
else
{
return utf8_decode($nc->nodeValue);
}
}
return $xml_array;
}
In response to lutfi at smartconsultant dot us :
(see my post on
http://fr2.php.net/manual/en/
function.dom-domdocument-getelementsbytagname.php
)
Use this class I wrote:
class XPathableNode extends DOMNode
{
protected $Node;
protected $DOMDocument_from_node;
protected $DOMXpath_for_node;
public function __construct(/* DOMNode */ $node)
{
$this->Node=$node;
$this->DOMDocument_from_node=new
DomDocument();
$domNode=$this->DOMDocument_from_node
->importNode($this->Node, true);
$this->DOMDocument_from_node
->appendChild($domNode);
$this->DomXpath_for_node =
new Domxpath($this->
DOMDocument_from_node);
}
public function convertHTML()
{ return $this->DOMDocument_from_node
->saveHTML();
}
public /*DomNodeList*/ function applyXpath($xpath)
{ return $this->DomXpath_for_node
->query($xpath);
}
}
(sorry for the display... What a terrible hinderance on the
part of php.net !)
Then :
Make a new XPathableNode out of your parent node.
You may then retrieve a DOMNodeList from it by applying a
xpath (thus being able to specify the depth and name of
elements you want).
Has got me around some (of the many) DOM awkwardnesses a few times.
;o)
The Yanik's dom2array() function (added on 14-Mar-2007 08:40) does not handle multiple nodes with the same name, i.e.:
<foo>
<name>aa</name>
<name>bb</name>
</foo>
It will overwrite former and your array will contain just the last one ("bb")
Array to DOM
Here is a recursive function to turn a multidimensional array into an XML document. It will handle multiple elements of the same tag, but only one per parent element. IE:
Can't generate: Can generate:
<root> <root>
<sub1>data1</sub1> <subs1>
<sub1>data2</sub1> <value>data1</value>
<sub2>data1</sub2> <value>data2</value>
<sub2>data2</sub2> </subs1>
</root> <subs2>
<value>data1</value>
<value>data2</value>
<subs2>
</root>
Also, the function performs no type of error checking on your array and will throw a DOMException if a key value you used in your array contains invalid characters for a proper DOM tag. This function is untested for "deep" multidimensional arrays.
Complete code ready to run with example:
<?PHP
function AtoX($array, $DOM=null, $root=null){
if($DOM == null){$DOM = new DOMDocument('1.0', 'iso-8859-1');}
if($root == null){$root = $DOM->appendChild($DOM->createElement('root'));}
$name = $array['#MULTIPLE_ELEMENT_NAME'];
foreach($array as $key => $value){
if(is_int($key) && $name != null){
if(is_array($value)){
$subroot = $root->appendChild($DOM->createElement($name));
AtoX($value, $DOM, $subroot);
}
else if(is_scalar($value)){
$root->appendChild($DOM->createElement($name, $value));
}
}
else if(is_string($key) && $key != '#MULTIPLE_ELEMENT_NAME'){
if(is_array($value)){
$subroot = $root->appendChild($DOM->createElement($key));
AtoX($value, $DOM, $subroot);
}
else if(is_scalar($value)){
$root->appendChild($DOM->createElement($key, $value));
}
}
}
return $DOM;
}
$array = array(
'#MULTIPLE_ELEMENT_NAME' => 'GenericDatas',
'Date' => 'November 03, 2007',
'Company' => 'Facility One',
'Field' => 'Facility Management Software',
'Employees' => array(
'#MULTIPLE_ELEMENT_NAME' => 'Employee',
'Cindy',
'Sean',
'Joe',
'Owen',
'Jim',
'Dale',
'Kelly',
'Ryan',
'Johnathan',
'Robin',
'William Marcus',
'NewCoops' => array(
'#MULTIPLE_ELEMENT_NAME' => 'Coop',
'John',
'Tyler',
'Ray',
'Dawn'
)
),
'Datas',
'DATAS',
'OtherDatas'
);
$DOM = new DOMDocument('1.0', 'iso-8859-1');
$root = $DOM->appendChild($DOM->createElement('CompanyData'));
$DOM = AtoX($array, $DOM, $root);
$DOM->save('C:\test.xml');
?>
The following can take a XML_TEXT_NODE node and return the contents in an array. Yanick's contribution rocks - but
it overwrote with duplicates only keeping the last line
in the returned array. All the other functions i tested from various sources failed to handle text nodes correctly. Hope this helps someone. It is adapted from code on this site.
function myTextNode($n, &$a)
{
static $depth = 0;
static $sz = '';
if ($cn = $n->firstChild)
{
while ($cn)
{
if ($cn->nodeType == XML_TEXT_NODE)
{
$sz .= $cn->nodeValue;
}
elseif ($cn->nodeType == XML_ELEMENT_NODE)
{
$b = 1;
if ($cn->hasChildNodes())
{
$depth++;
if ($this->myHeadings($cn, $a))
{
if ($sz){
array_push($a, $sz);
$sz = '';
}
}
$depth--;
}
}
$cn = $cn->nextSibling;
}
return $b;
}
}
so you could use:
$nodes = $dom->getElementsByTagName("td");
if($nodes){
foreach ($nodes as $node){
$a = Array();
myTextNode($node, $a);
}
}
I use DOM to generate dynamically XHTML document.
When trying to extend the DOMDocument and DOMElement classes, I found a very annoying bug concerning DOMDocument::$documentElement.
If you create your own custom element extending DOMElement and append him in place of the document element, you cannot access to any new members newly defined in your custom class via DOMDocument::$documentElement.
In my situation, I cannot use DOMDocument::registerNodeClass() because the document element is not necessarily the base class for all the elements in my document.
*******
problem
*******
See bellow for the repro step:
<?php
class MyElement extends DOMElement{
public $myProp="myProp";
public function myMethod(){
return 'myMethod()';
}
}
$myDocument=new DOMDocument();
$myDocument->appendChild(new MyElement('myElement','myElement'));
echo ('$myElement->myProp :'.$myDocument->documentElement->myProp.'<br />');
echo ('$myElement->myMethod :'.$myDocument->documentElement->myMethod().'<br />');
?>
will output:
Notice: Undefined property: DOMElement::$myProp in C:\Program Files\EasyPHP 2.0b1\www\testDOMBug\test2.php on line 11
$myElement->myProp :
Fatal error: Call to undefined method DOMElement::myMethod() in C:\Program Files\EasyPHP 2.0b1\www\testDOMBug\test2.php on line 12
*******
solution
*******
After searching around, I found a pretty odd way to fix this problem. It seems that you have to stock a reference to your appended document element in an user-defined (and persistent) variable (in other words, not only in DOMDocument::$documentElement). See below:
<?php
class MyElement extends DOMElement{
public $myProp="myProp";
public function myMethod(){
return 'myMethod()';
}
}
$myDocument=new DOMDocument();
$mydocumentElement=$myDocument->appendChild(new MyElement('myElement','myElement')); //here is the hack
echo ('$myElement->myProp :'.$myDocument->documentElement->myProp.'<br />');
echo ('$myElement->myMethod :'.$myDocument->documentElement->myMethod().'<br />');
?>
will output:
$myElement->myProp :myProp
$myElement->myMethod :myMethod()
Hope it will help.
In response to...
"If you create your own custom element extending DOMElement and append him in place of the document element, you cannot access to any new members newly defined in your custom class via DOMDocument::$documentElement."
... it is not a bug, it is a feature. The DOMDocument::$documentElement property name may be misleading but according to the DOM Level 2 Core specification it is a convenience attribute meant to access the root element of your DOMDocument. See here: http://www.w3.org/TR/DOM-Level-2-Core/core.html#i-Document
This is a couple of classes to deal with yahoo yui menu.
/*
$menubar = new MenuBar();
$file = new Menu("File");
$file->setAttribute("href", "http://file.com");
$quit = new Menu("Quit");
$quit->setAttribute("href", "http://quit.com");
$file->appendChild($quit);
$menubar->appendChild($file);
echo $menubar->grab();
*/
//
// Author: Daniel Queirolo.
// LGPL
//
/** ---------------------------------
/** Class MenuBar()
/** Creates a the menubar and appends
/** yuimenubaritems to it.
/** ---------------------------------*/
class MenuBar extends DOMDocument
{
public $menuID = "nav_menu"; // holds the css id that javascript yui menu code should have to recognize
private $UL; // This node holds every menu, This is THE node.
/** ---------------------------------
/** Constructor
/** Generates a menubar skeleton and the UL node
/** ---------------------------------*/
public function __construct() {
parent::__construct();
$rootdiv = parent::createElement("div");
$rootdiv->setAttribute("class", "yui-skin-sam");
parent::appendChild($rootdiv);
$yui_menubar = parent::createElement("div");
$yui_menubar->setAttribute("id", $this->menuID);
$yui_menubar->setAttribute("class", "yuimenubar");
$rootdiv->appendChild($yui_menubar);
$bd = parent::createElement("div");
$bd->setAttribute("class", "bd");
$yui_menubar->appendChild($bd);
$ul = parent::createElement("ul");
$ul->setAttribute("class", "first-of-type");
// ALL Menu() instances ocurr inside an <ul> tag.
$this->UL = $bd->appendChild($ul);
}
/** ---------------------------------
/** appendChild()
/** Appends a new yuimenubaritem to the menubar UL node.
/** This function changes <li> and <a> classes to yuiMENUBARsomething
/** ---------------------------------*/
public function appendChild($child) {
$li = parent::importNode($child->LI, true);
$li->setAttribute("class", "yuimenubaritem");
$li->getElementsByTagName("a")->item(0)->setAttribute("class", "yuimenubaritemlabel");
$this->UL->appendChild($li);
}
public function grab() {
return parent::saveHTML();
}
}
/** ---------------------------------
/** Class Menu()
/** Creates a yuimenuitem li node
/** ---------------------------------*/
class Menu extends DOMDocument {
public $LI; // stores the <li> node (THE link) that will be exported to MenuBar() or used on appendChild()
/** ---------------------------------
/** Constructor
/** Generates a yuimenuitem li node
/** No yuimenubar items are created here. MenuBar handles that.
/** ---------------------------------*/
public function __construct($link_name) {
parent::__construct();
$li = parent::createElement("li");
$li->setAttribute("class", "yuimenuitem");
// LI node stores THE link.
// if appendChild is used, the new (sub) Menu() would be LI node child.
$this->LI = parent::appendChild($li);
$a = parent::createElement("a", $link_name);
$a->setAttribute("class", "yuimenuitemlabel");
$li->appendChild($a);
$this->li = $li;
$this->a = $a;
}
/** ---------------------------------
/** appendChild
/** Appends a (sub) Menu() to current Menu() in LI
/** ---------------------------------*/
public function appendChild($child) {
$yuimenu = parent::createElement("div");
$yuimenu->setAttribute("class", "yuimenu");
$this->LI->appendChild($yuimenu);
$bd = parent::createElement("div");
$bd->setAttribute("class", "bd");
$yuimenu->appendChild($bd);
$ul = parent::createElement("ul");
$bd->appendChild($ul);
// child->NODE holds THE link from the new child (from child's __construct())
$ul->appendChild(parent::importNode($child->LI, true));
}
public function setAttribute($name, $value, $node="a") {
if ($node == "a") {
$this->a->setAttribute($name, $value);
}
else {
$this->li->setAttribute($name, $value);
}
}
}
I needed a function that quickly converts an xml with key/value pairs to an array.
for example:
<?xml version="1.0" encoding="UTF-8"?>
<test>
<key id="Array key #1" value="Value #1"/>
<key id="Array key #2" value="Value #2"/>
<key id="Dupe" value="Duplicate keys"/>
<key id="Dupe" value="create numeric arrays"/>
<key id="And another key">
<![CDATA[
Multiline
data
works
just
as
well.
]]>
</key>
<nested>
<key id="Nested key" value="Nested data works as well, but it still results in a 1 dimensional array."/>
</nested>
</test>
Results in the following array:
array(5) {
["Array key #1"]=>
string(8) "Value #1"
["Array key #2"]=>
string(8) "Value #2"
["Dupe"]=>
array(2) {
[0]=>
string(14) "Duplicate keys"
[1]=>
string(21) "create numeric arrays"
}
["And another key"]=>
string(49) "Multiline
data
works
just
as
well."
["Nested key"]=>
string(73) "Nested data works as well, but it still results in a 1 dimensional array."
}
Here's the code:
<?php
function xml2array($xml) {
$domDocument = new DOMDocument;
$domDocument->loadXML($xml);
$domXPath = new DOMXPath($domDocument);
$array = array();
foreach ($domXPath->query('//key') as $keyDOM) {
$id = $keyDOM->getAttribute('id');
$value = $keyDOM->hasAttribute('value') ? $keyDOM->getAttribute('value') : trim($keyDOM->textContent);
if (array_key_exists($id, $array)) {
if (is_array($array[$id])) {
$array[$id][] = $value;
} else {
$array[$id] = array($array[$id]);
$array[$id][] = $value;
}
} else {
$array[$id] = $value;
}
}
return $array;
}
?>
If you need simple interface to DOM check out phpQuery - jQuery port to PHP:
http://code.google.com/p/phpquery/
It uses CSS selectors to fetch nodes.
Here's example how it works:
<?php
// just one file to include
require('phpQuery/phpQuery.php');
$html = '
<div>
mydiv
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</div>';
// intialize new DOM from markup
phpQuery::newDocument($markup)
->find('ul > li')
->addClass('my-new-class')
->filter(':last')
->addClass('last-li');
// query all unordered lists in last used DOM
pq('ul')->insertAfter('div');
// iterate all LIs from last used DOM
foreach(pq('li') as $li) {
// iteration returns plain DOM nodes, not phpQuery objects
pq($li)->addClass('my-second-new-class');
}
// same as pq('anything')->htmlOuter()
// but on document root (returns doctype etc)
print phpQuery::getDocument();
?>
It uses DOM extension and XPath so it works only in PHP5.
After searching how to extend the DOMDocument and DOMElement I found a way in the bug: http://bugs.php.net/bug.php?id=35104. The following code shows how:
<?php
class extDOMDocument extends DOMDocument {
public function createElement($name, $value=null) {
$orphan = new extDOMElement($name, $value); // new sub-class object
$docFragment = $this->createDocumentFragment(); // lightweight container maintains "ownerDocument"
$docFragment->appendChild($orphan); // attach
$ret = $docFragment->removeChild($orphan); // remove
return $ret; // ownerDocument set; won't be destroyed on method exit
}
// .. more class definition
}
class extDOMElement extends DOMElement {
function __construct($name, $value='', $namespaceURI=null) {
parent::__construct($name, $value, $namespaceURI);
}
// ... more class definition here
}
$doc = new extDOMDocument('test');
$el = $doc->createElement('tagname');
$el->setAttribute("attr", "val");
$doc->appendChild($el);
// append discards the DOMDocumentFragment and just adds its child nodes, but ownerDocument is maintained.
echo get_class($el)."<br/>";
echo get_class($doc->documentElement)."<br/>";
echo "<xmp>".$doc->saveXML()."</xmp>";
?>
In response to "simlee at indiana dot edu",
- First of all thanks for sharing your funciton.
- It didn't work for me so i rewrite it from scratch using different method.
Here is the new version, hope it helps someone :
<?php
/**
* result sample : /html[1]/body[1]/span[1]/fieldset[1]/div[1]
* @return string
*/
function getNodeXPath( $node ) {
$result='';
while ($parentNode = $node->parentNode) {
$nodeIndex=-1;
$nodeTagIndex=0;
do {
$nodeIndex++;
$testNode = $parentNode->childNodes->item( $nodeIndex );
if ($testNode->nodeName==$node->nodeName and $testNode->parentNode->isSameNode($node->parentNode) and $testNode->childNodes->length>0) {
//echo "{$testNode->parentNode->nodeName}-{$testNode->nodeName}-{}<br/>";
$nodeTagIndex++;
}
} while (!$node->isSameNode($testNode));
$result="/{$node->nodeName}[{$nodeTagIndex}]".$result;
$node=$parentNode;
};
return $result;
}
?>
By Sina.Salek.ws
<?PHP
function dom2array_full($node){
$result = array();
if($node->nodeType == XML_TEXT_NODE) {
$result = $node->nodeValue;
}
else {
if($node->hasAttributes()) {
$attributes = $node->attributes;
if(!is_null($attributes))
foreach ($attributes as $index=>$attr)
$result[$attr->name] = $attr->value;
}
if($node->hasChildNodes()){
$children = $node->childNodes;
for($i=0;$i<$children->length;$i++) {
$child = $children->item($i);
if($child->nodeName != '#text')
if(!isset($result[$child->nodeName]))
$result[$child->nodeName] = dom2array($child);
else {
$aux = $result[$child->nodeName];
$result[$child->nodeName] = array( $aux );
$result[$child->nodeName][] = dom2array($child);
}
}
}
}
return $result;
}
?>
innerHTML in PHP DOM
<?php
function DOMinnerHTML($element)
{
$innerHTML = "";
$children = $element->childNodes;
foreach ($children as $child)
{
$tmp_dom = new DOMDocument();
$tmp_dom->appendChild($tmp_dom->importNode($child, true));
$innerHTML.=trim($tmp_dom->saveHTML());
}
return $innerHTML;
}
?>
Example:
<?php
$dom= new DOMDocument();
$dom->load($html_string);
$dom->preserveWhiteSpace = false;
$domTable = $dom->getElementsByTagName("table");
foreach ($domTable as $tables)
{
echo DOMinnerHTML($tables);
}
?>
You can get the "innerHTML" by nodeValue so
<?php
$doc = new DOMDocument( );
$ele = $doc->createElement( 'p', 'Sensei Ninja' );
print $ele->nodeValue;
?>
You can even set it if you want
<?php
$doc = new DOMDocument( );
$ele = $doc->createElement( 'p' );
$ele->nodeValue = 'Sensei Ninja';
$doc->appendChild( $ele );
print $doc->saveHTML( );
?>
I had the hardest time updating a complex XML document. Here's a quick example on how to do it.
<?php
// Load the XML from a file.
$xml = "a2062.xml"; // This is an XFDL form previously unencoded and ungzipped.
$dom = new DOMDocument();
$dom->preserveWhiteSpace = false;
$dom->Load($xml);
// Create an XPath query.
// Note: you must define the namespace if the XML document has defined namespaces.
$xpath = new DOMXPath($dom);
$xpath->registerNamespace('xfdl', "http://www.PureEdge.com/XFDL/6.5");
// Locate the value for the first Item Description field.
$query = "//xfdl:page/xfdl:field[@sid='ITEMDESA']/xfdl:value";
$nodeList = $xpath->query($query);
$nodeList->item(0)->nodeValue = "This is the text in the value node of the first Item Description field inside the DA 2062 PureEdge form.";
$dom->save($xml);
?>
I hope this helps someone.
I had problems with the dom2array_full function by "nospam at ya dot ru". Here's my function, which works correctly for my project, and might work for yours:
<?php
function dom_to_array($root)
{
$result = array();
if ($root->hasAttributes())
{
$attrs = $root->attributes;
foreach ($attrs as $i => $attr)
$result[$attr->name] = $attr->value;
}
$children = $root->childNodes;
if ($children->length == 1)
{
$child = $children->item(0);
if ($child->nodeType == XML_TEXT_NODE)
{
$result['_value'] = $child->nodeValue;
if (count($result) == 1)
return $result['_value'];
else
return $result;
}
}
$group = array();
for($i = 0; $i < $children->length; $i++)
{
$child = $children->item($i);
if (!isset($result[$child->nodeName]))
$result[$child->nodeName] = dom_to_array($child);
else
{
if (!isset($group[$child->nodeName]))
{
$tmp = $result[$child->nodeName];
$result[$child->nodeName] = array($tmp);
$group[$child->nodeName] = 1;
}
$result[$child->nodeName][] = dom_to_array($child);
}
}
return $result;
}
?>
A function among several others to parse a google results page, I wrote this some time ago - google has probably changed their site since then, but I thought this might be helpful to someone.
I'm moving servers, but I will probably throw this up on my blog when I get it back up.
<?php
function googleResult($listItem) {
// given a LIST ITEM element, this will validate, and return an array for that LI entry as an inline result from google.
/*
* <li class='g w0'>
* <h3 class='r'>
* <a href='the URL' class='l'>
* Description <em>description</em>
* </a>
* </h3>
* </li>
*
UPDATE:
This function will now look for any subcontainer that has an href, it doesn't have to be an H3
this will make it work with a few more formatted search results.
*/
$listItem = $listItem->childNodes;
// Yes I don't use instanceof - I guess you'll have to deal.
foreach($listItem as $element) {
if(is_object($element) && get_class($element) == 'DOMElement' && $element->hasChildNodes()) {
$hrefContainer = $element->childNodes;
foreach($hrefContainer as $element2) {
if(is_object($element2) && get_class($element2) == 'DOMElement' && $element2->nodeName == 'a' && $element2->hasAttribute('href')) {
$anchor = $element2;
unset($h3);
unset($element2);
break;
} else {
//print __LINE__ ." :: Breaking out of loop (normal result) element is not an annchor Element='".$element2->nodeName."'\n";
}
}
unset($element);
unset($listItem);
break;
}
}
if(empty($anchor) || !is_object($anchor) || get_class($anchor) != 'DOMElement') {
//print __LINE__ ." :: Returning false, did not locate anchor through iteration...";
return false;
}
$href = $anchor->getAttribute('href');
if(empty($href)) {
//print __LINE__ ." :: Found anchor object, could not read href attribute / href is empty? href='$href'\n";
return false;
}
$description = $anchor->childNodes;
$urlDescription = '';
foreach($description as $words) {
$name = trim($words->nodeName);
if($name == 'em' || $name == '#text' || $name == 'b') {
if(!empty($words->nodeValue)) {
$text = trim($words->nodeValue);
$urlDescription = $urlDescription . $text . ' ';
}
}
}
$urlDescription = htmlspecialchars_decode($urlDescription, ENT_QUOTES);
$urlDescription = trim($urlDescription);
return array('description' => $urlDescription, 'href' => $href);
}
Here is a fast innerHTML function that returns the result without iterating over child nodes.
<?php
function innerHTML($el) {
$doc = new DOMDocument();
$doc->appendChild($doc->importNode($el, TRUE));
$html = trim($doc->saveHTML());
$tag = $el->nodeName;
return preg_replace('@^<' . $tag . '[^>]*>|</' . $tag . '>$@', '', $html);
}
?>
Example
<?php
$doc = new DOMDocument();
// A corrupt HTML string
$doc->loadHTML('<HTML><A HREF="ss">asd</A>');
$body = $doc->getElementsByTagName('body')->item(0);
print htmlspecialchars(innerHTML($body));
// Prints <a href="ss">asd</a>
?>
If you want to print the DOM XML file content, you can use the next code:
$doc = new DOMDocument();
$doc->load($xmlFileName);
echo "<br>" . $doc->documentURI;
$x = $doc->documentElement;
getNodeContent($x->childNodes, 0);
function getNodeContent($nodes, $level){
foreach ($nodes AS $item) {
// print "<br><br>TIPO: " . $item->nodeType ;
printValues($item, $level);
if ($item->nodeType == 1) { //DOMElement
foreach ($item->attributes AS $itemAtt) {
printValues($itemAtt, $level+3);
}
if($item->childNodes || $item->childNodes->lenth > 0) {
getNodeContent($item->childNodes, $level+5);
}
}
}
}
function printValues($item, $level){
if ($item->nodeType == 1) { //DOMElement
printLevel($level);
print $item->nodeName . " = " . $item->nodeValue;
}
if ($item->nodeType == 2) { //DOMAttr
printLevel($level);
print $item->name . " = " . $item->value ;
}
if ($item->nodeType == 3) { //DOMText
if ($item->isWhitespaceInElementContent() == false){
printLevel($level);
print $item->wholeText ;
}
}
}
function printLevel($level)
{
print "<br>";
if ($level == 0) {
print "<br>";
}
for($i=0; $i < $level; $i++) {
print "-";
}
}
<?php
// this note is about how to get a DOMNode's outerHTML and innerHTML.
$dom = new DOMDocument('1.0','UTF-8');
$dom->loadHTML('<html><body><div><p>p1</p><p>p2</p></div></body></html>');
$node = $dom->getElementsByTagName('div')->item(0);
$outerHTML = $node->ownerDocument->saveHTML($node);
$innerHTML = '';
foreach ($node->childNodes as $childNode){
$innerHTML .= $childNode->ownerDocument->saveHTML($childNode);
}
echo '<h2>outerHTML: </h2>';
echo htmlspecialchars($outerHTML);
echo '<h2>innerHTML: </h2>';
echo htmlspecialchars($innerHTML);
?>
If you want to use DOMDocument in your PHPUnit Tests drive on Symfony Controller (testing form)! Use like :
namespace Tests\YourBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
use YourBundle\Controller\TextController;
class DefaultControllerTest extends WebTestCase
{
public function testIndex()
{
$client = static::createClient(array(), array());
$crawler = $client->request('GET', '/text/add');
$this->assertTrue($crawler->filter("form")->count() > 0, "Text form exist !");
$form = $crawler->filter("form")->form();
$domDocument = new \DOMDocument;
$domInput = $domDocument->createElement('input');
$dom = $domDocument->appendChild($domInput);
$dom->setAttribute('slug', 'bloc');
$formInput = new \Symfony\Component\DomCrawler\Field\InputFormField($domInput);
$form->set($formInput);
$crawler = $client->submit($form);
if ($client->getResponse()->isRedirect())
{
$crawler = $client->followRedirect(false);
}
// $this->assertTrue($client->getResponse()->isSuccessful());
//$this->assertEquals(200, $client->getResponse()->getStatusCode(),
// "Unexpected HTTP status code for GET /backoffice/login");
}
}
This extension allows editing an HTML snippet, for example removing an element:
$dom = new DOMDocument();
$dom->loadHTML('keep<b id="simple">REMOVE</b>');
$remove = $dom->getElementById('simple');
$dom->removeChild($remove);
echo $dom->saveHTML();
...would output "keep".
But it does not preserve formatting. If your code defines attributes using double quotes, saveHTML() may for instance return single quotes instead.