View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.doxia.module.apt;
20  
21  import javax.swing.text.MutableAttributeSet;
22  
23  import java.io.PrintWriter;
24  import java.io.Writer;
25  import java.util.ArrayList;
26  import java.util.Collection;
27  import java.util.LinkedList;
28  import java.util.List;
29  import java.util.Stack;
30  
31  import org.apache.commons.lang3.StringUtils;
32  import org.apache.maven.doxia.sink.SinkEventAttributes;
33  import org.apache.maven.doxia.sink.impl.AbstractTextSink;
34  import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
35  import org.apache.maven.doxia.sink.impl.SinkUtils;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  /**
40   * APT generator implementation.
41   * <br>
42   * <b>Note</b>: The encoding used is UTF-8.
43   *
44   * @author eredmond
45   * @since 1.0
46   */
47  public class AptSink extends AbstractTextSink implements AptMarkup {
48      private static final Logger LOGGER = LoggerFactory.getLogger(AptSink.class);
49  
50      // ----------------------------------------------------------------------
51      // Instance fields
52      // ----------------------------------------------------------------------
53  
54      /**  A buffer that holds the current text when headerFlag or bufferFlag set to <code>true</code>. */
55      private StringBuffer buffer;
56  
57      /**  A buffer that holds the table caption. */
58      private StringBuilder tableCaptionBuffer;
59  
60      /**  authors. */
61      private Collection<String> authors;
62  
63      /**  title. */
64      private String title;
65  
66      /**  date. */
67      private String date;
68  
69      /** startFlag. */
70      private boolean startFlag;
71  
72      /**  tableCaptionFlag. */
73      private boolean tableCaptionFlag;
74  
75      /**  tableCellFlag. */
76      private boolean tableCellFlag;
77  
78      /**  headerFlag. */
79      private boolean headerFlag;
80  
81      /**  bufferFlag. */
82      private boolean bufferFlag;
83  
84      /**  itemFlag. */
85      private boolean itemFlag;
86  
87      /**  verbatimFlag. */
88      private boolean verbatimFlag;
89  
90      /**  verbatim source. */
91      private boolean isSource;
92  
93      /**  gridFlag for tables. */
94      private boolean gridFlag;
95  
96      /**  number of cells in a table. */
97      private int cellCount;
98  
99      /**  The writer to use. */
100     private final PrintWriter writer;
101 
102     /**  justification of table cells. */
103     private int[] cellJustif;
104 
105     /**  a line of a row in a table. */
106     private String rowLine;
107 
108     /**  listNestingIndent. */
109     private String listNestingIndent;
110 
111     /**  listStyles. */
112     private final Stack<String> listStyles;
113 
114     /** Keep track of the closing tags for inline events. */
115     protected Stack<List<String>> inlineStack = new Stack<>();
116 
117     // ----------------------------------------------------------------------
118     // Public protected methods
119     // ----------------------------------------------------------------------
120 
121     /**
122      * Constructor, initialize the Writer and the variables.
123      *
124      * @param writer not null writer to write the result. <b>Should</b> be an UTF-8 Writer.
125      * You could use <code>newWriter</code> methods from {@link org.codehaus.plexus.util.WriterFactory}.
126      */
127     protected AptSink(Writer writer) {
128         this.writer = new PrintWriter(writer);
129         this.listStyles = new Stack<>();
130 
131         init();
132     }
133 
134     /**
135      * Returns the buffer that holds the current text.
136      *
137      * @return A StringBuffer.
138      */
139     protected StringBuffer getBuffer() {
140         return buffer;
141     }
142 
143     /**
144      * Used to determine whether we are in head mode.
145      *
146      * @param headFlag True for head mode.
147      */
148     protected void setHeadFlag(boolean headFlag) {
149         this.headerFlag = headFlag;
150     }
151 
152     /**
153      * {@inheritDoc}
154      */
155     protected void init() {
156         super.init();
157 
158         resetBuffer();
159 
160         this.tableCaptionBuffer = new StringBuilder();
161         this.listNestingIndent = "";
162 
163         this.authors = new LinkedList<>();
164         this.title = null;
165         this.date = null;
166         this.startFlag = true;
167         this.tableCaptionFlag = false;
168         this.tableCellFlag = false;
169         this.headerFlag = false;
170         this.bufferFlag = false;
171         this.itemFlag = false;
172         this.verbatimFlag = false;
173         this.isSource = false;
174         this.gridFlag = false;
175         this.cellCount = 0;
176         this.cellJustif = null;
177         this.rowLine = null;
178         this.listStyles.clear();
179         this.inlineStack.clear();
180     }
181 
182     /**
183      * Reset the StringBuilder.
184      */
185     protected void resetBuffer() {
186         buffer = new StringBuffer();
187     }
188 
189     /**
190      * Reset the TableCaptionBuffer.
191      */
192     protected void resetTableCaptionBuffer() {
193         tableCaptionBuffer = new StringBuilder();
194     }
195 
196     /**
197      * {@inheritDoc}
198      */
199     public void head() {
200         boolean startFlag = this.startFlag;
201 
202         init();
203 
204         headerFlag = true;
205         this.startFlag = startFlag;
206     }
207 
208     /**
209      * {@inheritDoc}
210      */
211     public void head_() {
212         headerFlag = false;
213 
214         if (!startFlag) {
215             write(EOL);
216         }
217         write(HEADER_START_MARKUP + EOL);
218         if (title != null) {
219             write(" " + title + EOL);
220         }
221         write(HEADER_START_MARKUP + EOL);
222         for (String author : authors) {
223             write(" " + author + EOL);
224         }
225         write(HEADER_START_MARKUP + EOL);
226         if (date != null) {
227             write(" " + date + EOL);
228         }
229         write(HEADER_START_MARKUP + EOL);
230     }
231 
232     /**
233      * {@inheritDoc}
234      */
235     public void title_() {
236         if (buffer.length() > 0) {
237             title = buffer.toString();
238             resetBuffer();
239         }
240     }
241 
242     /**
243      * {@inheritDoc}
244      */
245     public void author_() {
246         if (buffer.length() > 0) {
247             authors.add(buffer.toString());
248             resetBuffer();
249         }
250     }
251 
252     /**
253      * {@inheritDoc}
254      */
255     public void date_() {
256         if (buffer.length() > 0) {
257             date = buffer.toString();
258             resetBuffer();
259         }
260     }
261 
262     /**
263      * {@inheritDoc}
264      */
265     public void section1_() {
266         write(EOL);
267     }
268 
269     /**
270      * {@inheritDoc}
271      */
272     public void section2_() {
273         write(EOL);
274     }
275 
276     /**
277      * {@inheritDoc}
278      */
279     public void section3_() {
280         write(EOL);
281     }
282 
283     /**
284      * {@inheritDoc}
285      */
286     public void section4_() {
287         write(EOL);
288     }
289 
290     /**
291      * {@inheritDoc}
292      */
293     public void section5_() {
294         write(EOL);
295     }
296 
297     /**
298      * {@inheritDoc}
299      */
300     public void sectionTitle1() {
301         write(EOL);
302     }
303 
304     /**
305      * {@inheritDoc}
306      */
307     public void sectionTitle1_() {
308         write(EOL + EOL);
309     }
310 
311     /**
312      * {@inheritDoc}
313      */
314     public void sectionTitle2() {
315         write(EOL + SECTION_TITLE_START_MARKUP);
316     }
317 
318     /**
319      * {@inheritDoc}
320      */
321     public void sectionTitle2_() {
322         write(EOL + EOL);
323     }
324 
325     /**
326      * {@inheritDoc}
327      */
328     public void sectionTitle3() {
329         write(EOL + StringUtils.repeat(SECTION_TITLE_START_MARKUP, 2));
330     }
331 
332     /**
333      * {@inheritDoc}
334      */
335     public void sectionTitle3_() {
336         write(EOL + EOL);
337     }
338 
339     /**
340      * {@inheritDoc}
341      */
342     public void sectionTitle4() {
343         write(EOL + StringUtils.repeat(SECTION_TITLE_START_MARKUP, 3));
344     }
345 
346     /**
347      * {@inheritDoc}
348      */
349     public void sectionTitle4_() {
350         write(EOL + EOL);
351     }
352 
353     /**
354      * {@inheritDoc}
355      */
356     public void sectionTitle5() {
357         write(EOL + StringUtils.repeat(SECTION_TITLE_START_MARKUP, 4));
358     }
359 
360     /**
361      * {@inheritDoc}
362      */
363     public void sectionTitle5_() {
364         write(EOL + EOL);
365     }
366 
367     /**
368      * {@inheritDoc}
369      */
370     public void list() {
371         listNestingIndent += " ";
372         listStyles.push(LIST_START_MARKUP);
373         write(EOL);
374     }
375 
376     /**
377      * {@inheritDoc}
378      */
379     public void list_() {
380         if (listNestingIndent.length() <= 1) {
381             write(EOL + listNestingIndent + LIST_END_MARKUP + EOL);
382         } else {
383             write(EOL);
384         }
385         listNestingIndent = StringUtils.chomp(listNestingIndent, " ");
386         listStyles.pop();
387         itemFlag = false;
388     }
389 
390     /**
391      * {@inheritDoc}
392      */
393     public void listItem() {
394         // if (!numberedList)
395         // write(EOL + listNestingIndent + "*");
396         // else
397         numberedListItem();
398         itemFlag = true;
399     }
400 
401     /**
402      * {@inheritDoc}
403      */
404     public void listItem_() {
405         write(EOL);
406         itemFlag = false;
407     }
408 
409     /** {@inheritDoc} */
410     public void numberedList(int numbering) {
411         listNestingIndent += " ";
412         write(EOL);
413 
414         String style;
415         switch (numbering) {
416             case NUMBERING_UPPER_ALPHA:
417                 style = String.valueOf(NUMBERING_UPPER_ALPHA_CHAR);
418                 break;
419             case NUMBERING_LOWER_ALPHA:
420                 style = String.valueOf(NUMBERING_LOWER_ALPHA_CHAR);
421                 break;
422             case NUMBERING_UPPER_ROMAN:
423                 style = String.valueOf(NUMBERING_UPPER_ROMAN_CHAR);
424                 break;
425             case NUMBERING_LOWER_ROMAN:
426                 style = String.valueOf(NUMBERING_LOWER_ROMAN_CHAR);
427                 break;
428             case NUMBERING_DECIMAL:
429             default:
430                 style = String.valueOf(NUMBERING);
431         }
432 
433         listStyles.push(style);
434     }
435 
436     /**
437      * {@inheritDoc}
438      */
439     public void numberedList_() {
440         if (listNestingIndent.length() <= 1) {
441             write(EOL + listNestingIndent + LIST_END_MARKUP + EOL);
442         } else {
443             write(EOL);
444         }
445         listNestingIndent = StringUtils.chomp(listNestingIndent, " ");
446         listStyles.pop();
447         itemFlag = false;
448     }
449 
450     /**
451      * {@inheritDoc}
452      */
453     public void numberedListItem() {
454         String style = listStyles.peek();
455         if (style.equals(String.valueOf(STAR))) {
456             write(EOL + listNestingIndent + STAR + SPACE);
457         } else {
458             write(EOL
459                     + listNestingIndent
460                     + LEFT_SQUARE_BRACKET
461                     + LEFT_SQUARE_BRACKET
462                     + style
463                     + RIGHT_SQUARE_BRACKET
464                     + RIGHT_SQUARE_BRACKET
465                     + SPACE);
466         }
467         itemFlag = true;
468     }
469 
470     /**
471      * {@inheritDoc}
472      */
473     public void numberedListItem_() {
474         write(EOL);
475         itemFlag = false;
476     }
477 
478     /**
479      * {@inheritDoc}
480      */
481     public void definitionList() {
482         listNestingIndent += " ";
483         listStyles.push("");
484         write(EOL);
485     }
486 
487     /**
488      * {@inheritDoc}
489      */
490     public void definitionList_() {
491         if (listNestingIndent.length() <= 1) {
492             write(EOL + listNestingIndent + LIST_END_MARKUP + EOL);
493         } else {
494             write(EOL);
495         }
496         listNestingIndent = StringUtils.chomp(listNestingIndent, " ");
497         listStyles.pop();
498         itemFlag = false;
499     }
500 
501     /**
502      * {@inheritDoc}
503      */
504     public void definedTerm() {
505         write(EOL + " [");
506     }
507 
508     /**
509      * {@inheritDoc}
510      */
511     public void definedTerm_() {
512         write("] ");
513     }
514 
515     /**
516      * {@inheritDoc}
517      */
518     public void definition() {
519         itemFlag = true;
520     }
521 
522     /**
523      * {@inheritDoc}
524      */
525     public void definition_() {
526         write(EOL);
527         itemFlag = false;
528     }
529 
530     /**
531      * {@inheritDoc}
532      */
533     public void pageBreak() {
534         write(EOL + PAGE_BREAK + EOL);
535     }
536 
537     /**
538      * {@inheritDoc}
539      */
540     public void paragraph() {
541         if (tableCellFlag) {
542             // ignore paragraphs in table cells
543         } else if (itemFlag) {
544             write(EOL + EOL + "  " + listNestingIndent);
545         } else {
546             write(EOL + " ");
547         }
548     }
549 
550     /**
551      * {@inheritDoc}
552      */
553     public void paragraph_() {
554         if (tableCellFlag) {
555             // ignore paragraphs in table cells
556         } else {
557             write(EOL + EOL);
558         }
559     }
560 
561     /** {@inheritDoc} */
562     @Override
563     public void verbatim() {
564         verbatim(null);
565     }
566 
567     /** {@inheritDoc} */
568     public void verbatim(SinkEventAttributes attributes) {
569         MutableAttributeSet atts = SinkUtils.filterAttributes(attributes, SinkUtils.SINK_VERBATIM_ATTRIBUTES);
570 
571         boolean source = false;
572 
573         if (atts != null && atts.isDefined(SinkEventAttributes.DECORATION)) {
574             source = "source"
575                     .equals(atts.getAttribute(SinkEventAttributes.DECORATION).toString());
576         }
577 
578         verbatimFlag = true;
579         this.isSource = source;
580         write(EOL);
581         if (source) {
582             write(EOL + VERBATIM_SOURCE_START_MARKUP + EOL);
583         } else {
584             write(EOL + VERBATIM_START_MARKUP + EOL);
585         }
586     }
587 
588     /**
589      * {@inheritDoc}
590      */
591     public void verbatim_() {
592         if (isSource) {
593             write(EOL + VERBATIM_SOURCE_END_MARKUP + EOL);
594         } else {
595             write(EOL + VERBATIM_END_MARKUP + EOL);
596         }
597         isSource = false;
598         verbatimFlag = false;
599     }
600 
601     /**
602      * {@inheritDoc}
603      */
604     public void horizontalRule() {
605         write(EOL + HORIZONTAL_RULE_MARKUP + EOL);
606     }
607 
608     /**
609      * {@inheritDoc}
610      */
611     public void table() {
612         write(EOL);
613     }
614 
615     /**
616      * {@inheritDoc}
617      */
618     public void table_() {
619         if (rowLine != null) {
620             write(rowLine);
621         }
622         rowLine = null;
623 
624         if (tableCaptionBuffer.length() > 0) {
625             text(tableCaptionBuffer.toString() + EOL);
626         }
627 
628         resetTableCaptionBuffer();
629     }
630 
631     @Override
632     public void tableRows() {
633         tableRows(null, false);
634     }
635 
636     /** {@inheritDoc} */
637     public void tableRows(int[] justification, boolean grid) {
638         cellJustif = justification;
639         gridFlag = grid;
640     }
641 
642     /**
643      * {@inheritDoc}
644      */
645     public void tableRows_() {
646         cellJustif = null;
647         gridFlag = false;
648     }
649 
650     /**
651      * {@inheritDoc}
652      */
653     public void tableRow() {
654         bufferFlag = true;
655         cellCount = 0;
656     }
657 
658     /**
659      * {@inheritDoc}
660      */
661     public void tableRow_() {
662         bufferFlag = false;
663 
664         // write out the header row first, then the data in the buffer
665         buildRowLine();
666 
667         write(rowLine);
668 
669         // TODO: This will need to be more clever, for multi-line cells
670         if (gridFlag) {
671             write(TABLE_ROW_SEPARATOR_MARKUP);
672         }
673 
674         write(buffer.toString());
675 
676         resetBuffer();
677 
678         write(EOL);
679 
680         // only reset cell count if this is the last row
681         cellCount = 0;
682     }
683 
684     /** Construct a table row. */
685     private void buildRowLine() {
686         StringBuilder rLine = new StringBuilder();
687         rLine.append(TABLE_ROW_START_MARKUP);
688 
689         for (int i = 0; i < cellCount; i++) {
690             if (cellJustif != null) {
691                 switch (cellJustif[i]) {
692                     case 1:
693                         rLine.append(TABLE_COL_LEFT_ALIGNED_MARKUP);
694                         break;
695                     case 2:
696                         rLine.append(TABLE_COL_RIGHT_ALIGNED_MARKUP);
697                         break;
698                     default:
699                         rLine.append(TABLE_COL_CENTERED_ALIGNED_MARKUP);
700                 }
701             } else {
702                 rLine.append(TABLE_COL_CENTERED_ALIGNED_MARKUP);
703             }
704         }
705         rLine.append(EOL);
706 
707         this.rowLine = rLine.toString();
708     }
709 
710     /**
711      * {@inheritDoc}
712      */
713     public void tableCell() {
714         tableCell(false);
715     }
716 
717     /**
718      * {@inheritDoc}
719      */
720     public void tableHeaderCell() {
721         tableCell(true);
722     }
723 
724     /**
725      * Starts a table cell.
726      *
727      * @param headerRow If this cell is part of a header row.
728      */
729     public void tableCell(boolean headerRow) {
730         if (headerRow) {
731             buffer.append(TABLE_CELL_SEPARATOR_MARKUP);
732         }
733         tableCellFlag = true;
734     }
735 
736     /**
737      * {@inheritDoc}
738      */
739     public void tableCell_() {
740         endTableCell();
741     }
742 
743     /**
744      * {@inheritDoc}
745      */
746     public void tableHeaderCell_() {
747         endTableCell();
748     }
749 
750     /**
751      * Ends a table cell.
752      */
753     private void endTableCell() {
754         tableCellFlag = false;
755         buffer.append(TABLE_CELL_SEPARATOR_MARKUP);
756         cellCount++;
757     }
758 
759     /**
760      * {@inheritDoc}
761      */
762     public void tableCaption() {
763         tableCaptionFlag = true;
764     }
765 
766     /**
767      * {@inheritDoc}
768      */
769     public void tableCaption_() {
770         tableCaptionFlag = false;
771     }
772 
773     /**
774      * {@inheritDoc}
775      */
776     public void figureCaption_() {
777         write(EOL);
778     }
779 
780     /** {@inheritDoc} */
781     public void figureGraphics(String name) {
782         write(EOL + "[" + name + "] ");
783     }
784 
785     /** {@inheritDoc} */
786     public void anchor(String name) {
787         write(ANCHOR_START_MARKUP);
788     }
789 
790     /**
791      * {@inheritDoc}
792      */
793     public void anchor_() {
794         write(ANCHOR_END_MARKUP);
795     }
796 
797     /** {@inheritDoc} */
798     public void link(String name) {
799         if (!headerFlag) {
800             write(LINK_START_1_MARKUP);
801             text(name.startsWith("#") ? name.substring(1) : name);
802             write(LINK_START_2_MARKUP);
803         }
804     }
805 
806     /**
807      * {@inheritDoc}
808      */
809     public void link_() {
810         if (!headerFlag) {
811             write(LINK_END_MARKUP);
812         }
813     }
814 
815     /**
816      * A link with a target.
817      *
818      * @param name The name of the link.
819      * @param target The link target.
820      */
821     public void link(String name, String target) {
822         if (!headerFlag) {
823             write(LINK_START_1_MARKUP);
824             text(target);
825             write(LINK_START_2_MARKUP);
826             text(name);
827         }
828     }
829 
830     /**
831      * {@inheritDoc}
832      */
833     public void inline() {
834         inline(null);
835     }
836 
837     /** {@inheritDoc} */
838     public void inline(SinkEventAttributes attributes) {
839         if (!headerFlag) {
840             List<String> tags = new ArrayList<>();
841 
842             if (attributes != null) {
843 
844                 if (attributes.containsAttribute(SinkEventAttributes.SEMANTICS, "italic")) {
845                     write(ITALIC_START_MARKUP);
846                     tags.add(0, ITALIC_END_MARKUP);
847                 }
848 
849                 if (attributes.containsAttribute(SinkEventAttributes.SEMANTICS, "bold")) {
850                     write(BOLD_START_MARKUP);
851                     tags.add(0, BOLD_END_MARKUP);
852                 }
853 
854                 if (attributes.containsAttribute(SinkEventAttributes.SEMANTICS, "code")) {
855                     write(MONOSPACED_START_MARKUP);
856                     tags.add(0, MONOSPACED_END_MARKUP);
857                 }
858             }
859 
860             inlineStack.push(tags);
861         }
862     }
863 
864     /**
865      * {@inheritDoc}
866      */
867     public void inline_() {
868         if (!headerFlag) {
869             for (String tag : inlineStack.pop()) {
870                 write(tag);
871             }
872         }
873     }
874 
875     /**
876      * {@inheritDoc}
877      */
878     public void italic() {
879         inline(SinkEventAttributeSet.Semantics.ITALIC);
880     }
881 
882     /**
883      * {@inheritDoc}
884      */
885     public void italic_() {
886         inline_();
887     }
888 
889     /**
890      * {@inheritDoc}
891      */
892     public void bold() {
893         inline(SinkEventAttributeSet.Semantics.BOLD);
894     }
895 
896     /**
897      * {@inheritDoc}
898      */
899     public void bold_() {
900         inline_();
901     }
902 
903     /**
904      * {@inheritDoc}
905      */
906     public void monospaced() {
907         inline(SinkEventAttributeSet.Semantics.CODE);
908     }
909 
910     /**
911      * {@inheritDoc}
912      */
913     public void monospaced_() {
914         inline_();
915     }
916 
917     /**
918      * {@inheritDoc}
919      */
920     public void lineBreak() {
921         if (headerFlag || bufferFlag) {
922             buffer.append(EOL);
923         } else if (verbatimFlag) {
924             write(EOL);
925         } else {
926             write("\\" + EOL);
927         }
928     }
929 
930     /**
931      * {@inheritDoc}
932      */
933     public void nonBreakingSpace() {
934         if (headerFlag || bufferFlag) {
935             buffer.append(NON_BREAKING_SPACE_MARKUP);
936         } else {
937             write(NON_BREAKING_SPACE_MARKUP);
938         }
939     }
940 
941     /** {@inheritDoc} */
942     public void text(String text) {
943         if (tableCaptionFlag) {
944             tableCaptionBuffer.append(text);
945         } else if (headerFlag || bufferFlag) {
946             buffer.append(text);
947         } else if (verbatimFlag) {
948             verbatimContent(text);
949         } else {
950             content(text);
951         }
952     }
953 
954     /** {@inheritDoc} */
955     public void rawText(String text) {
956         write(text);
957     }
958 
959     /** {@inheritDoc} */
960     public void comment(String comment) {
961         rawText((startFlag ? "" : EOL) + COMMENT + COMMENT + comment);
962     }
963 
964     /**
965      * {@inheritDoc}
966      *
967      * Unkown events just log a warning message but are ignored otherwise.
968      * @see org.apache.maven.doxia.sink.Sink#unknown(String,Object[],SinkEventAttributes)
969      */
970     public void unknown(String name, Object[] requiredParams, SinkEventAttributes attributes) {
971         LOGGER.warn("Unknown Sink event '{}', ignoring!", name);
972     }
973 
974     /**
975      * Write text to output.
976      *
977      * @param text The text to write.
978      */
979     protected void write(String text) {
980         startFlag = false;
981         if (tableCellFlag) {
982             buffer.append(text);
983         } else {
984             writer.write(unifyEOLs(text));
985         }
986     }
987 
988     /**
989      * Write Apt escaped text to output.
990      *
991      * @param text The text to write.
992      */
993     protected void content(String text) {
994         write(escapeAPT(text));
995     }
996 
997     /**
998      * Write Apt escaped text to output.
999      *
1000      * @param text The text to write.
1001      */
1002     protected void verbatimContent(String text) {
1003         write(escapeAPT(text));
1004     }
1005 
1006     /**
1007      * {@inheritDoc}
1008      */
1009     public void flush() {
1010         writer.flush();
1011     }
1012 
1013     /**
1014      * {@inheritDoc}
1015      */
1016     public void close() {
1017         writer.close();
1018 
1019         init();
1020     }
1021 
1022     // ----------------------------------------------------------------------
1023     // Private methods
1024     // ----------------------------------------------------------------------
1025 
1026     /**
1027      * Escape special characters in a text in APT:
1028      *
1029      * <pre>
1030      * \~, \=, \-, \+, \*, \[, \], \<, \>, \{, \}, \\
1031      * </pre>
1032      *
1033      * @param text the String to escape, may be null
1034      * @return the text escaped, "" if null String input
1035      */
1036     private static String escapeAPT(String text) {
1037         if (text == null) {
1038             return "";
1039         }
1040 
1041         int length = text.length();
1042         StringBuilder buffer = new StringBuilder(length);
1043 
1044         for (int i = 0; i < length; ++i) {
1045             char c = text.charAt(i);
1046             switch (c) { // 0080
1047                 case '\\':
1048                 case '~':
1049                 case '=':
1050                 case '-':
1051                 case '+':
1052                 case '*':
1053                 case '[':
1054                 case ']':
1055                 case '<':
1056                 case '>':
1057                 case '{':
1058                 case '}':
1059                     buffer.append('\\');
1060                     buffer.append(c);
1061                     break;
1062                 default:
1063                     buffer.append(c);
1064             }
1065         }
1066 
1067         return buffer.toString();
1068     }
1069 }