fix wrapper->fmt calls
[mdref/mdref] / mdref / Formatter / Wrapper.php
1 <?php
2 namespace mdref\Formatter;
3
4 use DomNode;
5 use DOMText;
6 use ReflectionExtension;
7 use mdref\Formatter;
8
9 class Wrapper {
10 protected $docref = "https://php.net/manual/en/%s";
11 protected $types = [
12 "language.types.declarations#language.types.declarations.%s" => ["void", "mixed"],
13 "language.types.%s" => ["null", "boolean", "integer", "float", "string", "resource", "array", "callable", "iterable"],
14 "language.types.null" => ["NULL"],
15 "language.types.boolean" => ["true", "TRUE", "false", "FALSE", "bool", "BOOL"],
16 "language.types.integer" => ["int", "long"],
17 "language.types.float" => ["double", "real"],
18 "language.types.object" => ["stdClass"],
19 "language.types.callable" => ["callback"],
20 "language.types.enumerations" => ["enum"],
21 "language.references" => ["reference"],
22 ];
23 protected $exts = ["standard", "core", "spl", "json", "date"];
24
25 function __construct(
26 protected Formatter $fmt
27 ) {}
28
29 public function wrap(DOMText $node, $pld) : void {
30 $nodes = [];
31
32 $split = "[&?\(\)\|\"'\s\][\.,-]+";
33 $items = preg_split("/($split)/", $node->textContent, 0, PREG_SPLIT_DELIM_CAPTURE);
34 foreach ($items as $item) {
35 if (preg_match("/^($split|[[:punct:]+])*$/", $item)) {
36 $nodes[] = $node->ownerDocument->createTextNode($item);
37 continue;
38 }
39
40 $new = $this->wrapType($node, $item, $pld)
41 ?: $this->wrapKeyWord($node, $item, $pld)
42 ?: $this->wrapSpecial($node, $item, $pld);
43 if (is_array($new)) {
44 foreach ($new as $n)
45 $nodes[] = $n;
46 } elseif ($new) {
47 $nodes[] = $new;
48 } else {
49 $nodes[] = $node->ownerDocument->createTextNode($item);
50 }
51 }
52 if ($nodes) {
53 $parent = $node->parentNode;
54 $new_node = array_pop($nodes);
55 $parent->replaceChild($new_node, $node);
56 foreach ($nodes as $prev_node) {
57 $parent->insertBefore($prev_node, $new_node);
58 }
59 }
60 }
61
62 protected function getType(string $item) : ?string {
63 static $types;
64 if (!$types) {
65 foreach ($this->types as $doc => $list) foreach ($list as $type) {
66 $types[$type] = sprintf($this->docref, sprintf($doc, $type));
67 }
68 foreach ($this->exts as $ext) foreach ((new ReflectionExtension($ext))->getClassNames() as $class) {
69 $types[$class] = sprintf($this->docref, "class." . strtolower($class));
70 }
71 }
72
73 $item = trim($item, "\\");
74 if (!isset($types[$item])) {
75 return null;
76 }
77 return $types[$item];
78 }
79 protected function wrapType(DOMText $node, string $item, $pld) : ?DOMNode {
80 if (!($type = $this->getType($item))) {
81 return null;
82 }
83 $a = $node->ownerDocument->createElement("a");
84 $a->setAttribute("href", $type);
85 $a->textContent = $item;
86 $code = $node->ownerDocument->createElement("code");
87 $code->insertBefore($a);
88 return $code;
89 }
90
91 protected function wrapKeyword(DOMText $node, string $item, $pld) : DomNode|array|null {
92 switch ($item) {
93 case "is":
94 if ($node->parentNode->nodeName !== "h1") {
95 break;
96 }
97 case "extends":
98 case "implements":
99 if ($node->parentNode->nodeName === "h1") {
100 $nodes = [
101 $node->ownerDocument->createElement("br"),
102 $node->ownerDocument->createEntityReference("nbsp"),
103 $new = $node->ownerDocument->createElement("em")
104 ];
105 $new->textContent = $item;
106 return $nodes;
107 }
108 case "class":
109 case "enum":
110 case "interface":
111 case "namespace":
112 case "public":
113 case "protected":
114 case "private":
115 case "static":
116 case "final":
117 case "abstract":
118 case "self":
119 case "parent":
120 $new = $node->ownerDocument->createElement("em");
121 $new->textContent = $item;
122 return $new;
123 }
124 return null;
125 }
126
127 protected function isFirstDeclaration(DOMNode $node, string $item, bool $is_slug = false) : bool {
128 return $node->parentNode->nodeName === "li"
129 && !$node->ownerDocument->getElementById($is_slug ? $item : $this->fmt->formatSlug($item));
130 }
131
132 protected function isVar(string $item) : bool {
133 return str_starts_with($item, "\$");
134 }
135
136 protected function wrapVar(DOMNode $node, string $item, $pld) : DOMNode {
137 $ele = $node->ownerDocument->createElement("span");
138 $ele->setAttribute("class", "var");
139 $ele->textContent = $item;
140
141 if (!empty($pld->currentSection)) {
142 $slug = $this->fmt->formatSlug($item);
143 if ($this->isFirstDeclaration($node, $slug, true)) {
144 $perm = $this->fmt->createPermaLink($ele, $slug, $pld);
145 $ele->insertBefore($perm);
146 }
147 }
148 return $ele;
149 }
150
151 protected function isNamespaced(DOMNode $node, string $item, $pld) : bool {
152 return str_contains($item, "\\") || str_contains($item, "::");
153 }
154
155 protected function wrapNamespaced(DOMNode $node, string $item, $pld) : ?DOMNode {
156 $href = preg_replace("/\\\\|::/", "/", trim($item, "\\:"));
157 $canonical = null;
158 $repo = $pld->refs->getRepoForEntry($href, $canonical);
159
160 if ($repo) {
161 if (!empty($canonical)) {
162 $href = $canonical;
163 }
164 $link = $node->ownerDocument->createElement("a");
165 $link->setAttribute("href", $href);
166 $link->textContent = $item;
167 return $link;
168 }
169
170 $hash = basename($href);
171 $href = dirname($href);
172 $repo = $pld->refs->getRepoForEntry($href, $canonical);
173 if ($repo) {
174 if (!empty($canonical)) {
175 $href = $canonical;
176 }
177 $link = $node->ownerDocument->createElement("a");
178 $link->setAttribute("href", "$href#$hash");
179 $link->textContent = $item;
180 return $link;
181 }
182
183 return null;
184 }
185
186 protected function wrapConstant(DOMNode $node, string $item, $pld) : ?DOMNode {
187 $strict = "_";
188 if (!empty($pld->currentSection)) {
189 switch ($pld->currentSection) {
190 case "Properties:":
191 case "Constants:":
192 $strict = "";
193 break;
194 }
195 }
196 if (preg_match("/^[A-Z]({$strict}[A-Z0-9_v])+\$/", $item)) {
197 // assume some constant
198 $span = $node->ownerDocument->createElement("span");
199 $span->setAttribute("class", "constant");
200 $span->textContent = $item;
201 if (!$strict && $pld->currentSection === "Constants:" && $node->parentNode->nodeName === "li" && $node->parentNode->firstChild === $node) {
202 $perm = $this->fmt->createPermaLink($span, $this->fmt->formatSlug($item), $pld);
203 $span->insertBefore($perm);
204 }
205 return $span;
206 }
207
208 return null;
209 }
210
211 protected function wrapSpecial(DOMNode $node, string $item, $pld) : ?DOMNode {
212 if ($this->isVar($item)) {
213 if (($ele = $this->wrapVar($node, $item, $pld))) {
214 return $ele;
215 }
216 }
217 if ($this->isNamespaced($node, $item, $pld)) {
218 if (($ele = $this->wrapNamespaced($node, $item, $pld))) {
219 return $ele;
220 }
221 }
222 return $this->wrapConstant($node, $item, $pld);
223 }
224 }