Skip to content

Commit

Permalink
Merge branch 'master' of github.com:hexydec/htmldoc
Browse files Browse the repository at this point in the history
  • Loading branch information
hexydec committed Feb 11, 2023
2 parents 552c44d + d9c4137 commit 7f195cd
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 42 deletions.
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ $ vendor/bin/phpunit

## Support

HTMLdoc supports PHP version 7.4+.
HTMLdoc supports PHP version 8.0+.

## Contributing

Expand Down
2 changes: 1 addition & 1 deletion src/helpers/selector.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public function parse(tokenise $tokens) {
case 'bracketclose':
$selectors[] = $parts;
$parts = [];
break;
break 2;
}
} while (($token = $tokens->next()) !== null);
if ($parts) {
Expand Down
54 changes: 29 additions & 25 deletions src/htmldoc.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ class htmldoc extends config implements \ArrayAccess, \Iterator {
* @param string $var The name of the property to retrieve, currently 'length' and output
* @return mixed The number of children in the object for length, the output config, or null if the parameter doesn't exist
*/
#[\ReturnTypeWillChange]
public function __get(string $var) {
public function __get(string $var) : mixed {
if ($var === 'config') {
return $this->config;
} elseif ($var === 'length') {
Expand All @@ -92,7 +91,7 @@ public function toArray() : array {
* @param mixed $i The key to be updated, can be a string or integer
* @param mixed $value The value of the array key in the children array to be updated
*/
public function offsetSet($i, $value) : void {
public function offsetSet(mixed $i, mixed $value) : void {
$this->children[$i] = $value;
}

Expand All @@ -102,7 +101,7 @@ public function offsetSet($i, $value) : void {
* @param mixed $i The key to be checked
* @return bool Whether the key exists in the config array
*/
public function offsetExists($i) : bool {
public function offsetExists(mixed $i) : bool {
return isset($this->children[$i]);
}

Expand All @@ -111,7 +110,7 @@ public function offsetExists($i) : bool {
*
* @param mixed $i The key to be removed
*/
public function offsetUnset($i) : void {
public function offsetUnset(mixed $i) : void {
unset($this->children[$i]);
}

Expand All @@ -121,8 +120,7 @@ public function offsetUnset($i) : void {
* @param mixed $i The key to be accessed, can be a string or integer
* @return mixed An HTMLdoc object containing the child node at the requested position or null if there is no child at the requested position
*/
#[\ReturnTypeWillChange]
public function offsetGet($i) { // return reference so you can set it like an array
public function offsetGet(mixed $i) : mixed { // return reference so you can set it like an array
if (isset($this->children[$i])) {
$obj = new htmldoc($this->config);
$obj->collection([$this->children[$i]]);
Expand All @@ -136,8 +134,7 @@ public function offsetGet($i) { // return reference so you can set it like an ar
*
* @return mixed An HTMLdoc object containing the child node at the current pointer position or null if there are no children
*/
#[\ReturnTypeWillChange]
public function current() {
public function current() : mixed {
if (isset($this->children[$this->pointer])) {
$obj = new htmldoc($this->config);
$obj->collection([$this->children[$this->pointer]]);
Expand All @@ -151,8 +148,7 @@ public function current() {
*
* @return mixed The current pointer position
*/
#[\ReturnTypeWillChange]
public function key() {
public function key() : mixed {
return $this->pointer;
}

Expand Down Expand Up @@ -191,7 +187,7 @@ public function valid() : bool {
* @param ?string &$error A reference to any user error that is generated
* @return string|false The loaded HTML, or false on error
*/
public function open(string $url, $context = null, ?string &$error = null) {
public function open(string $url, $context = null, ?string &$error = null) : string|false {

// check resource
if ($context !== null && !\is_resource($context)) {
Expand Down Expand Up @@ -295,9 +291,9 @@ protected function isEncodingValid(string $charset) : bool {
* Parses an array of tokens into an HTML document
*
* @param string|htmldoc $html A string of HTML, or an htmldoc object
* @return bool|array An array of node objects or false on error
* @return array|false An array of node objects or false on error
*/
protected function parse($html) {
protected function parse(string|htmldoc $html) : array|false {

// convert string to nodes
if (\is_string($html)) {
Expand Down Expand Up @@ -346,9 +342,9 @@ public function cache(string $key, array $values) : void {
* Retrieves the tag object at the specified index, or all children of type tag
*
* @param int $index The index of the child tag to retrieve
* @return mixed A tag object if index is specified, or an array of tag objects, or null if the specified index doesn't exist or the object is empty
* @return tag|array|null A tag object if index is specified, or an array of tag objects, or null if the specified index doesn't exist or the object is empty
*/
public function get(int $index = null) {
public function get(int $index = null) : tag|array|null {

// build children that are tags
$children = [];
Expand Down Expand Up @@ -494,7 +490,15 @@ public function text() : string {
* @return void
*/
protected function collection(array $nodes) : void {
$this->children = $nodes;

// only store unique nodes as some find operations can produce the same node multiple times
$unique = [];
foreach ($nodes AS $item) {
if (!\in_array($item, $unique, true)) {
$unique[] = $item;
}
}
$this->children = $unique;
}

/**
Expand Down Expand Up @@ -599,7 +603,7 @@ public function html(array $options = []) : string {
* @param string|htmldoc $html A string of HTML, or an htmldoc object
* @return htmldoc The current htmldoc object with the nodes appended
*/
public function append($html) : htmldoc {
public function append(string|htmldoc $html) : htmldoc {
if (($nodes = $this->parse($html)) !== false) {
foreach ($this->children AS $item) {
if (\get_class($item) === 'hexydec\\html\\tag') {
Expand All @@ -616,7 +620,7 @@ public function append($html) : htmldoc {
* @param string|htmldoc $html A string of HTML, or an htmldoc object
* @return htmldoc The current htmldoc object with the nodes appended
*/
public function prepend($html) : htmldoc {
public function prepend(string|htmldoc $html) : htmldoc {
if (($nodes = $this->parse($html)) !== false) {
foreach ($this->children AS $item) {
if (\get_class($item) === 'hexydec\\html\\tag') {
Expand All @@ -633,7 +637,7 @@ public function prepend($html) : htmldoc {
* @param string|htmldoc $html A string of HTML, or an htmldoc object
* @return htmldoc The current htmldoc object with the nodes appended
*/
public function before($html) : htmldoc {
public function before(string|htmldoc $html) : htmldoc {
if (($nodes = $this->parse($html)) !== false) {
foreach ($this->children AS $item) {
if (\get_class($item) === 'hexydec\\html\\tag') {
Expand All @@ -650,7 +654,7 @@ public function before($html) : htmldoc {
* @param string|htmldoc $html A string of HTML, or an htmldoc object
* @return htmldoc The current htmldoc object with the nodes appended
*/
public function after($html) : htmldoc {
public function after(string|htmldoc $html) : htmldoc {
if (($nodes = $this->parse($html)) !== false) {
foreach ($this->children AS $item) {
if (\get_class($item) === 'hexydec\\html\\tag') {
Expand All @@ -664,10 +668,10 @@ public function after($html) : htmldoc {
/**
* Removes all top level nodes, or if $selector is specified, the nodes matched by the selector
*
* @param string $selector A CSS selector to refine the nodes to delete or null to delete top level nodes
* @param ?string $selector A CSS selector to refine the nodes to delete or null to delete top level nodes
* @return htmldoc The current htmldoc object with the requested nodes deleted
*/
public function remove(string $selector = null) : htmldoc {
public function remove(?string $selector = null) : htmldoc {
$obj = $selector ? $this->find($selector) : $this;
foreach ($obj->children AS $item) {
if (\get_class($item) === 'hexydec\\html\\tag') {
Expand All @@ -682,9 +686,9 @@ public function remove(string $selector = null) : htmldoc {
*
* @param string|null $file The file location to save the document to, or null to just return the compiled code
* @param array $options An array indicating output options, this is merged with htmldoc::$output
* @return string|bool The compiled HTML, or false if the file could not be saved
* @return string|false The compiled HTML, or false if the file could not be saved
*/
public function save(string $file = null, array $options = []) {
public function save(?string $file = null, array $options = []) : string|false {

// compile html
$html = $this->html($options);
Expand Down
29 changes: 23 additions & 6 deletions src/tokens/tag.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public function __set(string $name, $value) : void {
*
* @return void
*/
public function __clone() {
public function __clone() : void {
foreach ($this->children AS &$item) {
$item = clone $item;
}
Expand Down Expand Up @@ -287,7 +287,7 @@ public function parseChildren(tokenise $tokens) : array {
/**
* Returns the parent of the current object
*
* @return tag The parent tag
* @return ?tag The parent tag
*/
public function parent() : ?tag {
return $this->parent;
Expand Down Expand Up @@ -641,11 +641,29 @@ public function find(array $selector, bool $searchChildren = true) : array {
}

// pass rest of selector to level below
if ($item['join'] && $i) {
if (\in_array($item['join'], [' ', '>'], true) && $i) {
$match = false;
$childselector = \array_slice($selector, $i);
foreach ($this->children AS $child) {
if (\get_class($child) === 'hexydec\\html\\tag') {
$found = \array_merge($found, $child->find(\array_slice($selector, $i)));
$found = \array_merge($found, $child->find($childselector));
}
}
break;

// find siblings
} elseif (\in_array($item['join'], ['+', '~'], true) && $i) {
$match = false;
$siblingselector = \array_slice($selector, $i);
$search = false;
foreach ($this->parent->children AS $sibling) {
if (!$search && $sibling === $this) {
$search = true;
} elseif ($search && \get_class($sibling) === 'hexydec\\html\\tag') {
$found = \array_merge($found, $sibling->find($siblingselector));
if ($item['join'] === '+') {
break;
}
}
}
break;
Expand Down Expand Up @@ -917,8 +935,7 @@ public function children() : array {
*
* @return mixed The value of the requested property
*/
#[\ReturnTypeWillChange]
public function __get(string $var) {
public function __get(string $var) : mixed {
return $this->$var;
}
}
4 changes: 2 additions & 2 deletions src/tokens/text.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function __construct(htmldoc $root, ?tag $parent = null) {
* @param mixed $value The value of the property to set
* @return void
*/
public function __set(string $name, $value) : void {
public function __set(string $name, mixed $value) : void {
if ($name === 'parent' && \get_class($value) === 'hexydec\\html\\tag') {
$this->parent = $value;
}
Expand Down Expand Up @@ -117,7 +117,7 @@ public function minify(array $minify) : void {
}
}

protected function getIndex($children) {
protected function getIndex(array $children) : int|false {
foreach ($children AS $key => $value) {
if ($value === $this) {
return $key;
Expand Down
27 changes: 20 additions & 7 deletions tests/findHtmldocTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,23 @@ public function testCanFindElements() {
$this->assertEquals($doc->length, 4, 'Can count elements');
// var_dump($doc->find('title'));
$tests = [

// basic selectors
'title' => '<title>Find</title>',
'.find' => '<div class="find"><h1 class="find__heading">Heading</h1><p class="find__paragraph" title="This is a paragraph">Paragraph</p><a class="find__anchor" href="https://github.com/hexydec/htmldoc/">Anchor</a></div>',
'#first' => '<div id="first" class="first">First</div>',
'.first, .find__heading, .find__paragraph' => '<div id="first" class="first">First</div><h1 class="find__heading">Heading</h1><p class="find__paragraph" title="This is a paragraph">Paragraph</p>',

// combination selectors
'body .find__paragraph' => '<p class="find__paragraph" title="This is a paragraph">Paragraph</p>',
'body > .find__paragraph' => null,
'.find > .find__paragraph' => '<p class="find__paragraph" title="This is a paragraph">Paragraph</p>',
'.find__paragraph + a' => '<a class="find__anchor" href="https://github.com/hexydec/htmldoc/">Anchor</a>',
'div[data-attr] ~ div' => '<div data-attr="">attr</div><div data-attr="attr">attr</div><div data-attr="attr-value1">attr</div><div data-attr="attr-value2">attr</div><div data-word="one two three four">attr</div>',
'.find h1 ~ a' => '<a class="find__anchor" href="https://github.com/hexydec/htmldoc/">Anchor</a>',
'.attributes div ~ div' => '<div data-attr="">attr</div><div data-attr="attr">attr</div><div data-attr="attr-value1">attr</div><div data-attr="attr-value2">attr</div><div data-word="one two three four">attr</div>',

// attribute selectors
'#first[class]' => '<div id="first" class="first">First</div>',
'[class=first]' => '<div id="first" class="first">First</div>',
'[class^=find]' => '<div class="find"><h1 class="find__heading">Heading</h1><p class="find__paragraph" title="This is a paragraph">Paragraph</p><a class="find__anchor" href="https://github.com/hexydec/htmldoc/">Anchor</a></div><h1 class="find__heading">Heading</h1><p class="find__paragraph" title="This is a paragraph">Paragraph</p><a class="find__anchor" href="https://github.com/hexydec/htmldoc/">Anchor</a>',
Expand All @@ -34,17 +48,16 @@ public function testCanFindElements() {
'a[href$="://github.com/hexydec/htmldoc"]' => null,
'a[href$="://github.com/Hexydec/Htmldoc/"]' => null,
'a[href$="://github.com/Hexydec/Htmldoc/" i]' => '<a class="find__anchor" href="https://github.com/hexydec/htmldoc/">Anchor</a>',
'[data-attr]' => '<div data-attr>attr</div><div data-attr="">attr</div><div data-attr="attr">attr</div><div data-attr="attr-value1">attr</div><div data-attr="attr-value2">attr</div>',
'[data-attr|=attr]' => '<div data-attr="attr">attr</div><div data-attr="attr-value1">attr</div><div data-attr="attr-value2">attr</div>',
'[data-word~=three]' => '<div data-word="one two three four">attr</div>',

// pseudo selectors
'.positions div:first-child' => '<div id="first" class="first">First</div>',
'.positions div:last-child' => '<div class="last">Last</div>',
'.first, .find__heading, .find__paragraph' => '<div id="first" class="first">First</div><h1 class="find__heading">Heading</h1><p class="find__paragraph" title="This is a paragraph">Paragraph</p>',
'body .find__paragraph' => '<p class="find__paragraph" title="This is a paragraph">Paragraph</p>',
'body > .find__paragraph' => null,
'.find > .find__paragraph' => '<p class="find__paragraph" title="This is a paragraph">Paragraph</p>',
'title:not([class])' => '<title>Find</title>',
'.positions div:not(.find)' => '<div id="first" class="first">First</div><div class="last">Last</div>',
'[data-attr]' => '<div data-attr>attr</div><div data-attr="">attr</div><div data-attr="attr">attr</div><div data-attr="attr-value1">attr</div><div data-attr="attr-value2">attr</div>',
'[data-attr|=attr]' => '<div data-attr="attr">attr</div><div data-attr="attr-value1">attr</div><div data-attr="attr-value2">attr</div>',
'[data-word~=three]' => '<div data-word="one two three four">attr</div>'
'body section:not(:first-child) div:last-child' => '<div data-word="one two three four">attr</div>',
];
foreach ($tests AS $key => $item) {
$this->assertEquals($item, $doc->find($key)->html());
Expand Down

0 comments on commit 7f195cd

Please sign in to comment.