|
@@ -1,883 +0,0 @@
|
|
|
-<?php /* <!-- xmd.lib.php -->
|
|
|
- <!-- XML MiniDom, 2006/12/13 -->
|
|
|
-
|
|
|
-<!-- Copyright (C) 2005 rene.haentjens@UGent.be - see note at end of text -->
|
|
|
-<!-- Released under the GNU GPL V2, see http://www.gnu.org/licenses/gpl.html -->
|
|
|
-
|
|
|
-*/
|
|
|
-
|
|
|
-/**
|
|
|
-* This is the XML Dom library for Dokeos.
|
|
|
-* Include/require it in your code to use its functionality.
|
|
|
-*
|
|
|
-* @author René Haentjens
|
|
|
-* @package chamilo.library
|
|
|
-*/
|
|
|
-
|
|
|
-class xmddoc
|
|
|
-{
|
|
|
- /* This MiniDom for XML essentially implements an array of elements, each
|
|
|
- with a parent, a name, a namespace-URI, attributes & namespace definitions,
|
|
|
- and children. Child nodes are a mix of texts and subelements. Parent
|
|
|
- and subelements are stored as elementnumbers, the root is element 0.
|
|
|
-
|
|
|
- Parsing is built on James Clark's expat, by default enabled in PHP.
|
|
|
-
|
|
|
- The MiniDom is an alternative to the experimental DOM XML functions.
|
|
|
- It is open source PHP and requires no extra libraries.
|
|
|
-
|
|
|
- Restrictions of the MiniDom:
|
|
|
- - no two attributes with same name (different namespaces) on one element;
|
|
|
- - only 'ISO-8859-1' right now; author will investigate 'UTF-8' later;
|
|
|
- - processing instructions & external entities are ignored;
|
|
|
- - no distinction between text and cdata child nodes;
|
|
|
- - xmd_xml(nonrootelement) may not generate all needed namespace definitions;
|
|
|
- - xmd_value, xmd_html_value, xmd_select_xxx, xmd_update, xmd_update_many:
|
|
|
- path parameter uses names without namespaces
|
|
|
- and supports only a small subset of XPath, with some extensions;
|
|
|
- - maximum 11 auto-generated namespace prefixes (can be changed in xmddoc)
|
|
|
-
|
|
|
- Namespace definitions are stored as attributes, with name = 'xmlns...'
|
|
|
- e.g. xmlns:xml='http://www.w3.org/XML/1998/namespace'
|
|
|
- e.g. xmlns='http://www.imsglobal.org/xsd/imscp_v1p1' (default namespace)
|
|
|
-
|
|
|
- Exposed methods:
|
|
|
-
|
|
|
- new xmddoc(array_of_strings, charset = 'ISO-8859-1'): parse strings
|
|
|
- new xmddoc(names, numbers, textstring): restore from cached arrays & string
|
|
|
-
|
|
|
- xmd_add_element(complete_name, parent, attributes_with_complete_names)
|
|
|
- complete name = [ URI + ':' + ] name
|
|
|
- xmd_set_attribute(element, complete_attribute_name, value) (id. as above)
|
|
|
- xmd_add_text(text, element)
|
|
|
- xmd_add_text_element(complete_name, text, parent, attributes) =
|
|
|
- xmd_add_text(text, xmd_add_element(complete_name, parent, attributes))
|
|
|
-
|
|
|
- xmd_get_element(element) => children, attributes, '?name', '?parent'
|
|
|
- xmd_get_ns_uri(element [, attribute_name_without_uri] )
|
|
|
-
|
|
|
- xmd_text(element): combines element and subelements' text nodes
|
|
|
- xmd_xml(): generate XML-formatted string (reverse parsing)
|
|
|
- xmd_xml(indent_increase, initial_indent, lbr): e.g. ' ', '', "\n"
|
|
|
-
|
|
|
- xmd_value(path): follow path from root, return attribute value or text
|
|
|
- e.g. 'manifest/organizations/@default' 'body/p[1]' (1=first, -1=last)
|
|
|
- xmd_value(path, parent, fix, function): find value(s) with path from parent,
|
|
|
- apply function and decorate with fix = ('pre'=>..., 'in'=>..., 'post')
|
|
|
- e.g. 'general/title/*' array('in' => ', ')
|
|
|
- extensions to XPath:
|
|
|
- - and + for previous and next sibling, e.g. general/title/+/string
|
|
|
- -name and +name for sibling with specific name, e.g. item[2]/+item
|
|
|
- .. for parent, e.g. general/title/../../technical/format (stops at root)
|
|
|
- @* for location (element number within siblings, starts at 1)
|
|
|
- @*name for location in siblings with specific name
|
|
|
- @. for element name, e.g. organization/*[1]/@.
|
|
|
- namespaces are not supported in paths: they use names without URI
|
|
|
-
|
|
|
- xmd_html_value(pathFix, parent, fun): 'path' 'path infix' 'prefix -% path'
|
|
|
- 'path infix %- postfix': fun = 'htmlspecialchars' by default
|
|
|
-
|
|
|
- xmd_select_elements(path, parent): find element nodes with path (see above)
|
|
|
- xmd_select_single_element (id.) returns -1 or elementnumber
|
|
|
- xmd_select_elements_where(path, subpath, value, parent): e.g. '@id', '12'
|
|
|
- is like XPath with path[@id='12']; subpath = '.' means text
|
|
|
- xmd_select_elements_where_notempty(path, subpath, parent): e.g. '@id'
|
|
|
- xmd_select_xxx methods only select elements, not attributes
|
|
|
-
|
|
|
- xmd_remove_element(childelement_number)
|
|
|
- xmd_remove_nodes(childelement_numbers_and_strings, parent)
|
|
|
-
|
|
|
- xmd_update(path, text, parent): select single element, then:
|
|
|
- text element: replace text by new text
|
|
|
- attribute: give attribute new value = text
|
|
|
- somepath/!newtag: create new child element containing text
|
|
|
- somepath/~: delete single (first or only) element
|
|
|
- xmd_update_many(paths, subpath, ...): paths can be path1,path2,...:
|
|
|
- for all elements selected by all paths, update with subpath
|
|
|
-
|
|
|
- xmd_copy_foreign_child(fdoc, child, parent):
|
|
|
- copies fdoc's child as a new child of parent;
|
|
|
- note this method hasn't been tested for all cases (namespaces...)
|
|
|
-
|
|
|
- xmd_cache(): dump xmddoc into names+numbers+textstring for serialization
|
|
|
-
|
|
|
- Order of parameters (if present) for xmd_xxx methods:
|
|
|
- name, text, children, path, subPath, value,
|
|
|
- parent, fix, fun, attributes (name value)
|
|
|
-
|
|
|
-
|
|
|
- Properties: (G)lobal to xmddoc or array (one for each xmddoc (E)lement)
|
|
|
-
|
|
|
- e.g. $this->name[0] is the name of the document root element
|
|
|
- e.g. $this->names[$this->ns[0]] is its namespace URI
|
|
|
- e.g. $this->attributes[0]['title'] is the value of its attribute 'title'
|
|
|
- e.g. $this->attributes[0]['xmlns:a'] is the URI for prefix 'a:'
|
|
|
- */
|
|
|
-
|
|
|
- var $names; //G array: n => namespace URI (0 => '')
|
|
|
- var $numbers; //G array: numeric dump of xmddoc for caching
|
|
|
- var $textstring; //G string: string dump of xmddoc for caching
|
|
|
- var $error; //G string: empty or parsing error message
|
|
|
- var $_nesting; //G array: nested elements while parsing (internal)
|
|
|
- var $_ns; //G array: namespace defs for upcoming element (id.)
|
|
|
- var $_concat; //G bool: concatenate cData with previous (id.)
|
|
|
- var $_nsp; //G array: namespace prefixes in use somewhere (id.)
|
|
|
- var $_last; //G int: last used elementnumber (id.)
|
|
|
- var $_strings; //G int: number of string child nodes cached (id.)
|
|
|
-
|
|
|
- var $parent; //E int: elementnumber: 0 is root, -1 is parent of root
|
|
|
- var $name; //E string: element name, without namespace
|
|
|
- var $ns; //E int: index into $names to find namespace URI
|
|
|
- var $attributes; //E array: attribute name(without namespace) => value
|
|
|
- var $atns; //E array: attribute name(id.) => index into $names
|
|
|
- var $children; //E array: elementnumbers and strings (text children)
|
|
|
-
|
|
|
-
|
|
|
- function xmd_get_element($parent = 0) // for convenience, readonly copy
|
|
|
- {
|
|
|
- // returns mixed array: children + texts have numeric key,
|
|
|
- // other elements are attributes, '?name' and '?parent'
|
|
|
-
|
|
|
- if ($parent < 0 || $parent > $this->_last) return array();
|
|
|
-
|
|
|
- return array_merge($this->children[$parent], $this->attributes[$parent],
|
|
|
- array('?name' => $this->name[$parent],
|
|
|
- '?parent' => $this->parent[$parent]));
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function xmd_get_ns_uri($parent = 0, $attName = '')
|
|
|
- {
|
|
|
- if ($parent < 0 || $parent > $this->_last) return '';
|
|
|
-
|
|
|
- return $attName ? $this->names[$this->atns[$parent][$attName]] :
|
|
|
- $this->names[$this->ns[$parent]];
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function xmd_remove_element($child) // success = TRUE
|
|
|
- {
|
|
|
- if ($child <= 0 || $child > $this->_last) return FALSE;
|
|
|
-
|
|
|
- $parent = $this->parent[$child];
|
|
|
-
|
|
|
- foreach ($this->children[$parent] as $key => $value)
|
|
|
- if ($value === $child)
|
|
|
- {
|
|
|
- unset($this->children[$parent][$key]); return TRUE;
|
|
|
- }
|
|
|
-
|
|
|
- return FALSE;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function xmd_remove_nodes($children, $parent = 0) // success = TRUE
|
|
|
- {
|
|
|
- if ($parent < 0 || $parent > $this->_last) return FALSE;
|
|
|
-
|
|
|
- if (!is_array($children)) $children = array($children);
|
|
|
-
|
|
|
- foreach ($children as $child)
|
|
|
- {
|
|
|
- $childFound = FALSE;
|
|
|
- foreach ($this->children[$parent] as $key => $value)
|
|
|
- if ($value === $child)
|
|
|
- {
|
|
|
- unset($this->children[$parent][$key]);
|
|
|
- $childFound = TRUE; break;
|
|
|
- }
|
|
|
- if (!$childFound) return FALSE;
|
|
|
- }
|
|
|
-
|
|
|
- return TRUE;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function xmd_update($xmPath, $text = '', $parent = 0) // success = TRUE
|
|
|
- {
|
|
|
- if ($parent < 0 || $parent > $this->_last ||
|
|
|
- !is_string($text) || !is_string($xmPath)) return FALSE;
|
|
|
-
|
|
|
- $m = array();
|
|
|
- if (api_ereg('^(.*)([~!@])(.*)$', $xmPath, $m)) // split on ~ or ! or @
|
|
|
- {
|
|
|
- $xmPath = $m[1]; $op = $m[2]; $name = $m[3];
|
|
|
- }
|
|
|
-
|
|
|
- if (($elem = $this->xmd_select_single_element($xmPath, $parent)) == -1)
|
|
|
- return FALSE;
|
|
|
-
|
|
|
- if (isset($op))
|
|
|
- {
|
|
|
- if ($op == '!' && $name)
|
|
|
- {
|
|
|
- $this->xmd_add_text_element($name, $text, $elem); return TRUE;
|
|
|
- }
|
|
|
- elseif ($op == '@' && $name)
|
|
|
- {
|
|
|
- $this->attributes[$elem][$name] = $text; return TRUE;
|
|
|
- }
|
|
|
- elseif ($op == '~' && !$name)
|
|
|
- return $this->xmd_remove_element($elem);
|
|
|
-
|
|
|
- return FALSE;
|
|
|
- }
|
|
|
-
|
|
|
- if (($nch = count($this->children[$elem])) > 1) return FALSE;
|
|
|
-
|
|
|
- $this->children[$elem][0] = $text; return TRUE;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function xmd_update_many($xmPaths, $subPath = '', $text = '', $parent = 0)
|
|
|
- {
|
|
|
- $result = TRUE;
|
|
|
-
|
|
|
- foreach (explode(',', $xmPaths) as $xmPath)
|
|
|
- foreach ($this->xmd_select_elements($xmPath, $parent) as $elem)
|
|
|
- $result &= $this->xmd_update($subPath, $text, $elem);
|
|
|
- // '&=' always evaluates rhs, '&&=' skips it if $result is FALSE
|
|
|
-
|
|
|
- return $result;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function xmd_copy_foreign_child($fdoc, $fchild = 0, $parent = 0)
|
|
|
- {
|
|
|
- $my_queue = array($fchild, $parent); // optimization, see below
|
|
|
-
|
|
|
- while (!is_null($fchild = array_shift($my_queue)))
|
|
|
- {
|
|
|
- $parent = array_shift($my_queue);
|
|
|
-
|
|
|
- if (is_string($fchild))
|
|
|
- $this->xmd_add_text($fchild, $parent);
|
|
|
-
|
|
|
- elseif (isset($fdoc->name[$fchild]))
|
|
|
- {
|
|
|
- $fullname = $fdoc->name[$fchild];
|
|
|
- $attribs = array(); $nsdefs = array();
|
|
|
-
|
|
|
- if (($nsn = $fdoc->ns[$fchild]))
|
|
|
- $fullname = $fdoc->names[$nsn] . ':' . $fullname;
|
|
|
-
|
|
|
- foreach ($fdoc->attributes[$fchild] as $name => $value)
|
|
|
- {
|
|
|
- if (($p = strrpos($name, ':')) !== FALSE) // 'xmlns:...'
|
|
|
- $nsdefs[$name] = $value;
|
|
|
-
|
|
|
- else
|
|
|
- {
|
|
|
- if (($nsn = $fdoc->atns[$fchild][$name]))
|
|
|
- $name = $fdoc->names[$nsn] . ':' . $name;
|
|
|
- $attribs[$name] = $value;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- $child = $this->xmd_add_element($fullname, $parent,
|
|
|
- array_merge($attribs, $nsdefs));
|
|
|
-
|
|
|
- foreach ($fdoc->children[$fchild] as $ch)
|
|
|
- array_push($my_queue, $ch, $child);
|
|
|
- // recursive call was 10 times slower...
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function xmd_add_element($name, $parent = 0, $attribs = array())
|
|
|
- {
|
|
|
- if (!is_string($name) || $name == '') return -1;
|
|
|
-
|
|
|
- if (($p = strrpos($name, ':')) !== FALSE) // URI + ':' + name
|
|
|
- if ($p == 0 || $p == api_strlen($name) - 1) return -1;
|
|
|
-
|
|
|
- $child = ($this->_last += 1); $uris = array(); $uri = '';
|
|
|
-
|
|
|
- if ($p)
|
|
|
- {
|
|
|
- $uri = api_substr($name, 0, $p); $name = api_substr($name, $p + 1);
|
|
|
- $uris[] = $uri; // check uris after defining all attributes
|
|
|
- }
|
|
|
-
|
|
|
- $this->parent[$child] = $parent; $this->name[$child] = $name;
|
|
|
- $this->ns[$child] = $uri ? $this->_lookup($uri) : 0;
|
|
|
- $this->children[$child] = array();
|
|
|
-
|
|
|
- $this->attributes[$child] = array(); $this->atns[$child] = array();
|
|
|
-
|
|
|
- foreach ($attribs as $name => $value)
|
|
|
- if (($uri = $this->xmd_set_attribute($child, $name, $value, FALSE)))
|
|
|
- $uris[] = $uri; // check at end, not immediately
|
|
|
-
|
|
|
- if ($parent >= 0 && $parent <= $this->_last)
|
|
|
- $this->children[$parent][] = $child; // link to parent
|
|
|
-
|
|
|
- foreach ($uris as $uri) $this->_nsPfx($child, $uri);
|
|
|
- // find prefix (child and upwards) or create new prefix at root
|
|
|
-
|
|
|
- return $child;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function xmd_set_attribute($parent, $name, $value, $checkurihaspfx = TRUE)
|
|
|
- {
|
|
|
- if (!is_string($name) || $name == '') return '';
|
|
|
-
|
|
|
- if (($p = strrpos($name, ':')) !== FALSE) // URI + ':' + name
|
|
|
- if ($p == 0 || $p == api_strlen($name) - 1) return '';
|
|
|
-
|
|
|
- $uri = ''; // beware of 'xmlns...', which is a namespace def!
|
|
|
-
|
|
|
- if ($p) if (api_substr($name, 0, 6) != 'xmlns:')
|
|
|
- {
|
|
|
- $uri = api_substr($name, 0, $p); $name = api_substr($name, $p + 1);
|
|
|
- }
|
|
|
- $this->attributes[$parent][$name] = $value;
|
|
|
- $this->atns[$parent][$name] = $uri ? $this->_lookup($uri) : 0;
|
|
|
- if ($checkurihaspfx) if ($uri) $this->_nsPfx($parent, $uri);
|
|
|
-
|
|
|
- if (api_substr($name, 0, 6) == 'xmlns:') // namespace def with prefix
|
|
|
- $this->_nsp[api_substr($name, 6)] = $value; // prefix is in use
|
|
|
-
|
|
|
- return $uri;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function xmd_add_text($text, $parent = 0) // success = TRUE
|
|
|
- {
|
|
|
- if ($parent < 0 || $parent > $this->_last || !is_string($text))
|
|
|
- return FALSE;
|
|
|
-
|
|
|
- if ($text) $this->children[$parent][] = $text; return TRUE;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function xmd_add_text_element($name, $text, $parent = 0, $attribs = array())
|
|
|
- {
|
|
|
- $this->xmd_add_text($text,
|
|
|
- $child = $this->xmd_add_element($name, $parent, $attribs));
|
|
|
-
|
|
|
- return $child;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function xmd_text($parent = 0)
|
|
|
- {
|
|
|
- if ($parent < 0 || $parent > $this->_last) return '';
|
|
|
-
|
|
|
- $text = ''; // assemble text subnodes and text in child elements
|
|
|
-
|
|
|
- foreach ($this->children[$parent] as $child)
|
|
|
- $text .= is_string($child) ? $child : $this->xmd_text($child);
|
|
|
-
|
|
|
- return $text;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function xmd_xml($increase = ' ', $indent = '', $lbr = "\n", $parent = 0)
|
|
|
- {
|
|
|
- global $charset;
|
|
|
-
|
|
|
- if ($parent < 0 || $parent > $this->_last) return '';
|
|
|
-
|
|
|
- $uri = $this->names[$this->ns[$parent]];
|
|
|
- $pfxc = ($uri == '') ? '' : $this->_nsPfx($parent, $uri);
|
|
|
-
|
|
|
- $dbg = ''; // ($uri == '') ? '' : (' <!-- ' . $uri . ' -->');
|
|
|
-
|
|
|
- $result = $indent . '<' . ($element = $pfxc . $this->name[$parent]);
|
|
|
-
|
|
|
- $atnsp = $this->atns[$parent];
|
|
|
-
|
|
|
- foreach ($this->attributes[$parent] as $name => $value)
|
|
|
- {
|
|
|
- if (isset($atnsp[$name]))
|
|
|
- $atnsn = $atnsp[$name];
|
|
|
- elseif (isset($atnsn))
|
|
|
- unset($atnsn);
|
|
|
- $uri = isset($atnsn) && isset($this->names[$atnsn]) ?
|
|
|
- $this->names[$atnsn] : '';
|
|
|
- $pfxc = ($uri == '') ? '' : $this->_nsPfx($parent, $uri);
|
|
|
- $result .= ' ' . $pfxc . $name
|
|
|
- . '="' . htmlspecialchars($value, ENT_QUOTES, $charset) . '"';
|
|
|
- }
|
|
|
-
|
|
|
- if (count($this->children[$parent]) == 0)
|
|
|
- return $result . ' />' . $dbg;
|
|
|
-
|
|
|
- $result .= '>';
|
|
|
-
|
|
|
- foreach ($this->children[$parent] as $child)
|
|
|
- $result .= is_string($child) ? htmlspecialchars($child, ENT_QUOTES, $charset) : ($lbr .
|
|
|
- $this->xmd_xml($increase, $indent.$increase, $lbr, $child));
|
|
|
-
|
|
|
- if (!is_string($child)) $result .= $lbr . $indent; // last $child
|
|
|
-
|
|
|
- return $result . '</' . $element . '>' . $dbg;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function xmd_value($xmPath, $parent = 0, $fix = array(), $fun = '')
|
|
|
- {
|
|
|
- // extensions: @*[name] for element position (starts at 1)
|
|
|
- // @. for element (tag)name
|
|
|
-
|
|
|
- if ($parent < 0 || $parent > $this->_last || !is_string($xmPath))
|
|
|
- return '';
|
|
|
-
|
|
|
- if (($p = strrpos($xmPath, '@')) !== FALSE)
|
|
|
- {
|
|
|
- $attName = api_substr($xmPath, $p+1); $xmPath = api_substr($xmPath, 0, $p);
|
|
|
- }
|
|
|
-
|
|
|
- if (!($elems = $this->xmd_select_elements($xmPath, $parent))) return '';
|
|
|
-
|
|
|
- $result = ''; $fixin = isset($fix['in']) ? $fix['in'] : '';
|
|
|
-
|
|
|
- foreach ($elems as $elem)
|
|
|
- {
|
|
|
- $value = isset($attName) && api_strlen($attName) >= 1 ?
|
|
|
- ($attName == '.' ? $this->name[$elem] :
|
|
|
- ($attName{0} == '*' ?
|
|
|
- $this->_sibnum($elem, api_substr($attName, 1)) :
|
|
|
- $this->attributes[$elem][$attName])) :
|
|
|
- $this->xmd_text($elem);
|
|
|
- $result .= $fixin . ($fun ? $fun($value) : $value);
|
|
|
- }
|
|
|
-
|
|
|
- return (isset($fix['pre']) ? $fix['pre'] : '') .
|
|
|
- api_substr($result, api_strlen($fixin)) .
|
|
|
- (isset($fix['post']) ? $fix['post'] : '');
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function xmd_html_value($xmPath, $parent = 0, $fun = 'htmlspecialchars')
|
|
|
- {
|
|
|
- if (!is_string($xmPath)) return '';
|
|
|
-
|
|
|
- $fix = array();
|
|
|
-
|
|
|
- if (($p = api_strpos($xmPath, ' -% ')) !== FALSE)
|
|
|
- {
|
|
|
- $fix['pre'] = api_substr($xmPath, 0, $p);
|
|
|
- $xmPath = api_substr($xmPath, $p+4);
|
|
|
- }
|
|
|
- if (($p = api_strpos($xmPath, ' %- ')) !== FALSE)
|
|
|
- {
|
|
|
- $fix['post'] = api_substr($xmPath, $p+4);
|
|
|
- $xmPath = api_substr($xmPath, 0, $p);
|
|
|
- }
|
|
|
- if (($p = api_strpos($xmPath, ' ')) !== FALSE)
|
|
|
- {
|
|
|
- $fix['in'] = api_substr($xmPath, $p+1);
|
|
|
- $xmPath = api_substr($xmPath, 0, $p);
|
|
|
- }
|
|
|
-
|
|
|
- return $this->xmd_value($xmPath, $parent, $fix, $fun);
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function xmd_select_single_element($xmPath, $parent = 0) // for convenience
|
|
|
- {
|
|
|
- $elements = $this->xmd_select_elements($xmPath, $parent);
|
|
|
- if (count($elements) == 0) return -1;
|
|
|
- return $elements[0];
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function xmd_select_elements_where($xmPath,
|
|
|
- $subPath = '.', $value = '', $parent = 0)
|
|
|
- {
|
|
|
- if (!is_string($subPath)) return array();
|
|
|
-
|
|
|
- $elems = array(); if ($subPath == '.') $subPath = '';
|
|
|
-
|
|
|
- foreach ($this->xmd_select_elements($xmPath, $parent) as $elem)
|
|
|
- if ($this->xmd_value($subPath, $elem) == $value) $elems[] = $elem;
|
|
|
-
|
|
|
- return $elems;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function xmd_select_elements_where_notempty($xmPath,
|
|
|
- $subPath = '.', $parent = 0)
|
|
|
- {
|
|
|
- if (!is_string($subPath)) return array();
|
|
|
-
|
|
|
- $elems = array(); if ($subPath == '.') $subPath = '';
|
|
|
-
|
|
|
- foreach ($this->xmd_select_elements($xmPath, $parent) as $elem)
|
|
|
- if ($this->xmd_value($subPath, $elem)) $elems[] = $elem;
|
|
|
-
|
|
|
- return $elems;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function xmd_select_elements($xmPath, $parent = 0)
|
|
|
- {
|
|
|
- // XPath subset: e1/e2/.../en, also * and e[n] and *[n] (at 1 or -1)
|
|
|
- // /*/... starts from root, regardless of $parent
|
|
|
- // extensions: e= - or + (previous & next sibling)
|
|
|
- // e= -name or +name (sibling of specific name)
|
|
|
- // e= .. (stops at root, so too many doesn't matter)
|
|
|
-
|
|
|
- if (api_substr($xmPath, 0, 3) == '/*/')
|
|
|
- {
|
|
|
- $xmPath = api_substr($xmPath, 3); $parent = 0;
|
|
|
- }
|
|
|
-
|
|
|
- if ($parent < 0 || $parent > $this->_last) return array();
|
|
|
-
|
|
|
- while (api_substr($xmPath, 0, 1) == '/') $xmPath = api_substr($xmPath, 1);
|
|
|
- while (api_substr($xmPath, -1) == '/') $xmPath = api_substr($xmPath, 0, -1);
|
|
|
-
|
|
|
- if ($xmPath == '' || $xmPath == '.') return array($parent);
|
|
|
-
|
|
|
- if ($xmPath == '..')
|
|
|
- {
|
|
|
- if ($parent > 0) return array($this->parent[$parent]);
|
|
|
- return array($parent);
|
|
|
- }
|
|
|
-
|
|
|
- if ($xmPath{0} == '-' || $xmPath{0} == '+')
|
|
|
- {
|
|
|
- $sib = $this->_sibnum($parent, api_substr($xmPath, 1), $xmPath{0});
|
|
|
- if ($sib == -1) return array(); return array($sib);
|
|
|
- }
|
|
|
-
|
|
|
- $m = array();
|
|
|
- if (api_ereg('^(.+)/([^/]+)$', $xmPath, $m)) // split on last /
|
|
|
- {
|
|
|
- if (!($set = $this->xmd_select_elements($m[1], $parent)))
|
|
|
- return $set; // which is empty array
|
|
|
- if (count($set) == 1)
|
|
|
- return $this->xmd_select_elements($m[2], $set[0]);
|
|
|
-
|
|
|
- $bigset = array(); $m2 = $m[2];
|
|
|
- foreach ($set as $e)
|
|
|
- $bigset = array_merge($bigset,
|
|
|
- $this->xmd_select_elements($m2, $e));
|
|
|
- return $bigset;
|
|
|
- }
|
|
|
-
|
|
|
- $xmName = $xmPath; $xmNum = 0; $elems = array();
|
|
|
-
|
|
|
- if (api_ereg('^(.+)\[(-?[0-9]+)\]$', $xmPath, $m))
|
|
|
- {
|
|
|
- $xmName = $m[1]; $xmNum = (int) $m[2];
|
|
|
- }
|
|
|
-
|
|
|
- foreach ($this->children[$parent] as $child) if (!is_string($child))
|
|
|
- if ($xmName == '*' || ($this->name[$child]) == $xmName)
|
|
|
- $elems[] = $child;
|
|
|
-
|
|
|
- if ($xmNum == 0) return $elems;
|
|
|
-
|
|
|
- $xmNum = ($xmNum > 0) ? $xmNum - 1 : count($elems) + $xmNum;
|
|
|
-
|
|
|
- return ($xmNum < count($elems)) ? array($elems[$xmNum]) : array();
|
|
|
- }
|
|
|
-
|
|
|
- // Notes on parsing and caching:
|
|
|
- // - parsing 388 KB -> 0.94 sec
|
|
|
- // - caching 298 KB <- 1.63 sec: 11387 elements, 5137 string nodes
|
|
|
- // - uncache 298 KB -> 0.42 sec
|
|
|
- // - $this->children[$n][] in a loop is quicker than a temporary array
|
|
|
- // $children[] and copying $this->children[$n] = $children after the loop
|
|
|
- // - incremental operator ++$numptr is not quicker than ($numptr += 1)
|
|
|
- // - numbers & textstring: more compact with base64_encode(gzcompress())
|
|
|
-
|
|
|
- function xmd_cache() // store all data in numbers+names+textstring
|
|
|
- {
|
|
|
- $this->numbers = array(); $this->textstring = ''; $this->_strings = 0;
|
|
|
- // add all element- and attributenames to names - see below
|
|
|
-
|
|
|
- for ($n = 0; $n <= $this->_last; $n++)
|
|
|
- {
|
|
|
- $this->numbers[] = count($this->children[$n]);
|
|
|
-
|
|
|
- foreach ($this->children[$n] as $ch)
|
|
|
- {
|
|
|
- if (is_string($ch))
|
|
|
- {
|
|
|
- $this->numbers[] = 0; $this->_strings += 1;
|
|
|
- $this->numbers[] = strlen($ch); $this->textstring .= $ch; //!!! Here strlen() has not been changed to api_strlen(). To be investigated. Ivan Tcholakov, 29-AUG-2008.
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- $this->numbers[] = ($ch-$n); // more compact than $ch
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- $this->numbers[] = count($this->attributes[$n]);
|
|
|
-
|
|
|
- foreach ($this->attributes[$n] as $name => $value)
|
|
|
- {
|
|
|
- $this->numbers[] = $this->_lookup($name);
|
|
|
- $this->numbers[] = $this->atns[$n][$name];
|
|
|
- $this->numbers[] = strlen($value); $this->textstring .= $value; //!!! Here strlen() has not been changed to api_strlen(). To be investigated. Ivan Tcholakov, 29-AUG-2008.
|
|
|
- }
|
|
|
-
|
|
|
- $this->numbers[] = $this->_lookup($this->name[$n]);
|
|
|
- $this->numbers[] = $this->ns[$n];
|
|
|
- $this->numbers[] = $n - $this->parent[$n]; // more compact
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- function xmddoc($strings, $charset = null, $textstring = '')
|
|
|
- {
|
|
|
- if (empty($charset))
|
|
|
- {
|
|
|
- $charset = api_get_system_encoding();
|
|
|
- }
|
|
|
-
|
|
|
- $this->parent = array(); $this->name = array();
|
|
|
- $this->ns = array(); $this->attributes = array();
|
|
|
- $this->atns = array(); $this->children = array();
|
|
|
- $this->error = ''; $this->_nesting = array();
|
|
|
- $this->_ns = array(); $this->_last = -1;
|
|
|
-
|
|
|
- $this->_nsp = array();
|
|
|
- foreach (explode(',', 'eb,tn,eg,ut,as,ne,jt,ne,ah,en,er') as $pfx)
|
|
|
- $this->_nsp[$pfx] = '';
|
|
|
-
|
|
|
- if (is_array($charset)) // new xmddoc($names, $numbers, $textstring)
|
|
|
- {
|
|
|
- $this->names = $strings; $this->numbers = $charset;
|
|
|
- $this->textstring = $textstring; $this->_uncache(); return;
|
|
|
- }
|
|
|
-
|
|
|
- $this->names = array(); $this->_lookup(''); // empty ns is number 0
|
|
|
-
|
|
|
- // This is a quick workaround.
|
|
|
- // The xml-parser supports only ISO-8859-1, UTF-8 and US-ASCII.
|
|
|
- // See http://php.net/manual/en/function.xml-parser-create-ns.php
|
|
|
- //$xml_parser = xml_parser_create_ns($charset, ':');
|
|
|
- $xml_parser = xml_parser_create_ns(api_is_utf8($charset) ? 'UTF-8' : 'ISO-8859-1', ':');
|
|
|
-
|
|
|
- xml_set_object($xml_parser,$this); // instead of ...,&$this
|
|
|
- // See PHP manual: Passing by Reference vs. xml_set_object
|
|
|
- xml_set_element_handler($xml_parser, '_startElement', '_endElement');
|
|
|
- xml_set_character_data_handler($xml_parser, '_cData');
|
|
|
- xml_set_start_namespace_decl_handler($xml_parser, '_startNs');
|
|
|
- // xml_set_end_namespace_decl_handler($xml_parser, '_endNs');
|
|
|
- // xml_set_default_handler ($xml_parser, '');
|
|
|
- xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, FALSE);
|
|
|
-
|
|
|
- if (!is_array($strings)) $strings = array($strings);
|
|
|
-
|
|
|
- if (count($strings) && (api_substr($strings[0], 0, 5) != '<?xml') &&
|
|
|
- !xml_parse($xml_parser,
|
|
|
- '<?xml version="1.0" encoding="' . $charset . '"?>', FALSE))
|
|
|
- {
|
|
|
- $this->error = 'Encoding ' . $charset . ': ' .
|
|
|
- xml_error_string(xml_get_error_code($xml_parser));
|
|
|
- $strings = array();
|
|
|
- }
|
|
|
-
|
|
|
- foreach ($strings as $s)
|
|
|
- {
|
|
|
- if (api_substr($s, -1) != "\n") $s .= "\n";
|
|
|
-
|
|
|
- if (!xml_parse($xml_parser, $s, FALSE))
|
|
|
- {
|
|
|
- $errCode = xml_get_error_code($xml_parser);
|
|
|
- $this->error = 'Line '. xml_get_current_line_number($xml_parser) .
|
|
|
- ' (c' . xml_get_current_column_number($xml_parser) .
|
|
|
- // ', b' . xml_get_current_byte_index($xml_parser) .
|
|
|
- '): error ' . $errCode . '= ' . xml_error_string($errCode);
|
|
|
- break; // the error string is English...
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- xml_parse($xml_parser, '', TRUE); xml_parser_free($xml_parser);
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- // internal methods
|
|
|
-
|
|
|
- function _sibnum($parent, $name = '', $pmn = 'N') // sibling or number
|
|
|
- {
|
|
|
- if ($parent <= 0) return -1;
|
|
|
-
|
|
|
- $found = FALSE; $prev = -1; $next = -1; $num = 0;
|
|
|
-
|
|
|
- foreach ($this->children[$this->parent[$parent]] as $child)
|
|
|
- {
|
|
|
- if (is_string($child)) continue;
|
|
|
-
|
|
|
- $name_ok = $name ? ($this->name[$child] == $name) : TRUE;
|
|
|
-
|
|
|
- if ($found && $name_ok)
|
|
|
- {
|
|
|
- $next = $child; break;
|
|
|
- }
|
|
|
- elseif ($parent === $child)
|
|
|
- {
|
|
|
- $num ++; $found = TRUE;
|
|
|
- }
|
|
|
- elseif ($name_ok)
|
|
|
- {
|
|
|
- $num ++; $prev = $child;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return ($pmn == '-') ? $prev : (($pmn == '+') ? $next : $num);
|
|
|
- }
|
|
|
-
|
|
|
- function _uncache() // restore all data from numbers+names+textstring
|
|
|
- {
|
|
|
- $n = -1; $numptr = -1; $txtptr = 0; $count = count($this->numbers);
|
|
|
- $A0 = array(); // believe it or not, this makes the loops quicker!
|
|
|
-
|
|
|
- while (++$numptr < $count)
|
|
|
- {
|
|
|
- $n++;
|
|
|
-
|
|
|
- if (($countdown = $this->numbers[$numptr]) == 0)
|
|
|
- {
|
|
|
- $this->children[$n] = $A0;
|
|
|
- }
|
|
|
- else while (--$countdown >= 0)
|
|
|
- {
|
|
|
- if (($chc = $this->numbers[++$numptr]) == 0)
|
|
|
- {
|
|
|
- $this->children[$n][] = api_substr($this->textstring,
|
|
|
- $txtptr, ($len = $this->numbers[++$numptr]));
|
|
|
- $txtptr += $len;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- $this->children[$n][] = $n + $chc;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (($countdown = $this->numbers[++$numptr]) == 0)
|
|
|
- {
|
|
|
- $this->attributes[$n] = $this->atns[$n] = $A0;
|
|
|
- }
|
|
|
- else while (--$countdown >= 0)
|
|
|
- {
|
|
|
- $name = $this->names[$this->numbers[++$numptr]];
|
|
|
- $this->atns[$n][$name] = $this->numbers[++$numptr];
|
|
|
- $this->attributes[$n][$name] = api_substr($this->textstring,
|
|
|
- $txtptr, ($len = $this->numbers[++$numptr]));
|
|
|
- $txtptr += $len;
|
|
|
- }
|
|
|
-
|
|
|
- $this->name[$n] = $this->names[$this->numbers[++$numptr]];
|
|
|
- $this->ns[$n] = $this->numbers[++$numptr];
|
|
|
-
|
|
|
- $this->parent[$n] = $n - $this->numbers[++$numptr];
|
|
|
- }
|
|
|
-
|
|
|
- $this->_last = $n;
|
|
|
- }
|
|
|
-
|
|
|
- function _startElement($parser, $name, $attribs)
|
|
|
- {
|
|
|
- $level = count($this->_nesting);
|
|
|
- $parent = ($level == 0) ? -1 : $this->_nesting[$level-1];
|
|
|
-
|
|
|
- $child = $this->xmd_add_element($name, $parent,
|
|
|
- array_merge($attribs, $this->_ns));
|
|
|
-
|
|
|
- $this->_nesting[] = $child; $this->_ns = array();
|
|
|
-
|
|
|
- $this->_concat = FALSE; // see _cData
|
|
|
- }
|
|
|
-
|
|
|
- function _endElement($parser, $name)
|
|
|
- {
|
|
|
- array_pop($this->_nesting); $this->_concat = FALSE;
|
|
|
- }
|
|
|
-
|
|
|
- function _cData($parser, $data)
|
|
|
- {
|
|
|
- if (!ltrim($data)) return; // empty line, or whitespace preceding <tag>
|
|
|
-
|
|
|
- $level = count($this->_nesting);
|
|
|
- $parent = ($level == 0) ? -1 : $this->_nesting[$level-1];
|
|
|
-
|
|
|
- if ($parent >= 0)
|
|
|
- {
|
|
|
- $nc = count($this->children[$parent]);
|
|
|
- $pcs = ($nc > 0 && is_string($this->children[$parent][$nc - 1]));
|
|
|
-
|
|
|
- if ($pcs && api_strlen($data) == 1) $this->_concat = TRUE;
|
|
|
- // expat parser puts &xx; in a separate cData, try to re-assemble
|
|
|
-
|
|
|
- if ($pcs && $data{0} > '~') $this->_concat = TRUE;
|
|
|
- // PHP5 expat breaks before 8-bit characters
|
|
|
-
|
|
|
- if ($this->_concat)
|
|
|
- {
|
|
|
- $this->children[$parent][$nc - 1] .= $data;
|
|
|
- $this->_concat = (api_strlen($data) == 1);
|
|
|
- }
|
|
|
- else
|
|
|
- $this->children[$parent][] = $pcs ? "\n" . $data : $data;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- function _startNs($parser, $pfx, $uri) // called before _startElement
|
|
|
- {
|
|
|
- $this->_ns['xmlns' . ($pfx ? ':'.$pfx : '')] = $uri;
|
|
|
- $this->_nsp[$pfx] = $uri;
|
|
|
- }
|
|
|
-
|
|
|
- function _nsPfx($ppar, $uri) // find namespace prefix
|
|
|
- {
|
|
|
- while ($ppar >= 0)
|
|
|
- {
|
|
|
- foreach ($this->attributes[$ppar] as $name => $value)
|
|
|
- if (api_substr($name, 0, 5) == 'xmlns' && $value == $uri)
|
|
|
- {
|
|
|
- $pfxc = api_substr($name, 6) . api_substr($name, 5, 1); break 2;
|
|
|
- }
|
|
|
-
|
|
|
- $ppar = $this->parent[$ppar];
|
|
|
- }
|
|
|
-
|
|
|
- if ($ppar >= 0) return $pfxc; if ($uri == '') return '';
|
|
|
-
|
|
|
- if ($uri == 'http://www.w3.org/XML/1998/namespace') return 'xml:';
|
|
|
-
|
|
|
- foreach($this->_nsp as $pfx => $used) if (!$used) break;
|
|
|
-
|
|
|
- $this->_nsp[$pfx] = $uri; $xmlnspfx = 'xmlns:' . $pfx;
|
|
|
- $this->attributes[0][$xmlnspfx] = $uri; $this->atns[0][$xmlnspfx] = 0;
|
|
|
-
|
|
|
- return $pfx . ':';
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- function _lookup($name) // for namespaces + see cache
|
|
|
- {
|
|
|
- $where = array_search($name, $this->names);
|
|
|
-
|
|
|
- if ($where === FALSE || $where === NULL)
|
|
|
- {
|
|
|
- $where = count($this->names); $this->names[] = $name;
|
|
|
- }
|
|
|
- return $where;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/*
|
|
|
-<!--
|
|
|
- This program is free software; you can redistribute it and/or
|
|
|
- modify it under the terms of the GNU General Public License
|
|
|
- as published by the Free Software Foundation; either version 2
|
|
|
- of the License, or (at your option) any later version.
|
|
|
-
|
|
|
- This program is distributed in the hope that it will be useful,
|
|
|
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
- GNU General Public License for more details.
|
|
|
-
|
|
|
- -->
|
|
|
-*/
|
|
|
-?>
|