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