root/trunk/src/main/php/net/stubbles/xml/stubDomXMLStreamWriter.php

Revision 1359, 11.0 kB (checked in by mikey, 3 months ago)

added possiblity to check if a stream writer is finished, i.e it has no open elements left

Line 
1 <?php
2 /**
3  * XML STream Writer based on DOM
4  *
5  * @author      Stephan Schmidt <schst@stubbles.net>
6  * @author      Frank Kleine <mikey@stubbles.net>
7  * @package     stubbles
8  * @subpackage  xml
9  */
10 stubClassLoader::load('net::stubbles::xml::stubXMLStreamWriter',
11                       'net::stubbles::xml::stubAbstractXMLStreamWriter'
12 );
13 /**
14  * XML STream Writer based on DOM
15  *
16  * @package     stubbles
17  * @subpackage  xml
18  */
19 class stubDomXMLStreamWriter extends stubAbstractXMLStreamWriter implements stubXMLStreamWriter
20 {
21     /**
22      * List of supported features
23      *
24      * @var  array
25      */
26     protected $features = array(stubXMLStreamWriter::FEATURE_AS_DOM,
27                                 stubXMLStreamWriter::FEATURE_IMPORT_WRITER
28                           );
29     /**
30      * DOM Document
31      *
32      * @var  DOMDocument
33      */
34     protected $doc;
35     /**
36      * Stores al opened elements
37      *
38      * @var  array
39      */
40     protected $elementStack = array();
41
42     /**
43      * Create a new writer
44      *
45      * @param  string  $xmlVersion
46      * @param  string  $encoding
47      */
48     public function __construct($xmlVersion = '1.0', $encoding = 'UTF-8')
49     {
50         $this->xmlVersion = $xmlVersion;
51         $this->encoding   = $encoding;
52         $this->doc        = new DOMDocument($xmlVersion, $encoding);
53     }
54
55     /**
56      * Clear all data, that has been written
57      */
58     public function clear()
59     {
60         $this->doc = new DOMDocument($this->xmlVersion, $this->encoding);
61         $this->elementStack = array();
62     }
63
64     /**
65      * really writes an opening tag
66      *
67      * @param   string            $elementName
68      * @throws  stubXMLException
69      */
70     protected function doWriteStartElement($elementName)
71     {
72         try {
73             libxml_use_internal_errors(true);
74             $element = $this->doc->createElement($elementName);
75             if (count($this->elementStack) == 0) {
76                 $this->doc->appendChild($element);
77             } else {
78                 $parent = end($this->elementStack);
79                 $parent->appendChild($element);
80             }
81             array_push($this->elementStack, $element);
82             $errors = libxml_get_errors();
83             if (!empty($errors)) {
84                 libxml_clear_errors();
85                 throw new stubXMLException('Error writing start element: ' . $this->convertLibXmlErrorsToString($errors));
86             }
87         } catch (DOMException $e) {
88             throw new stubXMLException('Error writing start element.', $e);
89         }
90     }
91
92     /**
93      * Write a text node
94      *
95      * @param   string            $data
96      * @throws  stubXMLException
97      */
98     public function writeText($data)
99     {
100         try {
101             libxml_use_internal_errors(true);
102             $textNode = $this->doc->createTextNode($this->encode($data));
103             $this->addToDom($textNode);
104             $errors = libxml_get_errors();
105             if (!empty($errors)) {
106                 libxml_clear_errors();
107                 throw new stubXMLException('Error writing text: ' . $this->convertLibXmlErrorsToString($errors));
108             }
109         } catch (DOMException $e) {
110             throw new stubXMLException('Error writing text.', $e);
111         }
112     }
113
114     /**
115      * Write a cdata section
116      *
117      * @param   string            $cdata
118      * @throws  stubXMLException
119      */
120     public function writeCData($cdata)
121     {
122         try {
123             libxml_use_internal_errors(true);
124             $cdataNode = $this->doc->createCDATASection($this->encode($cdata));
125             $this->addToDom($cdataNode);
126             $errors = libxml_get_errors();
127             if (!empty($errors)) {
128                 libxml_clear_errors();
129                 throw new stubXMLException('Error writing cdata section: ' . $this->convertLibXmlErrorsToString($errors));
130             }
131         } catch (DOMException $e) {
132             throw new stubXMLException('Error writing cdata section.', $e);
133         }
134     }
135
136     /**
137      * Write a comment
138      *
139      * @param   string            $comment
140      * @throws  stubXMLException
141      */
142     public function writeComment($comment)
143     {
144         try {
145             libxml_use_internal_errors(true);
146             $commentNode = $this->doc->createComment($this->encode($comment));
147             $this->addToDom($commentNode);
148             $errors = libxml_get_errors();
149             if (!empty($errors)) {
150                 libxml_clear_errors();
151                 throw new stubXMLException('Error writing comment: ' . $this->convertLibXmlErrorsToString($errors));
152             }
153         } catch (DOMException $e) {
154             throw new stubXMLException('Error writing comment.', $e);
155         }
156     }
157
158     /**
159      * Write a processing instruction
160      *
161      * @param   string            $target
162      * @param   string            $data
163      * @throws  stubXMLException
164      */
165     public function writeProcessingInstruction($target, $data = '')
166     {
167         try {
168             libxml_use_internal_errors(true);
169             $piNode = $this->doc->createProcessingInstruction($target, $data);
170             $this->addToDom($piNode);
171             $errors = libxml_get_errors();
172             if (!empty($errors)) {
173                 libxml_clear_errors();
174                 throw new stubXMLException('Error writing processing instruction: ' . $this->convertLibXmlErrorsToString($errors));
175             }
176         } catch (DOMException $e) {
177             throw new stubXMLException('Error writing processing instruction.', $e);
178         }
179     }
180
181     /**
182      * Write an xml fragment
183      *
184      * @param   string            $fragment
185      * @throws  stubXMLException
186      */
187     public function writeXmlFragment($fragment)
188     {
189         try {
190             libxml_use_internal_errors(true);
191             $fragmentNode = $this->doc->createDocumentFragment();
192             $fragmentNode->appendXML($fragment);
193             $this->addToDom($fragmentNode);
194             $errors = libxml_get_errors();
195             if (!empty($errors)) {
196                 libxml_clear_errors();
197                 throw new stubXMLException('Error writing document fragment: ' . $this->convertLibXmlErrorsToString($errors));
198             }
199         } catch (DOMException $e) {
200             throw new stubXMLException('Error writing document fragment.', $e);
201         }
202     }
203
204     /**
205      * Write an attribute
206      *
207      * @param   string            $attributeName
208      * @param   string            $attributeValue
209      * @throws  stubXMLException
210      */
211     public function writeAttribute($attributeName, $attributeValue)
212     {
213         try {
214             libxml_use_internal_errors(true);
215             $currentElement = end($this->elementStack);
216             $currentElement->setAttribute($attributeName, $this->encode($attributeValue));
217             $errors = libxml_get_errors();
218             if (!empty($errors)) {
219                 libxml_clear_errors();
220                 throw new stubXMLException('Error writing attribute: ' . $this->convertLibXmlErrorsToString($errors));
221             }
222         } catch (DOMException $e) {
223             throw new stubXMLException('Error writing attribute.', $e);
224         }
225     }
226
227     /**
228      * really writes an end element
229      *
230      * @throws  stubXMLException
231      */
232     protected function doWriteEndElement()
233     {
234         if (count($this->elementStack) === 0) {
235             throw new stubXMLException('No open element available.');
236         }
237         
238         array_pop($this->elementStack);
239     }
240
241     /**
242      * Write a full element
243      *
244      * @param   string            $elementName
245      * @param   array             $attributes  optional
246      * @param   string            $cdata       optional
247      * @throws  stubXMLException
248      */
249     public function writeElement($elementName, array $attributes = array(), $cdata = null)
250     {
251         try {
252             libxml_use_internal_errors(true);
253             $element = $this->doc->createElement($elementName);
254             foreach ($attributes as $attName => $attValue) {
255                 $element->setAttribute($attName, $attValue);
256             }
257             if (null !== $cdata) {
258                 $element->appendChild($this->doc->createTextNode($cdata));
259             }
260             if (count($this->elementStack) == 0) {
261                 $this->doc->appendChild($element);
262             } else {
263                 $parent = end($this->elementStack);
264                 $parent->appendChild($element);
265             }
266             $errors = libxml_get_errors();
267             if (!empty($errors)) {
268                 libxml_clear_errors();
269                 throw new stubXMLException('Error writing element: ' . $this->convertLibXmlErrorsToString($errors));
270             }
271         } catch (DOMException $e) {
272             throw new stubXMLException('Error writing element.', $e);
273         }
274     }
275
276     /**
277      * Import another stream
278      *
279      * @param   stubXMLStreamWriter  $writer
280      * @throws  stubXMLException
281      */
282     public function importStreamWriter(stubXMLStreamWriter $writer)
283     {
284         try {
285             libxml_use_internal_errors(true);
286             $newNode = $writer->asDOM()->documentElement;
287             $newNodeImported = $this->doc->importNode($newNode, true);
288             $this->addToDom($newNodeImported);
289             $errors = libxml_get_errors();
290             if (!empty($errors)) {
291                 libxml_clear_errors();
292                 throw new stubXMLException('Error during import: ' . $this->convertLibXmlErrorsToString($errors));
293             }
294         } catch (DOMException $e) {
295             throw new stubXMLException('Error during import.', $e);
296         }
297     }
298
299     /**
300      * Add a node to the internal DOM tree
301      *
302      * @param   DOMNode           $node
303      * @throws  stubXMLException
304      */
305     protected function addToDom(DOMNode $node)
306     {
307         if (count($this->elementStack) < 1) {
308             throw new stubXMLException('No tag is currently open, you need to call writeStartElement() first.');
309         }
310         $current = end($this->elementStack);
311         $current->appendChild($node);
312     }
313
314     /**
315      * Return the XML as a DOM
316      *
317      * @return  DOMDocument
318      */
319     public function asDom()
320     {
321         return $this->doc;
322     }
323
324     /**
325      * Return the XML as a string
326      *
327      * @return  string
328      */
329     public function asXML()
330     {
331         return rtrim($this->doc->saveXML());
332     }
333
334     /**
335      * Converts all errors to a string
336      *
337      * @param   array   $errors
338      * @return  string
339      */
340     protected function convertLibXmlErrorsToString($errors)
341     {
342         $messages = array();
343         foreach ($errors as $error) {
344             $messages[] = trim($error->message);
345         }
346         return implode(', ', $messages);
347     }
348
349     /**
350      * helper method to transform data into correct encoding
351      *
352      * Data has to be encoded even if document encoding is not UTF-8.
353      *
354      * @param   string  $data
355      * @return  string
356      * @see     http://php.net/manual/en/function.dom-domdocument-save.php#67952
357      */
358     protected function encode($data)
359     {
360         if (mb_detect_encoding($data, 'UTF-8, ISO-8859-1') === 'UTF-8') {
361             return $data;
362         }
363         
364         return utf8_encode($data);
365     }
366 }
367 ?>
368
Note: See TracBrowser for help on using the browser.