HasSelectorConditionBuilder.php 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. <?php
  2. namespace Gt\CssXPath;
  3. class HasSelectorConditionBuilder {
  4. private SelectorListSplitter $selectorListSplitter;
  5. private SingleSelectorConverter $singleSelectorConverter;
  6. public function __construct(
  7. ?SelectorListSplitter $selectorListSplitter = null,
  8. ?SingleSelectorConverter $singleSelectorConverter = null,
  9. ) {
  10. $this->selectorListSplitter = $selectorListSplitter
  11. ?? new SelectorListSplitter();
  12. $this->singleSelectorConverter = $singleSelectorConverter
  13. ?? new SingleSelectorConverter();
  14. }
  15. public function build(string $selectorList, bool $htmlMode):?string {
  16. $selectorList = trim($selectorList);
  17. if($selectorList === "") {
  18. return null;
  19. }
  20. $this->assertSupported($selectorList);
  21. $selectors = $this->selectorListSplitter->split($selectorList);
  22. if(empty($selectors)) {
  23. return null;
  24. }
  25. $conditions = [];
  26. foreach($selectors as $selector) {
  27. $conditions[] = $this->buildCondition(trim($selector), $htmlMode);
  28. }
  29. if(count($conditions) === 1) {
  30. return $conditions[0];
  31. }
  32. $wrappedConditions = array_map(
  33. fn(string $condition):string => "({$condition})",
  34. $conditions
  35. );
  36. return implode(" or ", $wrappedConditions);
  37. }
  38. private function buildCondition(string $selector, bool $htmlMode):string {
  39. $prefix = str_starts_with($selector, ">")
  40. || str_starts_with($selector, "+")
  41. || str_starts_with($selector, "~")
  42. ? "."
  43. : ".//";
  44. return $this->singleSelectorConverter->convert(
  45. $selector,
  46. $prefix,
  47. $htmlMode
  48. );
  49. }
  50. private function assertSupported(string $selectorList):void {
  51. if(preg_match('/(^|[^[:alnum:]_-]):has\s*\(/', $selectorList) === 1) {
  52. throw new NotYetImplementedException(
  53. "Nested :has selector functionality is deferred"
  54. );
  55. }
  56. if(str_contains($selectorList, "::")) {
  57. throw new NotYetImplementedException(
  58. "Pseudo-element :has selector functionality is deferred"
  59. );
  60. }
  61. if(preg_match('/:nth-child\([^)]*\bof\b/', $selectorList) === 1) {
  62. throw new NotYetImplementedException(
  63. "':nth-child(of S)' in :has selector functionality is deferred"
  64. );
  65. }
  66. }
  67. }