1 package org.apache.maven.doxia.module.apt;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import org.apache.maven.doxia.macro.MacroExecutionException;
23 import org.apache.maven.doxia.macro.MacroRequest;
24 import org.apache.maven.doxia.macro.manager.MacroNotFoundException;
25 import org.apache.maven.doxia.parser.AbstractTextParser;
26 import org.apache.maven.doxia.parser.ParseException;
27 import org.apache.maven.doxia.sink.Sink;
28 import org.apache.maven.doxia.sink.SinkAdapter;
29 import org.apache.maven.doxia.sink.SinkEventAttributeSet;
30 import org.apache.maven.doxia.sink.SinkEventAttributes;
31 import org.apache.maven.doxia.util.DoxiaUtils;
32
33 import org.codehaus.plexus.util.IOUtil;
34 import org.codehaus.plexus.util.StringUtils;
35
36 import java.io.IOException;
37 import java.io.Reader;
38 import java.io.StringReader;
39 import java.io.StringWriter;
40 import java.util.HashMap;
41 import java.util.Map;
42 import java.util.Set;
43 import java.util.StringTokenizer;
44 import java.util.TreeSet;
45
46 /**
47 * The APT parser.
48 * <br/>
49 * Based on the <a href="http://www.xmlmind.com/aptconvert.html">APTconvert</a> project.
50 *
51 * @version $Id: AptParser.java 1096107 2011-04-23 06:22:09Z ltheussl $
52 * @since 1.0
53 * @plexus.component role="org.apache.maven.doxia.parser.Parser" role-hint="apt"
54 */
55 public class AptParser
56 extends AbstractTextParser
57 implements AptMarkup
58 {
59 /** Title event id */
60 private static final int TITLE = 0;
61
62 /** Section 1 event id */
63 private static final int SECTION1 = 1;
64
65 /** Section 2 event id */
66 private static final int SECTION2 = 2;
67
68 /** Section 3 event id */
69 private static final int SECTION3 = 3;
70
71 /** Section 4 event id */
72 private static final int SECTION4 = 4;
73
74 /** Section 5 event id */
75 private static final int SECTION5 = 5;
76
77 /** Paragraph event id */
78 private static final int PARAGRAPH = 6;
79
80 /** Verbatim event id */
81 private static final int VERBATIM = 7;
82
83 /** Figure event id */
84 private static final int FIGURE = 8;
85
86 /** Table event id */
87 private static final int TABLE = 9;
88
89 /** List event id */
90 private static final int LIST_ITEM = 10;
91
92 /** Numbered list event id */
93 private static final int NUMBERED_LIST_ITEM = 11;
94
95 /** Definition list event id */
96 private static final int DEFINITION_LIST_ITEM = 12;
97
98 /** Horizontal rule event id */
99 private static final int HORIZONTAL_RULE = 13;
100
101 /** Page break event id */
102 private static final int PG_BREAK = 14;
103
104 /** List break event id */
105 private static final int LIST_BREAK = 15;
106
107 /** Macro event id */
108 private static final int MACRO = 16;
109
110 /** Comment event id. */
111 private static final int COMMENT_BLOCK = 17;
112
113 /** String representations of event ids */
114 private static final String TYPE_NAMES[] = {
115 "TITLE",
116 "SECTION1",
117 "SECTION2",
118 "SECTION3",
119 "SECTION4",
120 "SECTION5",
121 "PARAGRAPH",
122 "VERBATIM",
123 "FIGURE",
124 "TABLE",
125 "LIST_ITEM",
126 "NUMBERED_LIST_ITEM",
127 "DEFINITION_LIST_ITEM",
128 "HORIZONTAL_RULE",
129 "PG_BREAK",
130 "LIST_BREAK",
131 "MACRO",
132 "COMMENT_BLOCK" };
133
134 /** An array of 85 spaces. */
135 protected static final char[] SPACES;
136
137 /** Default tab width. */
138 public static final int TAB_WIDTH = 8;
139
140 // ----------------------------------------------------------------------
141 // Instance fields
142 // ----------------------------------------------------------------------
143
144 /** the AptSource. */
145 private AptSource source;
146
147 /** a block of AptSource. */
148 private Block block;
149
150 /** blockFileName. */
151 private String blockFileName;
152
153 /** blockLineNumber. */
154 private int blockLineNumber;
155
156 /** sourceContent. */
157 protected String sourceContent;
158
159 /** the sink to receive the events. */
160 protected Sink sink;
161
162 /** a line of AptSource. */
163 protected String line;
164
165 /** Map of warn messages with a String as key to describe the error type and a Set as value.
166 * Using to reduce warn messages. */
167 protected Map<String, Set<String>> warnMessages;
168
169 private static final int NUMBER_OF_SPACES = 85;
170
171 static
172 {
173 SPACES = new char[NUMBER_OF_SPACES];
174
175 for ( int i = 0; i < NUMBER_OF_SPACES; i++ )
176 {
177 SPACES[i] = ' ';
178 }
179 }
180
181 // ----------------------------------------------------------------------
182 // Public methods
183 // ----------------------------------------------------------------------
184
185 /** {@inheritDoc} */
186 public void parse( Reader source, Sink sink )
187 throws ParseException
188 {
189 init();
190
191 try
192 {
193 StringWriter contentWriter = new StringWriter();
194 IOUtil.copy( source, contentWriter );
195 sourceContent = contentWriter.toString();
196 }
197 catch ( IOException e )
198 {
199 throw new AptParseException( "IOException: " + e.getMessage(), e );
200 }
201
202 try
203 {
204 this.source = new AptReaderSource( new StringReader( sourceContent ) );
205
206 this.sink = sink;
207 sink.enableLogging( getLog() );
208
209 blockFileName = null;
210
211 blockLineNumber = -1;
212
213 // Lookahead line.
214 nextLine();
215
216 // Lookahead block.
217 nextBlock( /*first*/true );
218
219 // traverse comments
220 while ( ( block != null ) && ( block.getType() == COMMENT_BLOCK ) )
221 {
222 block.traverse();
223 nextBlock( /*first*/true );
224 }
225
226 traverseHead();
227
228 traverseBody();
229 }
230 catch ( AptParseException ape )
231 {
232 // TODO handle column number
233 throw new AptParseException( ape.getMessage(), ape, getSourceName(), getSourceLineNumber(), -1 );
234 }
235 finally
236 {
237 logWarnings();
238
239 setSecondParsing( false );
240 init();
241 }
242 }
243
244 /**
245 * Returns the name of the Apt source document.
246 *
247 * @return the source name.
248 */
249 public String getSourceName()
250 {
251 // Use this rather than source.getName() to report errors.
252 return blockFileName;
253 }
254
255 /**
256 * Returns the current line number of the Apt source document.
257 *
258 * @return the line number.
259 */
260 public int getSourceLineNumber()
261 {
262 // Use this rather than source.getLineNumber() to report errors.
263 return blockLineNumber;
264 }
265
266 // ----------------------------------------------------------------------
267 // Protected methods
268 // ----------------------------------------------------------------------
269
270 /**
271 * Parse the next line of the Apt source document.
272 *
273 * @throws org.apache.maven.doxia.module.apt.AptParseException if something goes wrong.
274 */
275 protected void nextLine()
276 throws AptParseException
277 {
278 line = source.getNextLine();
279 }
280
281 /**
282 * Parse the given text.
283 *
284 * @param text the text to parse.
285 * @param begin offset.
286 * @param end offset.
287 * @param sink the sink to receive the events.
288 * @throws org.apache.maven.doxia.module.apt.AptParseException if something goes wrong.
289 */
290 protected void doTraverseText( String text, int begin, int end, Sink sink )
291 throws AptParseException
292 {
293 boolean anchor = false;
294 boolean link = false;
295 boolean italic = false;
296 boolean bold = false;
297 boolean monospaced = false;
298 StringBuffer buffer = new StringBuffer( end - begin );
299
300 for ( int i = begin; i < end; ++i )
301 {
302 char c = text.charAt( i );
303 switch ( c )
304 {
305 case BACKSLASH:
306 if ( i + 1 < end )
307 {
308 char escaped = text.charAt( i + 1 );
309 switch ( escaped )
310 {
311 case SPACE:
312 ++i;
313 flushTraversed( buffer, sink );
314 sink.nonBreakingSpace();
315 break;
316 case '\r':
317 case '\n':
318 ++i;
319 // Skip white space which may follow a line break.
320 while ( i + 1 < end && Character.isWhitespace( text.charAt( i + 1 ) ) )
321 {
322 ++i;
323 }
324 flushTraversed( buffer, sink );
325 sink.lineBreak();
326 break;
327 case BACKSLASH:
328 case PIPE:
329 case COMMENT:
330 case EQUAL:
331 case MINUS:
332 case PLUS:
333 case STAR:
334 case LEFT_SQUARE_BRACKET:
335 case RIGHT_SQUARE_BRACKET:
336 case LESS_THAN:
337 case GREATER_THAN:
338 case LEFT_CURLY_BRACKET:
339 case RIGHT_CURLY_BRACKET:
340 ++i;
341 buffer.append( escaped );
342 break;
343 case 'x':
344 if ( i + 3 < end && isHexChar( text.charAt( i + 2 ) )
345 && isHexChar( text.charAt( i + 3 ) ) )
346 {
347 int value = '?';
348 try
349 {
350 value = Integer.parseInt( text.substring( i + 2, i + 4 ), 16 );
351 }
352 catch ( NumberFormatException e )
353 {
354 if ( getLog().isDebugEnabled() )
355 {
356 getLog().debug( "Not a number: " + text.substring( i + 2, i + 4 ) );
357 }
358 }
359
360 i += 3;
361 buffer.append( (char) value );
362 }
363 else
364 {
365 buffer.append( BACKSLASH );
366 }
367 break;
368 case 'u':
369 if ( i + 5 < end && isHexChar( text.charAt( i + 2 ) )
370 && isHexChar( text.charAt( i + 3 ) ) && isHexChar( text.charAt( i + 4 ) )
371 && isHexChar( text.charAt( i + 5 ) ) )
372 {
373 int value = '?';
374 try
375 {
376 value = Integer.parseInt( text.substring( i + 2, i + 6 ), 16 );
377 }
378 catch ( NumberFormatException e )
379 {
380 if ( getLog().isDebugEnabled() )
381 {
382 getLog().debug( "Not a number: " + text.substring( i + 2, i + 6 ) );
383 }
384 }
385
386 i += 5;
387 buffer.append( (char) value );
388 }
389 else
390 {
391 buffer.append( BACKSLASH );
392 }
393 break;
394 default:
395 if ( isOctalChar( escaped ) )
396 {
397 int octalChars = 1;
398 if ( isOctalChar( charAt( text, end, i + 2 ) ) )
399 {
400 ++octalChars;
401 if ( isOctalChar( charAt( text, end, i + 3 ) ) )
402 {
403 ++octalChars;
404 }
405 }
406 int value = '?';
407 try
408 {
409 value = Integer.parseInt( text.substring( i + 1, i + 1 + octalChars ), 8 );
410 }
411 catch ( NumberFormatException e )
412 {
413 if ( getLog().isDebugEnabled() )
414 {
415 getLog().debug(
416 "Not a number: "
417 + text.substring( i + 1, i + 1 + octalChars ) );
418 }
419 }
420
421 i += octalChars;
422 buffer.append( (char) value );
423 }
424 else
425 {
426 buffer.append( BACKSLASH );
427 }
428 }
429 }
430 else
431 {
432 buffer.append( BACKSLASH );
433 }
434 break;
435
436 case LEFT_CURLY_BRACKET: /*}*/
437 if ( !anchor && !link )
438 {
439 if ( i + 1 < end && text.charAt( i + 1 ) == LEFT_CURLY_BRACKET /*}*/ )
440 {
441 ++i;
442 link = true;
443 flushTraversed( buffer, sink );
444
445 String linkAnchor = null;
446
447 if ( i + 1 < end && text.charAt( i + 1 ) == LEFT_CURLY_BRACKET /*}*/ )
448 {
449 ++i;
450 StringBuffer buf = new StringBuffer();
451 i = skipTraversedLinkAnchor( text, i + 1, end, buf );
452 linkAnchor = buf.toString();
453 }
454
455 if ( linkAnchor == null )
456 {
457 linkAnchor = getTraversedLink( text, i + 1, end );
458 }
459
460 if ( AptUtils.isInternalLink( linkAnchor ) )
461 {
462 linkAnchor = "#" + linkAnchor;
463 }
464
465 int hashIndex = linkAnchor.indexOf( "#" );
466
467 if ( hashIndex != -1 && !AptUtils.isExternalLink( linkAnchor ) )
468 {
469 String hash = linkAnchor.substring( hashIndex + 1 );
470
471 if ( hash.endsWith( ".html" ) && !hash.startsWith( "./" ) )
472 {
473 String msg = "Ambiguous link: '" + hash
474 + "'. If this is a local link, prepend \"./\"!";
475 logMessage( "ambiguousLink", msg );
476 }
477
478 if ( !DoxiaUtils.isValidId( hash ) )
479 {
480 linkAnchor =
481 linkAnchor.substring( 0, hashIndex ) + "#"
482 + DoxiaUtils.encodeId( hash, true );
483
484 String msg = "Modified invalid link: '" + hash + "' to '" + linkAnchor + "'";
485 logMessage( "modifiedLink", msg );
486 }
487 }
488
489 sink.link( linkAnchor );
490 }
491 else
492 {
493 anchor = true;
494 flushTraversed( buffer, sink );
495
496 String linkAnchor = getTraversedAnchor( text, i + 1, end );
497
498 linkAnchor = AptUtils.encodeAnchor( linkAnchor );
499
500 sink.anchor( linkAnchor );
501 }
502 }
503 else
504 {
505 buffer.append( c );
506 }
507 break;
508
509 case /*{*/RIGHT_CURLY_BRACKET:
510 if ( link && i + 1 < end && text.charAt( i + 1 ) == /*{*/RIGHT_CURLY_BRACKET )
511 {
512 ++i;
513 link = false;
514 flushTraversed( buffer, sink );
515 sink.link_();
516 }
517 else if ( anchor )
518 {
519 anchor = false;
520 flushTraversed( buffer, sink );
521 sink.anchor_();
522 }
523 else
524 {
525 buffer.append( c );
526 }
527 break;
528
529 case LESS_THAN:
530 if ( !italic && !bold && !monospaced )
531 {
532 if ( i + 1 < end && text.charAt( i + 1 ) == LESS_THAN )
533 {
534 if ( i + 2 < end && text.charAt( i + 2 ) == LESS_THAN )
535 {
536 i += 2;
537 monospaced = true;
538 flushTraversed( buffer, sink );
539 sink.monospaced();
540 }
541 else
542 {
543 ++i;
544 bold = true;
545 flushTraversed( buffer, sink );
546 sink.bold();
547 }
548 }
549 else
550 {
551 italic = true;
552 flushTraversed( buffer, sink );
553 sink.italic();
554 }
555 }
556 else
557 {
558 buffer.append( c );
559 }
560 break;
561
562 case GREATER_THAN:
563 if ( monospaced && i + 2 < end && text.charAt( i + 1 ) == GREATER_THAN
564 && text.charAt( i + 2 ) == GREATER_THAN )
565 {
566 i += 2;
567 monospaced = false;
568 flushTraversed( buffer, sink );
569 sink.monospaced_();
570 }
571 else if ( bold && i + 1 < end && text.charAt( i + 1 ) == GREATER_THAN )
572 {
573 ++i;
574 bold = false;
575 flushTraversed( buffer, sink );
576 sink.bold_();
577 }
578 else if ( italic )
579 {
580 italic = false;
581 flushTraversed( buffer, sink );
582 sink.italic_();
583 }
584 else
585 {
586 buffer.append( c );
587 }
588 break;
589
590 default:
591 if ( Character.isWhitespace( c ) )
592 {
593 buffer.append( SPACE );
594
595 // Skip to the last char of a sequence of white spaces.
596 while ( i + 1 < end && Character.isWhitespace( text.charAt( i + 1 ) ) )
597 {
598 ++i;
599 }
600 }
601 else
602 {
603 buffer.append( c );
604 }
605 }
606 }
607
608 if ( monospaced )
609 {
610 throw new AptParseException( "missing '" + MONOSPACED_END_MARKUP + "'" );
611 }
612 if ( bold )
613 {
614 throw new AptParseException( "missing '" + BOLD_END_MARKUP + "'" );
615 }
616 if ( italic )
617 {
618 throw new AptParseException( "missing '" + ITALIC_END_MARKUP + "'" );
619 }
620 if ( link )
621 {
622 throw new AptParseException( "missing '" + LINK_END_MARKUP + "'" );
623 }
624 if ( anchor )
625 {
626 throw new AptParseException( "missing '" + ANCHOR_END_MARKUP + "'" );
627 }
628
629 flushTraversed( buffer, sink );
630 }
631
632 // -----------------------------------------------------------------------
633
634 /**
635 * Returns the character at position i of the given string.
636 *
637 * @param string the string.
638 * @param length length.
639 * @param i offset.
640 * @return the character, or '\0' if i > length.
641 */
642 protected static char charAt( String string, int length, int i )
643 {
644 return ( i < length ) ? string.charAt( i ) : '\0';
645 }
646
647 /**
648 * Skip spaces.
649 *
650 * @param string string.
651 * @param length length.
652 * @param i offset.
653 * @return int.
654 */
655 protected static int skipSpace( String string, int length, int i )
656 {
657 loop: for ( ; i < length; ++i )
658 {
659 switch ( string.charAt( i ) )
660 {
661 case SPACE:
662 case TAB:
663 break;
664 default:
665 break loop;
666 }
667 }
668 return i;
669 }
670
671 /**
672 * Replace part of a string.
673 *
674 * @param string the string
675 * @param oldSub the substring to replace
676 * @param newSub the replacement string
677 * @return String
678 */
679 protected static String replaceAll( String string, String oldSub, String newSub )
680 {
681 StringBuffer replaced = new StringBuffer();
682 int oldSubLength = oldSub.length();
683 int begin, end;
684
685 begin = 0;
686 while ( ( end = string.indexOf( oldSub, begin ) ) >= 0 )
687 {
688 if ( end > begin )
689 {
690 replaced.append( string.substring( begin, end ) );
691 }
692 replaced.append( newSub );
693 begin = end + oldSubLength;
694 }
695 if ( begin < string.length() )
696 {
697 replaced.append( string.substring( begin ) );
698 }
699
700 return replaced.toString();
701 }
702
703 /** {@inheritDoc} */
704 protected void init()
705 {
706 super.init();
707
708 this.sourceContent = null;
709 this.sink = null;
710 this.source = null;
711 this.block = null;
712 this.blockFileName = null;
713 this.blockLineNumber = 0;
714 this.line = null;
715 this.warnMessages = null;
716 }
717
718 // ----------------------------------------------------------------------
719 // Private methods
720 // ----------------------------------------------------------------------
721
722 /**
723 * Parse the head of the Apt source document.
724 *
725 * @throws AptParseException if something goes wrong.
726 */
727 private void traverseHead()
728 throws AptParseException
729 {
730 sink.head();
731
732 if ( block != null && block.getType() == TITLE )
733 {
734 block.traverse();
735 nextBlock();
736 }
737
738 sink.head_();
739 }
740
741 /**
742 * Parse the body of the Apt source document.
743 *
744 * @throws AptParseException if something goes wrong.
745 */
746 private void traverseBody()
747 throws AptParseException
748 {
749 sink.body();
750
751 if ( block != null )
752 {
753 traverseSectionBlocks();
754 }
755
756 while ( block != null )
757 {
758 traverseSection( 0 );
759 }
760
761 sink.body_();
762 }
763
764 /**
765 * Parse a section of the Apt source document.
766 *
767 * @param level The section level.
768 * @throws AptParseException if something goes wrong.
769 */
770 private void traverseSection( int level )
771 throws AptParseException
772 {
773 if ( block == null )
774 {
775 return;
776 }
777
778 int type = SECTION1 + level;
779
780 expectedBlock( type );
781
782 switch ( level )
783 {
784 case 0:
785 sink.section1();
786 break;
787 case 1:
788 sink.section2();
789 break;
790 case 2:
791 sink.section3();
792 break;
793 case 3:
794 sink.section4();
795 break;
796 case 4:
797 sink.section5();
798 break;
799 default:
800 break;
801 }
802
803 block.traverse();
804
805 nextBlock();
806
807 traverseSectionBlocks();
808
809 while ( block != null )
810 {
811 if ( block.getType() <= type )
812 {
813 break;
814 }
815
816 traverseSection( level + 1 );
817 }
818
819 switch ( level )
820 {
821 case 0:
822 sink.section1_();
823 break;
824 case 1:
825 sink.section2_();
826 break;
827 case 2:
828 sink.section3_();
829 break;
830 case 3:
831 sink.section4_();
832 break;
833 case 4:
834 sink.section5_();
835 break;
836 default:
837 break;
838 }
839 }
840
841 /**
842 * Parse the section blocks of the Apt source document.
843 *
844 * @throws AptParseException if something goes wrong.
845 */
846 private void traverseSectionBlocks()
847 throws AptParseException
848 {
849 loop: while ( block != null )
850 {
851 switch ( block.getType() )
852 {
853 case PARAGRAPH:
854 case VERBATIM:
855 case FIGURE:
856 case TABLE:
857 case HORIZONTAL_RULE:
858 case PG_BREAK:
859 case MACRO:
860 case COMMENT_BLOCK:
861 block.traverse();
862 nextBlock();
863 break;
864
865 case LIST_ITEM:
866 traverseList();
867 break;
868
869 case NUMBERED_LIST_ITEM:
870 traverseNumberedList();
871 break;
872
873 case DEFINITION_LIST_ITEM:
874 traverseDefinitionList();
875 break;
876
877 case LIST_BREAK:
878 // May be this is a list break which has not been indented
879 // very precisely.
880 nextBlock();
881 break;
882
883 default:
884 // A section block which starts a new section.
885 break loop;
886 }
887 }
888 }
889
890 /**
891 * Parse a list of the Apt source document.
892 *
893 * @throws AptParseException if something goes wrong.
894 */
895 private void traverseList()
896 throws AptParseException
897 {
898 if ( block == null )
899 {
900 return;
901 }
902
903 expectedBlock( LIST_ITEM );
904
905 int listIndent = block.getIndent();
906
907 sink.list();
908
909 sink.listItem();
910
911 block.traverse();
912
913 nextBlock();
914
915 loop: while ( block != null )
916 {
917 int blockIndent = block.getIndent();
918
919 switch ( block.getType() )
920 {
921 case PARAGRAPH:
922 if ( blockIndent < listIndent )
923 {
924 break loop;
925 }
926 /*FALLTHROUGH*/
927 case VERBATIM:
928 case MACRO:
929 case FIGURE:
930 case TABLE:
931 case HORIZONTAL_RULE:
932 case PG_BREAK:
933 block.traverse();
934 nextBlock();
935 break;
936
937 case LIST_ITEM:
938 if ( blockIndent < listIndent )
939 {
940 break loop;
941 }
942
943 if ( blockIndent > listIndent )
944 {
945 traverseList();
946 }
947 else
948 {
949 sink.listItem_();
950 sink.listItem();
951 block.traverse();
952 nextBlock();
953 }
954 break;
955
956 case NUMBERED_LIST_ITEM:
957 if ( blockIndent < listIndent )
958 {
959 break loop;
960 }
961
962 traverseNumberedList();
963 break;
964
965 case DEFINITION_LIST_ITEM:
966 if ( blockIndent < listIndent )
967 {
968 break loop;
969 }
970
971 traverseDefinitionList();
972 break;
973
974 case LIST_BREAK:
975 if ( blockIndent >= listIndent )
976 {
977 nextBlock();
978 }
979 /*FALLTHROUGH*/
980 default:
981 // A block which ends the list.
982 break loop;
983 }
984 }
985
986 sink.listItem_();
987 sink.list_();
988 }
989
990 /**
991 * Parse a numbered list of the Apt source document.
992 *
993 * @throws AptParseException if something goes wrong.
994 */
995 private void traverseNumberedList()
996 throws AptParseException
997 {
998 if ( block == null )
999 {
1000 return;
1001 }
1002 expectedBlock( NUMBERED_LIST_ITEM );
1003 int listIndent = block.getIndent();
1004
1005 sink.numberedList( ( (NumberedListItem) block ).getNumbering() );
1006 sink.numberedListItem();
1007 block.traverse();
1008 nextBlock();
1009
1010 loop: while ( block != null )
1011 {
1012 int blockIndent = block.getIndent();
1013
1014 switch ( block.getType() )
1015 {
1016 case PARAGRAPH:
1017 if ( blockIndent < listIndent )
1018 {
1019 break loop;
1020 }
1021 /*FALLTHROUGH*/
1022 case VERBATIM:
1023 case FIGURE:
1024 case TABLE:
1025 case HORIZONTAL_RULE:
1026 case PG_BREAK:
1027 block.traverse();
1028 nextBlock();
1029 break;
1030
1031 case LIST_ITEM:
1032 if ( blockIndent < listIndent )
1033 {
1034 break loop;
1035 }
1036
1037 traverseList();
1038 break;
1039
1040 case NUMBERED_LIST_ITEM:
1041 if ( blockIndent < listIndent )
1042 {
1043 break loop;
1044 }
1045
1046 if ( blockIndent > listIndent )
1047 {
1048 traverseNumberedList();
1049 }
1050 else
1051 {
1052 sink.numberedListItem_();
1053 sink.numberedListItem();
1054 block.traverse();
1055 nextBlock();
1056 }
1057 break;
1058
1059 case DEFINITION_LIST_ITEM:
1060 if ( blockIndent < listIndent )
1061 {
1062 break loop;
1063 }
1064
1065 traverseDefinitionList();
1066 break;
1067
1068 case LIST_BREAK:
1069 if ( blockIndent >= listIndent )
1070 {
1071 nextBlock();
1072 }
1073 /*FALLTHROUGH*/
1074 default:
1075 // A block which ends the list.
1076 break loop;
1077 }
1078 }
1079
1080 sink.numberedListItem_();
1081 sink.numberedList_();
1082 }
1083
1084 /**
1085 * Parse a definition list of the Apt source document.
1086 *
1087 * @throws AptParseException if something goes wrong.
1088 */
1089 private void traverseDefinitionList()
1090 throws AptParseException
1091 {
1092 if ( block == null )
1093 {
1094 return;
1095 }
1096 expectedBlock( DEFINITION_LIST_ITEM );
1097 int listIndent = block.getIndent();
1098
1099 sink.definitionList();
1100 sink.definitionListItem();
1101 block.traverse();
1102 nextBlock();
1103
1104 loop: while ( block != null )
1105 {
1106 int blockIndent = block.getIndent();
1107
1108 switch ( block.getType() )
1109 {
1110 case PARAGRAPH:
1111 if ( blockIndent < listIndent )
1112 {
1113 break loop;
1114 }
1115 /*FALLTHROUGH*/
1116 case VERBATIM:
1117 case FIGURE:
1118 case TABLE:
1119 case HORIZONTAL_RULE:
1120 case PG_BREAK:
1121 block.traverse();
1122 nextBlock();
1123 break;
1124
1125 case LIST_ITEM:
1126 if ( blockIndent < listIndent )
1127 {
1128 break loop;
1129 }
1130
1131 traverseList();
1132 break;
1133
1134 case NUMBERED_LIST_ITEM:
1135 if ( blockIndent < listIndent )
1136 {
1137 break loop;
1138 }
1139
1140 traverseNumberedList();
1141 break;
1142
1143 case DEFINITION_LIST_ITEM:
1144 if ( blockIndent < listIndent )
1145 {
1146 break loop;
1147 }
1148
1149 if ( blockIndent > listIndent )
1150 {
1151 traverseDefinitionList();
1152 }
1153 else
1154 {
1155 sink.definition_();
1156 sink.definitionListItem_();
1157 sink.definitionListItem();
1158 block.traverse();
1159 nextBlock();
1160 }
1161 break;
1162
1163 case LIST_BREAK:
1164 if ( blockIndent >= listIndent )
1165 {
1166 nextBlock();
1167 }
1168 /*FALLTHROUGH*/
1169 default:
1170 // A block which ends the list.
1171 break loop;
1172 }
1173 }
1174
1175 sink.definition_();
1176 sink.definitionListItem_();
1177 sink.definitionList_();
1178 }
1179
1180 /**
1181 * Parse the next block of the Apt source document.
1182 *
1183 * @throws AptParseException if something goes wrong.
1184 */
1185 private void nextBlock()
1186 throws AptParseException
1187 {
1188 nextBlock( /*first*/false );
1189 }
1190
1191 /**
1192 * Parse the next block of the Apt source document.
1193 *
1194 * @param firstBlock True if this is the first block of the Apt source document.
1195 * @throws AptParseException if something goes wrong.
1196 */
1197 private void nextBlock( boolean firstBlock )
1198 throws AptParseException
1199 {
1200 // Skip open lines.
1201 int length, indent, i;
1202
1203 skipLoop: for ( ;; )
1204 {
1205 if ( line == null )
1206 {
1207 block = null;
1208 return;
1209 }
1210
1211 length = line.length();
1212 indent = 0;
1213 for ( i = 0; i < length; ++i )
1214 {
1215 switch ( line.charAt( i ) )
1216 {
1217 case SPACE:
1218 ++indent;
1219 break;
1220 case TAB:
1221 indent += 8;
1222 break;
1223 default:
1224 break skipLoop;
1225 }
1226 }
1227
1228 if ( i == length )
1229 {
1230 nextLine();
1231 }
1232 }
1233
1234 blockFileName = source.getName();
1235 blockLineNumber = source.getLineNumber();
1236 block = null;
1237 switch ( line.charAt( i ) )
1238 {
1239 case STAR:
1240 if ( indent == 0 )
1241 {
1242 if ( charAt( line, length, i + 1 ) == MINUS && charAt( line, length, i + 2 ) == MINUS )
1243 {
1244 block = new Table( indent, line );
1245 }
1246 else if ( charAt( line, length, i + 1 ) == STAR )
1247 {
1248 if ( charAt( line, length, i + 2 ) == STAR )
1249 {
1250 if ( charAt( line, length, i + 3 ) == STAR )
1251 {
1252 block = new Section5( indent, line );
1253 }
1254 else
1255 {
1256 block = new Section4( indent, line );
1257 }
1258 }
1259 else
1260 {
1261 block = new Section3( indent, line );
1262 }
1263 }
1264 else
1265 {
1266 block = new Section2( indent, line );
1267 }
1268 }
1269 else
1270 {
1271 block = new ListItem( indent, line );
1272 }
1273 break;
1274 case LEFT_SQUARE_BRACKET:
1275 if ( charAt( line, length, i + 1 ) == RIGHT_SQUARE_BRACKET )
1276 {
1277 block = new ListBreak( indent, line );
1278 }
1279 else
1280 {
1281 if ( indent == 0 )
1282 {
1283 block = new Figure( indent, line );
1284 }
1285 else
1286 {
1287 if ( charAt( line, length, i + 1 ) == LEFT_SQUARE_BRACKET )
1288 {
1289 int numbering;
1290
1291 switch ( charAt( line, length, i + 2 ) )
1292 {
1293 case NUMBERING_LOWER_ALPHA_CHAR:
1294 numbering = Sink.NUMBERING_LOWER_ALPHA;
1295 break;
1296 case NUMBERING_UPPER_ALPHA_CHAR:
1297 numbering = Sink.NUMBERING_UPPER_ALPHA;
1298 break;
1299 case NUMBERING_LOWER_ROMAN_CHAR:
1300 numbering = Sink.NUMBERING_LOWER_ROMAN;
1301 break;
1302 case NUMBERING_UPPER_ROMAN_CHAR:
1303 numbering = Sink.NUMBERING_UPPER_ROMAN;
1304 break;
1305 case NUMBERING:
1306 default:
1307 // The first item establishes the numbering
1308 // scheme for the whole list.
1309 numbering = Sink.NUMBERING_DECIMAL;
1310 }
1311
1312 block = new NumberedListItem( indent, line, numbering );
1313 }
1314 else
1315 {
1316 block = new DefinitionListItem( indent, line );
1317 }
1318 }
1319 }
1320 break;
1321 case MINUS:
1322 if ( charAt( line, length, i + 1 ) == MINUS && charAt( line, length, i + 2 ) == MINUS )
1323 {
1324 if ( indent == 0 )
1325 {
1326 block = new Verbatim( indent, line );
1327 }
1328 else
1329 {
1330 if ( firstBlock )
1331 {
1332 block = new Title( indent, line );
1333 }
1334 }
1335 }
1336 break;
1337 case PLUS:
1338 if ( indent == 0 && charAt( line, length, i + 1 ) == MINUS && charAt( line, length, i + 2 ) == MINUS )
1339 {
1340 block = new Verbatim( indent, line );
1341 }
1342 break;
1343 case EQUAL:
1344 if ( indent == 0 && charAt( line, length, i + 1 ) == EQUAL && charAt( line, length, i + 2 ) == EQUAL )
1345 {
1346 block = new HorizontalRule( indent, line );
1347 }
1348 break;
1349 case PAGE_BREAK:
1350 if ( indent == 0 )
1351 {
1352 block = new PageBreak( indent, line );
1353 }
1354 break;
1355 case PERCENT:
1356 if ( indent == 0 && charAt( line, length, i + 1 ) == LEFT_CURLY_BRACKET )
1357 {
1358 block = new MacroBlock( indent, line );
1359 }
1360 break;
1361 case COMMENT:
1362 if ( charAt( line, length, i + 1 ) == COMMENT )
1363 {
1364 block = new Comment( line.substring( i + 2 ).trim() );
1365 }
1366 break;
1367 default:
1368 break;
1369 }
1370
1371 if ( block == null )
1372 {
1373 if ( indent == 0 )
1374 {
1375 block = new Section1( indent, line );
1376 }
1377 else
1378 {
1379 block = new Paragraph( indent, line );
1380 }
1381 }
1382 }
1383
1384 /**
1385 * Checks that the current block is of the expected type.
1386 *
1387 * @param type the expected type.
1388 * @throws AptParseException if something goes wrong.
1389 */
1390 private void expectedBlock( int type )
1391 throws AptParseException
1392 {
1393 int blockType = block.getType();
1394
1395 if ( blockType != type )
1396 {
1397 throw new AptParseException( "expected " + TYPE_NAMES[type] + ", found " + TYPE_NAMES[blockType] );
1398 }
1399 }
1400
1401 // -----------------------------------------------------------------------
1402
1403 /**
1404 * Determine if c is an octal character.
1405 *
1406 * @param c the character.
1407 * @return boolean
1408 */
1409 private static boolean isOctalChar( char c )
1410 {
1411 return ( c >= '0' && c <= '7' );
1412 }
1413
1414 /**
1415 * Determine if c is an hex character.
1416 *
1417 * @param c the character.
1418 * @return boolean
1419 */
1420 private static boolean isHexChar( char c )
1421 {
1422 return ( ( c >= '0' && c <= '9' ) || ( c >= 'a' && c <= 'f' ) || ( c >= 'A' && c <= 'F' ) );
1423 }
1424
1425 /**
1426 * Emits the text so far parsed into the given sink.
1427 *
1428 * @param buffer A StringBuffer that contains the text to be flushed.
1429 * @param sink The sink to receive the text.
1430 */
1431 private static void flushTraversed( StringBuffer buffer, Sink sink )
1432 {
1433 if ( buffer.length() > 0 )
1434 {
1435 sink.text( buffer.toString() );
1436 buffer.setLength( 0 );
1437 }
1438 }
1439
1440 /**
1441 * Parse the given text.
1442 *
1443 * @param text the text to parse.
1444 * @param begin offset.
1445 * @param end offset.
1446 * @param linkAnchor a StringBuffer.
1447 * @return int
1448 * @throws AptParseException if something goes wrong.
1449 */
1450 private static int skipTraversedLinkAnchor( String text, int begin, int end, StringBuffer linkAnchor )
1451 throws AptParseException
1452 {
1453 int i;
1454 loop: for ( i = begin; i < end; ++i )
1455 {
1456 char c = text.charAt( i );
1457 switch ( c )
1458 {
1459 case RIGHT_CURLY_BRACKET:
1460 break loop;
1461 case BACKSLASH:
1462 if ( i + 1 < end )
1463 {
1464 ++i;
1465 linkAnchor.append( text.charAt( i ) );
1466 }
1467 else
1468 {
1469 linkAnchor.append( BACKSLASH );
1470 }
1471 break;
1472 default:
1473 linkAnchor.append( c );
1474 }
1475 }
1476 if ( i == end )
1477 {
1478 throw new AptParseException( "missing '" + RIGHT_CURLY_BRACKET + "'" );
1479 }
1480
1481 return i;
1482 }
1483
1484 /**
1485 * Parse the given text.
1486 *
1487 * @param text the text to parse.
1488 * @param begin offset.
1489 * @param end offset.
1490 * @return String
1491 * @throws AptParseException if something goes wrong.
1492 */
1493 private String getTraversedLink( String text, int begin, int end )
1494 throws AptParseException
1495 {
1496 char previous2 = LEFT_CURLY_BRACKET;
1497 char previous = LEFT_CURLY_BRACKET;
1498 int i;
1499
1500 for ( i = begin; i < end; ++i )
1501 {
1502 char c = text.charAt( i );
1503 if ( c == RIGHT_CURLY_BRACKET && previous == RIGHT_CURLY_BRACKET && previous2 != BACKSLASH )
1504 {
1505 break;
1506 }
1507
1508 previous2 = previous;
1509 previous = c;
1510 }
1511 if ( i == end )
1512 {
1513 throw new AptParseException( "missing '" + LEFT_CURLY_BRACKET + LEFT_CURLY_BRACKET + "'" );
1514 }
1515
1516 return doGetTraversedLink( text, begin, i - 1 );
1517 }
1518
1519 /**
1520 * Parse the given text.
1521 *
1522 * @param text the text to parse.
1523 * @param begin offset.
1524 * @param end offset.
1525 * @return String
1526 * @throws AptParseException if something goes wrong.
1527 */
1528 private String getTraversedAnchor( String text, int begin, int end )
1529 throws AptParseException
1530 {
1531 char previous = LEFT_CURLY_BRACKET;
1532 int i;
1533
1534 for ( i = begin; i < end; ++i )
1535 {
1536 char c = text.charAt( i );
1537 if ( c == RIGHT_CURLY_BRACKET && previous != BACKSLASH )
1538 {
1539 break;
1540 }
1541
1542 previous = c;
1543 }
1544 if ( i == end )
1545 {
1546 throw new AptParseException( "missing '" + RIGHT_CURLY_BRACKET + "'" );
1547 }
1548
1549 return doGetTraversedLink( text, begin, i );
1550 }
1551
1552 /**
1553 * Parse the given text.
1554 *
1555 * @param text the text to parse.
1556 * @param begin offset.
1557 * @param end offset.
1558 * @return String
1559 * @throws AptParseException if something goes wrong.
1560 */
1561 private String doGetTraversedLink( String text, int begin, int end )
1562 throws AptParseException
1563 {
1564 final StringBuffer buffer = new StringBuffer( end - begin );
1565
1566 Sink linkSink = new SinkAdapter()
1567 {
1568 /** {@inheritDoc} */
1569 public void lineBreak()
1570 {
1571 buffer.append( SPACE );
1572 }
1573
1574 /** {@inheritDoc} */
1575 public void nonBreakingSpace()
1576 {
1577 buffer.append( SPACE );
1578 }
1579
1580 /** {@inheritDoc} */
1581 public void text( String text )
1582 {
1583 buffer.append( text );
1584 }
1585 };
1586 doTraverseText( text, begin, end, linkSink );
1587
1588 return buffer.toString().trim();
1589 }
1590
1591 /**
1592 * If debug mode is enabled, log the <code>msg</code> as is, otherwise add unique msg in <code>warnMessages</code>.
1593 *
1594 * @param key not null
1595 * @param msg not null
1596 * @see #parse(Reader, Sink)
1597 * @since 1.1.1
1598 */
1599 private void logMessage( String key, String msg )
1600 {
1601 msg = "[APT Parser] " + msg;
1602 if ( getLog().isDebugEnabled() )
1603 {
1604 getLog().debug( msg );
1605
1606 return;
1607 }
1608
1609 if ( warnMessages == null )
1610 {
1611 warnMessages = new HashMap<String, Set<String>>();
1612 }
1613
1614 Set<String> set = warnMessages.get( key );
1615 if ( set == null )
1616 {
1617 set = new TreeSet<String>();
1618 }
1619 set.add( msg );
1620 warnMessages.put( key, set );
1621 }
1622
1623 /**
1624 * @since 1.1.2
1625 */
1626 private void logWarnings()
1627 {
1628 if ( getLog().isWarnEnabled() && this.warnMessages != null && !isSecondParsing() )
1629 {
1630 for ( Map.Entry<String, Set<String>> entry : this.warnMessages.entrySet() )
1631 {
1632 for ( String msg : entry.getValue() )
1633 {
1634 getLog().warn( msg );
1635 }
1636 }
1637
1638 this.warnMessages = null;
1639 }
1640 }
1641
1642 // -----------------------------------------------------------------------
1643
1644 /** A block of an apt source document. */
1645 private abstract class Block
1646 {
1647 /** type. */
1648 protected int type;
1649
1650 /** indent. */
1651 protected int indent;
1652
1653 /** text. */
1654 protected String text;
1655
1656 /** textLength. */
1657 protected int textLength;
1658
1659 /**
1660 * Constructor.
1661 *
1662 * @param type the block type.
1663 * @param indent indent.
1664 * @throws AptParseException AptParseException
1665 */
1666 public Block( int type, int indent )
1667 throws AptParseException
1668 {
1669 this( type, indent, null );
1670 }
1671
1672 /**
1673 * Constructor.
1674 *
1675 * @param type type.
1676 * @param indent indent.
1677 * @param firstLine the first line.
1678 * @throws AptParseException AptParseException
1679 */
1680 public Block( int type, int indent, String firstLine )
1681 throws AptParseException
1682 {
1683 this.type = type;
1684 this.indent = indent;
1685
1686 // Skip first line ---
1687 AptParser.this.nextLine();
1688
1689 if ( firstLine == null )
1690 {
1691 text = null;
1692 textLength = 0;
1693 }
1694 else
1695 {
1696 // Read block ---
1697 StringBuffer buffer = new StringBuffer( firstLine );
1698
1699 while ( AptParser.this.line != null )
1700 {
1701 String l = AptParser.this.line;
1702 int length = l.length();
1703 int i = 0;
1704
1705 i = skipSpace( l, length, i );
1706 if ( i == length )
1707 {
1708 // Stop after open line and skip it.
1709 AptParser.this.nextLine();
1710 break;
1711 }
1712 else if ( ( AptParser.charAt( l, length, i ) == COMMENT
1713 && AptParser.charAt( l, length, i + 1 ) == COMMENT )
1714 || type == COMMENT_BLOCK )
1715 {
1716 // parse comments as separate blocks line by line
1717 break;
1718 }
1719
1720 buffer.append( EOL );
1721 buffer.append( l );
1722
1723 AptParser.this.nextLine();
1724 }
1725
1726 text = buffer.toString();
1727 textLength = text.length();
1728 }
1729 }
1730
1731 /**
1732 * Return the block type.
1733 *
1734 * @return int
1735 */
1736 public final int getType()
1737 {
1738 return type;
1739 }
1740
1741 /**
1742 * Return the block indent.
1743 *
1744 * @return int
1745 */
1746 public final int getIndent()
1747 {
1748 return indent;
1749 }
1750
1751 /**
1752 * Parse the block.
1753 *
1754 * @throws AptParseException if something goes wrong.
1755 */
1756 public abstract void traverse()
1757 throws AptParseException;
1758
1759 /**
1760 * Traverse the text.
1761 *
1762 * @param begin offset.
1763 * @throws AptParseException if something goes wrong.
1764 */
1765 protected void traverseText( int begin )
1766 throws AptParseException
1767 {
1768 traverseText( begin, text.length() );
1769 }
1770
1771 /**
1772 * Traverse the text.
1773 *
1774 * @param begin offset.
1775 * @param end offset.
1776 * @throws AptParseException if something goes wrong.
1777 */
1778 protected void traverseText( int begin, int end )
1779 throws AptParseException
1780 {
1781 AptParser.this.doTraverseText( text, begin, end, AptParser.this.sink );
1782 }
1783
1784 /**
1785 * Skip spaces.
1786 *
1787 * @return int.
1788 */
1789 protected int skipLeadingBullets()
1790 {
1791 int i = skipSpaceFrom( 0 );
1792 for ( ; i < textLength; ++i )
1793 {
1794 if ( text.charAt( i ) != STAR )
1795 {
1796 break;
1797 }
1798 }
1799 return skipSpaceFrom( i );
1800 }
1801
1802 /**
1803 * Skip brackets.
1804 *
1805 * @param i offset.
1806 * @return int.
1807 * @throws AptParseException if something goes wrong.
1808 */
1809 protected int skipFromLeftToRightBracket( int i )
1810 throws AptParseException
1811 {
1812 char previous = LEFT_SQUARE_BRACKET;
1813 for ( ++i; i < textLength; ++i )
1814 {
1815 char c = text.charAt( i );
1816 if ( c == RIGHT_SQUARE_BRACKET && previous != BACKSLASH )
1817 {
1818 break;
1819 }
1820 previous = c;
1821 }
1822 if ( i == textLength )
1823 {
1824 throw new AptParseException( "missing '" + RIGHT_SQUARE_BRACKET + "'" );
1825 }
1826
1827 return i;
1828 }
1829
1830 /**
1831 * Skip spaces.
1832 *
1833 * @param i offset.
1834 * @return int.
1835 */
1836 protected final int skipSpaceFrom( int i )
1837 {
1838 return AptParser.skipSpace( text, textLength, i );
1839 }
1840 }
1841
1842 /** A ListBreak Block. */
1843 private class ListBreak
1844 extends AptParser.Block
1845 {
1846 /**
1847 * Constructor.
1848 *
1849 * @param indent indent.
1850 * @param firstLine the first line.
1851 * @throws AptParseException AptParseException
1852 */
1853 public ListBreak( int indent, String firstLine )
1854 throws AptParseException
1855 {
1856 super( AptParser.LIST_BREAK, indent, firstLine );
1857 }
1858
1859 /** {@inheritDoc} */
1860 public void traverse()
1861 throws AptParseException
1862 {
1863 throw new AptParseException( "internal error: traversing list break" );
1864 }
1865 }
1866
1867 /** A Title Block. */
1868 private class Title
1869 extends Block
1870 {
1871 /**
1872 * Constructor.
1873 *
1874 * @param indent indent.
1875 * @param firstLine the first line.
1876 * @throws AptParseException AptParseException
1877 */
1878 public Title( int indent, String firstLine )
1879 throws AptParseException
1880 {
1881 super( TITLE, indent, firstLine );
1882 }
1883
1884 /** {@inheritDoc} */
1885 public void traverse()
1886 throws AptParseException
1887 {
1888 StringTokenizer lines = new StringTokenizer( text, EOL );
1889 int separator = -1;
1890 boolean firstLine = true;
1891 boolean title = false;
1892 boolean author = false;
1893 boolean date = false;
1894
1895 loop: while ( lines.hasMoreTokens() )
1896 {
1897 String line = lines.nextToken().trim();
1898 int lineLength = line.length();
1899
1900 if ( AptParser.charAt( line, lineLength, 0 ) == MINUS
1901 && AptParser.charAt( line, lineLength, 1 ) == MINUS
1902 && AptParser.charAt( line, lineLength, 2 ) == MINUS )
1903 {
1904 switch ( separator )
1905 {
1906 case 0:
1907 if ( title )
1908 {
1909 AptParser.this.sink.title_();
1910 }
1911 else
1912 {
1913 throw new AptParseException( "missing title" );
1914 }
1915 break;
1916 case 1:
1917 if ( author )
1918 {
1919 AptParser.this.sink.author_();
1920 }
1921 break;
1922 case 2:
1923 // Note that an extra decorative line is allowed
1924 // at the end of the author.
1925 break loop;
1926 default:
1927 break;
1928 }
1929
1930 ++separator;
1931 firstLine = true;
1932 }
1933 else
1934 {
1935 if ( firstLine )
1936 {
1937 firstLine = false;
1938 switch ( separator )
1939 {
1940 case 0:
1941 title = true;
1942 AptParser.this.sink.title();
1943 break;
1944 case 1:
1945 author = true;
1946 AptParser.this.sink.author();
1947 break;
1948 case 2:
1949 date = true;
1950 AptParser.this.sink.date();
1951 break;
1952 default:
1953 break;
1954 }
1955 }
1956 else
1957 {
1958 // An implicit lineBreak separates title lines.
1959 AptParser.this.sink.lineBreak();
1960 }
1961
1962 AptParser.this.doTraverseText( line, 0, lineLength, AptParser.this.sink );
1963 }
1964 }
1965
1966 switch ( separator )
1967 {
1968 case 0:
1969 if ( title )
1970 {
1971 AptParser.this.sink.title_();
1972 }
1973 else
1974 {
1975 throw new AptParseException( "missing title" );
1976 }
1977 break;
1978 case 1:
1979 if ( author )
1980 {
1981 AptParser.this.sink.author_();
1982 }
1983 break;
1984 case 2:
1985 if ( date )
1986 {
1987 AptParser.this.sink.date_();
1988 }
1989 break;
1990 default:
1991 break;
1992 }
1993 }
1994 }
1995
1996 /** A Section Block. */
1997 private abstract class Section
1998 extends Block
1999 {
2000 /**
2001 * Constructor.
2002 *
2003 * @param type type.
2004 * @param indent indent.
2005 * @param firstLine the first line.
2006 * @throws AptParseException AptParseException
2007 */
2008 public Section( int type, int indent, String firstLine )
2009 throws AptParseException
2010 {
2011 super( type, indent, firstLine );
2012 }
2013
2014 /** {@inheritDoc} */
2015 public void traverse()
2016 throws AptParseException
2017 {
2018 Title();
2019 traverseText( skipLeadingBullets() );
2020 Title_();
2021 }
2022
2023 /** Start a title. */
2024 public abstract void Title();
2025
2026 /** End a title. */
2027 public abstract void Title_();
2028 }
2029
2030 /** A Section1 Block. */
2031 private class Section1
2032 extends Section
2033 {
2034 /**
2035 * Constructor.
2036 *
2037 * @param indent indent.
2038 * @param firstLine the first line.
2039 * @throws AptParseException AptParseException
2040 */
2041 public Section1( int indent, String firstLine )
2042 throws AptParseException
2043 {
2044 super( SECTION1, indent, firstLine );
2045 }
2046
2047 /** {@inheritDoc} */
2048 public void Title()
2049 {
2050 AptParser.this.sink.sectionTitle1();
2051 }
2052
2053 /** {@inheritDoc} */
2054 public void Title_()
2055 {
2056 AptParser.this.sink.sectionTitle1_();
2057 }
2058 }
2059
2060 /** A Section2 Block. */
2061 private class Section2
2062 extends Section
2063 {
2064 /**
2065 * Constructor.
2066 *
2067 * @param indent indent.
2068 * @param firstLine the first line.
2069 * @throws AptParseException AptParseException
2070 */
2071 public Section2( int indent, String firstLine )
2072 throws AptParseException
2073 {
2074 super( SECTION2, indent, firstLine );
2075 }
2076
2077 /** {@inheritDoc} */
2078 public void Title()
2079 {
2080 AptParser.this.sink.sectionTitle2();
2081 }
2082
2083 /** {@inheritDoc} */
2084 public void Title_()
2085 {
2086 AptParser.this.sink.sectionTitle2_();
2087 }
2088 }
2089
2090 /** A Section3 Block. */
2091 private class Section3
2092 extends Section
2093 {
2094 /**
2095 * Constructor.
2096 *
2097 * @param indent indent.
2098 * @param firstLine the first line.
2099 * @throws AptParseException AptParseException
2100 */
2101 public Section3( int indent, String firstLine )
2102 throws AptParseException
2103 {
2104 super( SECTION3, indent, firstLine );
2105 }
2106
2107 /** {@inheritDoc} */
2108 public void Title()
2109 {
2110 AptParser.this.sink.sectionTitle3();
2111 }
2112
2113 /** {@inheritDoc} */
2114 public void Title_()
2115 {
2116 AptParser.this.sink.sectionTitle3_();
2117 }
2118 }
2119
2120 /** A Section4 Block. */
2121 private class Section4
2122 extends Section
2123 {
2124 /**
2125 * Constructor.
2126 *
2127 * @param indent indent.
2128 * @param firstLine the first line.
2129 * @throws AptParseException AptParseException
2130 */
2131 public Section4( int indent, String firstLine )
2132 throws AptParseException
2133 {
2134 super( SECTION4, indent, firstLine );
2135 }
2136
2137 /** {@inheritDoc} */
2138 public void Title()
2139 {
2140 AptParser.this.sink.sectionTitle4();
2141 }
2142
2143 /** {@inheritDoc} */
2144 public void Title_()
2145 {
2146 AptParser.this.sink.sectionTitle4_();
2147 }
2148 }
2149
2150 /** A Section5 Block. */
2151 private class Section5
2152 extends Section
2153 {
2154 /**
2155 * Constructor.
2156 *
2157 * @param indent indent.
2158 * @param firstLine the first line.
2159 * @throws AptParseException AptParseException
2160 */
2161 public Section5( int indent, String firstLine )
2162 throws AptParseException
2163 {
2164 super( SECTION5, indent, firstLine );
2165 }
2166
2167 /** {@inheritDoc} */
2168 public void Title()
2169 {
2170 AptParser.this.sink.sectionTitle5();
2171 }
2172
2173 /** {@inheritDoc} */
2174 public void Title_()
2175 {
2176 AptParser.this.sink.sectionTitle5_();
2177 }
2178 }
2179
2180 /** A Paragraph Block. */
2181 private class Paragraph
2182 extends Block
2183 {
2184 /**
2185 * Constructor.
2186 *
2187 * @param indent indent.
2188 * @param firstLine the first line.
2189 * @throws AptParseException AptParseException
2190 */
2191 public Paragraph( int indent, String firstLine )
2192 throws AptParseException
2193 {
2194 super( PARAGRAPH, indent, firstLine );
2195 }
2196
2197 /** {@inheritDoc} */
2198 public void traverse()
2199 throws AptParseException
2200 {
2201 AptParser.this.sink.paragraph();
2202 traverseText( skipSpaceFrom( 0 ) );
2203 AptParser.this.sink.paragraph_();
2204 }
2205 }
2206
2207 /** A Comment Block. */
2208 private class Comment
2209 extends Block
2210 {
2211 /**
2212 * Constructor.
2213 *
2214 * @param line the comment line.
2215 * @throws AptParseException AptParseException
2216 */
2217 public Comment( String line )
2218 throws AptParseException
2219 {
2220 super( COMMENT_BLOCK, 0, line );
2221 }
2222
2223 /** {@inheritDoc} */
2224 public void traverse()
2225 throws AptParseException
2226 {
2227 AptParser.this.sink.comment( text );
2228 }
2229 }
2230
2231 /** A Verbatim Block. */
2232 private class Verbatim
2233 extends Block
2234 {
2235 /** boxed. */
2236 private boolean boxed;
2237
2238 /**
2239 * Constructor.
2240 *
2241 * @param indent indent.
2242 * @param firstLine the first line.
2243 * @throws AptParseException AptParseException
2244 */
2245 public Verbatim( int indent, String firstLine )
2246 throws AptParseException
2247 {
2248 super( VERBATIM, indent, null );
2249
2250 // Read block (first line already skipped) ---
2251
2252 StringBuffer buffer = new StringBuffer();
2253 char firstChar = firstLine.charAt( 0 );
2254 boxed = ( firstChar == PLUS );
2255
2256 while ( AptParser.this.line != null )
2257 {
2258 String l = AptParser.this.line;
2259 int length = l.length();
2260
2261 if ( AptParser.charAt( l, length, 0 ) == firstChar && AptParser.charAt( l, length, 1 ) == MINUS
2262 && AptParser.charAt( l, length, 2 ) == MINUS )
2263 {
2264 AptParser.this.nextLine();
2265
2266 break;
2267 }
2268
2269 // Expand tabs ---
2270
2271 int prevColumn, column;
2272
2273 column = 0;
2274
2275 for ( int i = 0; i < length; ++i )
2276 {
2277 char c = l.charAt( i );
2278
2279 if ( c == TAB )
2280 {
2281 prevColumn = column;
2282
2283 column = ( ( column + 1 + TAB_WIDTH - 1 ) / TAB_WIDTH ) * TAB_WIDTH;
2284
2285 buffer.append( SPACES, 0, column - prevColumn );
2286 }
2287 else
2288 {
2289 ++column;
2290 buffer.append( c );
2291 }
2292 }
2293 buffer.append( EOL );
2294
2295 AptParser.this.nextLine();
2296 }
2297
2298 // The last '\n' is mandatory before the "---" delimeter but is
2299 // not part of the verbatim text.
2300 textLength = buffer.length();
2301
2302 if ( textLength > 0 )
2303 {
2304 --textLength;
2305
2306 buffer.setLength( textLength );
2307 }
2308
2309 text = buffer.toString();
2310 }
2311
2312 /** {@inheritDoc} */
2313 public void traverse()
2314 throws AptParseException
2315 {
2316 AptParser.this.sink.verbatim( boxed ? SinkEventAttributeSet.BOXED : null );
2317 AptParser.this.sink.text( text );
2318 AptParser.this.sink.verbatim_();
2319 }
2320 }
2321
2322 /** A Figure Block. */
2323 private class Figure
2324 extends Block
2325 {
2326 /**
2327 * Constructor.
2328 *
2329 * @param indent indent.
2330 * @param firstLine the first line.
2331 * @throws AptParseException AptParseException
2332 */
2333 public Figure( int indent, String firstLine )
2334 throws AptParseException
2335 {
2336 super( FIGURE, indent, firstLine );
2337 }
2338
2339 /** {@inheritDoc} */
2340 public void traverse()
2341 throws AptParseException
2342 {
2343 AptParser.this.sink.figure();
2344
2345 int i = skipFromLeftToRightBracket( 0 );
2346 AptParser.this.sink.figureGraphics( text.substring( 1, i ) );
2347
2348 i = skipSpaceFrom( i + 1 );
2349 if ( i < textLength )
2350 {
2351 AptParser.this.sink.figureCaption();
2352 traverseText( i );
2353 AptParser.this.sink.figureCaption_();
2354 }
2355
2356 AptParser.this.sink.figure_();
2357 }
2358 }
2359
2360 /** A Table Block. */
2361 private class Table
2362 extends Block
2363 {
2364 /**
2365 * Constructor.
2366 *
2367 * @param indent indent.
2368 * @param firstLine the first line.
2369 * @throws AptParseException AptParseException
2370 */
2371 public Table( int indent, String firstLine )
2372 throws AptParseException
2373 {
2374 super( TABLE, indent, firstLine );
2375 }
2376
2377 /** {@inheritDoc} */
2378 public void traverse()
2379 throws AptParseException
2380 {
2381 int captionIndex = -1;
2382 int nextLineIndex = 0;
2383 int init = 2;
2384 int[] justification = null;
2385 int rows = 0;
2386 int columns = 0;
2387 StringBuffer[] cells = null;
2388 boolean[] headers = null;
2389 boolean grid;
2390
2391 AptParser.this.sink.table();
2392
2393 while ( nextLineIndex < textLength )
2394 {
2395 int i = text.indexOf( "*--", nextLineIndex );
2396 if ( i < 0 )
2397 {
2398 captionIndex = nextLineIndex;
2399 break;
2400 }
2401
2402 String line;
2403 i = text.indexOf( '\n', nextLineIndex );
2404 if ( i < 0 )
2405 {
2406 line = text.substring( nextLineIndex );
2407 nextLineIndex = textLength;
2408 }
2409 else
2410 {
2411 line = text.substring( nextLineIndex, i );
2412 nextLineIndex = i + 1;
2413 }
2414 int lineLength = line.length();
2415
2416 if ( line.indexOf( "*--" ) == 0 )
2417 {
2418 if ( init == 2 )
2419 {
2420 init = 1;
2421 justification = parseJustification( line, lineLength );
2422 columns = justification.length;
2423 cells = new StringBuffer[columns];
2424 headers = new boolean[columns];
2425 for ( i = 0; i < columns; ++i )
2426 {
2427 cells[i] = new StringBuffer();
2428 headers[i] = false;
2429 }
2430 }
2431 else
2432 {
2433 if ( traverseRow( cells, headers, justification ) )
2434 {
2435 ++rows;
2436 }
2437 justification = parseJustification( line, lineLength );
2438 }
2439 }
2440 else
2441 {
2442 if ( init == 1 )
2443 {
2444 init = 0;
2445 grid = ( AptParser.charAt( line, lineLength, 0 ) == PIPE );
2446 AptParser.this.sink.tableRows( justification, grid );
2447 }
2448
2449 line = replaceAll( line, "\\|", "\\u007C" );
2450
2451 StringTokenizer cellLines = new StringTokenizer( line, "|", true );
2452
2453 i = 0;
2454 boolean processedGrid = false;
2455 while ( cellLines.hasMoreTokens() )
2456 {
2457 String cellLine = cellLines.nextToken();
2458 if ( "|".equals( cellLine ) )
2459 {
2460 if ( processedGrid )
2461 {
2462 headers[i] = true;
2463 }
2464 else
2465 {
2466 processedGrid = true;
2467 headers[i] = false;
2468 }
2469 continue;
2470 }
2471 processedGrid = false;
2472 cellLine = replaceAll( cellLine, "\\", "\\u00A0" ); // linebreak
2473 // Escaped special characters: \~, \=, \-, \+, \*, \[, \], \<, \>, \{, \}, \\.
2474 cellLine = replaceAll( cellLine, "\\u00A0~", "\\~" );
2475 cellLine = replaceAll( cellLine, "\\u00A0=", "\\=" );
2476 cellLine = replaceAll( cellLine, "\\u00A0-", "\\-" );
2477 cellLine = replaceAll( cellLine, "\\u00A0+", "\\+" );
2478 cellLine = replaceAll( cellLine, "\\u00A0*", "\\*" );
2479 cellLine = replaceAll( cellLine, "\\u00A0[", "\\[" );
2480 cellLine = replaceAll( cellLine, "\\u00A0]", "\\]" );
2481 cellLine = replaceAll( cellLine, "\\u00A0<", "\\<" );
2482 cellLine = replaceAll( cellLine, "\\u00A0>", "\\>" );
2483 cellLine = replaceAll( cellLine, "\\u00A0{", "\\{" );
2484 cellLine = replaceAll( cellLine, "\\u00A0}", "\\}" );
2485 cellLine = replaceAll( cellLine, "\\u00A0\\u00A0", "\\\\" );
2486 cellLine = cellLine.trim();
2487
2488 StringBuffer cell = cells[i];
2489 if ( cellLine.length() > 0 )
2490 {
2491 // line break in table cells
2492 if ( cell.toString().trim().endsWith( "\\u00A0" ) )
2493 {
2494 cell.append( "\\\n" );
2495 }
2496 else
2497 {
2498 if ( cell.length() != 0 )
2499 {
2500 // Always add a space for multi line tables cells
2501 cell.append( " " );
2502 }
2503 }
2504
2505 cell.append( cellLine );
2506 }
2507
2508 ++i;
2509 if ( i == columns )
2510 {
2511 break;
2512 }
2513 }
2514 }
2515 }
2516 if ( rows == 0 )
2517 {
2518 throw new AptParseException( "no table rows" );
2519 }
2520 AptParser.this.sink.tableRows_();
2521
2522 if ( captionIndex >= 0 )
2523 {
2524 AptParser.this.sink.tableCaption();
2525 AptParser.this.doTraverseText( text, captionIndex, textLength, AptParser.this.sink );
2526 AptParser.this.sink.tableCaption_();
2527 }
2528
2529 AptParser.this.sink.table_();
2530 }
2531
2532 /**
2533 * Parse a table justification line.
2534 *
2535 * @param jline the justification line.
2536 * @param lineLength the length of the line. Must be > 2.
2537 * @return int[]
2538 * @throws AptParseException if something goes wrong.
2539 */
2540 private int[] parseJustification( String jline, int lineLength )
2541 throws AptParseException
2542 {
2543 int columns = 0;
2544
2545 for ( int i = 2 /*Skip '*--'*/; i < lineLength; ++i )
2546 {
2547 switch ( jline.charAt( i ) )
2548 {
2549 case STAR:
2550 case PLUS:
2551 case COLON:
2552 ++columns;
2553 break;
2554 default:
2555 break;
2556 }
2557 }
2558
2559 if ( columns == 0 )
2560 {
2561 throw new AptParseException( "no columns specified" );
2562 }
2563
2564 int[] justification = new int[columns];
2565 columns = 0;
2566 for ( int i = 2; i < lineLength; ++i )
2567 {
2568 switch ( jline.charAt( i ) )
2569 {
2570 case STAR:
2571 justification[columns++] = Sink.JUSTIFY_CENTER;
2572 break;
2573 case PLUS:
2574 justification[columns++] = Sink.JUSTIFY_LEFT;
2575 break;
2576 case COLON:
2577 justification[columns++] = Sink.JUSTIFY_RIGHT;
2578 break;
2579 default:
2580 break;
2581 }
2582 }
2583
2584 return justification;
2585 }
2586
2587 /**
2588 * Traverse a table row.
2589 *
2590 * @param cells The table cells.
2591 * @param headers true for header cells.
2592 * @param justification the justification for each cell.
2593 * @return boolean
2594 * @throws AptParseException if something goes wrong.
2595 */
2596 private boolean traverseRow( StringBuffer[] cells, boolean[] headers, int[] justification )
2597 throws AptParseException
2598 {
2599 // Skip empty row (a decorative line).
2600 boolean traversed = false;
2601 for ( int i = 0; i < cells.length; ++i )
2602 {
2603 if ( cells[i].length() > 0 )
2604 {
2605 traversed = true;
2606 break;
2607 }
2608 }
2609
2610 if ( traversed )
2611 {
2612 AptParser.this.sink.tableRow();
2613 for ( int i = 0; i < cells.length; ++i )
2614 {
2615 StringBuffer cell = cells[i];
2616
2617 SinkEventAttributes justif;
2618 switch ( justification[i] )
2619 {
2620 case Sink.JUSTIFY_CENTER:
2621 justif = SinkEventAttributeSet.CENTER;
2622 break;
2623 case Sink.JUSTIFY_LEFT:
2624 justif = SinkEventAttributeSet.LEFT;
2625 break;
2626 case Sink.JUSTIFY_RIGHT:
2627 justif = SinkEventAttributeSet.RIGHT;
2628 break;
2629 default:
2630 justif = SinkEventAttributeSet.LEFT;
2631 break;
2632 }
2633 SinkEventAttributeSet event = new SinkEventAttributeSet();
2634 event.addAttributes( justif );
2635
2636 if ( headers[i] )
2637 {
2638 AptParser.this.sink.tableHeaderCell( event );
2639 }
2640 else
2641 {
2642 AptParser.this.sink.tableCell( event );
2643 }
2644 if ( cell.length() > 0 )
2645 {
2646 AptParser.this.doTraverseText( cell.toString(), 0, cell.length(), AptParser.this.sink );
2647 cell.setLength( 0 );
2648 }
2649 if ( headers[i] )
2650 {
2651 AptParser.this.sink.tableHeaderCell_();
2652 }
2653 else
2654 {
2655 AptParser.this.sink.tableCell_();
2656 }
2657 }
2658 AptParser.this.sink.tableRow_();
2659 }
2660
2661 return traversed;
2662 }
2663 }
2664
2665 /** A ListItem Block. */
2666 private class ListItem
2667 extends Block
2668 {
2669 /**
2670 * Constructor.
2671 *
2672 * @param indent indent.
2673 * @param firstLine the first line.
2674 * @throws AptParseException AptParseException
2675 */
2676 public ListItem( int indent, String firstLine )
2677 throws AptParseException
2678 {
2679 super( LIST_ITEM, indent, firstLine );
2680 }
2681
2682 /** {@inheritDoc} */
2683 public void traverse()
2684 throws AptParseException
2685 {
2686 traverseText( skipLeadingBullets() );
2687 }
2688 }
2689
2690 /** A NumberedListItem Block. */
2691 private class NumberedListItem
2692 extends Block
2693 {
2694 /** numbering. */
2695 private int numbering;
2696
2697 /**
2698 * Constructor.
2699 *
2700 * @param indent indent.
2701 * @param firstLine the first line.
2702 * @param number numbering.
2703 * @throws AptParseException AptParseException
2704 */
2705 public NumberedListItem( int indent, String firstLine, int number )
2706 throws AptParseException
2707 {
2708 super( NUMBERED_LIST_ITEM, indent, firstLine );
2709 this.numbering = number;
2710 }
2711
2712 /**
2713 * getNumbering.
2714 *
2715 * @return int
2716 */
2717 public int getNumbering()
2718 {
2719 return numbering;
2720 }
2721
2722 /** {@inheritDoc} */
2723 public void traverse()
2724 throws AptParseException
2725 {
2726 traverseText( skipItemNumber() );
2727 }
2728
2729 /**
2730 * skipItemNumber.
2731 *
2732 * @return int
2733 * @throws AptParseException AptParseException
2734 */
2735 private int skipItemNumber()
2736 throws AptParseException
2737 {
2738 int i = skipSpaceFrom( 0 );
2739
2740 char prevChar = SPACE;
2741 for ( ; i < textLength; ++i )
2742 {
2743 char c = text.charAt( i );
2744 if ( c == RIGHT_SQUARE_BRACKET && prevChar == RIGHT_SQUARE_BRACKET )
2745 {
2746 break;
2747 }
2748 prevChar = c;
2749 }
2750
2751 if ( i == textLength )
2752 {
2753 throw new AptParseException( "missing '" + RIGHT_SQUARE_BRACKET + RIGHT_SQUARE_BRACKET + "'" );
2754 }
2755
2756 return skipSpaceFrom( i + 1 );
2757 }
2758 }
2759
2760 /** A DefinitionListItem Block. */
2761 private class DefinitionListItem
2762 extends Block
2763 {
2764 /**
2765 * Constructor.
2766 *
2767 * @param indent indent.
2768 * @param firstLine the first line.
2769 * @throws AptParseException AptParseException
2770 */
2771 public DefinitionListItem( int indent, String firstLine )
2772 throws AptParseException
2773 {
2774 super( DEFINITION_LIST_ITEM, indent, firstLine );
2775 }
2776
2777 /** {@inheritDoc} */
2778 public void traverse()
2779 throws AptParseException
2780 {
2781 int i = skipSpaceFrom( 0 );
2782 int j = skipFromLeftToRightBracket( i );
2783
2784 AptParser.this.sink.definedTerm();
2785 traverseText( i + 1, j );
2786 AptParser.this.sink.definedTerm_();
2787
2788 j = skipSpaceFrom( j + 1 );
2789 if ( j == textLength )
2790 {
2791 // TODO: this doesn't handle the case of a dd in a paragraph
2792 //throw new AptParseException( "no definition" );
2793 }
2794
2795 AptParser.this.sink.definition();
2796 traverseText( j );
2797 }
2798 }
2799
2800 /** A HorizontalRule Block. */
2801 private class HorizontalRule
2802 extends Block
2803 {
2804 /**
2805 * Constructor.
2806 *
2807 * @param indent indent.
2808 * @param firstLine the first line.
2809 * @throws AptParseException AptParseException
2810 */
2811 public HorizontalRule( int indent, String firstLine )
2812 throws AptParseException
2813 {
2814 super( HORIZONTAL_RULE, indent, firstLine );
2815 }
2816
2817 /** {@inheritDoc} */
2818 public void traverse()
2819 throws AptParseException
2820 {
2821 AptParser.this.sink.horizontalRule();
2822 }
2823 }
2824
2825 /** A PageBreak Block. */
2826 private class PageBreak
2827 extends Block
2828 {
2829 /**
2830 * Constructor.
2831 *
2832 * @param indent indent.
2833 * @param firstLine the first line.
2834 * @throws AptParseException AptParseException
2835 */
2836 public PageBreak( int indent, String firstLine )
2837 throws AptParseException
2838 {
2839 super( PG_BREAK, indent, firstLine );
2840 }
2841
2842 /** {@inheritDoc} */
2843 public void traverse()
2844 throws AptParseException
2845 {
2846 AptParser.this.sink.pageBreak();
2847 }
2848 }
2849
2850 /** A MacroBlock Block. */
2851 private class MacroBlock
2852 extends Block
2853 {
2854 /**
2855 * Constructor.
2856 *
2857 * @param indent indent.
2858 * @param firstLine the first line.
2859 * @throws AptParseException AptParseException
2860 */
2861 public MacroBlock( int indent, String firstLine )
2862 throws AptParseException
2863 {
2864 super( MACRO, indent );
2865
2866 text = firstLine;
2867 }
2868
2869 /** {@inheritDoc} */
2870 public void traverse()
2871 throws AptParseException
2872 {
2873 if ( isSecondParsing() )
2874 {
2875 return;
2876 }
2877
2878 final int start = text.indexOf( '{' );
2879 final int end = text.indexOf( '}' );
2880
2881 String s = text.substring( start + 1, end );
2882
2883 s = escapeForMacro( s );
2884
2885 String[] params = StringUtils.split( s, "|" );
2886
2887 String macroId = params[0];
2888
2889 Map<String, Object> parameters = new HashMap<String, Object>();
2890
2891 for ( int i = 1; i < params.length; i++ )
2892 {
2893 String[] param = StringUtils.split( params[i], "=" );
2894
2895 if ( param.length == 1 )
2896 {
2897 throw new AptParseException( "Missing 'key=value' pair for macro parameter: " + params[i] );
2898 }
2899
2900 String key = unescapeForMacro( param[0] );
2901 String value = unescapeForMacro( param[1] );
2902
2903 parameters.put( key, value );
2904 }
2905
2906 parameters.put( "sourceContent", sourceContent );
2907
2908 AptParser aptParser = new AptParser();
2909 aptParser.setSecondParsing( true );
2910 aptParser.enableLogging( getLog() );
2911 parameters.put( "parser", aptParser );
2912
2913 // getBasedir() does not work in multi-module builds, see DOXIA-373
2914 // the basedir should be injected from here, see DOXIA-224
2915 MacroRequest request = new MacroRequest( parameters, getBasedir() );
2916 try
2917 {
2918 AptParser.this.executeMacro( macroId, request, sink );
2919 }
2920 catch ( MacroExecutionException e )
2921 {
2922 throw new AptParseException( "Unable to execute macro in the APT document", e );
2923 }
2924 catch ( MacroNotFoundException e )
2925 {
2926 throw new AptParseException( "Unable to find macro used in the APT document", e );
2927 }
2928 }
2929
2930 /**
2931 * escapeForMacro
2932 *
2933 * @param s String
2934 * @return String
2935 */
2936 private String escapeForMacro( String s )
2937 {
2938 if ( s == null || s.length() < 1 )
2939 {
2940 return s;
2941 }
2942
2943 String result = s;
2944
2945 // use some outrageously out-of-place chars for text
2946 // (these are device control one/two in unicode)
2947 result = StringUtils.replace( result, "\\=", "\u0011" );
2948 result = StringUtils.replace( result, "\\|", "\u0012" );
2949
2950 return result;
2951 }
2952
2953 /**
2954 * unescapeForMacro
2955 *
2956 * @param s String
2957 * @return String
2958 */
2959 private String unescapeForMacro( String s )
2960 {
2961 if ( s == null || s.length() < 1 )
2962 {
2963 return s;
2964 }
2965
2966 String result = s;
2967
2968 result = StringUtils.replace( result, "\u0011", "=" );
2969 result = StringUtils.replace( result, "\u0012", "|" );
2970
2971 return result;
2972 }
2973 }
2974 }