Grima  2018-08
Whispering into Alma's ear
grima-xmlbag.php
Go to the documentation of this file.
1 <?php
2 
3 /******************************************************************************
4  *
5  * XML Bags!
6  *
7  * Wish you could treat XML as an array? Now you can!
8  *
9  * What does xml['item'] = ?
10  *
11  * Three plausible answers:
12  * (1) A single DOMNode, so you have to use innerXML and such to get its string value
13  * (2) A DOMNodeList, so you can have multiple nodes that match
14  * (3) A string, just the innerXML
15  * (4) An array of strings, just the innerXMLs
16  */
17 
18 
19 function xml_debug( $obj ) {
20  $retval = get_class($obj)."\n";
21  switch(true) {
22  case ($obj instanceof DOMDocument):
23  $retval .= "XPath: {$obj->getNodePath()}\n".$obj->saveXML($obj);
24  break;
25  case ($obj instanceof DOMElement):
26  $retval .= "XPath: {$obj->getNodePath()}\n".$obj->ownerDocument->saveXML($obj);
27  break;
28  case ($obj instanceof DOMAttr):
29  $retval .= "XPath: {$obj->getNodePath()}\n".$obj->ownerDocument->saveXML($obj);
30  break;
31  case ($obj instanceof DOMNodeList):
32  $retval .= "NodeList of length {$obj->length}\n";
33  print $retval;
34  for ($i = 0; $i < $obj->length; $i++) {
35  $retval .= "Item #$i, " . xml_debug($obj->item($i));
36  }
37  break;
38  default:
39  return "Instance of unknown class";
40  }
41  return $retval;
42 }
43 
44 abstract class XMLAsArrayOfWhateversSpecifiedByXPath implements ArrayAccess {
45  public $xml;
46  public $context_node;
47  private $xpath = null;
49  if( is_string( $xml ) ) {
50  $this->xml = new DOMDocument();
51  $this->xml->loadXML( $xml );
52  } elseif( $xml instanceof DOMDocument ) {
53  $this->xml = $xml;
54  } else {
55  throw new Exception("<xml> must be of type DOMDocument in new XMLAsArrayOf...(xml,context)");
56  }
57  $this->xpath = new DOMXPath($this->xml);
58  if( is_string( $context_node ) ) {
59  $nodeList = $this->xpath->query( $context_node );
60  //print( "Context ($context_node) is: " . xml_debug($nodeList) . "\n" );
61  if( $nodeList->length === 1 ) {
62  $this->context_node = $nodeList->item(0);
63  } else {
64  $this->context_node = $this->xml->documentElement;
65  }
66  } elseif( $context_node instanceof DOMElement ) {
67  $this->context_node = $context_node;
68  } else {
69  $this->context_node = $this->xml->documentElement;
70  }
71  }
72  protected function query( string $offset ) {
73  if( isset( $this->context_node ) ) {
74  return $this->xpath->query( $offset, $this->context_node );
75  } else {
76  return $this->xpath->query( $offset );
77  }
78  }
79  public function __toString() {
80  return $this->xml->saveXML( $this->context_node );
81  }
82  public function __get( string $name ) {
83  $class = get_class($this);
84  return new $class( $this->xml, $this->query($name) );
85  }
86  public function __set( string $name, $value ) {
87  $this[$name] = $value;
88  }
89  public function __isset( string $name ) {
90  return isset( $this[$name] );
91  }
92  public function __unset( string $name ) {
93  unset( $this[$name] );
94  }
95 }
96 
98  public function offsetExists ( $offset ) {
99  return $this->query( $offset )->length === 1;
100  }
101  public function offsetGet ( $offset ) {
102  $nodeList = $this->query( $offset );
103  if( $nodeList->length === 1 )
104  return $nodeList->item(0);
105  }
106  public function offsetSet ( $offset, $value ) {
107  $nodeList = $this->query( $offset );
108  if( $nodeList->length === 1 ) {
109  $node = $nodeList->item(0);
110  if($node->parentNode !== NULL) {
111  $node->parentNode->replaceChild( $value, $node );
112  }
113  } elseif( $nodeList->length === 0 ) {
114  $nodeList->context_node->appendChild( $value );
115  }
116  }
117  public function offsetUnset ( $offset ) {
118  $nodeList = $this->query( $offset );
119  if( $nodeList->length === 1 ) {
120  $node = $nodeList->item(0);
121  if($node->parentNode !== NULL) {
122  $node->parentNode->removeChild( $node );
123  }
124  }
125  }
126 }
127 
129  public function offsetExists ( $offset ) {
130  return $this->query( $offset )->length > 0; // technically it always exists, but...
131  }
132  public function offsetGet ( $offset ) {
133  return $this->query( $offset );
134  }
135  public function offsetSet ( $offset, $value ) {
136  $nodeList = $this->query( $offset );
137  $cur_length = $nodeList->length;
138  $new_length = $value->length;
139  for( $i = 0 ; $i < min( $cur_length, $new_length ) ; $i++ ) {
140  $cur_node = $nodeList->item($i);
141  $new_node = $value->item($i);
142  $cur_node->parentNode->replaceChild( $new_node, $cur_node );
143  }
144  for( $i = min( $cur_length, $new_length ) ; $i < $cur_length ; $i++ ) {
145  $cur_node = $nodeList->item($i);
146  $cur_node->parentNode->removeChild( $cur_node );
147  }
148  for( $i = min( $cur_length, $new_length ) ; $i < $new_length ; $i++ ) {
149  $new_node = $value->item($i);
150  $this->context_node->appendChild( $new_node );
151  }
152  }
153  public function offsetUnset ( $offset ) {
154  $nodeList = $this->query( $offset );
155  $cur_length = $nodeList->length;
156  for( $i = 0 ; $i < $cur_length ; $i++ ) {
157  $cur_node = $nodeList->item($i);
158  $cur_node->parentNode->removeChild( $cur_node );
159  }
160  }
161 }
162 
164  public function offsetExists ( $offset ) {
165  return $this->query( $offset )->length === 1;
166  }
167  public function offsetGet ( $offset ) {
168  $nodeList = $this->query( $offset );
169  if( $nodeList->length === 1 ) {
170  return $nodeList->item(0)->nodeValue;
171  }
172  }
173  public function offsetSet ( $offset, $value ) {
174  $nodeList = $this->query( $offset );
175  if( $nodeList->length === 1 ) {
176  print "Setting!\n";
177  $nodeList->item(0)->nodeValue = $value;
178  } else if( $nodeList->length === 0 ) {
179  $node = $this->xml->createElement( basename($offset) );
180  $node->nodeValue = $value;
181  $this->contextNode->appendChild( $node );
182  }
183  }
184  public function offsetUnset ( $offset ) {
185  if( $nodeList->length === 1 ) {
186  $node = $nodeList->item(0);
187  $node->parentNode->removeChild( $node );
188  }
189  }
190 }
191 
192 function test() {
193  $xml = file_get_contents("item-sample.xml");
194  $item_item_data = new XMLAsArrayOfStringsSpecifiedByXPath( $xml, "/item/item_data" );
195  print "EnumerationA: " . $item_item_data['enumeration_a'] . "\n\n";
196  $item_item_data['enumeration_a'] = "NOOOOOOOOO";
197  print $item_item_data->xml->saveXML();
198 }
199 
200 //test();
xml_debug( $obj)
test()