AbstractNode.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505
  1. <?php
  2. namespace PHPHtmlParser\Dom;
  3. use PHPHtmlParser\Exceptions\CircularException;
  4. use PHPHtmlParser\Exceptions\ParentNotFoundException;
  5. use PHPHtmlParser\Exceptions\ChildNotFoundException;
  6. use PHPHtmlParser\Selector\Selector;
  7. use PHPHtmlParser\Selector\Parser as SelectorParser;
  8. use stringEncode\Encode;
  9. use PHPHtmlParser\Finder;
  10. /**
  11. * Dom node object.
  12. *
  13. * @property string outerhtml
  14. * @property string innerhtml
  15. * @property string text
  16. * @property int prev
  17. * @property int next
  18. * @property \PHPHtmlParser\Dom\Tag tag
  19. * @property InnerNode parent
  20. */
  21. abstract class AbstractNode
  22. {
  23. private static $count = 0;
  24. /**
  25. * Contains the tag name/type
  26. *
  27. * @var \PHPHtmlParser\Dom\Tag
  28. */
  29. protected $tag;
  30. /**
  31. * Contains a list of attributes on this tag.
  32. *
  33. * @var array
  34. */
  35. protected $attr = [];
  36. /**
  37. * Contains the parent Node.
  38. *
  39. * @var InnerNode
  40. */
  41. protected $parent = null;
  42. /**
  43. * The unique id of the class. Given by PHP.
  44. *
  45. * @var int
  46. */
  47. protected $id;
  48. /**
  49. * The encoding class used to encode strings.
  50. *
  51. * @var mixed
  52. */
  53. protected $encode;
  54. /**
  55. * An array of all the children.
  56. *
  57. * @var array
  58. */
  59. protected $children = [];
  60. /**
  61. * Creates a unique id for this node.
  62. */
  63. public function __construct()
  64. {
  65. $this->id = self::$count;
  66. self::$count++;
  67. }
  68. /**
  69. * Magic get method for attributes and certain methods.
  70. *
  71. * @param string $key
  72. * @return mixed
  73. */
  74. public function __get(string $key)
  75. {
  76. // check attribute first
  77. if ( ! is_null($this->getAttribute($key))) {
  78. return $this->getAttribute($key);
  79. }
  80. switch (strtolower($key)) {
  81. case 'outerhtml':
  82. return $this->outerHtml();
  83. case 'innerhtml':
  84. return $this->innerHtml();
  85. case 'text':
  86. return $this->text();
  87. case 'tag':
  88. return $this->getTag();
  89. case 'parent':
  90. return $this->getParent();
  91. }
  92. return null;
  93. }
  94. /**
  95. * Attempts to clear out any object references.
  96. */
  97. public function __destruct()
  98. {
  99. $this->tag = null;
  100. $this->attr = [];
  101. $this->parent = null;
  102. $this->children = [];
  103. }
  104. /**
  105. * Simply calls the outer text method.
  106. *
  107. * @return string
  108. */
  109. public function __toString()
  110. {
  111. return $this->outerHtml();
  112. }
  113. /**
  114. * Reset node counter
  115. *
  116. * @return void
  117. */
  118. public static function resetCount()
  119. {
  120. self::$count = 0;
  121. }
  122. /**
  123. * Returns the id of this object.
  124. *
  125. * @return int
  126. */
  127. public function id(): int
  128. {
  129. return $this->id;
  130. }
  131. /**
  132. * Returns the parent of node.
  133. *
  134. * @return AbstractNode
  135. */
  136. public function getParent()
  137. {
  138. return $this->parent;
  139. }
  140. /**
  141. * Sets the parent node.
  142. *
  143. * @param InnerNode $parent
  144. * @return AbstractNode
  145. * @throws CircularException
  146. * @chainable
  147. */
  148. public function setParent(InnerNode $parent): AbstractNode
  149. {
  150. // remove from old parent
  151. if ( ! is_null($this->parent)) {
  152. if ($this->parent->id() == $parent->id()) {
  153. // already the parent
  154. return $this;
  155. }
  156. $this->parent->removeChild($this->id);
  157. }
  158. $this->parent = $parent;
  159. // assign child to parent
  160. $this->parent->addChild($this);
  161. return $this;
  162. }
  163. /**
  164. * Removes this node and all its children from the
  165. * DOM tree.
  166. *
  167. * @return void
  168. */
  169. public function delete()
  170. {
  171. if ( ! is_null($this->parent)) {
  172. $this->parent->removeChild($this->id);
  173. }
  174. $this->parent->clear();
  175. $this->clear();
  176. }
  177. /**
  178. * Sets the encoding class to this node.
  179. *
  180. * @param Encode $encode
  181. * @return void
  182. */
  183. public function propagateEncoding(Encode $encode)
  184. {
  185. $this->encode = $encode;
  186. $this->tag->setEncoding($encode);
  187. }
  188. /**
  189. * Checks if the given node id is an ancestor of
  190. * the current node.
  191. *
  192. * @param int $id
  193. * @return bool
  194. */
  195. public function isAncestor(int $id): Bool
  196. {
  197. if ( ! is_null($this->getAncestor($id))) {
  198. return true;
  199. }
  200. return false;
  201. }
  202. /**
  203. * Attempts to get an ancestor node by the given id.
  204. *
  205. * @param int $id
  206. * @return null|AbstractNode
  207. */
  208. public function getAncestor(int $id)
  209. {
  210. if ( ! is_null($this->parent)) {
  211. if ($this->parent->id() == $id) {
  212. return $this->parent;
  213. }
  214. return $this->parent->getAncestor($id);
  215. }
  216. return null;
  217. }
  218. /**
  219. * Checks if the current node has a next sibling.
  220. *
  221. * @return bool
  222. */
  223. public function hasNextSibling(): bool
  224. {
  225. try
  226. {
  227. $this->nextSibling();
  228. // sibling found, return true;
  229. return true;
  230. }
  231. catch (ParentNotFoundException $e)
  232. {
  233. // no parent, no next sibling
  234. return false;
  235. }
  236. catch (ChildNotFoundException $e)
  237. {
  238. // no sibling found
  239. return false;
  240. }
  241. }
  242. /**
  243. * Attempts to get the next sibling.
  244. *
  245. * @return AbstractNode
  246. * @throws ParentNotFoundException
  247. */
  248. public function nextSibling(): AbstractNode
  249. {
  250. if (is_null($this->parent)) {
  251. throw new ParentNotFoundException('Parent is not set for this node.');
  252. }
  253. return $this->parent->nextChild($this->id);
  254. }
  255. /**
  256. * Attempts to get the previous sibling
  257. *
  258. * @return AbstractNode
  259. * @throws ParentNotFoundException
  260. */
  261. public function previousSibling(): AbstractNode
  262. {
  263. if (is_null($this->parent)) {
  264. throw new ParentNotFoundException('Parent is not set for this node.');
  265. }
  266. return $this->parent->previousChild($this->id);
  267. }
  268. /**
  269. * Gets the tag object of this node.
  270. *
  271. * @return Tag
  272. */
  273. public function getTag(): Tag
  274. {
  275. return $this->tag;
  276. }
  277. /**
  278. * A wrapper method that simply calls the getAttribute method
  279. * on the tag of this node.
  280. *
  281. * @return array
  282. */
  283. public function getAttributes(): array
  284. {
  285. $attributes = $this->tag->getAttributes();
  286. foreach ($attributes as $name => $info) {
  287. $attributes[$name] = $info['value'];
  288. }
  289. return $attributes;
  290. }
  291. /**
  292. * A wrapper method that simply calls the getAttribute method
  293. * on the tag of this node.
  294. *
  295. * @param string $key
  296. * @return mixed
  297. */
  298. public function getAttribute(string $key)
  299. {
  300. $attribute = $this->tag->getAttribute($key);
  301. if ( ! is_null($attribute)) {
  302. $attribute = $attribute['value'];
  303. }
  304. return $attribute;
  305. }
  306. /**
  307. * A wrapper method that simply calls the hasAttribute method
  308. * on the tag of this node.
  309. *
  310. * @param string $key
  311. * @return bool
  312. */
  313. public function hasAttribute(string $key): bool
  314. {
  315. return $this->tag->hasAttribute($key);
  316. }
  317. /**
  318. * A wrapper method that simply calls the setAttribute method
  319. * on the tag of this node.
  320. *
  321. * @param string $key
  322. * @param string|null $value
  323. * @return AbstractNode
  324. * @chainable
  325. */
  326. public function setAttribute(string $key, $value): AbstractNode
  327. {
  328. $this->tag->setAttribute($key, $value);
  329. //clear any cache
  330. $this->clear();
  331. return $this;
  332. }
  333. /**
  334. * A wrapper method that simply calls the removeAttribute method
  335. * on the tag of this node.
  336. *
  337. * @param string $key
  338. * @return void
  339. */
  340. public function removeAttribute(string $key): void
  341. {
  342. $this->tag->removeAttribute($key);
  343. //clear any cache
  344. $this->clear();
  345. }
  346. /**
  347. * A wrapper method that simply calls the removeAllAttributes
  348. * method on the tag of this node.
  349. *
  350. * @return void
  351. */
  352. public function removeAllAttributes(): void
  353. {
  354. $this->tag->removeAllAttributes();
  355. //clear any cache
  356. $this->clear();
  357. }
  358. /**
  359. * Function to locate a specific ancestor tag in the path to the root.
  360. *
  361. * @param string $tag
  362. * @return AbstractNode
  363. * @throws ParentNotFoundException
  364. */
  365. public function ancestorByTag(string $tag): AbstractNode
  366. {
  367. // Start by including ourselves in the comparison.
  368. $node = $this;
  369. while ( ! is_null($node)) {
  370. if ($node->tag->name() == $tag) {
  371. return $node;
  372. }
  373. $node = $node->getParent();
  374. }
  375. throw new ParentNotFoundException('Could not find an ancestor with "'.$tag.'" tag');
  376. }
  377. /**
  378. * Find elements by css selector
  379. *
  380. * @param string $selector
  381. * @param int $nth
  382. * @return mixed
  383. */
  384. public function find(string $selector, int $nth = null)
  385. {
  386. $selector = new Selector($selector, new SelectorParser());
  387. $nodes = $selector->find($this);
  388. if ( ! is_null($nth)) {
  389. // return nth-element or array
  390. if (isset($nodes[$nth])) {
  391. return $nodes[$nth];
  392. }
  393. return null;
  394. }
  395. return $nodes;
  396. }
  397. /**
  398. * Find node by id
  399. *
  400. * @param int $id
  401. * @return bool|AbstractNode
  402. */
  403. public function findById(int $id)
  404. {
  405. $finder= new Finder($id);
  406. return $finder->find($this);
  407. }
  408. /**
  409. * Gets the inner html of this node.
  410. *
  411. * @return string
  412. */
  413. abstract public function innerHtml(): string;
  414. /**
  415. * Gets the html of this node, including it's own
  416. * tag.
  417. *
  418. * @return string
  419. */
  420. abstract public function outerHtml(): string;
  421. /**
  422. * Gets the text of this node (if there is any text).
  423. *
  424. * @return string
  425. */
  426. abstract public function text(): string;
  427. /**
  428. * Call this when something in the node tree has changed. Like a child has been added
  429. * or a parent has been changed.
  430. *
  431. * @return void
  432. */
  433. abstract protected function clear(): void;
  434. /**
  435. * Check is node type textNode
  436. *
  437. * @return boolean
  438. */
  439. public function isTextNode(): bool
  440. {
  441. return false;
  442. }
  443. }