Initial commit
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
/**
|
||||
* @package dompdf
|
||||
* @link https://github.com/dompdf/dompdf
|
||||
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
|
||||
*/
|
||||
namespace Dompdf\Frame;
|
||||
|
||||
use Dompdf\Dompdf;
|
||||
use Dompdf\Exception;
|
||||
use Dompdf\Frame;
|
||||
use Dompdf\FrameDecorator\AbstractFrameDecorator;
|
||||
use Dompdf\FrameDecorator\Page as PageFrameDecorator;
|
||||
use Dompdf\FrameReflower\Page as PageFrameReflower;
|
||||
use Dompdf\Positioner\AbstractPositioner;
|
||||
use DOMXPath;
|
||||
|
||||
/**
|
||||
* Contains frame decorating logic
|
||||
*
|
||||
* This class is responsible for assigning the correct {@link AbstractFrameDecorator},
|
||||
* {@link AbstractPositioner}, and {@link AbstractFrameReflower} objects to {@link Frame}
|
||||
* objects. This is determined primarily by the Frame's display type, but
|
||||
* also by the Frame's node's type (e.g. DomElement vs. #text)
|
||||
*
|
||||
* @package dompdf
|
||||
*/
|
||||
class Factory
|
||||
{
|
||||
|
||||
/**
|
||||
* Array of positioners for specific frame types
|
||||
*
|
||||
* @var AbstractPositioner[]
|
||||
*/
|
||||
protected static $_positioners;
|
||||
|
||||
/**
|
||||
* Decorate the root Frame
|
||||
*
|
||||
* @param Frame $root The frame to decorate
|
||||
* @param Dompdf $dompdf The dompdf instance
|
||||
*
|
||||
* @return PageFrameDecorator
|
||||
*/
|
||||
public static function decorate_root(Frame $root, Dompdf $dompdf): PageFrameDecorator
|
||||
{
|
||||
$frame = new PageFrameDecorator($root, $dompdf);
|
||||
$frame->set_reflower(new PageFrameReflower($frame));
|
||||
$root->set_decorator($frame);
|
||||
|
||||
return $frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorate a Frame
|
||||
*
|
||||
* @param Frame $frame The frame to decorate
|
||||
* @param Dompdf $dompdf The dompdf instance
|
||||
* @param Frame|null $root The root of the frame
|
||||
*
|
||||
* @throws Exception
|
||||
* @return AbstractFrameDecorator|null
|
||||
* FIXME: this is admittedly a little smelly...
|
||||
*/
|
||||
public static function decorate_frame(Frame $frame, Dompdf $dompdf, ?Frame $root = null): ?AbstractFrameDecorator
|
||||
{
|
||||
$style = $frame->get_style();
|
||||
$display = $style->display;
|
||||
|
||||
switch ($display) {
|
||||
|
||||
case "block":
|
||||
$positioner = "Block";
|
||||
$decorator = "Block";
|
||||
$reflower = "Block";
|
||||
break;
|
||||
|
||||
case "inline-block":
|
||||
$positioner = "Inline";
|
||||
$decorator = "Block";
|
||||
$reflower = "Block";
|
||||
break;
|
||||
|
||||
case "inline":
|
||||
$positioner = "Inline";
|
||||
if ($frame->is_text_node()) {
|
||||
$decorator = "Text";
|
||||
$reflower = "Text";
|
||||
} else {
|
||||
$decorator = "Inline";
|
||||
$reflower = "Inline";
|
||||
}
|
||||
break;
|
||||
|
||||
case "table":
|
||||
$positioner = "Block";
|
||||
$decorator = "Table";
|
||||
$reflower = "Table";
|
||||
break;
|
||||
|
||||
case "inline-table":
|
||||
$positioner = "Inline";
|
||||
$decorator = "Table";
|
||||
$reflower = "Table";
|
||||
break;
|
||||
|
||||
case "table-row-group":
|
||||
case "table-header-group":
|
||||
case "table-footer-group":
|
||||
$positioner = "NullPositioner";
|
||||
$decorator = "TableRowGroup";
|
||||
$reflower = "TableRowGroup";
|
||||
break;
|
||||
|
||||
case "table-row":
|
||||
$positioner = "NullPositioner";
|
||||
$decorator = "TableRow";
|
||||
$reflower = "TableRow";
|
||||
break;
|
||||
|
||||
case "table-cell":
|
||||
$positioner = "TableCell";
|
||||
$decorator = "TableCell";
|
||||
$reflower = "TableCell";
|
||||
break;
|
||||
|
||||
case "list-item":
|
||||
$positioner = "Block";
|
||||
$decorator = "Block";
|
||||
$reflower = "Block";
|
||||
break;
|
||||
|
||||
case "-dompdf-list-bullet":
|
||||
if ($style->list_style_position === "inside") {
|
||||
$positioner = "Inline";
|
||||
} else {
|
||||
$positioner = "ListBullet";
|
||||
}
|
||||
|
||||
if ($style->list_style_image !== "none") {
|
||||
$decorator = "ListBulletImage";
|
||||
} else {
|
||||
$decorator = "ListBullet";
|
||||
}
|
||||
|
||||
$reflower = "ListBullet";
|
||||
break;
|
||||
|
||||
case "-dompdf-image":
|
||||
$positioner = "Inline";
|
||||
$decorator = "Image";
|
||||
$reflower = "Image";
|
||||
break;
|
||||
|
||||
case "-dompdf-br":
|
||||
$positioner = "Inline";
|
||||
$decorator = "Inline";
|
||||
$reflower = "Inline";
|
||||
break;
|
||||
|
||||
default:
|
||||
case "none":
|
||||
if ($style->_dompdf_keep !== "yes") {
|
||||
// Remove the node and the frame
|
||||
$frame->get_parent()->remove_child($frame);
|
||||
return null;
|
||||
}
|
||||
|
||||
$positioner = "NullPositioner";
|
||||
$decorator = "NullFrameDecorator";
|
||||
$reflower = "NullFrameReflower";
|
||||
break;
|
||||
}
|
||||
|
||||
// Handle CSS position
|
||||
$position = $style->position;
|
||||
|
||||
if ($position === "absolute") {
|
||||
$positioner = "Absolute";
|
||||
} elseif ($position === "fixed") {
|
||||
$positioner = "Fixed";
|
||||
}
|
||||
|
||||
$node = $frame->get_node();
|
||||
|
||||
// Handle nodeName
|
||||
if ($node->nodeName === "img") {
|
||||
$style->set_prop("display", "-dompdf-image");
|
||||
$decorator = "Image";
|
||||
$reflower = "Image";
|
||||
}
|
||||
|
||||
$decorator = "Dompdf\\FrameDecorator\\$decorator";
|
||||
$reflower = "Dompdf\\FrameReflower\\$reflower";
|
||||
|
||||
/** @var AbstractFrameDecorator $deco */
|
||||
$deco = new $decorator($frame, $dompdf);
|
||||
|
||||
$deco->set_positioner(self::getPositionerInstance($positioner));
|
||||
$deco->set_reflower(new $reflower($deco, $dompdf->getFontMetrics()));
|
||||
|
||||
if ($root) {
|
||||
$deco->set_root($root);
|
||||
}
|
||||
|
||||
if ($display === "list-item") {
|
||||
// Insert a list-bullet frame
|
||||
$xml = $dompdf->getDom();
|
||||
$bullet_node = $xml->createElement("bullet"); // arbitrary choice
|
||||
$b_f = new Frame($bullet_node);
|
||||
|
||||
$node = $frame->get_node();
|
||||
$parent_node = $node->parentNode;
|
||||
if ($parent_node && $parent_node instanceof \DOMElement) {
|
||||
if (!$parent_node->hasAttribute("dompdf-children-count")) {
|
||||
$xpath = new DOMXPath($xml);
|
||||
$count = $xpath->query("li", $parent_node)->length;
|
||||
$parent_node->setAttribute("dompdf-children-count", $count);
|
||||
}
|
||||
|
||||
if (is_numeric($node->getAttribute("value"))) {
|
||||
$index = intval($node->getAttribute("value"));
|
||||
} else {
|
||||
if (!$parent_node->hasAttribute("dompdf-counter")) {
|
||||
$index = ($parent_node->hasAttribute("start") ? $parent_node->getAttribute("start") : 1);
|
||||
} else {
|
||||
$index = (int)$parent_node->getAttribute("dompdf-counter") + 1;
|
||||
}
|
||||
}
|
||||
|
||||
$parent_node->setAttribute("dompdf-counter", $index);
|
||||
$bullet_node->setAttribute("dompdf-counter", $index);
|
||||
}
|
||||
|
||||
$new_style = $dompdf->getCss()->create_style();
|
||||
$new_style->set_prop("display", "-dompdf-list-bullet");
|
||||
$new_style->inherit($style);
|
||||
$b_f->set_style($new_style);
|
||||
|
||||
$deco->prepend_child(Factory::decorate_frame($b_f, $dompdf, $root));
|
||||
}
|
||||
|
||||
return $deco;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates Positioners
|
||||
*
|
||||
* @param string $type Type of positioner to use
|
||||
*
|
||||
* @return AbstractPositioner
|
||||
*/
|
||||
protected static function getPositionerInstance(string $type): AbstractPositioner
|
||||
{
|
||||
if (!isset(self::$_positioners[$type])) {
|
||||
$class = '\\Dompdf\\Positioner\\'.$type;
|
||||
self::$_positioners[$type] = new $class();
|
||||
}
|
||||
return self::$_positioners[$type];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/**
|
||||
* @package dompdf
|
||||
* @link https://github.com/dompdf/dompdf
|
||||
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
|
||||
*/
|
||||
namespace Dompdf\Frame;
|
||||
|
||||
use Iterator;
|
||||
use Dompdf\Frame;
|
||||
|
||||
/**
|
||||
* Linked-list Iterator
|
||||
*
|
||||
* Returns children in order and allows for the list to change during iteration,
|
||||
* provided the changes occur to or after the current element.
|
||||
*
|
||||
* @package dompdf
|
||||
*/
|
||||
class FrameListIterator implements Iterator
|
||||
{
|
||||
/**
|
||||
* @var Frame
|
||||
*/
|
||||
protected $parent;
|
||||
|
||||
/**
|
||||
* @var Frame|null
|
||||
*/
|
||||
protected $cur;
|
||||
|
||||
/**
|
||||
* @var Frame|null
|
||||
*/
|
||||
protected $prev;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $num;
|
||||
|
||||
/**
|
||||
* @param Frame $frame
|
||||
*/
|
||||
public function __construct(Frame $frame)
|
||||
{
|
||||
$this->parent = $frame;
|
||||
$this->rewind();
|
||||
}
|
||||
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->cur = $this->parent->get_first_child();
|
||||
$this->prev = null;
|
||||
$this->num = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function valid(): bool
|
||||
{
|
||||
return $this->cur !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function key(): int
|
||||
{
|
||||
return $this->num;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Frame|null
|
||||
*/
|
||||
public function current(): ?Frame
|
||||
{
|
||||
return $this->cur;
|
||||
}
|
||||
|
||||
public function next(): void
|
||||
{
|
||||
if ($this->cur === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->cur->get_parent() === $this->parent) {
|
||||
$this->prev = $this->cur;
|
||||
$this->cur = $this->cur->get_next_sibling();
|
||||
$this->num++;
|
||||
} else {
|
||||
// Continue from the previous child if the current frame has been
|
||||
// moved to another parent
|
||||
$this->cur = $this->prev !== null
|
||||
? $this->prev->get_next_sibling()
|
||||
: $this->parent->get_first_child();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
<?php
|
||||
/**
|
||||
* @package dompdf
|
||||
* @link https://github.com/dompdf/dompdf
|
||||
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
|
||||
*/
|
||||
namespace Dompdf\Frame;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMNode;
|
||||
use DOMElement;
|
||||
use DOMXPath;
|
||||
|
||||
use Dompdf\Exception;
|
||||
use Dompdf\Frame;
|
||||
use IteratorAggregate;
|
||||
|
||||
/**
|
||||
* Represents an entire document as a tree of frames
|
||||
*
|
||||
* The FrameTree consists of {@link Frame} objects each tied to specific
|
||||
* DOMNode objects in a specific DomDocument. The FrameTree has the same
|
||||
* structure as the DomDocument, but adds additional capabilities for
|
||||
* styling and layout.
|
||||
*
|
||||
* @package dompdf
|
||||
*/
|
||||
class FrameTree implements IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* Tags to ignore while parsing the tree
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $HIDDEN_TAGS = [
|
||||
"area",
|
||||
"base",
|
||||
"basefont",
|
||||
"head",
|
||||
"style",
|
||||
"meta",
|
||||
"title",
|
||||
"colgroup",
|
||||
"noembed",
|
||||
"param",
|
||||
"#comment"
|
||||
];
|
||||
|
||||
/**
|
||||
* The main DomDocument
|
||||
*
|
||||
* @see http://ca2.php.net/manual/en/ref.dom.php
|
||||
* @var DOMDocument
|
||||
*/
|
||||
protected $_dom;
|
||||
|
||||
/**
|
||||
* The root node of the FrameTree.
|
||||
*
|
||||
* @var Frame
|
||||
*/
|
||||
protected $_root;
|
||||
|
||||
/**
|
||||
* Subtrees of absolutely positioned elements
|
||||
*
|
||||
* @var array of Frames
|
||||
*/
|
||||
protected $_absolute_frames;
|
||||
|
||||
/**
|
||||
* A mapping of {@link Frame} objects to DOMNode objects
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $_registry;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param DOMDocument $dom the main DomDocument object representing the current html document
|
||||
*/
|
||||
public function __construct(DomDocument $dom)
|
||||
{
|
||||
$this->_dom = $dom;
|
||||
$this->_root = null;
|
||||
$this->_registry = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DOMDocument object representing the current html document
|
||||
*
|
||||
* @return DOMDocument
|
||||
*/
|
||||
public function get_dom()
|
||||
{
|
||||
return $this->_dom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the root frame of the tree
|
||||
*
|
||||
* @return Frame
|
||||
*/
|
||||
public function get_root()
|
||||
{
|
||||
return $this->_root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a specific frame given its id
|
||||
*
|
||||
* @param string $id
|
||||
*
|
||||
* @return Frame|null
|
||||
*/
|
||||
public function get_frame($id)
|
||||
{
|
||||
return isset($this->_registry[$id]) ? $this->_registry[$id] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a post-order iterator for all frames in the tree
|
||||
*
|
||||
* @deprecated Iterate the tree directly instead
|
||||
* @return FrameTreeIterator
|
||||
*/
|
||||
public function get_frames(): FrameTreeIterator
|
||||
{
|
||||
return new FrameTreeIterator($this->_root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a post-order iterator for all frames in the tree
|
||||
*
|
||||
* @return FrameTreeIterator
|
||||
*/
|
||||
public function getIterator(): FrameTreeIterator
|
||||
{
|
||||
return new FrameTreeIterator($this->_root);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the tree
|
||||
*/
|
||||
public function build_tree()
|
||||
{
|
||||
$html = $this->_dom->getElementsByTagName("html")->item(0);
|
||||
if (is_null($html)) {
|
||||
$html = $this->_dom->firstChild;
|
||||
}
|
||||
|
||||
if (is_null($html)) {
|
||||
throw new Exception("Requested HTML document contains no data.");
|
||||
}
|
||||
|
||||
$this->fix_tables();
|
||||
|
||||
$this->_root = $this->_build_tree_r($html);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds missing TBODYs around TR
|
||||
*/
|
||||
protected function fix_tables()
|
||||
{
|
||||
$xp = new DOMXPath($this->_dom);
|
||||
|
||||
// Move table caption before the table
|
||||
// FIXME find a better way to deal with it...
|
||||
$captions = $xp->query('//table/caption');
|
||||
foreach ($captions as $caption) {
|
||||
$table = $caption->parentNode;
|
||||
$table->parentNode->insertBefore($caption, $table);
|
||||
}
|
||||
|
||||
$firstRows = $xp->query('//table/tr[1]');
|
||||
/** @var DOMElement $tableChild */
|
||||
foreach ($firstRows as $tableChild) {
|
||||
$tbody = $this->_dom->createElement('tbody');
|
||||
$tableNode = $tableChild->parentNode;
|
||||
do {
|
||||
if ($tableChild->nodeName === 'tr') {
|
||||
$tmpNode = $tableChild;
|
||||
$tableChild = $tableChild->nextSibling;
|
||||
$tableNode->removeChild($tmpNode);
|
||||
$tbody->appendChild($tmpNode);
|
||||
} else {
|
||||
if ($tbody->hasChildNodes() === true) {
|
||||
$tableNode->insertBefore($tbody, $tableChild);
|
||||
$tbody = $this->_dom->createElement('tbody');
|
||||
}
|
||||
$tableChild = $tableChild->nextSibling;
|
||||
}
|
||||
} while ($tableChild);
|
||||
if ($tbody->hasChildNodes() === true) {
|
||||
$tableNode->appendChild($tbody);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: temporary hack, preferably we will improve rendering of sequential #text nodes
|
||||
/**
|
||||
* Remove a child from a node
|
||||
*
|
||||
* Remove a child from a node. If the removed node results in two
|
||||
* adjacent #text nodes then combine them.
|
||||
*
|
||||
* @param DOMNode $node the current DOMNode being considered
|
||||
* @param array $children an array of nodes that are the children of $node
|
||||
* @param int $index index from the $children array of the node to remove
|
||||
*/
|
||||
protected function _remove_node(DOMNode $node, array &$children, $index)
|
||||
{
|
||||
$child = $children[$index];
|
||||
$previousChild = $child->previousSibling;
|
||||
$nextChild = $child->nextSibling;
|
||||
$node->removeChild($child);
|
||||
if (isset($previousChild, $nextChild)) {
|
||||
if ($previousChild->nodeName === "#text" && $nextChild->nodeName === "#text") {
|
||||
$previousChild->nodeValue .= $nextChild->nodeValue;
|
||||
$this->_remove_node($node, $children, $index+1);
|
||||
}
|
||||
}
|
||||
array_splice($children, $index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively adds {@link Frame} objects to the tree
|
||||
*
|
||||
* Recursively build a tree of Frame objects based on a dom tree.
|
||||
* No layout information is calculated at this time, although the
|
||||
* tree may be adjusted (i.e. nodes and frames for generated content
|
||||
* and images may be created).
|
||||
*
|
||||
* @param DOMNode $node the current DOMNode being considered
|
||||
*
|
||||
* @return Frame
|
||||
*/
|
||||
protected function _build_tree_r(DOMNode $node)
|
||||
{
|
||||
$frame = new Frame($node);
|
||||
$id = $frame->get_id();
|
||||
$this->_registry[$id] = $frame;
|
||||
|
||||
if (!$node->hasChildNodes()) {
|
||||
return $frame;
|
||||
}
|
||||
|
||||
// Store the children in an array so that the tree can be modified
|
||||
$children = [];
|
||||
$length = $node->childNodes->length;
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$children[] = $node->childNodes->item($i);
|
||||
}
|
||||
$index = 0;
|
||||
// INFO: We don't advance $index if a node is removed to avoid skipping nodes
|
||||
while ($index < count($children)) {
|
||||
$child = $children[$index];
|
||||
$nodeName = strtolower($child->nodeName);
|
||||
|
||||
// Skip non-displaying nodes
|
||||
if (in_array($nodeName, self::$HIDDEN_TAGS)) {
|
||||
if ($nodeName !== "head" && $nodeName !== "style") {
|
||||
$this->_remove_node($node, $children, $index);
|
||||
} else {
|
||||
$index++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Skip empty text nodes
|
||||
if ($nodeName === "#text" && $child->nodeValue === "") {
|
||||
$this->_remove_node($node, $children, $index);
|
||||
continue;
|
||||
}
|
||||
// Skip empty image nodes
|
||||
if ($nodeName === "img" && $child->getAttribute("src") === "") {
|
||||
$this->_remove_node($node, $children, $index);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_object($child)) {
|
||||
$frame->append_child($this->_build_tree_r($child), false);
|
||||
}
|
||||
$index++;
|
||||
}
|
||||
|
||||
return $frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DOMElement $node
|
||||
* @param DOMElement $new_node
|
||||
* @param string $pos
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function insert_node(DOMElement $node, DOMElement $new_node, $pos)
|
||||
{
|
||||
if ($pos === "after" || !$node->firstChild) {
|
||||
$node->appendChild($new_node);
|
||||
} else {
|
||||
$node->insertBefore($new_node, $node->firstChild);
|
||||
}
|
||||
|
||||
$this->_build_tree_r($new_node);
|
||||
|
||||
$frame_id = $new_node->getAttribute("frame_id");
|
||||
$frame = $this->get_frame($frame_id);
|
||||
|
||||
$parent_id = $node->getAttribute("frame_id");
|
||||
$parent = $this->get_frame($parent_id);
|
||||
|
||||
if ($parent) {
|
||||
if ($pos === "before") {
|
||||
$parent->prepend_child($frame, false);
|
||||
} else {
|
||||
$parent->append_child($frame, false);
|
||||
}
|
||||
}
|
||||
|
||||
return $frame_id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* @package dompdf
|
||||
* @link https://github.com/dompdf/dompdf
|
||||
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
|
||||
*/
|
||||
namespace Dompdf\Frame;
|
||||
|
||||
use Iterator;
|
||||
use Dompdf\Frame;
|
||||
|
||||
/**
|
||||
* Pre-order Iterator
|
||||
*
|
||||
* Returns frames in preorder traversal order (parent then children)
|
||||
*
|
||||
* @package dompdf
|
||||
*/
|
||||
class FrameTreeIterator implements Iterator
|
||||
{
|
||||
/**
|
||||
* @var Frame
|
||||
*/
|
||||
protected $_root;
|
||||
|
||||
/**
|
||||
* @var Frame[]
|
||||
*/
|
||||
protected $_stack = [];
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $_num;
|
||||
|
||||
/**
|
||||
* @param Frame $root
|
||||
*/
|
||||
public function __construct(Frame $root)
|
||||
{
|
||||
$this->_stack[] = $this->_root = $root;
|
||||
$this->_num = 0;
|
||||
}
|
||||
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->_stack = [$this->_root];
|
||||
$this->_num = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function valid(): bool
|
||||
{
|
||||
return count($this->_stack) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function key(): int
|
||||
{
|
||||
return $this->_num;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Frame
|
||||
*/
|
||||
public function current(): Frame
|
||||
{
|
||||
return end($this->_stack);
|
||||
}
|
||||
|
||||
public function next(): void
|
||||
{
|
||||
$b = array_pop($this->_stack);
|
||||
$this->_num++;
|
||||
|
||||
// Push all children onto the stack in reverse order
|
||||
if ($c = $b->get_last_child()) {
|
||||
$this->_stack[] = $c;
|
||||
while ($c = $c->get_prev_sibling()) {
|
||||
$this->_stack[] = $c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user