new search query parser; support for NOT
[grauphel.git] / lib / converter / html.php
1 <?php
2 /**
3  * Part of grauphel
4  *
5  * PHP version 5
6  *
7  * @category  Tools
8  * @package   Grauphel
9  * @author    Christian Weiske <cweiske@cweiske.de>
10  * @copyright 2014 Christian Weiske
11  * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
12  * @link      http://cweiske.de/grauphel.htm
13  */
14 namespace OCA\Grauphel\Lib\Converter;
15 use \XMLReader;
16
17 /**
18  * Convert Tomboy note XML to HTML
19  *
20  * Tomboy already ships with a converter:
21  * https://git.gnome.org/browse/tomboy/tree/Tomboy/Addins/ExportToHtml/ExportToHtml.xsl
22  * We cannot use it since we want nice callbacks, and we do not want to rely
23  * on the PHP XSL extension, and we have to fix the links.
24  *
25  * @category  Tools
26  * @package   Grauphel
27  * @author    Christian Weiske <cweiske@cweiske.de>
28  * @copyright 2014 Christian Weiske
29  * @license   http://www.gnu.org/licenses/agpl.html GNU AGPL v3
30  * @version   Release: @package_version@
31  * @link      http://cweiske.de/grauphel.htm
32  */
33 class Html
34 {
35     protected static $tagMap = array(
36         'list'      => 'ul',
37         'list-item' => 'li',
38         'bold'      => 'b',
39         'italic'    => 'i',
40         'monospace' => 'tt',
41     );
42
43     protected static $styleClassMap = array(
44         'strikethrough' => 'strikethrough',
45         'highlight'  => 'highlight',
46         'size:small' => 'small',
47         'size:large' => 'large',
48         'size:huge'  => 'huge',
49     );
50
51     public $internalLinkHandler;
52
53
54
55     public function __construct()
56     {
57         $this->internalLinkHandler = array($this, 'internalLinkHandler');
58     }
59
60     /**
61      * Converts the tomboy note XML into HTML
62      *
63      * @param string $xmlContent Tomboy note content
64      *
65      * @return string HTML
66      */
67     public function convert($xmlContent)
68     {
69         if (strpos($xmlContent, '</link:internal><link:internal>') !== false) {
70             $xmlContent = $this->fixNastyLinks($xmlContent);
71         }
72
73         $html = '';
74         $reader = new XMLReader();
75         $reader->xml(
76             '<?xml version="1.0" encoding="utf-8"?>' . "\n"
77             . '<content xmlns:size="size" xmlns:link="link">'
78             . $xmlContent
79             . '</content>'
80         );
81
82         $withinLink = false;
83         $store = &$html;
84         while ($reader->read()) {
85             switch ($reader->nodeType) {
86             case XMLReader::ELEMENT:
87                 //echo $reader->name . "\n";
88                 if (isset(static::$tagMap[$reader->name])) {
89                     $store .= '<' . static::$tagMap[$reader->name] . '>';
90                 } else if (isset(static::$styleClassMap[$reader->name])) {
91                     $store .= '<span class="'
92                         . static::$styleClassMap[$reader->name]
93                         . '">';
94                 } else if (substr($reader->name, 0, 5) == 'link:') {
95                     $withinLink = true;
96                     $linkText    = '';
97                     $store       = &$linkText;
98                 }
99                 break;
100             case XMLReader::END_ELEMENT:
101                 if (isset(static::$tagMap[$reader->name])) {
102                     $store .= '</' . static::$tagMap[$reader->name] . '>';
103                 } else if (isset(static::$styleClassMap[$reader->name])) {
104                     $store .= '</span>';
105                 } else if (substr($reader->name, 0, 5) == 'link:') {
106                     $withinLink = false;
107                     $store      = &$html;
108                     $linkUrl = htmlspecialchars_decode(strip_tags($linkText));
109                     if ($reader->name == 'link:internal') {
110                         $linkUrl = call_user_func($this->internalLinkHandler, $linkUrl);
111                     } else {
112                         $linkUrl = $this->fixLinkUrl($linkUrl);
113                     }
114                     $store .= '<a href="' . htmlspecialchars($linkUrl) . '">'
115                         . $linkText
116                         . '</a>';
117                 }
118                 break;
119             case XMLReader::TEXT:
120             case XMLReader::SIGNIFICANT_WHITESPACE:
121                 $store .= nl2br(htmlspecialchars($reader->value));
122                 break;
123             default:
124                 throw new \Exception(
125                     'Unsupported XML node type: ' . $reader->nodeType
126                 );
127             }
128         }
129
130         $html = str_replace("</ul><br />\n", "</ul>\n", $html);
131
132         return $html;
133     }
134
135     /**
136      * Fixes external URLs without a protocol
137      *
138      * @param string $linkUrl URL to fix
139      *
140      * @return string Fixed URL
141      */
142     protected function fixLinkUrl($linkUrl)
143     {
144         if ($linkUrl{0} == '/') {
145             //Unix file path
146             $linkUrl = 'file://' . $linkUrl;
147         }
148         return $linkUrl;
149     }
150
151     /**
152      * Dummy internal link handler that simply adds ".htm" to the note title
153      *
154      * @param string $linkUrl Title of page that is linked
155      *
156      * @return string URL to link to
157      */
158     public function internalLinkHandler($linkUrl)
159     {
160         return $linkUrl . '.htm';
161     }
162
163     /**
164      * Re-arranges the XML of formatted links to that clean link tags can
165      * be generated.
166      *
167      * Tomboy 1.15.2 allows link formatting, and the resulting XML is a
168      * mess of multiple(!) link tags that are within or around other formatting
169      * tags.
170      *
171      * This method tries to re-arrange the links so that only a single link tag
172      * appears with all the formatting inside.
173      * 
174      * @param string $xmlContent Tomboy note content
175      *
176      * @return string XML content, with re-arranged link tags.
177      */
178     protected function fixNastyLinks($xmlContent)
179     {
180         preg_match_all(
181             '#(?:<.*>)?<link:internal>.+</link:internal><link:internal>.+</link:internal>#U',
182             $xmlContent,
183             $matches
184         );
185
186         foreach ($matches[0] as $nastyLink) {
187             $cleaner = str_replace('</link:internal><link:internal>', '', $nastyLink);
188             $cleaner = preg_replace('#<([a-z]+)><(link:internal)>#U', '<\2><\1>', $cleaner);
189             $cleaner = preg_replace('#</(link:internal)></([a-z]+)>#U', '</\2></\1>', $cleaner);
190             $cleaner = str_replace('</link:internal><link:internal>', '', $cleaner);
191             $xmlContent = str_replace($nastyLink, $cleaner, $xmlContent);
192         }
193
194         return $xmlContent;
195     }
196 }
197 ?>