From 51609351f2c4b5082b7e6f0744cd3811c325303f Mon Sep 17 00:00:00 2001 From: emkael Date: Tue, 11 Oct 2016 14:01:29 +0200 Subject: * initial template --- lib/querypath/Extension/QPTPL.php | 275 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 lib/querypath/Extension/QPTPL.php (limited to 'lib/querypath/Extension/QPTPL.php') diff --git a/lib/querypath/Extension/QPTPL.php b/lib/querypath/Extension/QPTPL.php new file mode 100644 index 0000000..6e47935 --- /dev/null +++ b/lib/querypath/Extension/QPTPL.php @@ -0,0 +1,275 @@ + + * @license http://opensource.org/licenses/lgpl-2.1.php LGPL or MIT-like license. + * @see QueryPathExtension + * @see QueryPathExtensionRegistry::extend() + * @see https://fedorahosted.org/querypath/wiki/QueryPathTemplate + * @ingroup querypath_extensions + */ +class QPTPL implements QueryPathExtension { + protected $qp; + public function __construct(QueryPath $qp) { + $this->qp = $qp; + } + + /** + * Apply a template to an object and then insert the results. + * + * This takes a template (an arbitrary fragment of XML/HTML) and an object + * or array and inserts the contents of the object into the template. The + * template is then appended to all of the nodes in the current list. + * + * Note that the data in the object is *not* escaped before it is merged + * into the template. For that reason, an object can return markup (as + * long as it is well-formed). + * + * @param mixed $template + * The template. It can be of any of the types that {@link qp()} supports + * natively. Typically it is a string of XML/HTML. + * @param mixed $object + * Either an object or an associative array. + * - In the case where the parameter + * is an object, this will introspect the object, looking for getters (a la + * Java bean behavior). It will then search the document for CSS classes + * that match the method name. The function is then executed and its contents + * inserted into the document. (If the function returns NULL, nothing is + * inserted.) + * - In the case where the paramter is an associative array, the function will + * look through the template for CSS classes that match the keys of the + * array. When an array key is found, the array value is inserted into the + * DOM as a child of the currently matched element(s). + * @param array $options + * The options for this function. Valid options are: + * - + * @return QueryPath + * Returns a QueryPath object with all of the changes from the template + * applied into the QueryPath elements. + * @see QueryPath::append() + */ + public function tpl($template, $object, $options = array()) { + // Handle default options here. + + //$tqp = ($template instanceof QueryPath) ? clone $template: qp($template); + $tqp = qp($template); + + if (is_array($object) || $object instanceof Traversable) { + $this->tplArrayR($tqp, $object, $options); + return $this->qp->append($tqp->top()); + } + elseif (is_object($object)) { + $this->tplObject($tqp, $object, $options); + } + + return $this->qp->append($tqp->top()); + } + + /** + * Given one template, do substitutions for all objects. + * + * Using this method, one template can be populated from a variety of + * sources. That one template is then appended to the QueryPath object. + * @see tpl() + * @param mixed $template + * The template. It can be of any of the types that {@link qp()} supports + * natively. Typically it is a string of XML/HTML. + * @param array $objects + * An indexed array containing a list of objects or arrays (See {@link tpl()}) + * that will be merged into the template. + * @param array $options + * An array of options. See {@link tpl()} for a list. + * @return QueryPath + * Returns the QueryPath object. + */ + public function tplAll($template, $objects, $options = array()) { + $tqp = qp($template, ':root'); + foreach ($objects as $object) { + if (is_array($object)) + $tqp = $this->tplArrayR($tqp, $object, $options); + elseif (is_object($object)) + $tqp = $this->tplObject($tqp, $object, $options); + } + return $this->qp->append($tqp->top()); + } + + /* + protected function tplArray($tqp, $array, $options = array()) { + + // If we find something that's not an array, we try to handle it. + if (!is_array($array)) { + is_object($array) ? $this->tplObject($tqp, $array, $options) : $tqp->append($array); + } + // An assoc array means we have mappings of classes to content. + elseif ($this->isAssoc($array)) { + print 'Assoc array found.' . PHP_EOL; + foreach ($array as $key => $value) { + $first = substr($key,0,1); + + // We allow classes and IDs if explicit. Otherwise we assume + // a class. + if ($first != '.' && $first != '#') $key = '.' . $key; + + if ($tqp->top()->find($key)->size() > 0) { + print "Value: " . $value . PHP_EOL; + if (is_array($value)) { + //$newqp = qp($tqp)->cloneAll(); + print $tqp->xml(); + $this->tplArray($tqp, $value, $options); + print "Finished recursion\n"; + } + else { + print 'QP is ' . $tqp->size() . " inserting value: " . $value . PHP_EOL; + + $tqp->append($value); + } + } + } + } + // An indexed array means we have multiple instances of class->content matches. + // We copy the portion of the template and then call repeatedly. + else { + print "Array of arrays found..\n"; + foreach ($array as $array2) { + $clone = qp($tqp->xml()); + $this->tplArray($clone, $array2, $options); + print "Now appending clone.\n" . $clone->xml(); + $tqp->append($clone->parent()); + } + } + + + //return $tqp->top(); + return $tqp; + } + */ + + /** + * Introspect objects to map their functions to CSS classes in a template. + */ + protected function tplObject($tqp, $object, $options = array()) { + $ref = new ReflectionObject($object); + $methods = $ref->getMethods(); + foreach ($methods as $method) { + if (strpos($method->getName(), 'get') === 0) { + $cssClass = $this->method2class($method->getName()); + if ($tqp->top()->find($cssClass)->size() > 0) { + $tqp->append($method->invoke($object)); + } + else { + // Revert to the find() that found something. + $tqp->end(); + } + } + } + //return $tqp->top(); + return $tqp; + } + + /** + * Recursively merge array data into a template. + */ + public function tplArrayR($qp, $array, $options = NULL) { + // If the value looks primitive, append it. + if (!is_array($array) && !($array instanceof Traversable)) { + $qp->append($array); + } + // If we are dealing with an associative array, traverse it + // and merge as we go. + elseif ($this->isAssoc($array)) { + // Do key/value substitutions + foreach ($array as $k => $v) { + + // If no dot or hash, assume class. + $first = substr($k,0,1); + if ($first != '.' && $first != '#') $k = '.' . $k; + + // If value is an array, recurse. + if (is_array($v)) { + // XXX: Not totally sure that starting at the + // top is right. Perhaps it should start + // at some other context? + $this->tplArrayR($qp->top($k), $v, $options); + } + // Otherwise, try to append value. + else { + $qp->branch()->children($k)->append($v); + } + } + } + // Otherwise we have an indexed array, and we iterate through + // it. + else { + // Get a copy of the current template and then recurse. + foreach ($array as $entry) { + $eles = $qp->get(); + $template = array(); + + // We manually deep clone the template. + foreach ($eles as $ele) { + $template = $ele->cloneNode(TRUE); + } + $tpl = qp($template); + $tpl = $this->tplArrayR($tpl, $entry, $options); + $qp->before($tpl); + } + // Remove the original template without loosing a handle to the + // newly injected one. + $dead = $qp->branch(); + $qp->parent(); + $dead->remove(); + unset($dead); + } + return $qp; + } + + /** + * Check whether an array is associative. + * If the keys of the array are not consecutive integers starting with 0, + * this will return false. + * + * @param array $array + * The array to test. + * @return Boolean + * TRUE if this is an associative array, FALSE otherwise. + */ + public function isAssoc($array) { + $i = 0; + foreach ($array as $k => $v) if ($k !== $i++) return TRUE; + // If we get here, all keys passed. + return FALSE; + } + + /** + * Convert a function name to a CSS class selector (e.g. myFunc becomes '.myFunc'). + * @param string $mname + * Method name. + * @return string + * CSS 3 Class Selector. + */ + protected function method2class($mname) { + return '.' . substr($mname, 3); + } +} +QueryPathExtensionRegistry::extend('QPTPL'); \ No newline at end of file -- cgit v1.2.3