PluginProbe
MBE eShip / trunk
MBE eShip vtrunk
2.8.1 trunk 1.0.0 1.1.0 1.1.3 1.2.1 1.2.2 1.3.0 1.4.0 1.4.1 1.5.0 1.5.1 1.5.2 1.6.0 1.7.0 1.7.1 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.1.0 2.1.1 2.1.2 2.2.1 All 37 releases
mail-boxes-etc / lib / dompdf / src / Frame / FrameTree.php

FrameTree.php in MBE eShip trunk, at lib/dompdf/src/Frame/FrameTree.php

325 lines 8.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package dompdf
4 * @link https://github.com/dompdf/dompdf
5 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
6 */
7 namespace Dompdf\Frame;
8
9 use DOMDocument;
10 use DOMNode;
11 use DOMElement;
12 use DOMXPath;
13
14 use Dompdf\Exception;
15 use Dompdf\Frame;
16 use IteratorAggregate;
17
18 /**
19 * Represents an entire document as a tree of frames
20 *
21 * The FrameTree consists of {@link Frame} objects each tied to specific
22 * DOMNode objects in a specific DomDocument. The FrameTree has the same
23 * structure as the DomDocument, but adds additional capabilities for
24 * styling and layout.
25 *
26 * @package dompdf
27 */
28 class FrameTree implements IteratorAggregate
29 {
30 /**
31 * Tags to ignore while parsing the tree
32 *
33 * @var array
34 */
35 protected static $HIDDEN_TAGS = [
36 "area",
37 "base",
38 "basefont",
39 "head",
40 "style",
41 "meta",
42 "title",
43 "colgroup",
44 "noembed",
45 "param",
46 "#comment"
47 ];
48
49 /**
50 * The main DomDocument
51 *
52 * @see http://ca2.php.net/manual/en/ref.dom.php
53 * @var DOMDocument
54 */
55 protected $_dom;
56
57 /**
58 * The root node of the FrameTree.
59 *
60 * @var Frame
61 */
62 protected $_root;
63
64 /**
65 * Subtrees of absolutely positioned elements
66 *
67 * @var array of Frames
68 */
69 protected $_absolute_frames;
70
71 /**
72 * A mapping of {@link Frame} objects to DOMNode objects
73 *
74 * @var array
75 */
76 protected $_registry;
77
78 /**
79 * Class constructor
80 *
81 * @param DOMDocument $dom the main DomDocument object representing the current html document
82 */
83 public function __construct(DomDocument $dom)
84 {
85 $this->_dom = $dom;
86 $this->_root = null;
87 $this->_registry = [];
88 }
89
90 /**
91 * Returns the DOMDocument object representing the current html document
92 *
93 * @return DOMDocument
94 */
95 public function get_dom()
96 {
97 return $this->_dom;
98 }
99
100 /**
101 * Returns the root frame of the tree
102 *
103 * @return Frame
104 */
105 public function get_root()
106 {
107 return $this->_root;
108 }
109
110 /**
111 * Returns a specific frame given its id
112 *
113 * @param string $id
114 *
115 * @return Frame|null
116 */
117 public function get_frame($id)
118 {
119 return isset($this->_registry[$id]) ? $this->_registry[$id] : null;
120 }
121
122 /**
123 * Returns a post-order iterator for all frames in the tree
124 *
125 * @deprecated Iterate the tree directly instead
126 * @return FrameTreeIterator
127 */
128 public function get_frames(): FrameTreeIterator
129 {
130 return new FrameTreeIterator($this->_root);
131 }
132
133 /**
134 * Returns a post-order iterator for all frames in the tree
135 *
136 * @return FrameTreeIterator
137 */
138 public function getIterator(): FrameTreeIterator
139 {
140 return new FrameTreeIterator($this->_root);
141 }
142
143 /**
144 * Builds the tree
145 */
146 public function build_tree()
147 {
148 $html = $this->_dom->getElementsByTagName("html")->item(0);
149 if (is_null($html)) {
150 $html = $this->_dom->firstChild;
151 }
152
153 if (is_null($html)) {
154 throw new Exception("Requested HTML document contains no data.");
155 }
156
157 $this->fix_tables();
158
159 $this->_root = $this->_build_tree_r($html);
160 }
161
162 /**
163 * Adds missing TBODYs around TR
164 */
165 protected function fix_tables()
166 {
167 $xp = new DOMXPath($this->_dom);
168
169 // Move table caption before the table
170 // FIXME find a better way to deal with it...
171 $captions = $xp->query('//table/caption');
172 foreach ($captions as $caption) {
173 $table = $caption->parentNode;
174 $table->parentNode->insertBefore($caption, $table);
175 }
176
177 $firstRows = $xp->query('//table/tr[1]');
178 /** @var DOMElement $tableChild */
179 foreach ($firstRows as $tableChild) {
180 $tbody = $this->_dom->createElement('tbody');
181 $tableNode = $tableChild->parentNode;
182 do {
183 if ($tableChild->nodeName === 'tr') {
184 $tmpNode = $tableChild;
185 $tableChild = $tableChild->nextSibling;
186 $tableNode->removeChild($tmpNode);
187 $tbody->appendChild($tmpNode);
188 } else {
189 if ($tbody->hasChildNodes() === true) {
190 $tableNode->insertBefore($tbody, $tableChild);
191 $tbody = $this->_dom->createElement('tbody');
192 }
193 $tableChild = $tableChild->nextSibling;
194 }
195 } while ($tableChild);
196 if ($tbody->hasChildNodes() === true) {
197 $tableNode->appendChild($tbody);
198 }
199 }
200 }
201
202 // FIXME: temporary hack, preferably we will improve rendering of sequential #text nodes
203 /**
204 * Remove a child from a node
205 *
206 * Remove a child from a node. If the removed node results in two
207 * adjacent #text nodes then combine them.
208 *
209 * @param DOMNode $node the current DOMNode being considered
210 * @param array $children an array of nodes that are the children of $node
211 * @param int $index index from the $children array of the node to remove
212 */
213 protected function _remove_node(DOMNode $node, array &$children, $index)
214 {
215 $child = $children[$index];
216 $previousChild = $child->previousSibling;
217 $nextChild = $child->nextSibling;
218 $node->removeChild($child);
219 if (isset($previousChild, $nextChild)) {
220 if ($previousChild->nodeName === "#text" && $nextChild->nodeName === "#text") {
221 $previousChild->nodeValue .= $nextChild->nodeValue;
222 $this->_remove_node($node, $children, $index+1);
223 }
224 }
225 array_splice($children, $index, 1);
226 }
227
228 /**
229 * Recursively adds {@link Frame} objects to the tree
230 *
231 * Recursively build a tree of Frame objects based on a dom tree.
232 * No layout information is calculated at this time, although the
233 * tree may be adjusted (i.e. nodes and frames for generated content
234 * and images may be created).
235 *
236 * @param DOMNode $node the current DOMNode being considered
237 *
238 * @return Frame
239 */
240 protected function _build_tree_r(DOMNode $node)
241 {
242 $frame = new Frame($node);
243 $id = $frame->get_id();
244 $this->_registry[$id] = $frame;
245
246 if (!$node->hasChildNodes()) {
247 return $frame;
248 }
249
250 // Store the children in an array so that the tree can be modified
251 $children = [];
252 $length = $node->childNodes->length;
253 for ($i = 0; $i < $length; $i++) {
254 $children[] = $node->childNodes->item($i);
255 }
256 $index = 0;
257 // INFO: We don't advance $index if a node is removed to avoid skipping nodes
258 while ($index < count($children)) {
259 $child = $children[$index];
260 $nodeName = strtolower($child->nodeName);
261
262 // Skip non-displaying nodes
263 if (in_array($nodeName, self::$HIDDEN_TAGS)) {
264 if ($nodeName !== "head" && $nodeName !== "style") {
265 $this->_remove_node($node, $children, $index);
266 } else {
267 $index++;
268 }
269 continue;
270 }
271 // Skip empty text nodes
272 if ($nodeName === "#text" && $child->nodeValue === "") {
273 $this->_remove_node($node, $children, $index);
274 continue;
275 }
276 // Skip empty image nodes
277 if ($nodeName === "img" && $child->getAttribute("src") === "") {
278 $this->_remove_node($node, $children, $index);
279 continue;
280 }
281
282 if (is_object($child)) {
283 $frame->append_child($this->_build_tree_r($child), false);
284 }
285 $index++;
286 }
287
288 return $frame;
289 }
290
291 /**
292 * @param DOMElement $node
293 * @param DOMElement $new_node
294 * @param string $pos
295 *
296 * @return mixed
297 */
298 public function insert_node(DOMElement $node, DOMElement $new_node, $pos)
299 {
300 if ($pos === "after" || !$node->firstChild) {
301 $node->appendChild($new_node);
302 } else {
303 $node->insertBefore($new_node, $node->firstChild);
304 }
305
306 $this->_build_tree_r($new_node);
307
308 $frame_id = $new_node->getAttribute("frame_id");
309 $frame = $this->get_frame($frame_id);
310
311 $parent_id = $node->getAttribute("frame_id");
312 $parent = $this->get_frame($parent_id);
313
314 if ($parent) {
315 if ($pos === "before") {
316 $parent->prepend_child($frame, false);
317 } else {
318 $parent->append_child($frame, false);
319 }
320 }
321
322 return $frame_id;
323 }
324 }
325