Apache Zeta Components Manual :: File Source for xml.php
Source for file xml.php
Documentation is available at xml.php
* File containing the ezcTreeXml class.
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @version //autogentag//
* ezcTreeXml is an implementation of a tree backend that operates on
* // Create a new tree in a new XML file
* $tree = ezcTreeXml::create(
* new ezcTreeXmlInternalDataStore()
* // Open an existing XML file containing a tree structure
* $tree = new ezcTreeXml(
* new ezcTreeXmlInternalDataStore()
* See {@link ezcTree} for examples on how to operate on the tree.
* @property-read ezcTreeXmlDataStore $store
* The data store that is used for retrieving/storing data.
* @property-read string $prefix
* The prefix that is used to prefix node IDs with in the tree.
* @property string $nodeClassName
* Which class is used as tree node - this class *must* inherit
* @version //autogentag//
* Contains the relax-NG schema to validate the tree XML.
const relaxNG =
'<?xml version="1.0" encoding="UTF-8"?>
<grammar xmlns:etd="http://components.ez.no/Tree/data" ns="http://components.ez.no/Tree" xmlns="http://relaxng.org/ns/structure/1.0" datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes">
<attribute name="prefix">
<attribute name="lastNodeId">
<element name="etd:data">
* Contains the DOM Tree that all operations will be done on.
* When the tree object is constructed the XML is parsed with DOM and
* stored into this member variable. When the tree is modified the changes
* are then flushed to disk with the saveFile() method.
* The file name that contains the tree as XML string.
* Stores the last auto generated ID that was used.
* @var integer $autoNodeId
* Constructs a new ezcTreeXml object from the XML data in $xmlFile and using
* the $store to retrieve data from.
* @param ezcTreeXmlDataStore $store
public function __construct( $xmlFile, ezcTreeXmlDataStore $store )
$dom =
new DomDocument();
$dom->formatOutput =
true;
$valid =
$dom->relaxNGValidateSource( self::relaxNG );
// Associate the DOM tree with the data store
$store->setDomTree( $dom );
// Figure out the prefix - which is the "prefix" attribute on the root node.
$document =
$dom->documentElement;
$prefix =
$document->getAttribute( 'prefix' );
// Figure out the last auto generated ID
$this->autoNodeId =
$document->getAttribute( 'lastNodeId' );
if ( $this->autoNodeId !==
"" )
$this->xmlFile =
$xmlFile;
* Returns the value of the property $name.
* @throws ezcBasePropertyNotFoundException if the property does not exist.
public function __get( $name )
return parent::__get( $name );
* Sets the property $name to $value.
* @throws ezcBasePropertyNotFoundException if the property does not exist.
* @throws ezcBasePropertyPermissionException if a read-only property is
public function __set( $name, $value )
return parent::__set( $name, $value );
* Returns true if the property $name is set, otherwise false.
public function __isset( $name )
return parent::__isset( $name );
* Creates a new XML tree in the file $xmlFile using $store as data store.
* The $prefix option can be used to change the prefix that is used for IDs
* in the created tree. By default this is set to "id" so that numerical
* node IDs are not a problem.
* @param ezcTreeXmlDataStore $store
public static function create( $xmlFile, ezcTreeXmlDataStore $store, $prefix =
'id' )
$dom =
new DomDocument( '1.0', 'utf-8' );
$dom->formatOutput =
true;
$element =
$dom->createElement( 'tree' );
$element->setAttributeNode( new DOMAttr( 'xmlns', 'http://components.ez.no/Tree' ) );
$element->setAttributeNode( new DOMAttr( 'xmlns:etd', 'http://components.ez.no/Tree/data' ) );
$element->setAttributeNode( new DOMAttr( 'prefix', $prefix ) );
$dom->appendChild( $element );
return new ezcTreeXml( $xmlFile, $store, $prefix );
* Saves the internal DOM representation of the tree back to disk.
$this->dom->save( $this->xmlFile );
* This method generates the next node ID.
* It uses the stored last generated ID, and also stores this in an
* attribute on the root node so that it can be correctly incremented
* without having to search for the last generated ID in the whole tree.
$document =
$this->dom->documentElement;
$document->setAttributeNode( new DOMAttr( 'lastNodeId', $this->autoNodeId ) );
return $this->autoNodeId;
* Returns whether the node with ID $nodeId exists.
$elem =
$this->dom->getElementById( "{
$this->properties['prefix']}$nodeId" );
return ( $elem !==
null ) ?
true :
false;
* Retrieves a DOMElement containing the node with node ID $nodeId.
private function getNodeById( $nodeId )
$node =
$this->dom->getElementById( "{
$this->properties['prefix']}$nodeId" );
* Fetches all the child "node" DOM elements of the node with ID $nodeId.
private function fetchChildIds( $nodeId )
$elem =
$this->getNodeById( $nodeId );
$children =
$elem->childNodes;
foreach ( $children as $child )
if ( $child->nodeType ===
XML_ELEMENT_NODE &&
$child->tagName ==
"node" )
* Returns all the children of the node with ID $nodeId.
* @return ezcTreeNodeList
foreach ( $this->fetchChildIds( $nodeId ) as $childId )
$list->addNode( new $className( $this, $childId ) );
* Returns the parent node of the node with ID $nodeId.
* This method returns null if there is no parent node.
$elem =
$this->getNodeById( $nodeId );
$elem =
$elem->parentNode;
$parentId =
$elem !==
null ?
substr( $elem->getAttribute( 'id' ), strlen( $this->properties['prefix'] ) ) :
null;
if ( $parentId ===
false )
return new $className( $this, $parentId );
* Returns all the nodes in the path from the root node to the node with ID
* $nodeId, including those two nodes.
* @return ezcTreeNodeList
$nodes[] =
new $className( $this, $nodeId );
$elem =
$this->getNodeById( $nodeId );
$elem =
$elem->parentNode;
while ( $elem !==
null &&
$elem->nodeType ==
XML_ELEMENT_NODE &&
$elem->tagName ==
'node' )
$nodes[] =
new $className( $this, $id );
$elem =
$elem->parentNode;
* Adds the children nodes of the node with ID $nodeId to the
* @param ezcTreeNodeList $list
private function addChildNodesDepthFirst( ezcTreeNodeList $list, $nodeId )
foreach ( $this->fetchChildIds( $nodeId ) as $childId )
$list->addNode( new $className( $this, $childId ) );
$this->addChildNodesDepthFirst( $list, $childId );
* Returns the node with ID $nodeId and all its children, sorted according to
* the {@link http://en.wikipedia.org/wiki/Depth-first_search Depth-first sorting}
* @return ezcTreeNodeList
$list->addNode( new $className( $this, $nodeId ) );
$this->addChildNodesDepthFirst( $list, $nodeId );
* Alias for fetchSubtreeDepthFirst().
* @return ezcTreeNodeList
* Adds the children nodes of the node with ID $nodeId to the
* @param ezcTreeNodeList $list
private function addChildNodesBreadthFirst( ezcTreeNodeList $list, $nodeId )
$childIds =
$this->fetchChildIds( $nodeId );
foreach ( $childIds as $childId )
$list->addNode( new $className( $this, $childId ) );
foreach ( $childIds as $childId )
$this->addChildNodesDepthFirst( $list, $childId );
* Returns the node with ID $nodeId and all its children, sorted according to
* the {@link http://en.wikipedia.org/wiki/Breadth-first_search Breadth-first sorting}
* @return ezcTreeNodeList
$list->addNode( new $className( $this, $nodeId ) );
$this->addChildNodesBreadthFirst( $list, $nodeId );
* Returns the number of direct children of the node with ID $nodeId.
$elem =
$this->getNodeById( $nodeId );
$children =
$elem->childNodes;
foreach ( $children as $child )
if ( $child->nodeType ===
XML_ELEMENT_NODE &&
$child->tagName ==
"node" )
* Adds the number of children with for the node with ID $nodeId nodes to
private function countChildNodes( &$count, $nodeId )
foreach ( $this->fetchChildIds( $nodeId ) as $childId )
$this->countChildNodes( $count, $childId );
* Returns the number of children of the node with ID $nodeId, recursively.
$this->countChildNodes( $count, $nodeId );
* Returns the distance from the root node to the node with ID $nodeId.
$elem =
$this->getNodeById( $nodeId );
$elem =
$elem->parentNode;
while ( $elem !==
null &&
$elem->nodeType ==
XML_ELEMENT_NODE )
$elem =
$elem->parentNode;
* Returns whether the node with ID $nodeId has children.
$elem =
$this->getNodeById( $nodeId );
$children =
$elem->childNodes;
foreach ( $children as $child )
if ( $child->nodeType ===
XML_ELEMENT_NODE &&
$child->tagName ==
"node" )
* Returns whether the node with ID $childId is a direct child of the node
* @param string $parentId
public function isChildOf( $childId, $parentId )
$elem =
$this->getNodeById( $childId );
$parentElem =
$elem->parentNode;
$nodeId =
$parentElem->getAttribute( 'id' );
if ( $nodeId ===
"{
$this->properties['prefix']}$parentId" )
* Returns whether the node with ID $childId is a direct or indirect child
* of the node with ID $parentId.
* @param string $parentId
$elem =
$this->getNodeById( $childId );
$elem =
$elem->parentNode;
while ( $elem !==
null &&
$elem->nodeType ==
XML_ELEMENT_NODE )
$nodeId =
$elem->getAttribute( 'id' );
if ( $nodeId ===
"{
$this->properties['prefix']}$parentId" )
$elem =
$elem->parentNode;
* Returns whether the nodes with IDs $child1Id and $child2Id are siblings
* (ie, they share the same parent).
* @param string $child1Id
* @param string $child2Id
$elem1 =
$this->getNodeById( $child1Id );
$elem2 =
$this->getNodeById( $child2Id );
( $child1Id !==
$child2Id ) &&
( $elem1->parentNode->getAttribute( 'id' ) ===
$elem2->parentNode->getAttribute( 'id' ) )
* Sets a new node as root node, this also wipes out the whole tree.
* @param ezcTreeNode $node
$document =
$this->dom->documentElement;
// remove old root node(s)
foreach ( $document->childNodes as $childNode )
if ( $childNode->nodeType ==
XML_ELEMENT_NODE &&
$childNode->tagName ===
'node' )
$this->store->deleteDataForAllNodes();
$root =
$this->dom->createElement( 'node' );
$root->setAttributeNode( new DOMAttr( 'id', "{
$this->properties['prefix']}{$node->id}" ) );
$root->setIdAttribute( 'id', true );
$document->appendChild( $root );
$this->store->storeDataForNode( $node, $node->data );
* This methods returns null if there is no root node.
$document =
$this->dom->documentElement;
foreach ( $document->childNodes as $childNode )
if ( $childNode->nodeType ==
XML_ELEMENT_NODE &&
$childNode->tagName ==
'node' )
return new $className( $this, $nodeId );
* Adds the node $childNode as child of the node with ID $parentId.
* @param string $parentId
* @param ezcTreeNode $childNode
public function addChild( $parentId, ezcTreeNode $childNode )
$this->addTransactionItem( new ezcTreeTransactionItem( ezcTreeTransactionItem::ADD, $childNode, null, $parentId ) );
$elem =
$this->getNodeById( $parentId );
$child =
$this->dom->createElement( 'node' );
$child->setAttributeNode( new DOMAttr( 'id', "{
$this->properties['prefix']}{$childNode->id}" ) );
$child->setIdAttribute( 'id', true );
$elem->appendChild( $child );
$this->store->storeDataForNode( $childNode, $childNode->data );
* Deletes the node with ID $nodeId from the tree, including all its children.
public function delete( $nodeId )
$this->addTransactionItem( new ezcTreeTransactionItem( ezcTreeTransactionItem::DELETE, null, $nodeId ) );
// Delete all the associated data
$this->store->deleteDataForNodes( $nodeList );
$nodeToDelete =
$this->getNodeById( $nodeId );
// Remove the ID on all children by hand as this would crash in PHP <= 5.2.3
$nodeToDelete->removeAttribute( "id" );
$children =
$nodeToDelete->getElementsByTagName( 'node' );
foreach ( $children as $child )
$child->removeAttribute( "id" );
// Use the parent to remove the child
$nodeToDelete->parentNode->removeChild( $nodeToDelete );
* Moves the node with ID $nodeId as child to the node with ID $targetParentId.
* @param string $targetParentId
public function move( $nodeId, $targetParentId )
$this->addTransactionItem( new ezcTreeTransactionItem( ezcTreeTransactionItem::MOVE, null, $nodeId, $targetParentId ) );
$nodeToMove =
$this->getNodeById( $nodeId );
$parent =
$this->getNodeById( $targetParentId );
$parent->appendChild( $nodeToMove );
* Fixates the transaction (saves the XML file).