AttributeSelectorConverter.php 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. <?php
  2. namespace Gt\CssXPath;
  3. class AttributeSelectorConverter {
  4. /** @param array<string, mixed> $token */
  5. public function apply(
  6. array $token,
  7. XPathExpression $expression,
  8. bool $htmlMode
  9. ):void {
  10. $expression->ensureElement();
  11. $attribute = (string)$token["content"];
  12. if($htmlMode) {
  13. $attribute = strtolower($attribute);
  14. }
  15. $detail = $token["detail"] ?? null;
  16. $detailType = $detail[0] ?? null;
  17. $detailValue = $detail[1] ?? null;
  18. if(!$this->hasEqualsType($detailType)) {
  19. $expression->appendFragment("[@{$attribute}]");
  20. return;
  21. }
  22. $valueString = trim((string)$detailValue["content"], " '\"");
  23. $equalsType = $detailType["content"];
  24. $expression->appendFragment(
  25. $this->buildExpression($attribute, $valueString, $equalsType)
  26. );
  27. }
  28. /** @param array<string, mixed> $token */
  29. public function buildConditionFromToken(array $token, bool $htmlMode):string {
  30. $parts = $this->extractTokenParts($token, $htmlMode);
  31. return $this->buildConditionFromParts(
  32. $parts["attribute"],
  33. $parts["detailType"],
  34. $parts["detailValue"],
  35. );
  36. }
  37. /**
  38. * @param array<string, mixed>|null $detailType
  39. * @param array<string, mixed>|null $detailValue
  40. */
  41. private function buildConditionFromParts(
  42. string $attribute,
  43. ?array $detailType,
  44. ?array $detailValue,
  45. ):string {
  46. if(!$this->hasEqualsType($detailType)) {
  47. return "@{$attribute}";
  48. }
  49. $valueString = trim((string)$detailValue["content"], " '\"");
  50. $equalsType = $detailType["content"];
  51. return $this->buildCondition($attribute, $valueString, $equalsType);
  52. }
  53. /**
  54. * @param array<string, mixed> $token
  55. * @return array{
  56. * attribute: string,
  57. * detailType: array<string, mixed>|null,
  58. * detailValue: array<string, mixed>|null
  59. * }
  60. */
  61. private function extractTokenParts(array $token, bool $htmlMode):array {
  62. $attribute = (string)$token["content"];
  63. if($htmlMode) {
  64. $attribute = strtolower($attribute);
  65. }
  66. $detail = $token["detail"] ?? null;
  67. return [
  68. "attribute" => $attribute,
  69. "detailType" => $detail[0] ?? null,
  70. "detailValue" => $detail[1] ?? null,
  71. ];
  72. }
  73. /** @param array<string, mixed>|null $detailType */
  74. private function hasEqualsType(?array $detailType):bool {
  75. return isset($detailType["type"])
  76. && $detailType["type"] === "attribute_equals";
  77. }
  78. private function buildCondition(
  79. string $attribute,
  80. string $value,
  81. string $equalsType
  82. ):string {
  83. return match($equalsType) {
  84. Translator::EQUALS_EXACT => "@{$attribute}=\"{$value}\"",
  85. Translator::EQUALS_CONTAINS => "contains(@{$attribute},\"{$value}\")",
  86. Translator::EQUALS_CONTAINS_WORD => ""
  87. . "contains(concat(\" \",@{$attribute},\" \"),"
  88. . "concat(\" \",\"{$value}\",\" \"))"
  89. . "",
  90. Translator::EQUALS_OR_STARTS_WITH_HYPHENATED => ""
  91. . "@{$attribute}=\"{$value}\" or "
  92. . "starts-with(@{$attribute}, \"{$value}-\")"
  93. . "",
  94. Translator::EQUALS_STARTS_WITH => ""
  95. . "starts-with(@{$attribute}, \"{$value}\")"
  96. . "",
  97. Translator::EQUALS_ENDS_WITH => ""
  98. . "substring(@{$attribute},"
  99. . "string-length(@{$attribute}) - "
  100. . "string-length(\"{$value}\") + 1)"
  101. . "=\"{$value}\""
  102. . "",
  103. default => "@{$attribute}",
  104. };
  105. }
  106. private function buildExpression(
  107. string $attribute,
  108. string $value,
  109. string $equalsType
  110. ):string {
  111. return "[" . $this->buildCondition($attribute, $value, $equalsType) . "]";
  112. }
  113. }