workflow/publish: update to php-8.1
[mdref/mdref] / mdref / Formatter.php
1 <?php
2
3 namespace mdref;
4
5 use DOMDocument;
6 use DOMElement;
7 use DOMNode;
8 use League\CommonMark\GithubFlavoredMarkdownConverter;
9 use League\CommonMark\MarkdownConverter;
10 use League\CommonMark\Normalizer;
11 use League\CommonMark\Extension;
12 use mdref\Formatter\Wrapper;
13
14 class Formatter {
15 public function __construct(
16 protected ?MarkdownConverter $md = null,
17 protected ?Wrapper $wrapper = null,
18 ) {
19 if (!$this->md) {
20 $this->md = new GithubFlavoredMarkdownConverter([
21 "slug_normalizer" => [
22 "instance" => new class($this) implements Normalizer\TextNormalizerInterface {
23 protected $formatter;
24 function __construct(Formatter $fmt) {
25 $this->formatter = $fmt;
26 }
27 function normalize(string $text, $context = null) : string {
28 return $this->formatter->formatSlug($text);
29 }
30 }
31 ],
32 ]);
33 $this->md->getEnvironment()->addExtension(
34 new Extension\DescriptionList\DescriptionListExtension
35 );
36 $this->md->getEnvironment()->addExtension(
37 new Extension\Attributes\AttributesExtension
38 );
39 }
40 if (!$this->wrapper) {
41 $this->wrapper = new Wrapper($this);
42 }
43 }
44
45 public function formatString(string $string) : string {
46 return $this->md->convertToHtml($string);
47 }
48
49 public function formatFile(string $file) : string {
50 $string = file_get_contents($file);
51 if ($string === false) {
52 throw Exception::fromLastError();
53 }
54 return $this->md->convertToHtml($string);
55 }
56
57 /**
58 * Format a simplified url slug
59 * @param string $string input text, like a heading
60 * @return string the simplified slug
61 */
62 public function formatSlug(string $string) : string {
63 return preg_replace("/[^\$[:alnum:]:._-]+/", ".", trim($string, "/ \r\n\t"));
64 }
65
66 /**
67 * @param string $page HTML content
68 * @param object $pld Action payload
69 * @return string marked up HTML content
70 */
71 public function markup(string $page, $pld) : string {
72 $dom = new DOMDocument("1.0", "utf-8");
73 $dom->formatOutput = true;
74 $dom->loadHTML("<!doctype html>\n <meta charset=utf-8>\n" . $page, LIBXML_HTML_NOIMPLIED);
75 foreach ($dom->childNodes as $node) {
76 $this->walk($node, $pld);
77 }
78 $html = "";
79 foreach ($dom->childNodes as $child) {
80 $html .= $dom->saveHTML($child);
81 }
82 return $html;
83 }
84
85 public function createPermaLink(DOMElement $node, string $slug, $pld) {
86 if (strlen($slug)) {
87 $node->setAttribute("id", $slug);
88 }
89 $perm = $node->ownerDocument->createElement("a");
90 $perm->setAttribute("class", "permalink");
91 $perm->setAttribute("href", "$pld->ref#$slug");
92 $perm->textContent = "#";
93 return $perm;
94 }
95
96 protected function walk(DOMNode $node, $pld) {
97 switch ($node->nodeType) {
98 case XML_ELEMENT_NODE:
99 $this->walkElement($node, $pld);
100 break;
101 case XML_TEXT_NODE:
102 $this->wrapper->wrap($node, $pld);
103 break;
104 default:
105 break;
106 }
107 }
108
109 protected function highlightCode(DOMElement $node) {
110 foreach (["default", "comment", "html", "keyword", "string"] as $type) {
111 ini_set("highlight.$type", "inherit\" class=\"$type");
112 }
113 $code = highlight_string($node->textContent, true);
114 $temp = new DOMDocument("1.0", "utf-8");
115 $temp->loadHTML($code, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
116 return $node->ownerDocument->importNode($temp->firstChild, true);
117 }
118
119 protected function walkElement(DOMElement $node, $pld) {
120 switch ($node->tagName) {
121 case "h1":
122 $perm = $this->createPermaLink($node, "", $pld);
123 $node->insertBefore($perm, $node->firstChild);
124 $pld->currentSection = null;
125 break;
126 case "h2":
127 $pld->currentSection = $this->formatSlug($node->textContent);
128 case "h3":
129 case "h4":
130 case "h5":
131 case "h6":
132 $slug = $this->formatSlug($node->textContent);
133 $perm = $this->createPermaLink($node, $slug, $pld);
134 $node->appendChild($perm);
135 break;
136 case "span":
137 if (!empty($pld->currentSection) && $node->hasAttribute("class")) {
138 switch ($pld->currentSection) {
139 case "Properties:":
140 case "Constants:":
141 switch ($node->getAttribute("class")) {
142 case "constant":
143 case "var":
144 $slug = $this->formatSlug($node->textContent);
145 $perm = $this->createPermaLink($node, $slug, $pld);
146 $node->insertBefore($perm);
147 break;
148 }
149 break;
150 }
151 }
152 case "a":
153 case "br":
154 case "hr":
155 case "em":
156 return; // !
157 case "code":
158 if ($node->parentNode && $node->parentNode->nodeName === "pre") {
159 $code = $this->highlightCode($node);
160 $this->walk($code, $pld);
161 $node->parentNode->replaceChild($code, $node);
162 }
163 return; // !
164 }
165
166 // suck it out, because we're modifying the DOM
167 foreach (iterator_to_array($node->childNodes) as $child) {
168 $this->walk($child, $pld);
169 }
170 }
171 }