Added Kohana XML module

This commit is contained in:
Deon George 2010-08-21 13:45:09 +10:00
parent 0d35d89971
commit 3edca53599
9 changed files with 1230 additions and 0 deletions

View File

@ -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.

View File

@ -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 authors name, this regardless of your driver
configuration. As another example of node name abstraction, if youve 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

View File

@ -0,0 +1,13 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Document : xml.php
* Created on : 1 mai 2009, 13:03:03
* @author Cedric de Saint Leger <c.desaintleger@gmail.com>
*
* 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
{}

View File

@ -0,0 +1,727 @@
<?php defined('SYSPATH') OR die('No direct access allowed.');
/**
* Document : core.php
* Created on : 1 mai 2009, 13:03:03
* @author Cedric de Saint Leger <c.desaintleger@gmail.com>
*
* 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 :
* <node_name attr_name="val">
* <child_node_name>
* value1
* </child_node_name>
* <child_node_name>
* value2
* </child_node_name>
* </node_name>
*
* 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", "<feed><bla>bla</bla></feed>");
* $xml2 = XML::factory("rss", "<test></test>");
* $node_xml2 = $xml2->add_node("key");
*
* // outputs "<test><key><feed><bla>bla</bla></feed></key></test>"
* $node_xml2->import($xml1)->render();
*
* // outputs "<feed><bla>bla</bla></feed><key><feed><bla>bla</bla></feed></key>"
* $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

View File

@ -0,0 +1,108 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Document : atom.php
* Created on : 1 mai 2009, 13:03:03
* @author Cedric de Saint Leger <c.desaintleger@gmail.com>
*
* 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);
}
}

View File

@ -0,0 +1,40 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Document : rss2.php
* Created on : 1 mai 2009, 13:03:03
* @author Cedric de Saint Leger <c.desaintleger@gmail.com>
*
* 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);
}
}

View File

@ -0,0 +1,57 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Document : xrds.php
* Created on : 1 mai 2009, 13:03:03
* @author Cedric de Saint Leger <c.desaintleger@gmail.com>
*
* 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;
}
}

View File

@ -0,0 +1,12 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Document : meta.php
* Created on : 1 mai 2009, 13:03:03
* @author Cedric de Saint Leger <c.desaintleger@gmail.com>
*
* Description:
* XML_Meta class. Use this to override XML_Meta_Core
*/
class XML_Meta extends XML_Meta_Core
{}

View File

@ -0,0 +1,220 @@
<?php defined('SYSPATH') or die('No direct script access.');
/**
* Document : meta.php
* Created on : 1 mai 2009, 13:03:03
* @author Cedric de Saint Leger <c.desaintleger@gmail.com>
*
* 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;
}
}