Completed
Push — master ( 39b072...2fa9c0 )
by Thomas
02:12
created

src/FluentDOM/Nodes/Builder.php (1 issue)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Create list of nodes for a FluentDOM\Nodes object from different values
4
 *
5
 * @license http://www.opensource.org/licenses/mit-license.php The MIT License
6
 * @copyright Copyright (c) 2009-2014 Bastian Feder, Thomas Weinert
7
 */
8
9
namespace FluentDOM\Nodes {
10
11
  use FluentDOM\Constraints;
12
  use FluentDOM\Nodes;
13
  use FluentDOM\Exceptions;
14
15
  /**
16
    * Create list of nodes for a FluentDOM\Nodes object from different values
17
    */
18
  class Builder {
19
20
    /**
21
     * @var Nodes
22
     */
23
    private $_nodes;
24
25
    /**
26
     * @param Nodes $nodes
27
     */
28 32
    public function __construct(Nodes $nodes) {
29 32
      $this->_nodes = $nodes;
30 32
    }
31
32
    /**
33
     * @return Nodes
34
     */
35 23
    public function getOwner() {
36 23
      return $this->_nodes;
37
    }
38
39
    /**
40
     * @param mixed $content
41
     * @param bool $includeTextNodes
42
     * @param int $limit
43
     * @return array|\Traversable null
44
     */
45 22
    private function getNodeList(
46
      $content,
47
      $includeTextNodes = TRUE,
48
      $limit = -1
49
    ) {
50 22
      if ($callback = Constraints::isCallable($content)) {
51 1
        $content = $callback();
52 1
      }
53 22
      if ($content instanceof \DOMElement) {
54 2
        return array($content);
55 20
      } elseif ($includeTextNodes && Constraints::isNode($content)) {
56 2
        return array($content);
57 18
      } elseif (Constraints::isNodeList($content)) {
58 6
        return $this->getLimitedArray($content, $limit);
59
      }
60 12
      return NULL;
61
    }
62
63
    /**
64
     * Match selector against context and return matched elements.
65
     *
66
     * @param mixed $selector
67
     * @param \DOMNode $context optional, default value NULL
68
     * @throws \InvalidArgumentException
69
     * @return array
70
     */
71 16
    public function getTargetNodes($selector, \DOMNode $context = NULL) {
72 8
      if ($nodes = $this->getNodeList($selector)) {
73 4
        return $nodes;
74 4
      } elseif (is_string($selector)) {
75 16
        $result = $this->getOwner()->xpath(
76 3
          $this->getOwner()->prepareSelector(
77 3
            $selector,
78
            Nodes::CONTEXT_SELF
79 3
          ),
80
          $context
81 3
        );
82 3
        if (!($result instanceof \Traversable)) {
83 1
          throw new \InvalidArgumentException('Given selector did not return an node list.');
84
        }
85 2
        return iterator_to_array($result);
86
      }
87 1
      throw new \InvalidArgumentException('Invalid selector');
88
    }
89
90
    /**
91
     * Convert a given content into and array of nodes
92
     *
93
     * @param mixed $content
94
     * @param boolean $includeTextNodes
95
     * @param integer $limit
96
     * @throws Exceptions\LoadingError\EmptyResult
97
     * @return array
98
     */
99 14
    public function getContentNodes($content, $includeTextNodes = TRUE, $limit = -1) {
100 14
      $result = FALSE;
101 14
      if ($nodes = $this->getNodeList($content, $includeTextNodes, $limit)) {
102 5
        $result = $nodes;
103 14
      } elseif (is_string($content)) {
104 7
        $result = $this->getFragment($content, $this->getOwner()->contentType, $includeTextNodes, $limit);
105 7
      }
106 14
      if (!is_array($result) || empty($result)) {
107 3
        throw new Exceptions\LoadingError\EmptyResult();
108
      } else {
109
        //if a node is not in the current document import it
110 11
        $document = $this->getOwner()->getDocument();
111 11
        foreach ($result as $index => $node) {
112 11
          if ($node->ownerDocument !== $document) {
113 1
            $result[$index] = $document->importNode($node, TRUE);
114 1
          }
115 11
        }
116
      }
117 11
      return $result;
118
    }
119
120
    /**
121
     * Convert $content to a DOMElement. If $content contains several elements use the first.
122
     *
123
     * @param mixed $content
124
     * @return \DOMElement
125
     */
126 4
    public function getContentElement($content) {
127 4
      $contentNodes = $this->getContentNodes($content, FALSE, 1);
128 4
      return $contentNodes[0];
129
    }
130
131
    /**
132
     * Convert a given content string into and array of nodes
133
     *
134
     * @param string $xml
135
     * @param string $contentType
136
     * @param boolean $includeTextNodes
137
     * @param integer $limit
138
     * @throws Exceptions\InvalidFragmentLoader
139
     * @return array
140
     */
141 15
    public function getFragment($xml, $contentType = 'text/xml', $includeTextNodes = TRUE, $limit = -1) {
142 15
      $xml = $this->getContentAsString($xml);
143 13
      $loader = $this->getOwner()->loaders();
144 13
      if (!$loader->supports($contentType)) {
145 1
        throw new Exceptions\InvalidFragmentLoader(get_class($loader));
146
      }
147 12
      if (!$xml) {
148 4
        return array();
149
      }
150 8
      $result = array();
151 8
      $fragment = $loader->loadFragment(
152 8
        $xml, $contentType, $this->getOwner()->getLoadingOptions($contentType)
153 8
      );
154 8
      if ($fragment) {
155 8
        $fragment = $this->getOwner()->document->importNode($fragment, TRUE);
156 8
        for ($i = $fragment->childNodes->length - 1; $i >= 0; $i--) {
157 8
          $element = $fragment->childNodes->item($i);
158 8
          if ($element instanceof \DOMElement ||
159 8
            ($includeTextNodes && Constraints::isNode($element))) {
160 8
            array_unshift($result, $element);
161 8
            $element->parentNode->removeChild($element);
162 8
          }
163 8
        }
164 8
      }
165 8
      return $this->getLimitedArray($result, $limit);
166
    }
167
168
    /**
169
     * @param string $content
170
     * @return string bool
171
     * @throws \UnexpectedValueException
172
     */
173 15
    private function getContentAsString($content) {
174 15
      if (is_scalar($content) || method_exists($content, '__toString')) {
175 13
        $content = (string)$content;
176 13
        return ($content === '') ? FALSE : $content;
177
      }
178 2
      throw new Exceptions\LoadingError\EmptySource('Invalid document fragment');
0 ignored issues
show
The call to EmptySource::__construct() has too many arguments starting with 'Invalid document fragment'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
179
    }
180
181
    /**
182
     * Get the inner xml of a given node or in other words the xml of all children.
183
     *
184
     * @param \DOMNode $context
185
     * @return string
186
     */
187 1
    public function getInnerXml($context) {
188 1
      $result = '';
189 1
      $dom = $this->getOwner()->getDocument();
190 1
      $nodes = $this->getOwner()->xpath(
191 1
        '*|text()[normalize-space(.) != ""]|self::text()[normalize-space(.) != ""]',
192
        $context
193 1
      );
194 1
      foreach ($nodes as $child) {
195 1
        $result .= $dom->saveXML($child);
196 1
      }
197 1
      return $result;
198
    }
199
200
    /**
201
     * Get the inner and outer wrapper nodes. Simple means that they are the
202
     * same nodes.
203
     *
204
     * @param \DOMElement $template
205
     * @param bool $simple
206
     * @return \DOMElement[]
207
     */
208 2
    public function getWrapperNodes($template, &$simple) {
209 2
      $wrapper = $template->cloneNode(TRUE);
210 2
      $targets = NULL;
211 2
      $target = NULL;
212 2
      if (!$simple) {
213
        // get the first element without child elements.
214 2
        $targets = $this->getOwner()->xpath('.//*[count(*) = 0]', $wrapper);
215 2
      }
216 2
      if ($simple || $targets->length === 0) {
217 1
        $target = $wrapper;
218 1
        $simple = TRUE;
219 2
      } elseif ($targets instanceof \DOMNodeList) {
220 1
        $target = $targets->item(0);
221 1
      }
222 2
      return array($target, $wrapper);
223
    }
224
225
    /**
226
     * @param array|\Traversable $nodes
227
     * @param int $limit
228
     * @return array
229
     */
230 14
    private function getLimitedArray($nodes, $limit = -1) {
231 14
      if ($limit > 0) {
232 7
        if (is_array($nodes)) {
233 5
          return array_slice($nodes, 0, $limit);
234
        } else {
235 2
          return iterator_to_array(
236 2
            new \LimitIterator(
237 2
              new \IteratorIterator($nodes), 0, $limit
238 2
            ),
239
            FALSE
240 2
          );
241
        }
242
      }
243 7
      return is_array($nodes) ? $nodes : iterator_to_array($nodes, FALSE);
244
    }
245
  }
246
}