From 3edca53599943d76515a8d78975641f3305c3dbd Mon Sep 17 00:00:00 2001 From: Deon George Date: Sat, 21 Aug 2010 13:45:09 +1000 Subject: [PATCH] Added Kohana XML module --- includes/kohana/modules/xml/LICENSE | 22 + includes/kohana/modules/xml/README.md | 31 + includes/kohana/modules/xml/classes/xml.php | 13 + .../kohana/modules/xml/classes/xml/core.php | 727 ++++++++++++++++++ .../modules/xml/classes/xml/driver/atom.php | 108 +++ .../modules/xml/classes/xml/driver/rss2.php | 40 + .../modules/xml/classes/xml/driver/xrds.php | 57 ++ .../kohana/modules/xml/classes/xml/meta.php | 12 + .../modules/xml/classes/xml/meta/core.php | 220 ++++++ 9 files changed, 1230 insertions(+) create mode 100644 includes/kohana/modules/xml/LICENSE create mode 100644 includes/kohana/modules/xml/README.md create mode 100644 includes/kohana/modules/xml/classes/xml.php create mode 100644 includes/kohana/modules/xml/classes/xml/core.php create mode 100644 includes/kohana/modules/xml/classes/xml/driver/atom.php create mode 100644 includes/kohana/modules/xml/classes/xml/driver/rss2.php create mode 100644 includes/kohana/modules/xml/classes/xml/driver/xrds.php create mode 100644 includes/kohana/modules/xml/classes/xml/meta.php create mode 100644 includes/kohana/modules/xml/classes/xml/meta/core.php diff --git a/includes/kohana/modules/xml/LICENSE b/includes/kohana/modules/xml/LICENSE new file mode 100644 index 00000000..215018dc --- /dev/null +++ b/includes/kohana/modules/xml/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2010 Cédric Cazettes + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/includes/kohana/modules/xml/README.md b/includes/kohana/modules/xml/README.md new file mode 100644 index 00000000..6f7ad350 --- /dev/null +++ b/includes/kohana/modules/xml/README.md @@ -0,0 +1,31 @@ +Kohana_XML is a module used to generate and read XML documents in Kohana. +It is built for KO3, but there are barely one or two lines that makes it KO3 specific, +so I guess it should work for KO2.x without much trouble. + +## Notable Features + +* **Extendible, configurable drivers** — You can use the XML class to write simple XML, +or use drivers to generate RFC-specific compliant XML (Atom/RSS2...), or write your own driver (extending XML +or another driver) to generate XML compliant to any specs you want. Driver support initial +configuration, which will be used when using native functions, and your own function. +Namespaces and prefix, value filters, default attributes, node name abstraction are all part +of driver configuration and are then used as such by native functions, so they are dealt with +on the fly. But you can also write your own function very easily in your drivers, and writing +an add_author($user_model) function in the Atom driver would take a second. + +* **Dealing with objects of the same class whatever function you use** – $xml→add_node(“test”); +generates another XML instance of the same driver you can add nodes to, import array or XML files +to, search in, modify, export, combine… The whole XML document becomes modular, easy to read and +to modify, and to run through with method chaining. Just play Lego with your XML. + +* **Magic get and get()** — allows to easily run through the document. For instance +$atom→author→name will return an atom document author’s name, this regardless of your driver +configuration. As another example of node name abstraction, if you’ve decided to abstract “pubDate” +with “updated” in your RSS2 driver configuration and “published” with “updated” in you Atom driver, +then $atom→updated will give you the same result as $rss→updated. + +* **Jelly-style driver configuration** — I liked the way Jelly initializes its models, so you can +configure yours just the same way. Driver configuration then goes into a static meta class, which +improves performance. + +* You can still use **DOM functions** if you wish and reintegrate in Kohana_XML \ No newline at end of file diff --git a/includes/kohana/modules/xml/classes/xml.php b/includes/kohana/modules/xml/classes/xml.php new file mode 100644 index 00000000..c4c3cd1a --- /dev/null +++ b/includes/kohana/modules/xml/classes/xml.php @@ -0,0 +1,13 @@ + + * + * Description: + * XML class. Use this class to override XML_Core. + * Extend this class to make your own XML based driver (Atom, XRDS, GData, RSS, PodCast RSS, or your own brewed XML format) + */ + + class XML extends XML_Core + {} \ No newline at end of file diff --git a/includes/kohana/modules/xml/classes/xml/core.php b/includes/kohana/modules/xml/classes/xml/core.php new file mode 100644 index 00000000..19a7b0b4 --- /dev/null +++ b/includes/kohana/modules/xml/classes/xml/core.php @@ -0,0 +1,727 @@ + + * + * Description: + * XML_Core class. + */ + + class XML_Core + { + /** + * @var string XML document version + */ + public static $xml_version = "1.0"; + + /** + * @var string Root Node name + */ + public $root_node; + + /** + * The DOM_Element corresponding to this XML instance + * This is made public to use DOM functions directly if desired. + * @var DOM_Element XML instance DOM node. + */ + public $dom_node; + + /** + * Basically a handy shortcut of $this->dom_node->ownerDocument + * All XML instance belonging to the same document will have this attribute in common + * @var DOM_Document XML instance DOM document, owner of dom_node + */ + public $dom_doc; + + /** + * @var array Array of XML_Meta, containing metadata about XML drivers config + */ + protected static $_metas = array(); + + + /** + * This creates an XML object from the specified driver. + * Specify the driver name, or if there is no specific driver, the root node name + * @param string $driver [optional] Driver Name + * @param string $root_node [optional] Root Node name. Force the root node name. Must be used if no driver nor element is specified. + * @param string $element [optional] XML string or file to generate XML from. Must be used if no driver nor root_node is specified. + * @return XML XML object + */ + public static function factory($driver = NULL, $root_node = NULL, $element = NULL) + { + if ($driver) + { + // Let's attempt to generate a new instance of the subclass corresponding to the driver provided + $class = 'XML_Driver_'.ucfirst($driver); + + // Register a new meta object + XML::$_metas[strtolower($class)] = $meta = new XML_Meta; + + // Override the meta with driver-specific attributes + call_user_func(array($class, "initialize"), $meta); + + // Set content type to default if it is not already set, and report it as initialized + $meta->content_type("text/xml")->set_initialized(); + + return new $class($element, $root_node); + } + else + { + // Register a new meta object in the root node + XML::$_metas["xml"] = $meta = new XML_Meta; + + // Set content type to default if it is not already set, and report it as initialized + $meta->content_type("text/xml")->set_initialized(); + + return new XML($element, $root_node); + } + } + + + /** + * Class constructor. You should use the factory instead. + * @param string $element [optional] What to construct from. Could be some xml string, a file name, or a DOMNode + * @param string $root_node [optional] The root node name. To be specified if no driver are used. + * @return XML XML object instance + */ + public function __construct($element = NULL, $root_node = NULL) + { + // Create the initial DOMDocument + $this->dom_doc = new DOMDocument(XML::$xml_version, Kohana::$charset); + + if ($root_node) + { + // If a root node is specified, overwrite the current_one + $this->root_node = $root_node; + } + + // Initialize the document with the given element + if (is_string($element)) + { + if (is_file($element) OR validate::url($element)) + { + // Generate XML from a file + $this->dom_doc->load($element); + } + else + { + // Generate XML from a string + $this->dom_doc->loadXML($element); + } + // Node is the root node of the document, containing the whole tree + $this->dom_node = $this->dom_doc->documentElement; + } + elseif ($element instanceof DOMNode) + { + // This is called from another XML instance ( through add_node, or else...) + // Let's add that node to the new object node + $this->dom_node = $element; + + // And overwrite the document with that node's owner document + $this->dom_doc = $this->dom_node->ownerDocument; + } + elseif ( ! is_null($this->root_node)) + { + // Create the Root Element from the driver attributes + if ($this->meta()->ns($this->root_node)) + { + list($ns, $prefix) = $this->meta()->ns($this->root_node); + + $root_node_name = $prefix ? "$prefix:$this->root_node" : $this->root_node; + + // Create the root node in its prefixed namespace + $root_node = $this->dom_doc->createElementNS($ns, $root_node_name); + } + else + { + // Create the root node + $root_node = $this->dom_doc->createElement($this->root_node); + } + + // Append the root node to the object DOMDocument, and set the resulting DOMNode as it's node + $this->dom_node = $this->dom_doc->appendChild($root_node); + + // Add other attributes + $this->add_attributes($this->dom_node); + } + else + { + throw new Kohana_Exception("You have to specify a root_node, either in your driver or in the constructor if you're not using any."); + } + } + + + /** + * Adds a node to the document + * @param string $name Name of the node. Prefixed namespaces are handled automatically. + * @param value $value [optional] value of the node (will be filtered). If value is not valid CDATA, + * it will be wrapped into a CDATA section + * @param array $attributes [optional] array of attributes. Prefixed namespaces are handled automatically. + * @return XML instance for the node that's been added. + */ + public function add_node($name, $value = NULL, $attributes = array()) + { + // Trim the name + $name = trim($name); + + // Create the element + $node = $this->create_element($name); + + // Add the attributes + $this->add_attributes($node, $attributes); + + // Add the value if provided + if ($value !== NULL) + { + $value = strval($this->filter($name, $value)); + + if (str_replace(array('<', '>', '&'), "", $value) === $value) + { + // Value is valid CDATA, let's add it as a new text node + $value = $this->dom_doc->createTextNode($value); + } + else + { + // We shall create a CDATA section to wrap the text provided + $value = $this->dom_doc->createCDATASection($value); + } + $node->appendChild($value); + } + + // return a new XML instance of the same class from the child node + $class = get_class($this); + return new $class($this->dom_node->appendChild($node)); + } + + + + /** + * Magic get returns the first child node matching the value + * @param string $node_name + * @return mixed If trying to get a node: + * NULL will be return if nothing is matched, + * A string value is returned if it a text/cdata node is matched + * An XML instance is returned otherwise, allowing chaining. + */ + public function __get($value) + { + if ( ! isset($this->$value)) + { + $node = current($this->get($value)); + + if ($node instanceof XML) + { + // Return the whole XML document + return $node; + } + // We did not match any child nodes + return NULL; + } + parent::__get($value); + } + + + + /** + * Gets all nodes matching a name and returns them as an array. + * Can also be used to get a pointer to a particular node and then deal with that node as an XML instance. + * @param string $value name of the nodes desired + * @param bool $as_array [optional] whether or not the nodes should be returned as an array + * @return array Multi-dimensional array or array of XML instances + */ + public function get($value, $as_array = FALSE) + { + $return = array(); + + $value = $this->meta()->alias($value); + + foreach ($this->dom_node->getElementsByTagName($value) as $item) + { + if ($as_array) + { + // Return as array but ignore root node + $array = $this->_as_array($item); + foreach ($array as $val) + { + $return[] = $val; + } + } + else + { + $class = get_class($this); + $return[] = new $class($item); + } + } + return $return; + } + + + /** + * Queries the document with an XPath query + * @param string $query XPath query + * @param bool $as_array [optional] whether or not the nodes should be returned as an array + * @return array Multi-dimensional array or array of XML instances + */ + public function xpath($query, $as_array = TRUE) + { + $return = array(); + + $xpath = new DOMXPath($this->dom_doc); + + foreach ($xpath->query($query) as $item) + { + if ($as_array) + { + $array = $this->_as_array($item); + foreach ($array as $val) + { + $return[] = $val; + } + } + else + { + $class = get_class($this); + $return[] = new $class($item); + } + } + return $return; + } + + + + /** + * Exports the document as a multi-dimensional array. + * Handles element with the same name. + * + * Root node is ignored, as it is known and available in the driver. + * Example : + * + * + * value1 + * + * + * value2 + * + * + * + * Here's the resulting array structure : + * array ("node_name" => array( + * // array of nodes called "node_name" + * 0 => array( + * // Attributes of that node + * "xml_attributes" => array( + * "attr_name" => "val", + * ) + * // node contents + * "child_node_name" => array( + * // array of nodes called "child_node_name" + * 0 => value1, + * 1 => value2, + * ) + * The output is retro-actively convertible to XML using from_array(). + * @return array + */ + public function as_array() + { + $dom_element = $this->dom_node; + + $return = array(); + + // This function is run on a whole XML document and this is the root node. + // That root node shall be ignored in the array as it driven by the driver and handles document namespaces. + foreach($dom_element->childNodes as $dom_child) + { + if ($dom_child->nodeType == XML_ELEMENT_NODE) + { + // Let's run through the child nodes + $child = $this->_as_array($dom_child); + + foreach ($child as $key => $val) + { + $return[$key][]=$val; + } + } + } + + return $return; + } + + + + /** + * Recursive as_array for child nodes + * @param DOMNode $dom_node + * @return Array + */ + private function _as_array(DOMNode $dom_node) + { + // All other nodes shall be parsed normally : attributes then text value and child nodes, running through the XML tree + $object_element = array(); + + // Get the desired node name for this node + $node_name = $this->meta()->key($dom_node->tagName); + + // Get attributes + if ($dom_node->hasAttributes()) + { + $object_element[$dom_node->nodeName]['xml_attributes'] = array(); + foreach($dom_node->attributes as $att_name => $dom_attribute) + { + // Get the desired name for this attribute + $att_name = $this->meta()->key($att_name); + + $object_element[$node_name]['xml_attributes'][$att_name] = $dom_attribute->value; + } + } + + // Get children, run through XML tree + if ($dom_node->hasChildNodes()) + { + if (!$dom_node->firstChild->hasChildNodes()) + { + // Get text value + $object_element[$node_name] = trim($dom_node->firstChild->nodeValue); + } + + foreach($dom_node->childNodes as $dom_child) + { + if ($dom_child->nodeType === XML_ELEMENT_NODE) + { + $child = $this->_as_array($dom_child); + + foreach ($child as $key=>$val) + { + $object_element[$node_name][$key][]=$val; + } + } + } + } + return $object_element; + } + + + + /** + * Converts an array to XML. Expected structure is given in as_array(). + * However, from_array() is essentially more flexible regarding to the input array structure, + * as we don't have to bother about nodes having the same name. + * Try something logical, that should work as expected. + * @param object $mixed + * @return XML + */ + public function from_array($array) + { + $this->_from_array($array, $this->dom_node); + + return $this; + } + + + + /** + * Array shall be like : array('element_name' => array( 0 => text, 'xml_attributes' => array())); + * @param object $mixed + * @param DOMElement $dom_element + * @return + */ + protected function _from_array($mixed, DOMElement $dom_element) + { + if (is_array($mixed)) + { + foreach( $mixed as $index => $mixed_element ) + { + if ( is_numeric($index) ) + { + // If we have numeric keys, we're having multiple children of the same node. + // Append the new node to the current node's parent + // If this is the first node to add, $node = $dom_element + $node = $dom_element; + if ( $index != 0 ) + { + // If not, lets create a copy of the node with the same name + $node = $this->create_element($dom_element->tagName); + // And append it to the parent node + $node = $dom_element->parentNode->appendChild($node); + } + $this->_from_array($mixed_element, $node); + } + elseif ($index == "xml_attributes") + { + // Add attributes to the node + $this->add_attributes($dom_element, $mixed_element); + } + else + { + // Create a new element with the key as the element name. + // Create the element corresponding to the key + $node = $this->create_element($index); + // Append it + $dom_element->appendChild($node); + + // Treat the array by recursion + $this->_from_array($mixed_element, $node); + } + } + } + else + { + // This is a string value that shall be appended as such + $mixed = $this->filter($dom_element->tagName, $mixed); + $dom_element->appendChild($this->dom_doc->createTextNode($mixed)); + } + } + + + + /** + * This function is used to import another XML instance, or whatever we can construct XML from (string, filename, DOMNode...) + * + * $xml1 = XML::factory("atom", "bla"); + * $xml2 = XML::factory("rss", ""); + * $node_xml2 = $xml2->add_node("key"); + * + * // outputs "bla" + * $node_xml2->import($xml1)->render(); + * + * // outputs "blabla" + * $xml1->import($xml2->get("key"))->render(); + * + * @param object $xml XML instance or DOMNode + * @return object $this Chainable function + */ + public function import($xml) + { + if (! $xml instanceof XML) + { + // Attempt to construct XML from the input + $class = get_class($this); + $xml = new $class($xml); + } + // Import the node, and all its children, to the document + $node = $this->dom_doc->importNode($xml->dom_node, TRUE); + $this->dom_node->appendChild($node); + + return $this; + } + + + /** + * Creates an element, sorts out namespaces (default / prefixed) + * @param string $name element name + * @return DOMElement + */ + private function create_element($name) + { + $name = $this->meta()->alias($name); + + // Let's check if the element name has a namespace, and if this prefix is defined in our driver + if ($this->meta()->ns($name)) + { + list ($ns, $prefix) = $this->meta()->ns($name); + + if ($prefix) + { + // Register the prefixed namespace in the document root + $this->dom_doc->documentElement->setAttributeNS("http://www.w3.org/2000/xmlns/" ,"xmlns:$prefix", $ns); + + // Create the prefixed element within that namespace + $node = $this->dom_doc->createElementNS($ns, $name); + } + else + { + // Create the element normally + $node = $this->dom_doc->createElement($name); + // Add the new default namespace as an attribute. + $node->setAttribute("xmlns", $ns); + } + } + else + { + // Simply create the element + $node = $this->dom_doc->createElement($name); + } + return $node; + } + + + /** + * Applies attributes to a node + * @param DOMNode $node + * @param array $attributes as key => value + * @return DOMNode + */ + private function add_attributes(DOMNode $node, $attributes = array()) + { + $node_name = $this->meta()->alias($node->tagName); + + if ($this->meta()->attributes($node_name)) + { + $attributes = array_merge($this->meta()->attributes($node_name), $attributes); + } + + foreach ($attributes as $key => $val) + { + // Trim elements + $key = $this->meta()->alias(trim($key)); + $val = $this->filter($key, trim($val)); + + // Set the attribute + // Let's check if the attribute name has a namespace prefix, and if this prefix is defined in our driver + if ($this->meta()->ns($key)) + { + list ($ns, $prefix) = $this->meta()->ns($key); + // Register the prefixed namespace + $this->dom_node->setAttributeNS("http://www.w3.org/2000/xmlns/" ,"xmlns:$prefix", $ns); + // Add the prefixed attribute within that namespace + $node->setAttributeNS($ns, $key, $val); + } + else + { + // Simply add the attribute + $node->setAttribute($key, $val); + } + } + return $node; + } + + + + /** + * Applies filter on a value. + * These filters are callbacks usually defined in the driver. + * They allow to format dates, links, standard stuff, and play + * as you wish with the value before it is added to the document. + * + * You could even extend it and modidy the node name. + * + * @param string $name + * @param string $value + * @return string $value formatted value + */ + protected function filter($name, $value) + { + $name = $this->meta()->alias($name); + + if ($this->meta()->filter($name)) + { + return call_user_func(array($this, $this->meta()->filter($name)), $value); + } + return $value; + } + + + /** + * This is a classic filter that takes a uri and makes a proper link + * @param object $value + * @return $value + */ + public function normalize_uri($value) + { + if (strpos($value, '://') === FALSE) + { + // Convert URIs to URLs + $value = URL::site($value, TRUE); + } + return $value; + } + + + /** + * Another classic filter to deal with boolean + * @param boolean $value + * @return string $value, true or false + */ + public function normalize_bool($value) + { + return $value ? "true" : "false"; + } + + + + /** + * Returns this drivers XML metadata + * @return XML_Meta + */ + public function meta() + { + return XML::$_metas[strtolower(get_class($this))]; + } + + + + /** + * Outputs nicely formatted XML when converting as string + * @return string + */ + public function __toString() + { + return $this->render(TRUE); + } + + + + /** + * Render the XML. + * @param boolean $formatted [optional] Should the output be formatted and indented ? + * @return string + */ + public function render($formatted = FALSE) + { + $this->dom_doc->formatOutput = $formatted; + return $this->dom_doc->saveXML(); + } + + + /** + * Outputs the XML in a file + * @param string filename + * @return + */ + public function export($file) + { + return $this->dom_doc->save($file); + } + + + /** + * Returns this instance node value, if the dom_node is a text node + * + * @return string + */ + public function value() + { + if ($this->dom_node->hasChildNodes() AND $this->dom_node->firstChild->nodeType === XML_TEXT_NODE) + { + return $this->dom_node->nodeValue; + } + return NULL; + } + + + /** + * Returns this instance node value + * + * @return string|array attributes as array of attribute value if a name is specified + */ + public function attributes($attribute_name = NULL) + { + if ($attribute_name === NULL) + { + // Return an array of attributes + $attributes = array(); + + if ($this->dom_node->hasAttributes()) + { + foreach ($this->dom_node->attributes as $attribute) + { + $attributes[$attribute->name] = $attribute->value; + } + } + return $attributes; + } + + // Simply return the attribute value + return $this->dom_node->getAttribute($attribute_name); + } +} // End XML_Core diff --git a/includes/kohana/modules/xml/classes/xml/driver/atom.php b/includes/kohana/modules/xml/classes/xml/driver/atom.php new file mode 100644 index 00000000..45a58916 --- /dev/null +++ b/includes/kohana/modules/xml/classes/xml/driver/atom.php @@ -0,0 +1,108 @@ + + * + * Description: + * Atom driver + */ + +class XML_Driver_Atom extends XML +{ + public $root_node = 'feed'; + + + protected static function initialize(XML_Meta $meta) + { + $meta ->content_type("application/atom+xml") + ->nodes ( + array( + "feed" => array("namespace" => "http://www.w3.org/2005/Atom"), + "entry" => array("namespace" => "http://www.w3.org/2005/Atom"), + "href" => array("filter" => "normalize_uri"), + "logo" => array("filter" => "normalize_uri"), + "icon" => array("filter" => "normalize_uri"), + "id" => array("filter" => "normalize_uri"), + "updated" => array("filter" => "normalize_datetime"), + "published" => array("filter" => "normalize_datetime"), + "startDate" => array("filter" => "normalize_date"), + 'endDate' => array("filter" => "normalize_date"), + ) + ); + } + + + public function add_person($type, $name, $email = NULL, $uri = NULL) + { + $author = $this->add_node($type); + $author->add_node("name", $name); + if ($email) + { + $author->add_node("email", $email); + } + if ($uri) + { + $author->add_node("uri", $uri); + } + return $this; + } + + + public function add_content(XML $xml_document) + { + $this->add_node("content", NULL, array("type" => $xml_document->meta()->content_type()))->import($xml_document); + return $this; + } + + + public function normalize_datetime($value) + { + if ( ! is_numeric($value)) + { + $value = strtotime($value); + } + + // Convert timestamps to RFC 3339 formatted datetime + return date(DATE_RFC3339, $value); + } + + public function normalize_date($value) + { + if ( ! is_numeric($value)) + { + $value = strtotime($value); + } + + // Convert timestamps to RFC 3339 formatted dates + return date("Y-m-d", $value); + } + + + public function render($formatted = FALSE) + { + if ( ! $this->published) + { + // Add the published node with current date + $this->add_node("published", time()); + } + // Add the link to self + $this->add_node("link", NULL, array("rel" => "self", "href" => $_SERVER['REQUEST_URI'])); + + return parent::render($formatted); + } + + + public function export($file) + { + if ( ! $this->published) + { + // Add the published node with current date + $this->add_node("published", time()); + } + // Add the link to self + $this->add_node("link", NULL, array("rel" => "self", "href" => $_SERVER['REQUEST_URI'])); + + parent::export($file); + } +} \ No newline at end of file diff --git a/includes/kohana/modules/xml/classes/xml/driver/rss2.php b/includes/kohana/modules/xml/classes/xml/driver/rss2.php new file mode 100644 index 00000000..aa41a46e --- /dev/null +++ b/includes/kohana/modules/xml/classes/xml/driver/rss2.php @@ -0,0 +1,40 @@ + + * + * Description: + * RSS2 driver + */ + +class XML_Driver_Rss2 extends XML +{ + public $root_node = 'rss'; + + protected static function initialize(XML_Meta $meta) + { + $meta ->content_type("application/rss+xml") + ->nodes ( + array( + "rss" => array("attributes" => array("version" => "2.0")), + "link" => array("filter" => "normalize_uri"), + "docs" => array("filter" => "normalize_uri"), + "guid" => array("filter" => "normalize_uri"), + "pubDate" => array("filter" => "normalize_date"), + "lastBuildDate" => array("filter" => "normalize_date"), + ) + ); + } + + public function normalize_date($value) + { + if ( ! is_numeric($value)) + { + $value = strtotime($value); + } + + // Convert timestamps to RFC 822 formatted dates + return date(DATE_RFC822, $value); + } +} \ No newline at end of file diff --git a/includes/kohana/modules/xml/classes/xml/driver/xrds.php b/includes/kohana/modules/xml/classes/xml/driver/xrds.php new file mode 100644 index 00000000..4b52470f --- /dev/null +++ b/includes/kohana/modules/xml/classes/xml/driver/xrds.php @@ -0,0 +1,57 @@ + + * + * Description: + * XRDS driver. For Service Discovery. + */ + + class XML_Driver_XRDS extends XML + { + public $root_node = 'XRDS'; + + + protected static function initialize(XML_Meta $meta) + { + $meta ->content_type("application/xrds+xml") + ->nodes ( + array( + "XRDS" => array("namespace" => 'xri://$xrds', "prefix" => "xrds", "attributes" => array("xmlns" => 'xri://$xrd*($v*2.0)')), + "LocalID" => array("filter" => "normalize_uri"), + "Delegate" => array("filter" => "normalize_uri", "namespace" => "http://openid.net/xmlns/1.0", "prefix" => "openid"), + "URI" => array("filter" => "normalize_uri"), + ) + ); + } + + + public function add_service($type, $uri, $priority = NULL) + { + if (! is_null($priority)) + { + $priority = array("priority" => $priority); + } + else + { + $priority = array(); + } + + $service_node = $this->add_node("Service", NULL, $priority); + + if (! is_array($type)) + { + $type = array($type); + } + + foreach ($type as $t) + { + $service_node->add_node("Type", $t); + } + $service_node->add_node("URI", $uri); + + return $service_node; + } + +} \ No newline at end of file diff --git a/includes/kohana/modules/xml/classes/xml/meta.php b/includes/kohana/modules/xml/classes/xml/meta.php new file mode 100644 index 00000000..35df69dc --- /dev/null +++ b/includes/kohana/modules/xml/classes/xml/meta.php @@ -0,0 +1,12 @@ + + * + * Description: + * XML_Meta class. Use this to override XML_Meta_Core + */ + + class XML_Meta extends XML_Meta_Core + {} diff --git a/includes/kohana/modules/xml/classes/xml/meta/core.php b/includes/kohana/modules/xml/classes/xml/meta/core.php new file mode 100644 index 00000000..ddb555e3 --- /dev/null +++ b/includes/kohana/modules/xml/classes/xml/meta/core.php @@ -0,0 +1,220 @@ + + * + * Description: + * XML_Meta_Core class. This class contains XML drivers metadata + */ + + class XML_Meta_Core + { + /** + * @var array assoc array alias => $node_name + * This is used to abstract the node names + */ + protected $nodes = array(); + + /** + * @var array whole node configuration array + * array("node_name" => array( + * // Effective node name in the XML document. + * // This name is abstracted and "node_name" is always used when dealing with the object. + * "node" => "effective_node_name", + * // Defines a namespace URI for this node + * "namespace" => "http://www.namespace.uri", + * // Defines a prefix for the namespace above. If not defined, namespace is interpreted as a default namespace + * "prefix" => "ns", + * // Defines a callback function to filter/normalize the value + * "filter" => "filter_function_name", + * // Array of attributes + * "attributes" => array("default_attribute1" => "value") + * ), + * "alias" => "node_name", + * ) + */ + protected $nodes_config = array(); + + /** + * @var string content type for HTML headers + */ + protected $content_type; + + /** + * @var boolean whether the object is initialized + */ + protected $_initialized = FALSE; + + + + /** + * Returns the name of a node, sort out aliases + * @param string $name + * @return string $node_name + */ + public function alias($name) + { + if (isset($this->nodes_config[$name])) + { + if ( ! is_array($this->nodes_config[$name])) + { + $name = $this->nodes_config[$name]; + } + } + + return Arr::get($this->nodes, $name, $name); + } + + + /** + * Return namespace config for a given node + * @param string $name + * @return mixed array(uri, prefix) or NULL + */ + public function ns($name) + { + $name = $this->alias($name); + + if (isset($this->nodes_config[$name]) AND is_array($this->nodes_config[$name]) AND array_key_exists("namespace", $this->nodes_config[$name])) + { + return array($this->nodes_config[$name]["namespace"], Arr::get($this->nodes_config[$name], "prefix", NULL)); + } + return NULL; + } + + + /** + * Return default attributes for a given node + * @param string $name + * @return mixed attributes assoc array() or NULL + */ + public function attributes($name) + { + $name = $this->alias($name); + + if (isset($this->nodes_config[$name]) AND is_array($this->nodes_config[$name]) AND array_key_exists("attributes", $this->nodes_config[$name])) + { + return $this->nodes_config[$name]["attributes"]; + } + return NULL; + } + + + /** + * Return user-defined value filter function for a given node + * @param string $name + * @return mixed function name or NULL + */ + public function filter($name) + { + $name = $this->alias($name); + + if (isset($this->nodes_config[$name]) AND is_array($this->nodes_config[$name]) AND array_key_exists("filter", $this->nodes_config[$name])) + { + return $this->nodes_config[$name]["filter"]; + } + return NULL; + } + + + /** + * Set nodes config attribute + * Use it this way : + * nodes(array("node_name" => array("namespace" => "http://www.namespace.uri", "prefix" => "ns", "filter" => "filter_function_name", "attributes" => array("default_attribute1" => "value")))), + * OR to set up node alias names : + * nodes(array("alias" => "node_name")); + * + * @param array $nodes array formatted as mentionned above + * @param bool $overwrite [optional] Overwrite current values if they are set ? + * @return object $this + */ + public function nodes(Array $nodes) + { + $this->nodes_config = $this->_initialized ? + array_merge($nodes, $this->nodes_config) : + array_merge($this->nodes_config, $nodes); + + $this->generate_nodes_map(); + + return $this; + } + + + /** + * Sets the content type for headers + * @param string $type + * @return object $this + */ + public function content_type($type = NULL) + { + if ($type) + { + $this->content_type = $this->_initialized ? + $type : + $this->content_type ? + $this->content_type : + $type; + } + else + { + return $this->content_type; + } + + return $this; + } + + + /** + * Returns the key name corresponding to a node name + * This is used when using as_array(), to return array keys corresponding to the node names + * @param object $node_name + * @return + */ + public function key($node_name) + { + // Extract the name if it is prefixed + $expl = explode(":", $node_name); + $node_name = count($expl) > 1 ? end($expl) : current($expl); + + if (in_array($node_name, $this->nodes)) + { + return current(array_keys($this->nodes, $node_name)); + } + return $node_name; + } + + + /** + * Generates - or re-generates the node map + * @return object $this + */ + public function generate_nodes_map() + { + $map = array(); + foreach ($this->nodes_config as $key => $config) + { + if (is_array($config)) + { + if (isset ($config["node"])) + { + $map[$key] = $config["node"]; + } + } + } + $this->nodes = $map; + return $this; + } + + /** + * Reports the Meta as initialized. + * This basically allows Meta methods to overwrite existing value, if they are called explicitely + * @return object $this + */ + public function set_initialized() + { + $this->_initialized = TRUE; + return $this; + } + +} \ No newline at end of file