Display tab-based line indentation in HTML
[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\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 extends Base
34 {
35     protected static $tagMap = array(
36         'list'      => 'ul',
37         'list-item' => 'li',
38         'bold'      => 'b',
39         'italic'    => 'i',
40     );
41
42     protected static $styleClassMap = array(
43         'strikethrough' => 'strikethrough',
44         'highlight'  => 'highlight',
45         'size:small' => 'small',
46         'size:large' => 'large',
47         'size:huge'  => 'huge',
48     );
49
50     protected static $styleMap = array(
51         'monospace' => 'font-family:monospace; white-space: pre-wrap'
52     );
53
54     public $internalLinkHandler;
55
56
57
58     public function __construct()
59     {
60         $this->internalLinkHandler = array($this, 'internalLinkHandler');
61     }
62
63     /**
64      * Converts the tomboy note XML into HTML
65      *
66      * @param string $xmlContent Tomboy note content
67      *
68      * @return string HTML
69      */
70     public function convert($xmlContent)
71     {
72         if (strpos($xmlContent, '</link:internal><link:internal>') !== false) {
73             $xmlContent = $this->fixNastyLinks($xmlContent);
74         }
75
76         $html = '';
77         $reader = new XMLReader();
78         $reader->xml(
79             '<?xml version="1.0" encoding="utf-8"?>' . "\n"
80             . '<content xmlns:size="size" xmlns:link="link">'
81             . $xmlContent
82             . '</content>'
83         );
84
85         $withinLink = false;
86         $nesting = array();
87         $store = &$html;
88         while ($reader->read()) {
89             switch ($reader->nodeType) {
90             case XMLReader::ELEMENT:
91                 //echo $reader->name . "\n";
92                 array_unshift($nesting, $reader->name);
93                 if (isset(static::$tagMap[$reader->name])) {
94                     $store .= '<' . static::$tagMap[$reader->name] . '>';
95                 } else if (isset(static::$styleClassMap[$reader->name])) {
96                     $store .= '<span class="'
97                         . static::$styleClassMap[$reader->name]
98                         . '">';
99                 } else if (isset(static::$styleMap[$reader->name])) {
100                     $store .= '<span style="'
101                         . static::$styleMap[$reader->name]
102                         . '">';
103                 } else if (substr($reader->name, 0, 5) == 'link:') {
104                     $withinLink = true;
105                     $linkText    = '';
106                     $store       = &$linkText;
107                 }
108                 break;
109             case XMLReader::END_ELEMENT:
110                 array_shift($nesting);
111                 if (isset(static::$tagMap[$reader->name])) {
112                     $store .= '</' . static::$tagMap[$reader->name] . '>';
113                 } else if (isset(static::$styleClassMap[$reader->name])) {
114                     $store .= '</span>';
115                 } else if (isset(static::$styleMap[$reader->name])) {
116                     $store .= '</span>';
117                 } else if (substr($reader->name, 0, 5) == 'link:') {
118                     $withinLink = false;
119                     $store      = &$html;
120                     $linkUrl = htmlspecialchars_decode(strip_tags($linkText));
121                     if ($reader->name == 'link:internal') {
122                         $linkUrl = call_user_func($this->internalLinkHandler, $linkUrl);
123                     } else {
124                         $linkUrl = $this->fixLinkUrl($linkUrl);
125                     }
126                     $store .= '<a href="' . htmlspecialchars($linkUrl) . '">'
127                         . $linkText
128                         . '</a>';
129                 }
130                 break;
131             case XMLReader::TEXT:
132             case XMLReader::SIGNIFICANT_WHITESPACE:
133                 $text = htmlspecialchars($reader->value);
134                 if ($nesting[0] != 'monospace') {
135                     $text = nl2br($text);
136                 }
137                 $text = preg_replace_callback(
138                     "#^\t+#m",
139                     function ($matches) {
140                         return str_repeat(
141                             '&#160;', strlen($matches[0]) * 8
142                         );
143                     },
144                     $text
145                 );
146                 $store .= $text;
147                 break;
148             default:
149                 throw new Exception(
150                     'Unsupported XML node type: ' . $reader->nodeType
151                 );
152             }
153         }
154
155         $html = str_replace("</ul><br />\n", "</ul>\n", $html);
156
157         return $html;
158     }
159
160     /**
161      * Fixes external URLs without a protocol
162      *
163      * @param string $linkUrl URL to fix
164      *
165      * @return string Fixed URL
166      */
167     protected function fixLinkUrl($linkUrl)
168     {
169         if ($linkUrl{0} == '/') {
170             //Unix file path
171             $linkUrl = 'file://' . $linkUrl;
172         }
173         return $linkUrl;
174     }
175
176     /**
177      * Dummy internal link handler that simply adds ".htm" to the note title
178      *
179      * @param string $linkUrl Title of page that is linked
180      *
181      * @return string URL to link to
182      */
183     public function internalLinkHandler($linkUrl)
184     {
185         return $linkUrl . '.htm';
186     }
187 }
188 ?>