123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- <?php
- /** @file
- * QueryPath templates. See QPTPL.
- */
- /**
- * QPTPL is a template library for QueryPath.
- *
- * The QPTPL extension provides template tools that can be used in
- * conjunction with QueryPath.
- *
- * There are two basic modes in which this tool operates. Both merge data into
- * a pure HTML template. Both base their insertions on classes and IDs in the
- * HTML data. Where they differ is in the kind of data merged into the template.
- *
- * One mode takes array data and does a deep (recursive) merge into the template.
- * It can be used for simple substitutions, but it can also be used to loop through
- * "rows" of data and create tables.
- *
- * The second mode takes a classed object and introspects that object to find out
- * what CSS classes it is capable of filling. This is one way of bridging an object
- * model and QueryPath data.
- *
- * The unit tests are a good place for documentation, as is the QueryPath webste.
- *
- * @author M Butcher <matt@aleph-null.tv>
- * @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:
- * - <None defined yet>
- * @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');
|