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