1<?php
2
3/**
4 * DokuWiki Plugin doxycode (Parser Helper Component)
5 *
6 * @license     GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
7 * @author      Lukas Probsthain <lukas.probsthain@gmail.com>
8 */
9
10use dokuwiki\Extension\Plugin;
11
12class helper_plugin_doxycode_parser extends Plugin
13{
14    /**
15     * @var Array $mapping Associative array that maps certain highlight classes in the
16     * XML file to their corresponding DokuWiki CSS classes.
17     *
18     * This mapping is used in the `renderXMLToDokuWikiCode()` method to convert the XML code to DokuWiki syntax.
19     */
20    private $mapping = array(
21        'comment' => 'co1',
22        'keywordtype' => 'kw0',
23        'keywordflow' => 'kw1',
24        'preprocessor' => 'co2',
25        'stringliteral' => 'st0'
26    );
27
28    /**
29     * The function `renderXMLToDokuWikiCode` takes an XML string, line number setting, and tag
30     * configuration as input and returns DokuWiki code.
31     *
32     * @param string A string containing XML code.
33     * @param boolean A boolean value indicating whether line numbers should be included in the output
34     * or not.
35     * @param array The `tag_conf` parameter is an optional parameter that allows you to specify a
36     * configuration for parsing specific XML tags. It is used in the `_parseDoxygenXMLElement` function,
37     * which is called for each codeline element in the XML.
38     *
39     * @return string output string, which contains the DokuWiki code generated from the XML input.
40     */
41    public function renderXMLToDokuWikiCode($xmlString, $line_numbers, $tag_conf = null)
42    {
43        $output = '';
44
45
46        $dom = new DOMDocument();
47        $dom->loadXML($xmlString);
48
49        // find the programlisting element inside the doxygen XML
50        $xpath = new DOMXPath($dom);
51        $programListing = $xpath->query('//programlisting')->item(0);
52
53        // if linenumber setting is present output list elements around the codelines!
54        if ($line_numbers) {
55            $output .= '<ol>';
56
57            // $this->doc = str_replace("\n", "", $this->doc);
58        }
59
60        // loop over the codeline elements
61        foreach ($programListing->childNodes as $codeline) {
62            if ($codeline->hasChildNodes()) {
63                if ($line_numbers) {
64                    $output .= '<li class=\"li1\"><div>';
65                }
66
67                $this->parseDoxygenXMLElement($codeline, $output, $tag_conf);
68
69                if ($line_numbers) {
70                    $output .= '</div></li>';
71                } else {
72                    $output .= DOKU_LF;
73                }
74            }
75        }
76
77        if ($line_numbers) {
78            $output .= '</ol>';
79        }
80
81        return $output;
82    }
83
84    /**
85     * Parse the children of codeline elements of a doxygen XML output and their children.
86     *
87     * Individual lines from a source file are converted to <codeline>...</codeline> elements by doxygen.
88     * Here we parse the children of codeline elements and convert them to HTML elements that correspond
89     * to the elements of a default dokuwiki code snippet.
90     *
91     * Some of the elements also contain children (e.g. <highlight ...><ref ...>...</ref>...</highlight>).
92     * In those cases we recursivly call this function until no children are found.
93     *
94     * @param DOMElement $element Child element from the doxygen XML we want to parse
95     * @param String &$output Reference to the output string we append the generated HTML to
96     * @param Array $tag_conf Tag configuration used for generating the reference URLS
97     */
98    private function parseDoxygenXMLElement($element, &$output, $tag_conf = null)
99    {
100        global $conf;
101
102        // helper:
103        // highlight = <span> element
104        // sp = ' ' (space)
105        // ref = <a href="...">
106
107        foreach ($element->childNodes as $node) {
108            if ($node->nodeType === XML_ELEMENT_NODE) {
109                switch ($node->nodeName) {
110                    /**
111                     * The `case 'highlight'` matches the syntax highlighting elements inside
112                     * the XML and matches these to dokuwiki CSS classes for code blocks.
113                     */
114                    case 'highlight':
115                        $output .= '<span';
116                        if ($node->hasAttribute('class')) {
117                            $output .= ' class="';
118                            if ($this->mapping[$node->getAttribute('class')] !== '') {
119                                $output .= $this->mapping[$node->getAttribute('class')];
120                            } else {
121                                // if we cannot map a class from dokuwiki - just use the doxygen class for now
122                                $output .= $node->getAttribute('class');
123                            }
124                            $output .= '"';
125                        }
126                        $output .= '>';
127                        // check if $element has children or content
128                        if ($node->hasChildNodes()) {
129                            // parse the elements inside the span element
130                            $this->parseDoxygenXMLElement($node, $output, $tag_conf);
131                        }
132                        $output .= '</span>';
133                        break;
134                    case 'sp':
135                        // sp is just converted to space
136                        $output .= ' ';
137                        break;
138                    case 'ref':
139                        $output .= "<a";
140                        if ($node->hasAttribute('external') && $node->hasAttribute('refid')) {
141                            $output .= ' href="';
142                            $output .= $this->convertRefToURL($node, $tag_conf);
143                            $output .= '" target="' . $conf['target']['extern'];
144                            $output .= '"';
145                        }
146                        $output .= ">";
147                        $output .= $node->nodeValue;
148                        $output .= "</a>";
149                        break;
150                    default:
151                        break;
152                }
153            }
154
155            // plain text inside an element is just appended to the document output
156            if ($node->nodeType === XML_TEXT_NODE) {
157                $output .= $node->nodeValue;
158            }
159        }
160    }
161
162    /**
163     * Convert the external reference from a doxygen XML to the documentation URL.
164     *
165     * The <ref...> element in the doxygen XML output includes the following elements:
166     * - refid: page identifier + anchor to the element in the documentation
167     * - external: name of the tag file of the documentation this reference points to
168     *
169     * The external attribute should match one of the tag file names we used when building the
170     * documentation. We can use this attribute to find the tag file configuration, which in turn
171     * includes the documentation base URL.
172     *
173     * We then convert the refid to a doxygen documentation html file name and append the anchor if
174     * ther is one.
175     *
176     * @param DOMElement &$node reference to the XML reference element
177     * @param Array $tag_conf Tag file configuration
178     * @return String URL to the doxygen documentation for this reference
179     */
180    private function convertRefToURL(&$node, $tag_conf = null)
181    {
182        $output = '';
183
184        $external = $node->getAttribute('external');
185        $ref = $node->getAttribute('refid');
186
187        /** @var helper_plugin_doxycode_tagmanager $tagmanager */
188        $tagmanager = plugin_load('helper', 'doxycode_tagmanager');
189
190        // match the external attribute to the tag file and extract the documentation URL
191        foreach ($tag_conf as $key => $conf) {
192            if (realpath($tagmanager->getTagFileDir() . $key . '.xml') === $external) {
193                $output .= $conf['docu_url'];
194                break;
195            }
196        }
197
198        $kindref = '';
199
200        if ($node->hasAttribute('kindref')) {
201            $kindref = $node->getAttribute('kindref');
202        }
203
204        if ($kindref === 'member') {
205            $lastUnderscorePos = strrpos($ref, '_');
206
207            $first = substr($ref, 0, $lastUnderscorePos);
208            // we omit the underscore and the first character to get the anchor
209            $last = substr($ref, $lastUnderscorePos + 2);
210
211            $output .= $first;
212            $output .= ".html#";
213            $output .= $last;
214        } else {
215            // probably 'compound'
216            $output .= $ref;
217
218            // some refs are directly the wanted page (includes, ...)
219            if (substr($output, -5) !== '.html') {
220                $output .= ".html";
221            }
222        }
223
224        return $output;
225    }
226}
227