## Apache Zeta Components Manual :: File Source for xml.php

## Source for file xml.php

Documentation is available at xml.php

1. <?php
2. /**
3. * File containing the ezcTreeXml class.
4. *
5. * Licensed to the Apache Software Foundation (ASF) under one
6. * or more contributor license agreements. See the NOTICE file
7. * distributed with this work for additional information
8. * regarding copyright ownership. The ASF licenses this file
9. * to you under the Apache License, Version 2.0 (the
10. * "License"); you may not use this file except in compliance
11. * with the License. You may obtain a copy of the License at
12. *
13. * http://www.apache.org/licenses/LICENSE-2.0
14. *
15. * Unless required by applicable law or agreed to in writing,
16. * software distributed under the License is distributed on an
17. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18. * KIND, either express or implied. See the License for the
19. * specific language governing permissions and limitations
20. * under the License.
21. *
22. * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
23. * @version //autogentag//
24. * @filesource
25. * @package Tree
26. */
28. /**
29. * ezcTreeXml is an implementation of a tree backend that operates on
30. * an XML file.
31. *
32. * Example:
33. * <code>
34. * <?php
35. * // Create a new tree in a new XML file
36. * $tree = ezcTreeXml::create(
37. * 'new-tree.xml',
38. * new ezcTreeXmlInternalDataStore()
39. * );
40. *
41. * // Open an existing XML file containing a tree structure
42. * $tree = new ezcTreeXml(
43. * 'existing-tree.xml',
44. * new ezcTreeXmlInternalDataStore()
45. * );
46. * ?>
47. * </code>
48. *
49. * See {@link ezcTree} for examples on how to operate on the tree.
50. *
51. * @property-read ezcTreeXmlDataStore $store
52. * The data store that is used for retrieving/storing data.
53. * @property-read string $prefix
54. * The prefix that is used to prefix node IDs with in the tree.
55. * @property string $nodeClassName
56. * Which class is used as tree node - this class *must* inherit
57. * the ezcTreeNode class.
58. *
59. * @package Tree
60. * @version //autogentag//
61. * @mainclass
62. */
63. class ezcTreeXml extends ezcTree
64. {
65. /**
66. * Contains the relax-NG schema to validate the tree XML.
67. */
68. const relaxNG = '<?xml version="1.0" encoding="UTF-8"?>
69. <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">
70. <start>
71. <element name="tree">
72. <optional>
73. <attribute name="prefix">
74. <data type="ID"/>
75. </attribute>
76. </optional>
77. <optional>
78. <attribute name="lastNodeId">
79. <data type="string"/>
80. </attribute>
81. </optional>
82. <optional>
83. <ref name="node"/>
84. </optional>
85. </element>
86. </start>
87. <define name="node">
88. <element name="node">
89. <attribute name="id">
90. <data type="ID"/>
91. </attribute>
92. <optional>
93. <element name="etd:data">
94. <text/>
95. </element>
96. </optional>
97. <zeroOrMore>
98. <ref name="node"/>
99. </zeroOrMore>
100. </element>
101. </define>
102. </grammar>';
104. /**
105. * Contains the DOM Tree that all operations will be done on.
106. *
107. * When the tree object is constructed the XML is parsed with DOM and
108. * stored into this member variable. When the tree is modified the changes
109. * are then flushed to disk with the saveFile() method.
110. *
111. * @var DOMDocument $dom
112. */
113. private $dom;
115. /**
116. * The file name that contains the tree as XML string.
117. *
118. * @var string $xmlFile
119. */
120. private $xmlFile;
122. /**
123. * Stores the last auto generated ID that was used.
124. *
125. * @var integer $autoNodeId
126. */
127. private $autoNodeId = 0;
129. /**
130. * Constructs a new ezcTreeXml object from the XML data in $xmlFile and using
131. * the $store to retrieve data from.
132. *
133. * @param string $xmlFile
134. * @param ezcTreeXmlDataStore $store
135. */
136. public function __construct ( $xmlFile, ezcTreeXmlDataStore $store )
137. {
138. if ( ! file_exists ( $xmlFile ) )
139. {
140. throw new ezcBaseFileNotFoundException ( $xmlFile, "XML" );
141. }
143. $previous = libxml_use_internal_errors ( true );
145. $dom = new DomDocument();
146. $dom->load( $xmlFile );
147. $dom->formatOutput = true;
149. $errors = libxml_get_errors ();
150. libxml_clear_errors ();
151. if ( count ( $errors ) )
152. {
153. throw new ezcTreeInvalidXmlException ( $xmlFile, $errors );
154. }
156. $valid = $dom->relaxNGValidateSource( self::relaxNG );
157. if ( !$valid )
158. {
159. $errors = libxml_get_errors ();
160. libxml_clear_errors ();
161. throw new ezcTreeInvalidXmlFormatException ( $xmlFile, $errors );
162. }
164. libxml_use_internal_errors ( $previous );
166. // Associate the DOM tree with the data store
167. $store->setDomTree( $dom );
169. // Figure out the prefix - which is the "prefix" attribute on the root node.
170. $document = $dom->documentElement;
171. $prefix = $document->getAttribute( 'prefix' );
173. // Figure out the last auto generated ID
174. $autoId = false;
175. $this->autoNodeId = $document->getAttribute( 'lastNodeId' );
176. if ( $this->autoNodeId !== "" )
177. {
178. $autoId = true;
179. }
181. // Set member variables
182. $this->dom = $dom;
183. $this->xmlFile = $xmlFile;
184. $this-> properties ['store'] = $store;
185. $this-> properties ['prefix'] = $prefix;
186. $this-> properties ['autoId'] = $autoId;
187. }
189. /**
190. * Returns the value of the property $name.
191. *
192. * @throws ezcBasePropertyNotFoundException if the property does not exist.
193. * @param string $name
194. * @ignore
195. */
196. public function __get( $name )
197. {
198. switch ( $name )
199. {
200. case 'prefix':
201. return $this-> properties [$name];
202. }
203. return parent::__get( $name );
204. }
206. /**
207. * Sets the property $name to $value.
208. *
209. * @throws ezcBasePropertyNotFoundException if the property does not exist.
210. * @throws ezcBasePropertyPermissionException if a read-only property is
211. * tried to be modified.
212. * @param string $name
213. * @param mixed $value
214. * @ignore
215. */
216. public function __set( $name, $value )
217. {
218. switch ( $name )
219. {
220. case 'prefix':
221. throw new ezcBasePropertyPermissionException ( $name, ezcBasePropertyPermissionException ::READ );
223. default:
224. return parent::__set( $name, $value );
225. }
226. }
228. /**
229. * Returns true if the property $name is set, otherwise false.
230. *
231. * @param string $name
232. * @return bool
233. * @ignore
234. */
235. public function __isset( $name )
236. {
237. switch ( $name )
238. {
239. case 'prefix':
240. return isset( $this-> properties [$name] );
242. default:
243. return parent::__isset( $name );
244. }
245. }
247. /**
248. * Creates a new XML tree in the file $xmlFile using $store as data store.
249. *
250. * The $prefix option can be used to change the prefix that is used for IDs
251. * in the created tree. By default this is set to "id" so that numerical
252. * node IDs are not a problem.
253. *
254. * @param string $xmlFile
255. * @param ezcTreeXmlDataStore $store
256. * @param string $prefix
257. * @return ezcTreeXml
258. */
259. public static function create ( $xmlFile, ezcTreeXmlDataStore $store, $prefix = 'id' )
260. {
261. $dom = new DomDocument( '1.0', 'utf-8' );
262. $dom->formatOutput = true;
264. $element = $dom->createElement( 'tree' );
265. $element->setAttributeNode( new DOMAttr( 'xmlns', 'http://components.ez.no/Tree' ) );
266. $element->setAttributeNode( new DOMAttr( 'xmlns:etd', 'http://components.ez.no/Tree/data' ) );
267. if ( $prefix !== null )
268. {
269. $element->setAttributeNode( new DOMAttr( 'prefix', $prefix ) );
270. }
272. $dom->appendChild( $element );
274. $dom->save( $xmlFile );
275. return new ezcTreeXml ( $xmlFile, $store, $prefix );
276. }
278. /**
279. * Saves the internal DOM representation of the tree back to disk.
280. */
281. public function saveFile ()
282. {
283. $this->dom->save( $this->xmlFile );
284. }
286. /**
287. * This method generates the next node ID.
288. *
289. * It uses the stored last generated ID, and also stores this in an
290. * attribute on the root node so that it can be correctly incremented
291. * without having to search for the last generated ID in the whole tree.
292. *
293. * @return integer
294. */
295. protected function generateNodeID ()
296. {
297. $this->autoNodeId++;
299. $document = $this->dom->documentElement;
300. $document->setAttributeNode( new DOMAttr( 'lastNodeId', $this->autoNodeId ) );
302. return $this->autoNodeId;
303. }
305. /**
306. * Returns whether the node with ID $nodeId exists.
307. *
308. * @param string $nodeId
309. * @return bool
310. */
311. public function nodeExists ( $nodeId )
312. {
313. $elem = $this->dom->getElementById( "{$this-> properties ['prefix']}$nodeId" );
314. return ( $elem !== null ) ? true : false;
315. }
317. /**
318. * Retrieves a DOMElement containing the node with node ID $nodeId.
319. *
320. * @param string $nodeId
321. * @return DOMElement
322. */
323. private function getNodeById( $nodeId )
324. {
325. $node = $this->dom->getElementById( "{$this-> properties ['prefix']}$nodeId" );
326. if ( !$node )
327. {
328. throw new ezcTreeUnknownIdException ( $nodeId );
329. }
330. return $node;
331. }
333. /**
334. * Fetches all the child "node" DOM elements of the node with ID $nodeId.
335. *
336. * @param string $nodeId
337. * @return array(string)
338. */
339. private function fetchChildIds( $nodeId )
340. {
341. $childNodes = array();
342. $elem = $this->getNodeById( $nodeId );
343. $children = $elem->childNodes;
344. foreach ( $children as $child )
345. {
346. if ( $child->nodeType === XML_ELEMENT_NODE && $child->tagName == "node" )
347. {
348. $nodeId = substr ( $child->getAttribute( 'id' ), strlen ( $this-> properties ['prefix'] ) );
349. $childNodes[] = $nodeId;
350. }
351. }
352. return $childNodes;
353. }
355. /**
356. * Returns all the children of the node with ID $nodeId.
357. *
358. * @param string $nodeId
359. * @return ezcTreeNodeList
360. */
361. public function fetchChildren ( $nodeId )
362. {
363. $className = $this-> properties ['nodeClassName'];
364. $list = new ezcTreeNodeList ;
365. foreach ( $this->fetchChildIds( $nodeId ) as $childId )
366. {
367. $list->addNode( new $className( $this, $childId ) );
368. }
369. return $list;
370. }
372. /**
373. * Returns the parent node of the node with ID $nodeId.
374. *
375. * This method returns null if there is no parent node.
376. *
377. * @param string $nodeId
378. * @return ezcTreeNode
379. */
380. public function fetchParent ( $nodeId )
381. {
382. $className = $this-> properties ['nodeClassName'];
383. $elem = $this->getNodeById( $nodeId );
384. $elem = $elem->parentNode;
385. $parentId = $elem !== null ? substr ( $elem->getAttribute( 'id' ), strlen ( $this-> properties ['prefix'] ) ) : null;
386. if ( $parentId === false )
387. {
388. return null;
389. }
390. return new $className( $this, $parentId );
391. }
393. /**
394. * Returns all the nodes in the path from the root node to the node with ID
395. * $nodeId, including those two nodes.
396. *
397. * @param string $nodeId
398. * @return ezcTreeNodeList
399. */
400. public function fetchPath ( $nodeId )
401. {
402. $className = $this-> properties ['nodeClassName'];
404. $nodes = array();
405. $nodes[] = new $className( $this, $nodeId );
407. $elem = $this->getNodeById( $nodeId );
408. $elem = $elem->parentNode;
410. while ( $elem !== null && $elem->nodeType == XML_ELEMENT_NODE && $elem->tagName == 'node' )
411. {
412. $id = substr ( $elem->getAttribute( 'id' ), strlen ( $this-> properties ['prefix'] ) );
413. $nodes[] = new $className( $this, $id );
414. $elem = $elem->parentNode;
415. }
417. $list = new ezcTreeNodeList ;
418. foreach ( array_reverse ( $nodes ) as $node )
419. {
420. $list->addNode( $node );
421. }
422. return $list;
423. }
425. /**
426. * Adds the children nodes of the node with ID $nodeId to the
427. * ezcTreeNodeList $list.
428. *
429. * @param ezcTreeNodeList $list
430. * @param string $nodeId
431. */
432. private function addChildNodesDepthFirst( ezcTreeNodeList $list, $nodeId )
433. {
434. $className = $this-> properties ['nodeClassName'];
435. foreach ( $this->fetchChildIds( $nodeId ) as $childId )
436. {
437. $list->addNode( new $className( $this, $childId ) );
438. $this->addChildNodesDepthFirst( $list, $childId );
439. }
440. }
442. /**
443. * Returns the node with ID $nodeId and all its children, sorted according to
444. * the {@link http://en.wikipedia.org/wiki/Depth-first_search Depth-first sorting}
445. * algorithm.
446. *
447. * @param string $nodeId
448. * @return ezcTreeNodeList
449. */
450. public function fetchSubtreeDepthFirst ( $nodeId )
451. {
452. $className = $this-> properties ['nodeClassName'];
453. $list = new ezcTreeNodeList ;
454. $list->addNode( new $className( $this, $nodeId ) );
455. $this->addChildNodesDepthFirst( $list, $nodeId );
456. return $list;
457. }
459. /**
460. * Alias for fetchSubtreeDepthFirst().
461. *
462. * @param string $nodeId
463. * @return ezcTreeNodeList
464. */
465. public function fetchSubtree ( $nodeId )
466. {
467. return $this-> fetchSubtreeDepthFirst ( $nodeId );
468. }
470. /**
471. * Adds the children nodes of the node with ID $nodeId to the
472. * ezcTreeNodeList $list.
473. *
474. * @param ezcTreeNodeList $list
475. * @param string $nodeId
476. */
477. private function addChildNodesBreadthFirst( ezcTreeNodeList $list, $nodeId )
478. {
479. $className = $this-> properties ['nodeClassName'];
480. $childIds = $this->fetchChildIds( $nodeId );
481. foreach ( $childIds as $childId )
482. {
483. $list->addNode( new $className( $this, $childId ) );
484. }
485. foreach ( $childIds as $childId )
486. {
487. $this->addChildNodesDepthFirst( $list, $childId );
488. }
489. }
491. /**
492. * Returns the node with ID $nodeId and all its children, sorted according to
493. * the {@link http://en.wikipedia.org/wiki/Breadth-first_search Breadth-first sorting}
494. * algorithm.
495. *
496. * @param string $nodeId
497. * @return ezcTreeNodeList
498. */
499. public function fetchSubtreeBreadthFirst ( $nodeId )
500. {
501. $className = $this-> properties ['nodeClassName'];
502. $list = new ezcTreeNodeList ;
503. $list->addNode( new $className( $this, $nodeId ) );
504. $this->addChildNodesBreadthFirst( $list, $nodeId );
505. return $list;
506. }
508. /**
509. * Returns the number of direct children of the node with ID $nodeId.
510. *
511. * @param string $nodeId
512. * @return int
513. */
514. public function getChildCount ( $nodeId )
515. {
516. $count = 0;
517. $elem = $this->getNodeById( $nodeId );
518. $children = $elem->childNodes;
519. foreach ( $children as $child )
520. {
521. if ( $child->nodeType === XML_ELEMENT_NODE && $child->tagName == "node" )
522. {
523. $count++;
524. }
525. }
526. return $count;
527. }
529. /**
530. * Adds the number of children with for the node with ID $nodeId nodes to
531. * $count, recursively.
532. *
533. * @param int $count
534. * @param string $nodeId
535. */
536. private function countChildNodes( &$count, $nodeId )
537. {
538. foreach ( $this->fetchChildIds( $nodeId ) as $childId )
539. {
540. $count++;
541. $this->countChildNodes( $count, $childId );
542. }
543. }
545. /**
546. * Returns the number of children of the node with ID $nodeId, recursively.
547. *
548. * @param string $nodeId
549. * @return int
550. */
551. public function getChildCountRecursive ( $nodeId )
552. {
553. $count = 0;
554. $this->countChildNodes( $count, $nodeId );
555. return $count;
556. }
558. /**
559. * Returns the distance from the root node to the node with ID $nodeId.
560. *
561. * @param string $nodeId
562. * @return int
563. */
564. public function getPathLength ( $nodeId )
565. {
566. $elem = $this->getNodeById( $nodeId );
567. $elem = $elem->parentNode;
568. $length = -1;
570. while ( $elem !== null && $elem->nodeType == XML_ELEMENT_NODE )
571. {
572. $elem = $elem->parentNode;
573. $length++;
574. }
575. return $length;
576. }
578. /**
579. * Returns whether the node with ID $nodeId has children.
580. *
581. * @param string $nodeId
582. * @return bool
583. */
584. public function hasChildNodes ( $nodeId )
585. {
586. $elem = $this->getNodeById( $nodeId );
587. $children = $elem->childNodes;
588. foreach ( $children as $child )
589. {
590. if ( $child->nodeType === XML_ELEMENT_NODE && $child->tagName == "node" )
591. {
592. return true;
593. }
594. }
595. return false;
596. }
598. /**
599. * Returns whether the node with ID $childId is a direct child of the node
600. * with ID $parentId.
601. *
602. * @param string $childId
603. * @param string $parentId
604. * @return bool
605. */
606. public function isChildOf ( $childId, $parentId )
607. {
608. $elem = $this->getNodeById( $childId );
609. $parentElem = $elem->parentNode;
610. $nodeId = $parentElem->getAttribute( 'id' );
611. if ( $nodeId === "{$this-> properties ['prefix']}$parentId" )
612. {
613. return true;
614. }
615. return false;
616. }
618. /**
619. * Returns whether the node with ID $childId is a direct or indirect child
620. * of the node with ID $parentId.
621. *
622. * @param string $childId
623. * @param string $parentId
624. * @return bool
625. */
626. public function isDescendantOf ( $childId, $parentId )
627. {
628. $elem = $this->getNodeById( $childId );
629. $elem = $elem->parentNode;
631. while ( $elem !== null && $elem->nodeType == XML_ELEMENT_NODE )
632. {
633. $nodeId = $elem->getAttribute( 'id' );
634. if ( $nodeId === "{$this-> properties ['prefix']}$parentId" )
635. {
636. return true;
637. }
638. $elem = $elem->parentNode;
639. }
640. return false;
641. }
643. /**
644. * Returns whether the nodes with IDs $child1Id and $child2Id are siblings
645. * (ie, they share the same parent).
646. *
647. * @param string $child1Id
648. * @param string $child2Id
649. * @return bool
650. */
651. public function isSiblingOf ( $child1Id, $child2Id )
652. {
653. $elem1 = $this->getNodeById( $child1Id );
654. $elem2 = $this->getNodeById( $child2Id );
655. return (
656. ( $child1Id !== $child2Id ) &&
657. ( $elem1->parentNode->getAttribute( 'id' ) === $elem2->parentNode->getAttribute( 'id' ) )
658. );
659. }
661. /**
662. * Sets a new node as root node, this also wipes out the whole tree.
663. *
664. * @param ezcTreeNode $node
665. */
666. public function setRootNode ( ezcTreeNode $node )
667. {
668. $document = $this->dom->documentElement;
670. // remove old root node(s)
671. foreach ( $document->childNodes as $childNode )
672. {
673. if ( $childNode->nodeType == XML_ELEMENT_NODE && $childNode->tagName === 'node' )
674. {
675. $nodeId = substr ( $childNode->getAttribute( 'id' ), strlen ( $this-> properties ['prefix'] ) );
676. $this-> delete ( $nodeId );
677. }
678. }
679. $this->store->deleteDataForAllNodes();
681. // Create new root node
682. $root = $this->dom->createElement( 'node' );
683. $root->setAttributeNode( new DOMAttr( 'id', "{$this-> properties ['prefix']}{$node->id}" ) );
684. $root->setIdAttribute( 'id', true );
685. $document->appendChild( $root );
686. $this->store->storeDataForNode( $node, $node->data );
687. $this-> saveFile ();
688. }
690. /**
691. * Returns the root node.
692. *
693. * This methods returns null if there is no root node.
694. *
695. * @return ezcTreeNode
696. */
697. public function getRootNode ()
698. {
699. $className = $this-> properties ['nodeClassName'];
700. $document = $this->dom->documentElement;
702. foreach ( $document->childNodes as $childNode )
703. {
704. if ( $childNode->nodeType == XML_ELEMENT_NODE && $childNode->tagName == 'node' )
705. {
706. $nodeId = substr ( $childNode->getAttribute( 'id' ), strlen ( $this-> properties ['prefix'] ) );
707. return new $className( $this, $nodeId );
708. }
709. }
710. return null;
711. }
713. /**
714. * Adds the node $childNode as child of the node with ID $parentId.
715. *
716. * @param string $parentId
717. * @param ezcTreeNode $childNode
718. */
719. public function addChild ( $parentId, ezcTreeNode $childNode )
720. {
721. if ( $this-> inTransaction )
722. {
723. $this-> addTransactionItem ( new ezcTreeTransactionItem( ezcTreeTransactionItem::ADD, $childNode, null, $parentId ) );
724. return;
725. }
727. // locate parent node
728. $elem = $this->getNodeById( $parentId );
730. // Create new DOM node
731. $child = $this->dom->createElement( 'node' );
732. $child->setAttributeNode( new DOMAttr( 'id', "{$this-> properties ['prefix']}{$childNode->id}" ) );
733. $child->setIdAttribute( 'id', true );
735. // Append to parent node
736. $elem->appendChild( $child );
737. $this->store->storeDataForNode( $childNode, $childNode->data );
739. if ( !$this-> inTransactionCommit )
740. {
741. $this-> saveFile ();
742. }
743. }
745. /**
746. * Deletes the node with ID $nodeId from the tree, including all its children.
747. *
748. * @param string $nodeId
749. */
750. public function delete ( $nodeId )
751. {
752. if ( $this-> inTransaction )
753. {
754. $this-> addTransactionItem ( new ezcTreeTransactionItem( ezcTreeTransactionItem::DELETE, null, $nodeId ) );
755. return;
756. }
758. // Delete all the associated data
759. $nodeList = $this-> fetchSubtree ( $nodeId );
760. $this->store->deleteDataForNodes( $nodeList );
762. // locate node to move
763. $nodeToDelete = $this->getNodeById( $nodeId );
765. // Remove the ID on all children by hand as this would crash in PHP <= 5.2.3
766. $nodeToDelete->removeAttribute( "id" );
768. $children = $nodeToDelete->getElementsByTagName( 'node' );
769. foreach ( $children as $child )
770. {
771. $child->removeAttribute( "id" );
772. }
774. // Use the parent to remove the child
775. $nodeToDelete->parentNode->removeChild( $nodeToDelete );
777. if ( !$this-> inTransactionCommit )
778. {
779. $this-> saveFile ();
780. }
781. }
783. /**
784. * Moves the node with ID $nodeId as child to the node with ID $targetParentId.
785. *
786. * @param string $nodeId
787. * @param string $targetParentId
788. */
789. public function move ( $nodeId, $targetParentId )
790. {
791. if ( $this-> inTransaction )
792. {
793. $this-> addTransactionItem ( new ezcTreeTransactionItem( ezcTreeTransactionItem::MOVE, null, $nodeId, $targetParentId ) );
794. return;
795. }
797. // locate node to move
798. $nodeToMove = $this->getNodeById( $nodeId );
800. // locate new parent
801. $parent = $this->getNodeById( $targetParentId );
803. $parent->appendChild( $nodeToMove );
805. if ( !$this-> inTransactionCommit )
806. {
807. $this-> saveFile ();
808. }
809. }
811. /**
812. * Fixates the transaction (saves the XML file).
813. */
814. public function fixateTransaction ()
815. {
816. $this-> saveFile ();
817. }
818. }
819. ?>

Documentation generated by phpDocumentor 1.4.3