CssAttributeTokenBuilder.php 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. <?php
  2. namespace Gt\CssXPath;
  3. class CssAttributeTokenBuilder {
  4. /**
  5. * @return array<string, mixed>
  6. */
  7. public function build(string $content, ?callable $transform):array {
  8. $operatorData = $this->extractOperator($content);
  9. $token = $this->buildMatchPayload(
  10. "attribute",
  11. $operatorData["name"],
  12. $transform
  13. );
  14. if($operatorData["operator"] === null) {
  15. return $token;
  16. }
  17. $token["detail"] = [
  18. $this->buildMatchPayload(
  19. "attribute_equals",
  20. $operatorData["operator"],
  21. $transform
  22. ),
  23. $this->buildMatchPayload(
  24. "attribute_value",
  25. $operatorData["value"],
  26. $transform
  27. ),
  28. ];
  29. return $token;
  30. }
  31. /**
  32. * @return array{name: string, operator: string|null, value: string}
  33. */
  34. private function extractOperator(string $content):array {
  35. $operators = ["~=", "$=", "|=", "^=", "*=", "="];
  36. $quote = null;
  37. $length = strlen($content);
  38. for($index = 0; $index < $length; $index++) {
  39. $char = $content[$index];
  40. if($quote !== null) {
  41. if($char === $quote) {
  42. $quote = null;
  43. }
  44. continue;
  45. }
  46. if($char === "'" || $char === '"') {
  47. $quote = $char;
  48. continue;
  49. }
  50. $matchedOperator = $this->matchOperator(
  51. $content,
  52. $index,
  53. $operators
  54. );
  55. if($matchedOperator === null) {
  56. continue;
  57. }
  58. return [
  59. "name" => trim(substr($content, 0, $index)),
  60. "operator" => $matchedOperator,
  61. "value" => trim(
  62. substr($content, $index + strlen($matchedOperator))
  63. ),
  64. ];
  65. }
  66. return [
  67. "name" => trim($content),
  68. "operator" => null,
  69. "value" => "",
  70. ];
  71. }
  72. /**
  73. * @param array<int, string> $operators
  74. */
  75. private function matchOperator(
  76. string $content,
  77. int $index,
  78. array $operators
  79. ):?string {
  80. foreach($operators as $operator) {
  81. if(substr($content, $index, strlen($operator)) === $operator) {
  82. return $operator;
  83. }
  84. }
  85. return null;
  86. }
  87. /** @return array<string, string> */
  88. private function buildMatchPayload(
  89. string $groupKey,
  90. string $match,
  91. ?callable $transform
  92. ):array {
  93. if($transform) {
  94. return $transform($groupKey, $match);
  95. }
  96. return ["type" => $groupKey, "content" => $match];
  97. }
  98. }