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