FluentDOM
[ class tree: FluentDOM ] [ index: FluentDOM ] [ all elements ]

Source for file Core.php

Documentation is available at Core.php

  1. <?php
  2. /**
  3. * FluentDOMCore implements the core and interface functions for FluentDOM
  4. *
  5. @version $Id: Core.php 441 2010-04-16 18:24:08Z subjective $
  6. @license http://www.opensource.org/licenses/mit-license.php The MIT License
  7. @copyright Copyright (c) 2009 Bastian Feder, Thomas Weinert
  8. *
  9. @tutorial FluentDOM.pkg
  10. @package FluentDOM
  11. */
  12.  
  13. /**
  14. * Include the external iterator class.
  15. */
  16. require_once(dirname(__FILE__).'/Iterator.php');
  17. /**
  18. * Include the loader interface.
  19. */
  20. require_once(dirname(__FILE__).'/Loader.php');
  21. /**
  22. * Include the handler class.
  23. */
  24. require_once(dirname(__FILE__).'/Handler.php');
  25.  
  26. /**
  27. * FluentDOMCore implements the core and interface functions for FluentDOM
  28. *
  29. @property string $contentType Output type - text/xml or text/html
  30. @property-read integer $length The amount of elements found by selector.
  31. @property-read DOMDocument $document Internal DOMDocument object
  32. @property-read DOMXPath $xpath Internal XPath object
  33. *
  34. @package FluentDOM
  35. */
  36. class FluentDOMCore implements IteratorAggregateCountableArrayAccess {
  37.  
  38.   /**
  39.   * Associated DOMDocument object.
  40.   * @var DOMDocument $_document 
  41.   */
  42.   protected $_document = NULL;
  43.  
  44.   /**
  45.   * XPath object used to execute selectors
  46.   * @var DOMXPath $_xpath 
  47.   */
  48.   protected $_xpath = NULL;
  49.  
  50.   /**
  51.   * List of namespaces to be registered for xpath expressions
  52.   * @var array 
  53.   */
  54.   protected $_namespaces = array();
  55.  
  56.   /**
  57.   * Use document context for expression (not selected nodes).
  58.   * @var boolean $_useDocumentContext 
  59.   */
  60.   protected $_useDocumentContext = TRUE;
  61.  
  62.   /**
  63.   * Content type for output (xml, text/xml, html, text/html).
  64.   * @var string $_contentType 
  65.   */
  66.   protected $_contentType = 'text/xml';
  67.  
  68.   /**
  69.   * Parent FluentDOM object (previous selection in chain).
  70.   * @var FluentDOM $_parent 
  71.   */
  72.   protected $_parent = NULL;
  73.  
  74.   /**
  75.   * Seleted element and text nodes
  76.   * @var array $_array 
  77.   */
  78.   protected $_array = array();
  79.  
  80.   /**
  81.   * Document loader list.
  82.   *
  83.   * @see _initLoaders
  84.   * @see _setLoader
  85.   *
  86.   * @var array $_loaders 
  87.   */
  88.   protected $_loaders = NULL;
  89.  
  90.   /**
  91.   * Constructor
  92.   *
  93.   * @return FluentDOM 
  94.   */
  95.   public function __construct({
  96.     $this->_document = new DOMDocument();
  97.   }
  98.  
  99.   /**
  100.   * Load a $source. The type of the source depends on the loaders. If no explicit loaders are set
  101.   * FluentDOM will use a set of default loaders for xml/html and DOM.
  102.   *
  103.   * @param mixed $source 
  104.   * @param string $contentType optional, default value 'text/xml'
  105.   */
  106.   public function load($source$contentType 'text/xml'{
  107.     $this->_array = array();
  108.     $this->_setContentType($contentType);
  109.     if ($source instanceof FluentDOMCore{
  110.       $this->_useDocumentContext = FALSE;
  111.       $this->_document = $source->document;
  112.       $this->_xpath = $source->_xpath;
  113.       $this->_contentType = $source->_contentType;
  114.       $this->_parent = $source;
  115.       return $this;
  116.     else {
  117.       $this->_parent = NULL;
  118.       $this->_initLoaders();
  119.       foreach ($this->_loaders as $loader{
  120.         if ($loaded $loader->load($source$this->_contentType)) {
  121.           if ($loaded instanceof DOMDocument{
  122.             $this->_useDocumentContext = TRUE;
  123.             $this->_document = $loaded;
  124.           elseif (is_array($loaded&&
  125.                     isset($loaded[0]&&
  126.                     isset($loaded[1]&&
  127.                     $loaded[0instanceof DOMDocument &&
  128.                     is_array($loaded[1])) {
  129.             $this->_document = $loaded[0];
  130.             $this->push($loaded[1]);
  131.             $this->_useDocumentContext = FALSE;
  132.           }
  133.           return $this;
  134.         }
  135.       }
  136.       throw new InvalidArgumentException('Invalid source object.');
  137.     }
  138.     return $this;
  139.   }
  140.  
  141.   /**
  142.   * Initialize default loaders if they are not already initialized
  143.   *
  144.   * @return void 
  145.   */
  146.   protected function _initLoaders({
  147.     if (!is_array($this->_loaders)) {
  148.       $path dirname(__FILE__).'/';
  149.       include_once($path.'/Loader/DOMNode.php');
  150.       include_once($path.'/Loader/DOMDocument.php');
  151.       include_once($path.'/Loader/StringXML.php');
  152.       include_once($path.'/Loader/FileXML.php');
  153.       include_once($path.'/Loader/StringHTML.php');
  154.       include_once($path.'/Loader/FileHTML.php');
  155.       $this->_loaders = array(
  156.         new FluentDOMLoaderDOMNode(),
  157.         new FluentDOMLoaderDOMDocument(),
  158.         new FluentDOMLoaderStringXML(),
  159.         new FluentDOMLoaderFileXML(),
  160.         new FluentDOMLoaderStringHTML(),
  161.         new FluentDOMLoaderFileHTML(),
  162.       );
  163.     }
  164.   }
  165.  
  166.   /**
  167.   * Define own loading handlers
  168.   *
  169.   * @example iniloader/iniToXML.php Usage Example: Own loader object
  170.   * @param $loaders 
  171.   * @return FluentDOM 
  172.   */
  173.   public function setLoaders($loaders{
  174.     foreach ($loaders as $loader{
  175.       if (!($loader instanceof FluentDOMLoader)) {
  176.         throw new InvalidArgumentException('Array contains invalid loader object');
  177.       }
  178.     }
  179.     $this->_loaders = $loaders;
  180.     return $this;
  181.   }
  182.  
  183.   /**
  184.   * Setter for FluentDOM::_contentType property
  185.   *
  186.   * @param string $value 
  187.   * @return void 
  188.   */
  189.   protected function _setContentType($value{
  190.     switch (strtolower($value)) {
  191.     case 'xml' :
  192.     case 'application/xml' :
  193.     case 'text/xml' :
  194.       $newContentType 'text/xml';
  195.       break;
  196.     case 'html' :
  197.     case 'text/html' :
  198.       $newContentType 'text/html';
  199.       break;
  200.     default :
  201.       throw new UnexpectedValueException('Invalid content type value');
  202.     }
  203.     if ($this->_contentType != $newContentType{
  204.       $this->_contentType = $newContentType;
  205.       if (isset($this->_parent)) {
  206.         $this->_parent->contentType $newContentType;
  207.       }
  208.     }
  209.   }
  210.  
  211.   /**
  212.   * implement dynamic properties using magic methods
  213.   *
  214.   * @param string $name 
  215.   * @return mixed 
  216.   */
  217.   public function __get($name{
  218.     switch ($name{
  219.     case 'contentType' :
  220.       return $this->_contentType;
  221.     case 'document' :
  222.       return $this->_document;
  223.     case 'length' :
  224.       return count($this->_array);
  225.     case 'xpath' :
  226.       return $this->_xpath();
  227.     default :
  228.       return NULL;
  229.     }
  230.   }
  231.  
  232.   /**
  233.   * block changes of dynamic readonly property length
  234.   *
  235.   * @param string $name 
  236.   * @param mixed $value 
  237.   * @return void 
  238.   */
  239.   public function __set($name$value{
  240.     switch ($name{
  241.     case 'contentType' :
  242.       $this->_setContentType($value);
  243.       break;
  244.     case 'document' :
  245.     case 'length' :
  246.     case 'xpath' :
  247.       throw new BadMethodCallException('Can not set readonly value.');
  248.     default :
  249.       $this->$name $value;
  250.       break;
  251.     }
  252.   }
  253.  
  254.   /**
  255.   * support isset for dynamic properties length and document
  256.   *
  257.   * @param string $name 
  258.   * @return boolean 
  259.   */
  260.   public function __isset($name{
  261.     switch ($name{
  262.     case 'length' :
  263.     case 'xpath' :
  264.     case 'contentType' :
  265.       return TRUE;
  266.     case 'document' :
  267.       return isset($this->_document);
  268.     }
  269.     return FALSE;
  270.   }
  271.  
  272.  
  273.   /**
  274.   * Return the XML output of the internal dom document
  275.   *
  276.   * @return string 
  277.   */
  278.   public function __toString({
  279.     switch ($this->_contentType{
  280.     case 'html' :
  281.     case 'text/html' :
  282.       return $this->_document->saveHTML();
  283.     default :
  284.       return $this->_document->saveXML();
  285.     }
  286.   }
  287.  
  288.   /**
  289.   * The item() method is used to access elements in the node list,
  290.   * like in a DOMNodelist.
  291.   *
  292.   * @param integer $position 
  293.   * @return DOMNode 
  294.   */
  295.   public function item($position{
  296.     if (isset($this->_array[$position])) {
  297.       return $this->_array[$position];
  298.     }
  299.     return NULL;
  300.   }
  301.  
  302.   /**
  303.   * Formats the current document, resets internal node array and other properties.
  304.   *
  305.   * The document is saved and reloaded, all variables with DOMNodes
  306.   * of this document will get invalid.
  307.   *
  308.   * @return FluentDOM 
  309.   */
  310.   public function formatOutput($contentType NULL{
  311.     if (isset($contentType)) {
  312.       $this->_setContentType($contentType);
  313.     }
  314.     $this->_array = array();
  315.     $this->_position 0;
  316.     $this->_useDocumentContext = TRUE;
  317.     $this->_parent = NULL;
  318.     $this->_document->preserveWhiteSpace FALSE;
  319.     $this->_document->formatOutput TRUE;
  320.     if (!empty($this->_document->documentElement)) {
  321.       $this->_document->loadXML($this->_document->saveXML());
  322.     }
  323.     return $this;
  324.   }
  325.  
  326.   /*
  327.   * Interface - IteratorAggregate
  328.   */
  329.  
  330.   /**
  331.   * Get an iterator for this object.
  332.   *
  333.   * @example interfaces/Iterator.php Usage Example: Iterator Interface
  334.   * @example interfaces/RecursiveIterator.php Usage Example: Recursive Iterator Interface
  335.   * @return FluentDOMIterator 
  336.   */
  337.   public function getIterator({
  338.     return new FluentDOMIterator($this);
  339.   }
  340.  
  341.   /*
  342.   * Interface - Countable
  343.   */
  344.  
  345.   /**
  346.   * Get element count (Countable interface)
  347.   *
  348.   * @example interfaces/Countable.php Usage Example: Countable Interface
  349.   * @return integer 
  350.   */
  351.   public function count({
  352.     return count($this->_array);
  353.   }
  354.  
  355.   /*
  356.   * Interface - ArrayAccess
  357.   */
  358.  
  359.   /**
  360.   * If somebody tries to modify the internal array throw an exception.
  361.   *
  362.   * @example interfaces/ArrayAccess.php Usage Example: ArrayAccess Interface
  363.   * @param integer $offset 
  364.   * @param mixed $value 
  365.   * @return void 
  366.   */
  367.   public function offsetSet($offset$value{
  368.     throw new BadMethodCallException('List is read only');
  369.   }
  370.  
  371.   /**
  372.   * Check if index exists in internal array
  373.   *
  374.   * @example interfaces/ArrayAccess.php Usage Example: ArrayAccess Interface
  375.   * @param integer $offset 
  376.   * @return boolean 
  377.   */
  378.   public function offsetExists($offset{
  379.     return isset($this->_array[$offset]);
  380.   }
  381.  
  382.   /**
  383.   * If somebody tries to remove an element from the internal array throw an exception.
  384.   *
  385.   * @example interfaces/ArrayAccess.php Usage Example: ArrayAccess Interface
  386.   * @param integer $offset 
  387.   * @return void 
  388.   */
  389.   public function offsetUnset($offset{
  390.     throw new BadMethodCallException('List is read only');
  391.   }
  392.  
  393.   /**
  394.   * Get element from internal array
  395.   *
  396.   * @example interfaces/ArrayAccess.php Usage Example: ArrayAccess Interface
  397.   * @param integer $offset 
  398.   * @return DOMNode|NULL
  399.   */
  400.   public function offsetGet($offset{
  401.     return isset($this->_array[$offset]$this->_array[$offsetNULL;
  402.   }
  403.  
  404.   /*
  405.   * Core functions
  406.   */
  407.  
  408.   /**
  409.   * Create a new instance of the same class with $this as the parent. This is used for the chaining.
  410.   *
  411.   * @return FluentDOM 
  412.   */
  413.   public function spawn({
  414.     $className get_class($this);
  415.     $result new $className();
  416.     $result->_namespaces $this->_namespaces;
  417.     return $result->load($this);
  418.   }
  419.  
  420.   /**
  421.   * Push new element(s) an the internal element list
  422.   *
  423.   * @uses _inList
  424.   * @param DOMNode|DOMNodeList|FluentDOM$elements 
  425.   * @param boolean $ignoreTextNodes ignore text nodes
  426.   * @return void 
  427.   */
  428.   public function push($elements$ignoreTextNodes FALSE{
  429.     if ($this->_isNode($elements$ignoreTextNodes)) {
  430.       $elements array($elements);
  431.     }
  432.     if ($this->_isNodeList($elements)) {
  433.       foreach ($elements as $index => $node{
  434.         if ($this->_isNode($node$ignoreTextNodes)) {
  435.           if ($node->ownerDocument === $this->_document{
  436.             $this->_array[$node;
  437.           else {
  438.             throw new OutOfBoundsException(
  439.               sprintf(
  440.                 'Node #%d is not a part of this document'$index
  441.               )
  442.             );
  443.           }
  444.         }
  445.       }
  446.     elseif (!is_null($elements)) {
  447.       throw new InvalidArgumentException('Invalid elements variable.');
  448.     }
  449.   }
  450.  
  451.   /**
  452.   * Sorts an array of DOM nodes based on document position, in place, with the duplicates removed.
  453.   * Note that this only works on arrays of DOM nodes, not strings or numbers.
  454.   *
  455.   * @param array $array array of DOM nodes
  456.   * @return array 
  457.   */
  458.   public function unique(array $array{
  459.     $sortable array();
  460.     $unsortable array();
  461.     foreach ($array as $node{
  462.       if (!($node instanceof DOMNode)) {
  463.         throw new InvalidArgumentException(
  464.           sprintf(
  465.             'Array must only contain dom nodes, found "%s".',
  466.             is_object($nodeget_class($nodegettype($node)
  467.           )
  468.         );
  469.       }
  470.       if (isset($node->parentNode||
  471.           $node === $node->ownerDocument->documentElement{
  472.         $position = (integer)$this->_xpath()->evaluate('count(preceding::node())'$node);
  473.         /* use the document position as index, ignore duplicates */
  474.         if (!isset($sortable[$position])) {
  475.           $sortable[$position$node;
  476.         }
  477.       else {
  478.         $hash spl_object_hash($node);
  479.         /* use the object hash as index, ignore duplicates */
  480.         if (!isset($unsortable[$hash])) {
  481.           $unsortable[$hash$node;
  482.         }
  483.       }
  484.     }
  485.     ksort($sortableSORT_NUMERIC);
  486.     $result array_values($sortable);
  487.     array_splice($resultcount($result)0array_values($unsortable));
  488.     return $result;
  489.   }
  490.  
  491.   /**
  492.   * Sorts the selected nodes, with the duplicates removed.
  493.   *
  494.   * @uses FluentDOMCore::unique
  495.   *
  496.   * @param array $array array of DOM nodes
  497.   * @return array 
  498.   */
  499.   protected function _uniqueSort({
  500.     $this->_array $this->unique($this->_array);
  501.   }
  502.  
  503.   /**
  504.   * Gives access to an xpath evaluate on the current document
  505.   *
  506.   * @param string $expr 
  507.   * @param DOMNode $context 
  508.   */
  509.   public function evaluate($exprDOMNode $context NULL{
  510.     if (isset($context)) {
  511.       return $this->_xpath()->evaluate($expr$context);
  512.     else {
  513.       return $this->_xpath()->evaluate($expr);
  514.     }
  515.   }
  516.  
  517.   /**
  518.   * Register namespaces and or get namespaces
  519.   *
  520.   * @param array $namespaces If this parameter is empty the current namespaces are returned
  521.   * @return array|FluentDOMCore
  522.   */
  523.   public function namespaces(array $namespaces NULL{
  524.     if (is_null($namespaces)) {
  525.       return $this->_namespaces;
  526.     }
  527.     foreach ($namespaces as $prefix => $uri{
  528.       if ($this->_isNCName($prefix)) {
  529.         $this->_xpath()->registerNamespace($prefix$uri);
  530.         $this->_namespaces[$prefix$uri;
  531.       }
  532.     }
  533.     return $this;
  534.   }
  535.  
  536.   /**
  537.   * Get a XPath object associated with the internal DOMDocument and register
  538.   * default namespaces from the document element if availiable.
  539.   *
  540.   * @return DOMXPath 
  541.   */
  542.   protected function _xpath({
  543.     if (empty($this->_xpath|| $this->_xpath->document !== $this->_document{
  544.       $this->_xpath = new DOMXPath($this->_document);
  545.       foreach ($this->_namespaces as $prefix => $uri{
  546.         $this->_xpath->registerNamespace($prefix$uri);
  547.       }
  548.       if ($this->_document->documentElement{
  549.         $uri $this->_document->documentElement->lookupnamespaceURI('_');
  550.         if (!isset($uri)) {
  551.           $uri $this->_document->documentElement->lookupnamespaceURI(NULL);
  552.           if (isset($uri)) {
  553.             $this->_xpath->registerNamespace('_'$uri);
  554.           }
  555.         }
  556.       }
  557.     }
  558.     return $this->_xpath;
  559.   }
  560.  
  561.   /**
  562.   * Match XPath expression agains context and return matched elements.
  563.   *
  564.   * @param string $expr 
  565.   * @param DOMNode $context optional, default value NULL
  566.   * @return DOMNodeList 
  567.   */
  568.   protected function _match($expr$context NULL{
  569.     if (isset($context)) {
  570.       return $this->_xpath()->query($expr$context);
  571.     else {
  572.       return $this->_xpath()->query($expr);
  573.     }
  574.   }
  575.  
  576.   /**
  577.   * Test xpath expression against context and return true/false
  578.   *
  579.   * @param string $expr 
  580.   * @param DOMNode $context optional, default value NULL
  581.   * @return boolean 
  582.   */
  583.   protected function _test($expr$context NULL{
  584.     if (isset($context)) {
  585.       $check $this->_xpath()->evaluate($expr$context);
  586.     else {
  587.       $check $this->_xpath()->evaluate($expr);
  588.     }
  589.     if ($check instanceof DOMNodeList{
  590.       return $check->length 0;
  591.     else {
  592.       return (bool)$check;
  593.     }
  594.   }
  595.  
  596.   /**
  597.   * Check if object is already in internal list
  598.   *
  599.   * @param DOMNode $node 
  600.   * @return boolean 
  601.   */
  602.   protected function _inList($node{
  603.     foreach ($this->_array as $compareNode{
  604.       if ($compareNode === $node{
  605.         return TRUE;
  606.       }
  607.     }
  608.     return FALSE;
  609.   }
  610.  
  611.   /**
  612.   * Validate string as qualified node name
  613.   *
  614.   * @param string $name 
  615.   * @return boolean 
  616.   */
  617.   protected function _isQName($name{
  618.     if (empty($name)) {
  619.       throw new UnexpectedValueException('Invalid QName: QName is empty.');
  620.     elseif (FALSE !== ($position strpos($name':'))) {
  621.       $this->_isNCName($name0$position);
  622.       $this->_isNCName($name$position 1);
  623.       return TRUE;
  624.     }
  625.     $this->_isNCName($name);
  626.     return TRUE;
  627.   }
  628.  
  629.   /**
  630.   * Validate string as qualified node name part (namespace or local name)
  631.   *
  632.   * @param string $name full QName
  633.   * @param integer $offset Offset of NCName part in QName
  634.   * @param integer $length Length of NCName part in QName
  635.   * @return boolean 
  636.   */
  637.   protected function _isNCName($name$offset 0$length 0{
  638.     $nameStartChar =
  639.        'A-Z_a-z'.
  640.        '\\x{C0}-\\x{D6}\\x{D8}-\\x{F6}\\x{F8}-\\x{2FF}\\x{370}-\\x{37D}'.
  641.        '\\x{37F}-\\x{1FFF}\\x{200C}-\\x{200D}\\x{2070}-\\x{218F}'.
  642.        '\\x{2C00}-\\x{2FEF}\\x{3001}-\\x{D7FF}\\x{F900}-\\x{FDCF}'.
  643.        '\\x{FDF0}-\\x{FFFD}\\x{10000}-\\x{EFFFF}';
  644.     $nameChar =
  645.        $nameStartChar.
  646.        '\\.\\d\\x{B7}\\x{300}-\\x{36F}\\x{203F}-\\x{2040}';
  647.     if ($length 0{
  648.       $namePart substr($name$offset$length);
  649.     elseif ($offset 0{
  650.       $namePart substr($name$offset);
  651.     else {
  652.       $namePart $name;
  653.     }
  654.     if (empty($namePart)) {
  655.       throw new UnexpectedValueException(
  656.         'Invalid QName "'.$name.'": Missing QName part.'
  657.       );
  658.     elseif (preg_match('([^'.$nameChar.'-])u'$namePart$matchPREG_OFFSET_CAPTURE)) {
  659.       //invalid bytes and whitespaces
  660.       $position = (int)$match[0][1];
  661.       throw new UnexpectedValueException(
  662.         'Invalid QName "'.$name.'": Invalid character at index '.($offset $position).'.'
  663.       );
  664.     elseif (preg_match('(^[^'.$nameStartChar.'])u'$namePart)) {
  665.       //first char is a little more limited
  666.       throw new UnexpectedValueException(
  667.         'Invalid QName "'.$name.'": Invalid character at index '.$offset.'.'
  668.       );
  669.     }
  670.     return TRUE;
  671.   }
  672.  
  673.   /**
  674.   * Check if the DOMNode is DOMElement or DOMText with content
  675.   *
  676.   * @param DOMNode $node 
  677.   * @param boolean $ignoreTextNodes 
  678.   * @return boolean 
  679.   */
  680.   protected function _isNode($node$ignoreTextNodes FALSE{
  681.     if (is_object($node)) {
  682.       if ($node instanceof DOMElement{
  683.         return TRUE;
  684.       elseif ($node instanceof DOMText{
  685.         if (!$ignoreTextNodes &&
  686.             !$node->isWhitespaceInElementContent()) {
  687.           return TRUE;
  688.         }
  689.       }
  690.     }
  691.     return FALSE;
  692.   }
  693.  
  694.   /**
  695.   * Check if $elements is a iterateable node list
  696.   *
  697.   * @param DOMNodeList|DOMDocumentFragment|Iterator|IteratorAggregate|array$list 
  698.   * @return boolean 
  699.   */
  700.   protected function _isNodeList($elements{
  701.     if ($elements instanceof DOMNodeList ||
  702.         $elements instanceof DOMDocumentFragment ||
  703.         $elements instanceof Iterator ||
  704.         $elements instanceof IteratorAggregate ||
  705.         is_array($elements)) {
  706.       return TRUE;
  707.     }
  708.     return FALSE;
  709.   }
  710.  
  711.   /**
  712.   * check if parameter is a valid callback function
  713.   *
  714.   * @param callback $callback 
  715.   * @param boolean $allowGlobalFunctions 
  716.   * @param boolean $silent (no InvalidArgumentException)
  717.   * @return boolean 
  718.   */
  719.   protected function _isCallback($callback$allowGlobalFunctions$silent{
  720.     if ($callback instanceof Closure{
  721.       return TRUE;
  722.     elseif (is_string($callback&&
  723.               $allowGlobalFunctions &&
  724.               function_exists($callback)) {
  725.       return is_callable($callback);
  726.     elseif (is_array($callback&&
  727.               count($callback== &&
  728.               (is_object($callback[0]|| is_string($callback[0])) &&
  729.               is_string($callback[1])) {
  730.       return is_callable($callback);
  731.     elseif ($silent{
  732.       return FALSE;
  733.     else {
  734.       throw new InvalidArgumentException('Invalid callback argument');
  735.     }
  736.   }
  737.  
  738.   /**
  739.   * Convert a given content xml string into and array of nodes
  740.   *
  741.   * @param string $content 
  742.   * @param boolean $includeTextNodes 
  743.   * @param integer $limit 
  744.   * @return array 
  745.   */
  746.   protected function _getContentFragment($content$includeTextNodes TRUE$limit 0{
  747.     $result array();
  748.     $fragment $this->_document->createDocumentFragment();
  749.     if ($fragment->appendXML($content)) {
  750.       for ($i $fragment->childNodes->length 1$i >= 0$i--{
  751.         $element $fragment->childNodes->item($i);
  752.         if ($element instanceof DOMElement ||
  753.             ($includeTextNodes && $this->_isNode($element))) {
  754.           array_unshift($result$element);
  755.           $element->parentNode->removeChild($element);
  756.         }
  757.       }
  758.       if ($limit && count($result>= $limit{
  759.         return array_slice($result0$limit);
  760.       }
  761.       return $result;
  762.     else {
  763.       throw new UnexpectedValueException('Invalid document fragment');
  764.     }
  765.   }
  766.  
  767.   /**
  768.   * Convert a given content into and array of nodes
  769.   *
  770.   * @param string|array|DOMElement|DOMText|Iterator$content 
  771.   * @param boolean $includeTextNodes 
  772.   * @param integer $limit 
  773.   * @return array 
  774.   */
  775.   protected function _getContentNodes($content$includeTextNodes TRUE$limit 0{
  776.     $result array();
  777.     if ($content instanceof DOMElement{
  778.       $result array($content);
  779.     elseif ($includeTextNodes && $this->_isNode($content)) {
  780.       $result array($content);
  781.     elseif (is_string($content)) {
  782.       $result $this->_getContentFragment($content$includeTextNodes$limit);
  783.     elseif ($this->_isNodeList($content)) {
  784.       foreach ($content as $element{
  785.         if ($element instanceof DOMElement ||
  786.             ($includeTextNodes && $this->_isNode($element))) {
  787.           $result[$element;
  788.           if ($limit && count($result>= $limit{
  789.             break;
  790.           }
  791.         }
  792.       }
  793.     else {
  794.       throw new InvalidArgumentException('Invalid content parameter');
  795.     }
  796.     if (empty($result)) {
  797.       throw new UnexpectedValueException('No element found');
  798.     else {
  799.       //if a node is not in the current document import it
  800.       foreach ($result as $index => $node{
  801.         if ($node->ownerDocument !== $this->_document{
  802.           $result[$index$this->_document->importNode($nodeTRUE);
  803.         }
  804.       }
  805.     }
  806.     return $result;
  807.   }
  808.  
  809.   /**
  810.   * Convert $content to a DOMElement. If $content contains several elements use the first.
  811.   *
  812.   * @param string|array|DOMElement|DOMNodeList|Iterator$content 
  813.   * @return DOMElement 
  814.   */
  815.   protected function _getContentElement($content{
  816.     if ($content instanceof DOMElement{
  817.       return $content;
  818.     else {
  819.       $contentNodes $this->_getContentNodes($contentFALSE1);
  820.       return $contentNodes[0];
  821.     }
  822.   }
  823.  
  824.   /**
  825.   * Get the target nodes from a given $selector.
  826.   *
  827.   * A string will be used as XPath expression.
  828.   *
  829.   * @param string|array|DOMNode|DOMNodeList|Iterator$selector 
  830.   * @return array 
  831.   */
  832.   protected function _getTargetNodes($selector{
  833.     if ($this->_isNode($selector)) {
  834.       return array($selector);
  835.     elseif (is_string($selector)) {
  836.       return $this->_match($selector);
  837.     elseif ($this->_isNodeList($selector)) {
  838.       return $selector;
  839.     else {
  840.       throw new InvalidArgumentException('Invalid selector');
  841.     }
  842.   }
  843.  
  844.   /*
  845.   * the context is the target of a selector or the current selection
  846.   *
  847.   * @param string|array|DOMNode|DOMNodeList|Iterator $selector
  848.   * @return unknown_type
  849.   */
  850.   protected function _getContextNodes($selector{
  851.     if (is_null($selector)) {
  852.       return $this->_array;
  853.     else {
  854.       return $this->_getTargetNodes($selector);
  855.     }
  856.   }
  857.  
  858.   /**
  859.   * Get the inner xml of a given node or in other words the xml of all children.
  860.   * @param DOMElement $node 
  861.   * @return string 
  862.   */
  863.   protected function _getInnerXml($node{
  864.     $result '';
  865.     if ($node instanceof DOMElement{
  866.       foreach ($node->childNodes as $childNode{
  867.         if ($this->_isNode($childNode)) {
  868.           $result .= $this->_document->saveXML($childNode);
  869.         }
  870.       }
  871.     elseif ($node instanceof DOMText{
  872.       return $node->textContent;
  873.     }
  874.     return $result;
  875.   }
  876.  
  877.   /**
  878.   * Remove nodes from document tree
  879.   *
  880.   * @param string|array|DOMNode|DOMNodeList|Iterator$selector 
  881.   * @return array $result removed nodes
  882.   */
  883.   protected function _removeNodes($selector{
  884.     $targetNodes $this->_getTargetNodes($selector);
  885.     $result array();
  886.     foreach ($targetNodes as $node{
  887.       if ($node instanceof DOMNode &&
  888.           isset($node->parentNode)) {
  889.         $result[$node->parentNode->removeChild($node);
  890.       }
  891.     }
  892.     return $result;
  893.   }
  894.  
  895.   /**
  896.   * Get the class/object providing the handler functions
  897.   *
  898.   * @return string|object 
  899.   */
  900.   protected function _getHandler({
  901.     return 'FluentDOMHandler';
  902.   }
  903.  
  904.   /**
  905.   * Use a handler callback to apply a content argument to each node $targetNodes. The content
  906.   * argument can be an easy setter function
  907.   *
  908.   * @param array|DOMNodeList$targetNodes 
  909.   * @param string|array|DOMElement|DOMText|DOMNodeList|Iterator|callback|Closure$content 
  910.   * @param callback|Closure$handler 
  911.   */
  912.   protected function _applyContentToNodes($targetNodes$content$handler{
  913.     $result array();
  914.     $isEasySetterFunction $this->_isCallback($contentFALSETRUE);
  915.     if (!$isEasySetterFunction{
  916.       $contentNodes $this->_getContentNodes($content);
  917.     }
  918.     foreach ($targetNodes as $index => $node{
  919.       if ($isEasySetterFunction{
  920.         $contentNodes $this->_executeEasySetter(
  921.           $content$node$index$this->_getInnerXml($node)
  922.         );
  923.       }
  924.       if (!empty($contentNodes)) {
  925.         $resultNodes call_user_func($handler$node$contentNodes);
  926.         if (is_array($resultNodes)) {
  927.           $result array_merge($result$resultNodes);
  928.         }
  929.       }
  930.     }
  931.     return $result;
  932.   }
  933.  
  934.   /**
  935.   * Execute the easy setter function for a node and return the new elements
  936.   *
  937.   * @param callback|Closure$easySetter 
  938.   * @param DOMNode $node 
  939.   * @param integer $index 
  940.   * @param string $value 
  941.   * @return array 
  942.   */
  943.   protected function _executeEasySetter($easySetter$node$index$value{
  944.     $contentData call_user_func($easySetter$node$index$value);
  945.     if (!empty($contentData)) {
  946.       return $this->_getContentNodes($contentData);
  947.     }
  948.     return array();
  949.   }
  950. }

Documentation generated on Tue, 07 Sep 2010 01:00:36 +0200 by phpDocumentor 1.4.3