-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDomCreator.php
More file actions
246 lines (223 loc) · 6.63 KB
/
DomCreator.php
File metadata and controls
246 lines (223 loc) · 6.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
<?php
/**
* # DOM Creator for PHP
*
* A limited but simple API for creating DOMDocumentS in PHP.
*
* - Built with minimum verbosity in mind
* - Works very well for straightforward schemas
*
* Short example:
*
* ```php
* <?
* $foo = DomCreator::create('http://example.com/foo', 'f', 'foo');
* $foo->ident->name = 'John Doe';
* $foo->ident->number = '123';
* $foo->content = 'Hello, World!';
* ```
*
* Generates:
*
* ```xml
* <?xml version="1.0"?>
* <f:foo xmlns:f="http://example.com/foo">
* <f:ident>
* <f:name>John Doe</f:name>
* <f:number>123</f:number>
* </f:ident>
* <f:content>Hello, World!</f:content>
* </f:foo>
* ```
*
* You first create a `DomCreator` instance with one of the factory functions.
* Then get and set properties on the object to create a DOM. New complex
* elements (the ones that contain other elements) are automatically created
* when you try to read them/set a property on them. This might look a little
* like setting properties on a regular object. The difference is that each
* action appends an element to the tree, instead of overwriting it. So the
* sequence of actions matters, contrary to setting stuff on an object.
*
* When you try to get a property with the same name as the one you got
* previously, the previous node is returned. This is not always wanted, so this
* behaviour can be avoided with `closeChild()`.
*
* To write an attribute, prefix the property name with an underscore (this is
* set by `DomCreator::ATTRIBUTE_SIGN`). Elements and attributes that contain
* characters not allowed in a PHP identifier can be created by calling
* `__get(..)` and `__set(..)` yourself. Although you should think of using
* another API if this happens a lot.
*
* License: MIT
*/
class DomCreator
{
const ATTRIBUTE_SIGN = '_';
private $_doc;
private $_node;
private $_nsUri;
private $_prefix;
private $_qualifyAttributes;
private $_lastChild;
/**
* Create a new instance with a namespace and the given root element. The
* specified namespace URI and namespace prefix are used for all elements,
* but by default not for attributes. This can be changed by using true as
* the fourth argument.
*/
public static function create($nsUri, $nsPrefix, $root,
$qualifyAttributes = false)
{
$doc = new DOMDocument();
$prefix = $nsPrefix === null ? '' : $nsPrefix . ':';
$instance = new self($doc, $doc, $nsUri, $prefix, $qualifyAttributes);
return $instance->$root;
}
/**
* Create a new instance with the given root element, and without a
* namespace. You can optionally set a default namespace yourself with
* `$domCreator->_xmlns = 'http://example.com/foo'`.
*/
public static function createNoNamespace($root)
{
return self::create(null, null, $root);
}
/**
* Create a new instance for use as a fragment. This allows functions to
* create some part of the DOM on their own. It can later be added to
* another tree like so: `$domCreator->FragmentContents = $fragment`. That
* action discards the fragment's root element, so this function allows you
* not to care about it.
*/
public static function createFragment($nsUri, $nsPrefix,
$qualifyAttributes = false)
{
return self::create($nsUri, $nsPrefix, 'fragment', $qualifyAttributes);
}
/**
* Create a new instance for use as a fragment, without a namespace.
*/
public static function createFragmentNoNamespace()
{
return self::createFragment(null, null);
}
private function __construct(DOMDocument $doc, DOMNode $node, $nsUri,
$prefix, $qualifyAttributes)
{
$this->_doc = $doc;
$this->_node = $node;
$this->_nsUri = $nsUri;
$this->_prefix = $prefix;
$this->_qualifyAttributes = $qualifyAttributes;
}
/**
* Get a property. This creates and appends a new element with the name of
* the property and returns a new instance of this class to represent it.
* One exception: if the requested property has the same name as the last
* one requested and `closeChild()` has not been called, then return the
* same instance as on the last call (thus not creating a new element).
*/
public function __get($name)
{
if ($this->_lastChild !== null &&
$this->_lastChild->_node->nodeName === $this->_prefix . $name)
{
return $this->_lastChild;
}
else
{
$element = $this->_element($name);
$this->_lastChild = new self($this->_doc, $element,
$this->_nsUri, $this->_prefix, $this->_qualifyAttributes);
return $this->_lastChild;
}
}
/**
* Set a property. If the property name starts with an underscore
* (`DomCreator::ATTRIBUTE_SIGN`) this creates and appends a new attribute
* with the given name (attribute sign stripped) and value. Else a new
* element is created with the value as its contents. The value can be
* another instance of this class or a DOMNode, in which case all of their
* children are inserted in the new element.
*/
public function __set($name, $value)
{
if (strpos($name, self::ATTRIBUTE_SIGN) === 0)
{
$attributeName = substr($name, strlen(self::ATTRIBUTE_SIGN));
if ($this->_qualifyAttributes)
{
$attribute = $this->_doc->createAttributeNS($this->_nsUri,
$this->_prefix . $attributeName);
}
else
{
$attribute = $this->_doc->createAttribute($attributeName);
}
$attribute->value = $value;
$this->_node->appendChild($attribute);
}
else
{
$this->_lastChild = null;
$element = $this->_element($name, $value);
if ($value instanceof self)
{
$this->_import($element, $value->_node->childNodes);
}
else if ($value instanceof DOMNode)
{
$this->_import($element, $value->childNodes);
}
else
{
$text = $this->_doc->createTextNode($value);
$element->appendChild($text);
}
}
}
/**
* Close the last child so nothing more will be appended to it.
*/
public function closeChild()
{
$this->_lastChild = null;
}
/**
* Get the underlying DOMDocument of the tree, this is the same for each
* node in the tree.
*/
public function getDocument()
{
return $this->_doc;
}
/**
* Get the underlying DOMElement of this node.
*/
public function getNode()
{
return $this->_node;
}
// import into $element everything from $nodes
private function _import(DOMElement $element, DOMNodeList $nodes)
{
foreach ($nodes as $node)
{
$imported = $this->_doc->importNode($node, true);
$element->appendChild($imported);
}
}
// create and append an element with $name
private function _element($name)
{
$element = $this->_doc->createElementNS($this->_nsUri,
$this->_prefix . $name);
$this->_node->appendChild($element);
return $element;
}
// yay!
public function __toString()
{
return '<' . $this->_node->nodeName . '>..';
}
}