View Javadoc
1   /*
2    * Copyright (C) 2003-2010 eXo Platform SAS.
3    *
4    * This program is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Affero General Public License
6    * as published by the Free Software Foundation; either version 3
7    * of the License, or (at your option) any later version.
8    *
9    * This program is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   * GNU General Public License for more details.
13   *
14   * You should have received a copy of the GNU General Public License
15   * along with this program; if not, see<http://www.gnu.org/licenses/>.
16   */
17  package org.exoplatform.wiki.rendering.render.confluence;
18  
19  import java.util.Collections;
20  import java.util.LinkedList;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.apache.commons.lang.StringUtils;
25  import org.xwiki.rendering.listener.Format;
26  import org.xwiki.rendering.listener.HeaderLevel;
27  import org.xwiki.rendering.listener.ListType;
28  import org.xwiki.rendering.listener.MetaData;
29  import org.xwiki.rendering.listener.chaining.BlockStateChainingListener;
30  import org.xwiki.rendering.listener.chaining.ListenerChain;
31  import org.xwiki.rendering.listener.chaining.StackableChainingListener;
32  import org.xwiki.rendering.listener.reference.ResourceReference;
33  import org.xwiki.rendering.listener.reference.ResourceType;
34  import org.xwiki.rendering.renderer.AbstractChainingPrintRenderer;
35  import org.xwiki.rendering.renderer.printer.DefaultWikiPrinter;
36  import org.xwiki.rendering.renderer.printer.VoidWikiPrinter;
37  import org.xwiki.rendering.renderer.printer.WikiPrinter;
38  import org.xwiki.rendering.renderer.reference.ResourceReferenceSerializer;
39  import org.xwiki.rendering.transformation.icon.IconTransformationConfiguration;
40  
41  /**
42   * Convert listener events to Confluence Syntax 1.0 output.
43   */
44  public class ConfluenceSyntaxChainingRenderer extends AbstractChainingPrintRenderer implements StackableChainingListener {
45  
46    private ConfluenceSyntaxLinkRenderer linkRenderer;
47  
48    private ConfluenceSyntaxImageRenderer imageRenderer;
49    
50    private ConfluenceSyntaxIconRenderer  iconRenderer;
51  
52    private ConfluenceSyntaxMacroRenderer macroPrinter;
53  
54    private ResourceReferenceSerializer linkReferenceSerializer;
55  
56    // Custom States
57  
58    private boolean isFirstElementRendered = false;
59  
60    private StringBuffer listStyle = new StringBuffer();
61  
62    private Map<String, String> previousFormatParameters;
63    
64    private IconTransformationConfiguration iconTransformationConfiguration;
65  
66    public ConfluenceSyntaxChainingRenderer(ListenerChain listenerChain,
67                                            ResourceReferenceSerializer linkReferenceSerializer,
68                                            IconTransformationConfiguration iconTransformationConfiguration) {
69  
70      setListenerChain(listenerChain);
71  
72      this.linkReferenceSerializer = linkReferenceSerializer;
73      this.iconTransformationConfiguration = iconTransformationConfiguration;
74      this.linkRenderer = new ConfluenceSyntaxLinkRenderer(getConfluenceSyntaxListenerChain(), linkReferenceSerializer);
75      this.imageRenderer = new ConfluenceSyntaxImageRenderer();
76      this.iconRenderer = new ConfluenceSyntaxIconRenderer(iconTransformationConfiguration);
77      this.macroPrinter = new ConfluenceSyntaxMacroRenderer();
78    }
79  
80    // State
81  
82    private BlockStateChainingListener getBlockState() {
83      return getConfluenceSyntaxListenerChain().getBlockStateChainingListener();
84    }
85  
86    /**
87     * {@inheritDoc}
88     * 
89     * @see StackableChainingListener#createChainingListenerInstance()
90     */
91    public StackableChainingListener createChainingListenerInstance() {
92      ConfluenceSyntaxChainingRenderer renderer = new ConfluenceSyntaxChainingRenderer(getListenerChain(),
93                                                                                       this.linkReferenceSerializer,
94                                                                                       this.iconTransformationConfiguration);
95      renderer.setPrinter(getPrinter());
96      return renderer;
97    }
98  
99    private ConfluenceSyntaxListenerChain getConfluenceSyntaxListenerChain() {
100     return (ConfluenceSyntaxListenerChain) getListenerChain();
101   }
102 
103   private ConfluenceSyntaxLinkRenderer getLinkRenderer() {
104     return this.linkRenderer;
105   }
106 
107   private ConfluenceSyntaxImageRenderer getImageRenderer() {
108     return this.imageRenderer;
109   }
110   
111   private ConfluenceSyntaxIconRenderer getIconRenderer() {
112     return this.iconRenderer;
113   }
114 
115   private ConfluenceSyntaxMacroRenderer getMacroPrinter() {
116     return this.macroPrinter;
117   }
118 
119   /**
120    * {@inheritDoc}
121    * 
122    * @see org.xwiki.rendering.listener.chaining.AbstractChainingListener#beginGroup(Map)
123    */
124   @Override
125   public void beginGroup(Map<String, String> parameters) {
126     if (!getBlockState().isInLine()) {
127       printEmptyLine();
128     }
129 
130     if (parameters.size() > 0) {
131       printBeginParameters(parameters, true);
132     }
133 
134     // Create a new listener stack in order to preserve current states, to
135     // handle the group.
136     getListenerChain().pushAllStackableListeners();
137   }
138 
139   /**
140    * {@inheritDoc}
141    * 
142    * @see org.xwiki.rendering.listener.chaining.AbstractChainingListener#endDocument(MetaData)
143    */
144   @Override
145   public void endDocument(MetaData metaData) {
146     // Ensure that all data in the escape printer have been flushed
147     getConfluencePrinter().flush();
148   }
149 
150   /**
151    * {@inheritDoc}
152    * 
153    * @see org.xwiki.rendering.listener.chaining.AbstractChainingListener#endGroup(Map)
154    */
155   @Override
156   public void endGroup(Map<String, String> parameters) {
157 
158     // Restore previous listeners that were stacked
159     getListenerChain().popAllStackableListeners();
160   }
161 
162   /**
163    * {@inheritDoc}
164    * 
165    * @see org.xwiki.rendering.renderer.AbstractChainingPrintRenderer#beginLink(ResourceReference, boolean, Map)
166    */
167   @Override
168   public void beginLink(ResourceReference link, boolean isFreeStandingURI, Map<String, String> parameters) {
169     // Flush test content before the link.
170     // TODO: improve the block state renderer to be able to make the difference between what is bufferized
171     // before the link and what in the label link
172     getConfluencePrinter().setBeforeLink(true);
173     // escape open link syntax when before a link
174     if (getLinkRenderer().forceFullSyntax(getConfluencePrinter(), isFreeStandingURI, parameters)
175         && getConfluencePrinter().getBuffer().length() > 0
176         && getConfluencePrinter().getBuffer()
177                                  .charAt(getConfluencePrinter().getBuffer().length() - 1) == '[') {
178       getConfluencePrinter().setEscapeLastChar(true);
179     }
180     getConfluencePrinter().flush();
181     getConfluencePrinter().setBeforeLink(false);
182 
183     int linkDepth = getBlockState().getLinkDepth();
184 
185     // If we are at a depth of 2 or greater it means we're in a link inside a link and in this case we
186     // shouldn't output the nested link as a link unless it's a free standing link.
187     if (linkDepth < 2) {
188       getLinkRenderer().beginRenderLink(getConfluencePrinter(), link, isFreeStandingURI, parameters);
189 
190       ConfluenceSyntaxEscapeWikiPrinter linkLabelPrinter = new ConfluenceSyntaxEscapeWikiPrinter(new DefaultWikiPrinter(),
191                                                                                                  getConfluenceSyntaxListenerChain());
192 
193       // Make sure the escape handler knows there is already characters before
194       linkLabelPrinter.setOnNewLine(getConfluencePrinter().isOnNewLine());
195 
196       // Defer printing the link content since we need to gather all nested
197       // elements
198       pushPrinter(linkLabelPrinter);
199     } else if (isFreeStandingURI) {
200       print(getLinkRenderer().serialize(link));
201     }
202   }
203 
204   /**
205    * {@inheritDoc}
206    * 
207    * @see org.xwiki.rendering.renderer.AbstractChainingPrintRenderer#endLink(ResourceReference, boolean, Map)
208    */
209   @Override
210   public void endLink(ResourceReference link, boolean isFreeStandingURI, Map<String, String> parameters) {
211     // The links in a top level link label are not rendered as link (only the
212     // label is printed)
213     if (getBlockState().getLinkDepth() == 1) {
214       ConfluenceSyntaxEscapeWikiPrinter linkBlocksPrinter = getConfluencePrinter();
215       linkBlocksPrinter.flush();
216       String content = linkBlocksPrinter.toString();
217       popPrinter();
218 
219       getLinkRenderer().renderLinkContent(getConfluencePrinter(), content);
220       getLinkRenderer().endRenderLink(getConfluencePrinter(), link, isFreeStandingURI, parameters);
221     }
222   }
223 
224   /**
225    * {@inheritDoc}
226    * 
227    * @see org.xwiki.rendering.renderer.AbstractChainingPrintRenderer#beginFormat(org.xwiki.rendering.listener.Format,
228    *      java.util.Map)
229    */
230   @Override
231   public void beginFormat(Format format, Map<String, String> parameters) {
232     // If the previous format had parameters and the parameters are different
233     // from the current ones then close them
234     if (this.previousFormatParameters != null) {
235       if (parameters.isEmpty()) {
236         // print("(%%)");
237         // this.previousFormatParameters = null;
238       } else if (!this.previousFormatParameters.equals(parameters)) {
239         this.previousFormatParameters = null;
240         printBeginParameters(parameters, false);
241       } else {
242         this.previousFormatParameters = null;
243       }
244     } else if (this.previousFormatParameters == null) {
245       printBeginParameters(parameters, false);
246     }
247 
248     switch (format) {
249     case BOLD:
250       // Handle empty formatting parameters.
251       if (this.previousFormatParameters != null) {
252         getPrinter().print("(%%)");
253         this.previousFormatParameters = null;
254       }
255 
256       getConfluencePrinter().printBeginBold();
257       break;
258     case ITALIC:
259       // Handle empty formatting parameters.
260       if (this.previousFormatParameters != null) {
261         getPrinter().print("(%%)");
262         this.previousFormatParameters = null;
263       }
264 
265       getConfluencePrinter().printBeginItalic();
266       break;
267     case STRIKEDOUT:
268       print("-");
269       break;
270     case UNDERLINED:
271       print("+");
272       break;
273     case SUPERSCRIPT:
274       print("^");
275       break;
276     case SUBSCRIPT:
277       print("~");
278       break;
279     case MONOSPACE:
280       print("{{");
281       break;
282     }
283 
284   }
285 
286   /**
287    * {@inheritDoc}
288    * 
289    * @see AbstractChainingPrintRenderer#endFormat(org.xwiki.rendering.listener.Format,
290    *      java.util.Map)
291    */
292   @Override
293   public void endFormat(Format format, Map<String, String> parameters) {
294     switch (format) {
295     case BOLD:
296       print("*");
297       break;
298     case ITALIC:
299       getConfluencePrinter().printEndItalic();
300       break;
301     case STRIKEDOUT:
302       print("-");
303       break;
304     case UNDERLINED:
305       print("+");
306       break;
307     case SUPERSCRIPT:
308       print("^");
309       break;
310     case SUBSCRIPT:
311       print("~");
312       break;
313     case MONOSPACE:
314       print("}}");
315       break;
316     }
317 
318     StringBuffer parametersStr = new StringBuffer();
319     for (Map.Entry<String, String> entry : parameters.entrySet()) {
320       String value = entry.getValue();
321       String key = entry.getKey();
322 
323       if (key != null && value != null) {
324         if ("style".equals(key))
325           parametersStr.append("{span}");
326       }
327     }
328     print(parametersStr.toString());
329     if (!parameters.isEmpty()) {
330       this.previousFormatParameters = parameters;
331     }
332   }
333 
334   /**
335    * {@inheritDoc}
336    * 
337    * @see org.xwiki.rendering.renderer.PrintRenderer#beginParagraph(java.util.Map)
338    */
339   @Override
340   public void beginParagraph(Map<String, String> parameters) {
341     printEmptyLine();
342     printBeginParameters(parameters);
343   }
344 
345   /**
346    * {@inheritDoc}
347    * 
348    * @see org.xwiki.rendering.renderer.PrintRenderer#endParagraph(java.util.Map)
349    */
350   @Override
351   public void endParagraph(Map<String, String> parameters) {
352     this.previousFormatParameters = null;
353     StringBuffer parametersStr = new StringBuffer();
354     for (Map.Entry<String, String> entry : parameters.entrySet()) {
355       String value = entry.getValue();
356       String key = entry.getKey();
357 
358       if (key != null && value != null) {
359         if ("style".equals(key))
360           parametersStr.append("{div}");
361       }
362     }
363     print(parametersStr.toString());
364     // Ensure that any not printed characters are flushed.
365     // TODO: Fix this better by introducing a state listener to handle escapes
366     getConfluencePrinter().flush();
367   }
368 
369   /**
370    * {@inheritDoc}
371    * 
372    * @see org.xwiki.rendering.renderer.PrintRenderer#onNewLine()
373    */
374   @Override
375   public void onNewLine() {
376     // - If we're inside a table cell, a paragraph, a list or a section header then if we have already outputted
377     // a new line before then this new line should be a line break in order not to break the table cell,
378     // paragraph, list or section header.
379 
380     // - If the new line is the last element of the paragraph, list or section header then it should be a line break
381     // as otherwise it'll be considered as an empty line event next time the generated syntax is read by the Confluence
382     // parser.
383 
384     if (getBlockState().isInLine()) {
385       if (getBlockState().isInTableCell()) {
386         print("\\\\");
387       } else if (getConfluenceSyntaxListenerChain().getConsecutiveNewLineStateChainingListener()
388                                             .getNewLineCount() > 1) {
389         print("\\\\");
390       } else if (getConfluenceSyntaxListenerChain().getLookaheadChainingListener().getNextEvent().eventType.isInlineEnd()) {
391         print("\\\\");
392       } else {
393         print("\n");
394       }
395     } else {
396       print("\n");
397     }
398   }
399 
400   /**
401    * {@inheritDoc}
402    * 
403    * @see AbstractChainingPrintRenderer#onMacro(String, java.util.Map, String,
404    *      boolean)
405    */
406   @Override
407   public void onMacro(String id, Map<String, String> parameters, String content, boolean isInline) {
408     if (!isInline) {
409       printEmptyLine();
410       print(getMacroPrinter().renderMacro(id, parameters, content, isInline));
411     } else {
412       getConfluencePrinter().printInlineMacro(getMacroPrinter().renderMacro(id, parameters, content, isInline));
413     }
414   }
415 
416   /**
417    * {@inheritDoc}
418    * 
419    * @see org.xwiki.rendering.renderer.AbstractChainingPrintRenderer#beginHeader(org.xwiki.rendering.listener.HeaderLevel,
420    *      String, java.util.Map)
421    */
422   @Override
423   public void beginHeader(HeaderLevel level, String id, Map<String, String> parameters) {
424     printEmptyLine();
425     print("h" + level.getAsInt() + ". ");
426     printBeginParameters(parameters, false);
427   }
428 
429   /**
430    * {@inheritDoc}
431    * 
432    * @see org.xwiki.rendering.renderer.AbstractChainingPrintRenderer#endHeader(org.xwiki.rendering.listener.HeaderLevel,
433    *      String, java.util.Map)
434    */
435   @Override
436   public void endHeader(HeaderLevel level, String id, Map<String, String> parameters) {
437     printEndParameters(parameters, false);
438   }
439 
440   /**
441    * {@inheritDoc}
442    * 
443    * @see AbstractChainingPrintRenderer#onWord(String)
444    */
445   @Override
446   public void onWord(String word) {
447     printDelayed(word);
448   }
449 
450   /**
451    * {@inheritDoc}
452    * 
453    * @see org.xwiki.rendering.renderer.PrintRenderer#onSpace()
454    */
455   @Override
456   public void onSpace() {
457     printDelayed(" ");
458   }
459 
460   /**
461    * {@inheritDoc}
462    * 
463    * @see AbstractChainingPrintRenderer#onSpecialSymbol(char)
464    */
465   @Override
466   public void onSpecialSymbol(char symbol) {
467     printDelayed("" + symbol);
468   }
469 
470   /**
471    * {@inheritDoc}
472    * 
473    * @see org.xwiki.rendering.renderer.AbstractChainingPrintRenderer#beginList(org.xwiki.rendering.listener.ListType,
474    *      java.util.Map)
475    */
476   @Override
477   public void beginList(ListType listType, Map<String, String> parameters) {
478     if (getBlockState().getListDepth() == 1) {
479       if (!getBlockState().isInLine()) {
480         print("\n");
481       } else
482         printEmptyLine();
483     } else {
484       getPrinter().print("\n");
485     }
486 
487     if (listType == ListType.BULLETED) {
488       this.listStyle.append("*");
489     } else {
490       this.listStyle.append("#");
491     }
492     printBeginParameters(parameters);
493   }
494 
495   /**
496    * {@inheritDoc}
497    * 
498    * @see org.xwiki.rendering.renderer.PrintRenderer#beginListItem()
499    */
500   @Override
501   public void beginListItem() {
502     if (getBlockState().getListItemIndex() > 0) {
503       getPrinter().print("\n");
504     }
505 
506     print(this.listStyle.toString());
507     if (StringUtils.contains(this.listStyle.toString(), '#')) {
508       //print(".");
509     }
510     print(" ");
511   }
512 
513   /**
514    * {@inheritDoc}
515    * 
516    * @see AbstractChainingPrintRenderer#endList(org.xwiki.rendering.listener.ListType,
517    *      java.util.Map)
518    */
519   @Override
520   public void endList(ListType listType, Map<String, String> parameters) {
521     this.listStyle.setLength(this.listStyle.length() - 1);
522 
523     // Ensure that any not printed characters are flushed.
524     // TODO: Fix this better by introducing a state listener to handle escapes
525     getConfluencePrinter().flush();
526   }
527 
528   /**
529    * {@inheritDoc}
530    * 
531    * @see org.xwiki.rendering.listener.chaining.AbstractChainingListener#endListItem()
532    */
533   @Override
534   public void endListItem() {
535     this.previousFormatParameters = null;
536   }
537 
538   /**
539    * {@inheritDoc}
540    * 
541    * @see org.xwiki.rendering.renderer.AbstractChainingPrintRenderer#beginMacroMarker(String,
542    *      java.util.Map, String, boolean)
543    */
544   @Override
545   public void beginMacroMarker(String name, Map<String, String> parameters, String content, boolean isInline) {
546     if (!isInline) {
547       printEmptyLine();
548     }
549 
550     // When we encounter a macro marker we ignore all other blocks inside since we're going to use the macro
551     // definition wrapped by the macro marker to construct the confluence syntax.
552     pushPrinter(new ConfluenceSyntaxEscapeWikiPrinter(VoidWikiPrinter.VOIDWIKIPRINTER, getConfluenceSyntaxListenerChain()));
553   }
554 
555   /**
556    * {@inheritDoc}
557    * 
558    * @see AbstractChainingPrintRenderer#endMacroMarker(String, java.util.Map,
559    *      String, boolean)
560    */
561   @Override
562   public void endMacroMarker(String name, Map<String, String> parameters, String content, boolean isInline) {
563     
564     this.previousFormatParameters = null;
565 
566     popPrinter();
567 
568     print(getMacroPrinter().renderMacro(name, parameters, content, isInline));
569   }
570 
571   /**
572    * {@inheritDoc}
573    * 
574    * @see org.xwiki.rendering.renderer.AbstractChainingPrintRenderer#onId(String)
575    */
576   @Override
577   public void onId(String name) {
578     print("{{id name=\"" + name + "\"}}");
579   }
580 
581   /**
582    * {@inheritDoc}
583    * 
584    * @see org.xwiki.rendering.renderer.Renderer#onHorizontalLine(Map)
585    */
586   @Override
587   public void onHorizontalLine(Map<String, String> parameters) {
588     printEmptyLine();
589     printBeginParameters(parameters);
590     print("----");
591   }
592 
593   /**
594    * {@inheritDoc}
595    * 
596    * @see org.xwiki.rendering.renderer.Renderer#onVerbatim(String, boolean, Map)
597    */
598   @Override
599   public void onVerbatim(String protectedString, boolean isInline, Map<String, String> parameters) {
600     if (!isInline) {
601       printEmptyLine();
602     }
603     print("\\");
604     getConfluencePrinter().printVerbatimContent(protectedString);
605   }
606 
607   /**
608    * {@inheritDoc}
609    * 
610    * @see org.xwiki.rendering.renderer.Renderer#onEmptyLines(int)
611    */
612   @Override
613   public void onEmptyLines(int count) {
614     print(StringUtils.repeat("\n", count));
615   }
616 
617   /**
618    * {@inheritDoc}
619    * 
620    * @see org.xwiki.rendering.listener.Listener#beginDefinitionList(java.util.Map)
621    */
622   @Override
623   public void beginDefinitionList(Map<String, String> parameters) {
624     if (getBlockState().getDefinitionListDepth() == 1 && !getBlockState().isInList()) {
625       printEmptyLine();
626     } else {
627       print("\n");
628     }
629     printBeginParameters(parameters);
630   }
631 
632   /**
633    * {@inheritDoc}
634    * 
635    * @see org.xwiki.rendering.listener.Listener#beginDefinitionTerm()
636    */
637   @Override
638   public void beginDefinitionTerm() {
639     if (getBlockState().getDefinitionListItemIndex() > 0) {
640       getPrinter().print("\n");
641     }
642 
643     if (this.listStyle.length() > 0) {
644       print(this.listStyle.toString());
645       if (this.listStyle.charAt(0) == '#') {
646         //print(".");
647       }
648     }
649     print(StringUtils.repeat(":", getBlockState().getDefinitionListDepth() - 1));
650     print("; ");
651   }
652 
653   /**
654    * {@inheritDoc}
655    * 
656    * @see org.xwiki.rendering.listener.Listener#beginDefinitionDescription()
657    */
658   @Override
659   public void beginDefinitionDescription() {
660     if (getBlockState().getDefinitionListItemIndex() > 0) {
661       getPrinter().print("\n");
662     }
663 
664     if (this.listStyle.length() > 0) {
665       print(this.listStyle.toString());
666       if (this.listStyle.charAt(0) == '#') {
667         //print(".");
668       }
669     }
670     print(StringUtils.repeat(":", getBlockState().getDefinitionListDepth() - 1));
671     print(": ");
672   }
673 
674   /**
675    * {@inheritDoc}
676    * 
677    * @see org.xwiki.rendering.listener.chaining.AbstractChainingListener#endDefinitionDescription()
678    */
679   @Override
680   public void endDefinitionDescription() {
681     this.previousFormatParameters = null;
682 
683     getConfluencePrinter().flush();
684   }
685 
686   /**
687    * {@inheritDoc}
688    * 
689    * @see org.xwiki.rendering.listener.chaining.AbstractChainingListener#endDefinitionTerm()
690    */
691   @Override
692   public void endDefinitionTerm() {
693     this.previousFormatParameters = null;
694 
695     getConfluencePrinter().flush();
696   }
697 
698   /**
699    * {@inheritDoc}
700    * 
701    * @see org.xwiki.rendering.listener.Listener#beginQuotation(java.util.Map)
702    */
703   @Override
704   public void beginQuotation(Map<String, String> parameters) {
705     if (!getBlockState().isInQuotationLine()) {
706       printEmptyLine();
707     }
708 
709     if (!parameters.isEmpty()) {
710       printBeginParameters(parameters);
711     }
712   }
713 
714   /**
715    * {@inheritDoc}
716    * 
717    * @see org.xwiki.rendering.listener.Listener#beginQuotationLine()
718    */
719   @Override
720   public void beginQuotationLine() {
721     if (getBlockState().getQuotationLineIndex() > 0) {
722       getPrinter().print("\n");
723     }
724     print("bq. ");
725   }
726 
727   /**
728    * {@inheritDoc}
729    * 
730    * @see org.xwiki.rendering.listener.chaining.AbstractChainingListener#endQuotationLine()
731    */
732   @Override
733   public void endQuotationLine() {
734     this.previousFormatParameters = null;
735     getConfluencePrinter().flush();
736   }
737 
738   /**
739    * {@inheritDoc}
740    * 
741    * @see org.xwiki.rendering.listener.Listener#beginTable(java.util.Map)
742    */
743   @Override
744   public void beginTable(Map<String, String> parameters) {
745     printEmptyLine();
746     if (!parameters.isEmpty()) {
747       printBeginParameters(parameters);
748     }
749   }
750 
751   /**
752    * {@inheritDoc}
753    * 
754    * @see org.xwiki.rendering.listener.Listener#beginTableCell(java.util.Map)
755    */
756   @Override
757   public void beginTableCell(Map<String, String> parameters) {
758     print("|");
759     printBeginParameters(parameters, false);
760   }
761 
762   /**
763    * {@inheritDoc}
764    * 
765    * @see org.xwiki.rendering.listener.Listener#beginTableHeadCell(java.util.Map)
766    */
767   @Override
768   public void beginTableHeadCell(Map<String, String> parameters) {
769     print("||");
770     printBeginParameters(parameters, false);
771   }
772 
773   /**
774    * {@inheritDoc}
775    * 
776    * @see org.xwiki.rendering.listener.Listener#beginTableRow(java.util.Map)
777    */
778   @Override
779   public void beginTableRow(Map<String, String> parameters) {
780     if (getBlockState().getCellRow() > 0) {
781       print("\n");
782     }
783 
784     printBeginParameters(parameters, false);
785   }
786 
787   /**
788    * {@inheritDoc}
789    * 
790    * @see org.xwiki.rendering.listener.Listener#endTableCell(java.util.Map)
791    */
792   @Override
793   public void endTableCell(Map<String, String> parameters) {
794     this.previousFormatParameters = null;
795 
796     // Ensure that any not printed characters are flushed.
797     // TODO: Fix this better by introducing a state listener to handle escapes
798     getConfluencePrinter().flush();
799   }
800 
801   /**
802    * {@inheritDoc}
803    * 
804    * @see org.xwiki.rendering.listener.Listener#endTableHeadCell(java.util.Map)
805    */
806   @Override
807   public void endTableHeadCell(Map<String, String> parameters) {
808     this.previousFormatParameters = null;
809   }
810 
811   /**
812    * {@inheritDoc}
813    * 
814    * @see org.xwiki.rendering.listener.Listener#onImage(ResourceReference, boolean, Map)
815    */
816   @Override
817   public void onImage(ResourceReference image, boolean isFreeStandingURI, Map<String, String> parameters) {    
818     if (ResourceType.ICON.equals(image.getType())) {
819       getIconRenderer().renderIcon(getConfluencePrinter(), image);
820     } else{
821       getImageRenderer().beginRenderImage(getConfluencePrinter());
822       getImageRenderer().renderImageContent(getConfluencePrinter(), getImageRenderer().renderImage(image));
823       getImageRenderer().endRenderImage(getConfluencePrinter(), parameters);  
824     }
825   }
826 
827   protected void printBeginParameters(Map<String, String> parameters) {
828     printBeginParameters(parameters, true);
829   }
830   
831   protected void printEndParameters(Map<String, String> parameters) {
832     printEndParameters(parameters, true);
833   }
834   
835   protected void printEndParameters(Map<String, String> parameters, boolean newLine) {
836     StringBuffer parametersStr = new StringBuffer();
837     List<Map.Entry<String, String>> list = new LinkedList<Map.Entry<String,String>>(parameters.entrySet());
838     Collections.reverse(list);
839     for (Map.Entry<String, String> entry : list) {
840       String value = entry.getValue();
841       String key = entry.getKey();
842       if (key != null && value != null) {
843         if ("style".equals(key))
844           if (!newLine) {
845             parametersStr.append("{span}");
846           }
847           else {
848             parametersStr.append("{div}");
849           }
850       }
851     }
852     print(parametersStr.toString());
853   }
854   
855   protected void printBeginParameters(Map<String, String> parameters, boolean newLine) {
856     StringBuffer parametersStr = new StringBuffer();
857     for (Map.Entry<String, String> entry : parameters.entrySet()) {
858       String value = entry.getValue();
859       String key = entry.getKey();
860 
861       if (key != null && value != null) {
862         if ("style".equals(key))
863           if (!newLine) {
864             parametersStr.append("{span:").append("style=\"").append(value).append("\"}");
865           }
866           else {
867             parametersStr.append("{div:").append("style=\"").append(value).append("\"}");
868           }
869       }
870     }
871     print(parametersStr.toString());
872 
873   }
874 
875   private void printDelayed(String text) {
876     print(text, true);
877   }
878 
879   private void print(String text) {
880     print(text, false);
881   }
882 
883   private void print(String text, boolean isDelayed) {
884     // Handle empty formatting parameters.
885     if (this.previousFormatParameters != null) {      
886       this.previousFormatParameters = null;
887     }
888 
889     if (isDelayed) {
890       getConfluencePrinter().printDelayed(text);
891     } else {
892       getPrinter().print(text);
893     }
894   }
895 
896   private void printEmptyLine() {
897     if (this.isFirstElementRendered) {
898       print("\n\n");
899     } else {
900       this.isFirstElementRendered = true;
901     }
902   }
903 
904   /**
905    * {@inheritDoc}
906    * 
907    * @see org.xwiki.rendering.renderer.AbstractChainingPrintRenderer#setPrinter(org.xwiki.rendering.renderer.printer.WikiPrinter)
908    */
909   public void setPrinter(WikiPrinter printer) {
910     // If the printer is already a Confluence Syntax Escape printer don't wrap it again. This case happens when
911     // the createChainingListenerInstance() method is called, ie when this renderer's state is stacked
912     // (for example when a Group event is being handled).
913     if (printer instanceof ConfluenceSyntaxEscapeWikiPrinter) {
914       super.setPrinter(printer);
915     } else {
916       super.setPrinter(new ConfluenceSyntaxEscapeWikiPrinter(printer, (ConfluenceSyntaxListenerChain) getListenerChain()));
917     }
918   }
919 
920   /**
921    * Allows exposing the additional methods of {@link ConfluenceSyntaxEscapeWikiPrinter}, namely the ability to delay
922    * printing some text and the ability to escape characters that would otherwise have a meaning in Confluence syntax.
923    */
924   public ConfluenceSyntaxEscapeWikiPrinter getConfluencePrinter() {
925     return (ConfluenceSyntaxEscapeWikiPrinter) super.getPrinter();
926   }
927 
928   @Override
929   protected void popPrinter() {
930     // Ensure that any not printed characters are flushed
931     getConfluencePrinter().flush();
932 
933     super.popPrinter();
934   }
935 
936 }