1: <?php
2:
3: /**
4: * This file contains the base XML class.
5: *
6: * @package Core
7: * @subpackage XML
8: * @author Dominik Ziegler
9: * @copyright four for business AG <www.4fb.de>
10: * @license http://www.contenido.org/license/LIZENZ.txt
11: * @link http://www.4fb.de
12: * @link http://www.contenido.org
13: */
14:
15: defined('CON_FRAMEWORK') || die('Illegal call: Missing framework initialization - request aborted.');
16:
17: /**
18: * Base XML class
19: *
20: * @package Core
21: * @subpackage XML
22: */
23: abstract class cXmlBase {
24:
25: /**
26: *
27: * @var DOMDocument
28: */
29: protected $_dom = NULL;
30:
31: /**
32: *
33: * @var DOMXpath
34: */
35: protected $_xpath = NULL;
36:
37: /**
38: * Creates a new XML document using DOMDocument.
39: *
40: * @param string $version [optional, default: 1.0]
41: * version of DOMDocument
42: * @param string $encoding [optional, default: UTF-8]
43: * encoding of DOMDocument
44: */
45: protected function _createDocument($version = '', $encoding = '') {
46: if ($version == '') {
47: $version = '1.0';
48: }
49:
50: if ($encoding == '') {
51: $encoding = 'UTF-8';
52: }
53:
54: $this->_dom = new DOMDocument($version, $encoding);
55: }
56:
57: /**
58: * Returns the DOMDocument object.
59: * @return DOMDocument
60: */
61: public function getDomDocument() {
62: return $this->_dom;
63: }
64:
65: /**
66: * Sets a current DOMDocument object to class.
67: *
68: * @param DOMDocument $domDocument
69: * DOMDocument object
70: */
71: public function setDomDocument(DOMDocument $domDocument) {
72: $this->_dom = $domDocument;
73: $this->_initXpathInstance();
74: }
75:
76: /**
77: * Returns the encoding of the XML document.
78: *
79: * @throws cException if there is no DOM document
80: * @return string
81: * encoding
82: */
83: public function getEncoding() {
84: if ($this->_dom === NULL) {
85: throw new cException('Can not determine encoding: DOMDocument not found.');
86: }
87:
88: return $this->_dom->xmlEncoding;
89: }
90:
91: /**
92: *
93: * @param string $name
94: * @param string $value
95: */
96: public function registerXpathNamespace($name, $value) {
97: $this->_xpath->registerNamespace($name, $value);
98: }
99:
100: /**
101: * Initializes a new DOMXPath instance for DOMDocument.
102: *
103: * @throws cException if there is no valid DOM document
104: */
105: protected function _initXpathInstance() {
106: if (!($this->_dom instanceof DOMDocument)) {
107: throw new cException('Can not initialize XPath instance: DOMDocument not found.');
108: }
109:
110: $this->_xpath = new DOMXpath($this->_dom);
111: }
112:
113: /**
114: * Resolves a given path which contains ".." statement for moving up one
115: * level in path.
116: *
117: * @param string $path
118: * path to resolve
119: * @return string
120: * resolved path
121: */
122: public static function resolvePath($path) {
123: if (substr($path, 0, 1) != '/') {
124: $path = '/' . $path;
125: }
126:
127: $splits = explode('/', $path);
128:
129: foreach ($splits as $i => $sSplitter) {
130: if ($sSplitter == '..') {
131: unset($splits[$i]);
132: unset($splits[$i - 1]);
133: }
134: }
135:
136: $pathString = implode('/', $splits);
137:
138: if (substr($pathString, -1) == '/') {
139: $pathString = substr($pathString, 0, -1);
140: }
141:
142: return $pathString;
143: }
144:
145: /**
146: * Returns given XPath with integrad level definition.
147: *
148: * @param string $path
149: * XPath to extend
150: * @param int $level
151: * level
152: * @return string
153: * extended XPath
154: */
155: public static function getLevelXpath($path, $level) {
156: $splits = explode('/', $path);
157: $splitCount = count($splits);
158:
159: if ($splitCount <= 1) {
160: return $path;
161: }
162:
163: $lastElementName = $splits[$splitCount - 1];
164: unset($splits[$splitCount - 1]);
165:
166: $returnPath = implode('/', $splits);
167: $returnPath .= '[' . ($level + 1) . ']/' . $lastElementName;
168:
169: return $returnPath;
170: }
171:
172: /**
173: * Converts an array to a SimpleXMLElement. Example:
174: * array(
175: * 'key1' => 'value1',
176: * 'key2' => array('value21', 'value22'),
177: * 'key3' => array('key31' => 'value31', 'key32' => 'value32')
178: * );
179: *
180: * becomes
181: *
182: * <?/**
183: * Converts an array to a SimpleXMLElement.
184: * Example:
185: * array(
186: * 'key1' => 'value1',
187: * 'key2' => array('value21', 'value22'),
188: * 'key3' => array('key31' => 'value31', 'key32' => 'value32')
189: * );
190: *
191: * becomes
192: *
193: * <?xml version="1.0" encoding="utf-8"?>
194: * <root>
195: * <key1>value1</key1>
196: * <key2>
197: * <array_value>value21</array_value>
198: * <array_value>value22</array_value>
199: * </key2>
200: * <key3>
201: * <key31>value31</key31>
202: * <key32>value32</key32>
203: * </key3>
204: * </root>
205: *
206: * @param array $array
207: * the array which should be converted to XML
208: * @param SimpleXMLElement $xml [optional]
209: * the element to which the array should be added
210: * @param string $rootTagName [optional]
211: * the root tag name which should be used - is only used when $xml is NULL!
212: * @return SimpleXMLElement
213: * the array as a SimpleXMLElement
214: */
215:
216: public static function arrayToXml($array, $xml = NULL, $rootTagName = 'root') {
217: if ($xml == NULL) {
218: $xml = new SimpleXMLElement('<?xml version="1.0" encoding="utf-8"?><' . $rootTagName . '/>', LIBXML_NOCDATA);
219: }
220:
221: // check whether array is associative
222: if ($array !== array_values($array)) {
223: // if array is associative, use the keys as well as the values
224: foreach ($array as $key => $value) {
225: // recursion if value is an array
226: if (is_array($value)) {
227: self::arrayToXml($value, $xml->addChild($key));
228: } else {
229: $child = $xml->addChild($key);
230: $node = dom_import_simplexml($child);
231: $no = $node->ownerDocument;
232: $node->appendChild($no->createCDATASection($value));
233: }
234: }
235: } else {
236: // if array is not associative, use the array values as separate xml
237: // nodes
238: foreach ($array as $value) {
239: $child = $xml->addChild('array_value');
240: $node = dom_import_simplexml($child);
241: $no = $node->ownerDocument;
242: $node->appendChild($no->createCDATASection($value));
243: }
244: }
245:
246: return $xml;
247: }
248:
249: /**
250: * Converts the given XML string to an array.
251: * Example:
252: * <?xml version="1.0" encoding="utf-8"?>
253: * <root>
254: * <key1>value1</key1>
255: * <key2>
256: * <array_value>value21</array_value>
257: * <array_value>value22</array_value>
258: * </key2>
259: * <key3>
260: * <key31>value31</key31>
261: * <key32>value32</key32>
262: * </key3>
263: * </root>
264: *
265: * becomes
266: *
267: * array(
268: * 'key1' => 'value1',
269: * 'key2' => array('value21', 'value22'),
270: * 'key3' => array('key31' => 'value31', 'key32' => 'value32')
271: * );
272: *
273: * @param string $xmlString
274: * contains a valid XML structure
275: * @return array
276: */
277: public static function xmlStringToArray($xmlString) {
278: return self::xmlToArray(new SimpleXMLElement($xmlString, LIBXML_NOCDATA));
279: }
280:
281: /**
282: * Checks if a string is valid XML
283: *
284: * @param string $xmlString
285: * @return bool
286: * True if the XML is valid
287: */
288: public static function isValidXML($xmlString) {
289: $testArray = null;
290:
291: try {
292: $testArray = @cXmlBase::xmlStringToArray($xmlString);
293: } catch(Exception $e) {
294: return false;
295: }
296:
297: return is_array($testArray);
298: }
299:
300: /**
301: * Converts the given SimpleXMLElement object to an array.
302: * Example:
303: * <?xml version="1.0" encoding="utf-8"?>
304: * <root>
305: * <key1>value1</key1>
306: * <key2>
307: * <array_value>value21</array_value>
308: * <array_value>value22</array_value>
309: * </key2>
310: * <key3>
311: * <key31>value31</key31>
312: * <key32>value32</key32>
313: * </key3>
314: * </root>
315: *
316: * becomes
317: *
318: * array(
319: * 'key1' => 'value1',
320: * 'key2' => array('value21', 'value22'),
321: * 'key3' => array('key31' => 'value31', 'key32' => 'value32')
322: * );
323: *
324: * @param SimpleXMLElement $xml
325: * @return array
326: */
327: public static function xmlToArray($xml) {
328: $json = json_encode($xml);
329: $array = json_decode($json, true);
330: $array = self::_cleanArray($array);
331:
332: return $array;
333: }
334:
335: /**
336: * Cleans an array by replacing all empty arrays with empty strings.
337: * Additionally, the function replaces all associative arrays which have
338: * only empty values with the array keys of the array.
339: *
340: * @param array $array
341: * the array to clean
342: * @return array
343: * the cleaned array
344: */
345: private static function _cleanArray($array) {
346: // replace empty arrays with empty strings recursively
347: foreach ($array as $key => $value) {
348: if (is_array($value)) {
349: if (empty($value)) {
350: $array[$key] = '';
351: } else {
352: // if array contains array values, take them directly
353: if ($key == 'array_value') {
354: return $array['array_value'];
355: }
356: $array[$key] = self::_cleanArray($value);
357: }
358: }
359: }
360: // if array only contains empty values, return the array keys
361: if (count(array_keys($array, '')) == count($array)) {
362: return array_keys($array);
363: } else if (count(array_keys($array, 'array_value')) == count($array)) {
364: }
365:
366: return $array;
367: }
368:
369: }
370: