001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     bstefanescu
018 *
019 * $Id$
020 */
021
022package org.nuxeo.ecm.platform.rendering.wiki;
023
024import java.io.IOException;
025
026import org.apache.commons.logging.Log;
027import org.apache.commons.logging.LogFactory;
028import org.nuxeo.ecm.platform.rendering.wiki.extensions.WikiBlockWriter;
029import org.wikimodel.wem.PrintListener;
030import org.wikimodel.wem.WikiFormat;
031import org.wikimodel.wem.WikiParameters;
032
033import freemarker.core.Environment;
034import freemarker.template.TemplateException;
035
036/**
037 * @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
038 */
039public class WikiSerializerHandler extends PrintListener {
040
041    public static final Log log = LogFactory.getLog(WikiSerializerHandler.class);
042
043    protected static final String LINE_SEP = System.getProperty("line.separator");
044
045    protected final WikiSerializer engine;
046
047    protected final StringBuilder words = new StringBuilder();
048
049    protected Environment env;
050
051    protected WikiWriter writer;
052
053    protected int mark = -1; // used to mark the current buffer to be able to retrieve printed text that starts at the
054                             // mark
055
056    protected Toc toc;
057
058    public WikiSerializerHandler(WikiSerializer engine) {
059        super(null); // cannot base on the wikiprinter - so we don't use it
060        this.engine = engine;
061        writer = new WikiWriter();
062        if (engine.macros.containsKey("toc")) {
063            toc = new Toc();
064        }
065    }
066
067    @Override
068    protected void print(String str) {
069        writer.print(str);
070    }
071
072    @Override
073    protected void println() {
074        writer.println();
075    }
076
077    @Override
078    protected void println(String str) {
079        writer.println(str);
080    }
081
082    public WikiWriter getWriter() {
083        return writer;
084    }
085
086    public Environment getEnvironment() {
087        if (env == null) {
088            env = Environment.getCurrentEnvironment();
089        }
090        return env;
091    }
092
093    protected void beginElement() {
094        flushWords();
095    }
096
097    protected void endElement() {
098        flushWords();
099    }
100
101    protected void flushWords() {
102        if (words.length() == 0) {
103            return;
104        }
105        String text = words.toString();
106        words.setLength(0);
107        for (int i = 0, len = engine.filters.size(); i < len; i++) {
108            String result = engine.filters.get(i).apply(text);
109            if (result != null) {
110                print(result);
111                return;
112            }
113        }
114        print(text);
115    }
116
117    @Override
118    public void beginDefinitionDescription() {
119        beginElement();
120        super.beginDefinitionDescription();
121    }
122
123    @Override
124    public void beginDefinitionList(WikiParameters parameters) {
125        beginElement();
126        super.beginDefinitionList(parameters);
127    }
128
129    @Override
130    public void beginDefinitionTerm() {
131        beginElement();
132        super.beginDefinitionTerm();
133    }
134
135    @Override
136    public void beginDocument() {
137        beginElement();
138        super.beginDocument();
139    }
140
141    @Override
142    public void beginFormat(WikiFormat format) {
143        beginElement();
144        super.beginFormat(format);
145    }
146
147    @Override
148    public void beginHeader(int level, WikiParameters params) {
149        beginElement();
150        super.beginHeader(level, params);
151        if (toc != null) {
152            String id = toc.addHeading(null, level); // we don't know the title yet
153            print("<a name=\"heading_" + id + "\">");
154            mark = writer.getBuffer().length();
155        }
156    }
157
158    @Override
159    public void beginInfoBlock(char infoType, WikiParameters params) {
160        beginElement();
161        super.beginInfoBlock(infoType, params);
162    }
163
164    @Override
165    public void beginList(WikiParameters parameters, boolean ordered) {
166        beginElement();
167        super.beginList(parameters, ordered);
168    }
169
170    @Override
171    public void beginListItem() {
172        beginElement();
173        super.beginListItem();
174    }
175
176    @Override
177    public void beginParagraph(WikiParameters params) {
178        beginElement();
179        super.beginParagraph(params);
180    }
181
182    @Override
183    public void beginPropertyBlock(String propertyUri, boolean doc) {
184        beginElement();
185        if (propertyUri.startsWith("block:")) {
186            String name = propertyUri.substring(6);
187            WikiBlockWriter bwriter = new WikiBlockWriter(writer, name);
188            writer.writeText(bwriter);
189            writer = bwriter;
190        } else {
191            super.beginPropertyBlock(propertyUri, doc);
192        }
193    }
194
195    @Override
196    public void beginPropertyInline(String str) {
197        beginElement();
198        super.beginPropertyInline(str);
199    }
200
201    @Override
202    public void beginQuotation(WikiParameters params) {
203        beginElement();
204        super.beginQuotation(params);
205    }
206
207    @Override
208    public void beginQuotationLine() {
209        beginElement();
210        super.beginQuotationLine();
211    }
212
213    @Override
214    public void beginTable(WikiParameters params) {
215        beginElement();
216        super.beginTable(params);
217    }
218
219    @Override
220    public void beginTableCell(boolean tableHead, WikiParameters params) {
221        beginElement();
222        super.beginTableCell(tableHead, params);
223    }
224
225    @Override
226    public void beginTableRow(WikiParameters params) {
227        beginElement();
228        super.beginTableRow(params);
229    }
230
231    @Override
232    public void endDefinitionDescription() {
233        endElement();
234        super.endDefinitionDescription();
235    }
236
237    @Override
238    public void endDefinitionList(WikiParameters parameters) {
239        endElement();
240        super.endDefinitionList(parameters);
241    }
242
243    @Override
244    public void endDefinitionTerm() {
245        endElement();
246        super.endDefinitionTerm();
247    }
248
249    @Override
250    public void endDocument() {
251        endElement();
252        super.endDocument();
253    }
254
255    @Override
256    public void endFormat(WikiFormat format) {
257        endElement();
258        super.endFormat(format);
259    }
260
261    @Override
262    public void endHeader(int level, WikiParameters params) {
263        if (toc != null) {
264            if (mark == -1) {
265                throw new IllegalStateException("marker was not set");
266            }
267            toc.tail.title = writer.getBuffer().substring(mark);
268            mark = -1;
269            print("</a>");
270            super.endHeader(level, params);
271        } else {
272            super.endHeader(level, params);
273        }
274        endElement();
275    }
276
277    @Override
278    public void endInfoBlock(char infoType, WikiParameters params) {
279        endElement();
280        super.endInfoBlock(infoType, params);
281    }
282
283    @Override
284    public void endList(WikiParameters parameters, boolean ordered) {
285        endElement();
286        super.endList(parameters, ordered);
287    }
288
289    @Override
290    public void endListItem() {
291        endElement();
292        super.endListItem();
293    }
294
295    @Override
296    public void endParagraph(WikiParameters params) {
297        endElement();
298        super.endParagraph(params);
299    }
300
301    @Override
302    public void endPropertyBlock(String propertyUri, boolean doc) {
303        endElement();
304        if (propertyUri.startsWith("block:")) {
305            writer = writer.getParent();
306            if (writer == null) {
307                throw new IllegalStateException("block macro underflow");
308            }
309        } else {
310            super.endPropertyBlock(propertyUri, doc);
311        }
312    }
313
314    @Override
315    public void endPropertyInline(String inlineProperty) {
316        endElement();
317        super.endPropertyInline(inlineProperty);
318    }
319
320    @Override
321    public void endQuotation(WikiParameters params) {
322        endElement();
323        super.endQuotation(params);
324    }
325
326    @Override
327    public void endQuotationLine() {
328        endElement();
329        super.endQuotationLine();
330    }
331
332    @Override
333    public void endTable(WikiParameters params) {
334        endElement();
335        super.endTable(params);
336    }
337
338    @Override
339    public void endTableCell(boolean tableHead, WikiParameters params) {
340        endElement();
341        super.endTableCell(tableHead, params);
342    }
343
344    @Override
345    public void endTableRow(WikiParameters params) {
346        endElement();
347        super.endTableRow(params);
348    }
349
350    @Override
351    public void onEmptyLines(int count) {
352        flushWords();
353        super.onEmptyLines(count);
354    }
355
356    @Override
357    public void onHorizontalLine() {
358        flushWords();
359        super.onHorizontalLine();
360    }
361
362    @Override
363    public void onLineBreak() {
364        flushWords();
365        super.onLineBreak();
366    }
367
368    @Override
369    public void onReference(String ref, boolean explicitLink) {
370        flushWords();
371        super.onReference(ref, explicitLink);
372    }
373
374    @Override
375    public void onTableCaption(String str) {
376        flushWords();
377        super.onTableCaption(str);
378    }
379
380    @Override
381    public void onVerbatimBlock(String str) {
382        flushWords();
383        super.onVerbatimBlock(str);
384    }
385
386    @Override
387    public void onVerbatimInline(String str) {
388        flushWords();
389        super.onVerbatimInline(str);
390    }
391
392    @Override
393    public void onMacroBlock(String macroName, WikiParameters params, String content) {
394        flushWords();
395        WikiMacro expression = engine.macros.get(macroName);
396        if (expression != null) {
397            try {
398                expression.eval(params, content, this);
399            } catch (IOException | TemplateException e) {
400                log.error("Failed to eval macro", e);
401            }
402        } else {
403            log.warn("Unknown wiki macro: " + macroName);
404        }
405    }
406
407    @Override
408    public void onMacroInline(String macroName, WikiParameters params, String content) {
409        flushWords();
410        WikiMacro expression = engine.macros.get(macroName);
411        if (expression != null) {
412            try {
413                expression.evalInline(params, content, this);
414            } catch (IOException | TemplateException e) {
415                log.error("Failed to eval macro", e);
416            }
417        } else {
418            log.warn("Unknown wiki macro: " + macroName);
419        }
420    }
421
422    @Override
423    public void onExtensionBlock(String extensionName, WikiParameters params) {
424        flushWords();
425        log.warn("Unknown wiki expression: " + extensionName);
426    }
427
428    @Override
429    public void onExtensionInline(String extensionName, WikiParameters params) {
430        flushWords();
431        log.warn("Unknown wiki expression: " + extensionName);
432    }
433
434    @Override
435    public void onSpecialSymbol(String str) {
436        String entity = getSymbolEntity(str);
437        if (entity != null) {
438            words.append(entity);
439        } else { // do not escape - to be able to use filters on it
440            words.append(str);
441        }
442    }
443
444    @Override
445    public void onSpace(String str) {
446        flushWords();
447        super.onSpace(str);
448    }
449
450    @Override
451    public void onNewLine() {
452        flushWords();
453        super.onNewLine();
454    }
455
456    @Override
457    public void onEscape(String str) {
458        flushWords();
459        super.onEscape(str);
460    }
461
462    @Override
463    public void onWord(String word) {
464        words.append(word);
465        // writeWord(word);
466    }
467
468    protected void writeWord(String word) {
469        for (int i = 0, len = engine.filters.size(); i < len; i++) {
470            String result = engine.filters.get(i).apply(word);
471            if (result != null) {
472                print(result);
473                return;
474            }
475        }
476        print(word);
477    }
478
479    /**
480     * Returns an HTML/XML entity corresponding to the specified special symbol. Depending on implementation it can be
481     * real entities (like &amp;amp; &amp;lt; &amp;gt; or the corresponding digital codes (like &amp;#38;,
482     * &amp;#&amp;#38; or &amp;#8250;). Digital entity representation is better for generation of XML files.
483     *
484     * @param str the special string to convert to an HTML/XML entity
485     * @return an HTML/XML entity corresponding to the specified special symbol.
486     */
487    protected String getSymbolEntity(String str) {
488        String entity = null;
489        if (isHtmlEntities()) {
490            entity = WikiEntityUtil.getHtmlSymbol(str);
491        } else {
492            int code = WikiEntityUtil.getHtmlCodeByWikiSymbol(str);
493            if (code > 0) {
494                entity = "#" + Integer.toString(code);
495            }
496        }
497        if (entity != null) {
498            entity = "&" + entity + ";";
499            if (str.startsWith(" --")) {
500                entity = "&nbsp;" + entity + " ";
501            }
502        }
503        return entity;
504    }
505
506    /**
507     * Returns <code>true</code> if special Wiki entities should be represented as the corresponding HTML entities or
508     * they should be visualized using the corresponding XHTML codes (like &amp;amp; and so on). This method can be
509     * overloaded in subclasses to re-define the visualization style.
510     *
511     * @return <code>true</code> if special Wiki entities should be represented as the corresponding HTML entities or
512     *         they should be visualized using the corresponding XHTML codes (like &amp;amp; and so on).
513     */
514    protected boolean isHtmlEntities() {
515        return true;
516    }
517
518}