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 1137879 2011-06-21 06:48:28Z 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, "\\u00A0u", "\\u" );
2486 cellLine = replaceAll( cellLine, "\\u00A0\\u00A0", "\\\\" );
2487 cellLine = cellLine.trim();
2488
2489 StringBuffer cell = cells[i];
2490 if ( cellLine.length() > 0 )
2491 {
2492 // line break in table cells
2493 if ( cell.toString().trim().endsWith( "\\u00A0" ) )
2494 {
2495 cell.append( "\\\n" );
2496 }
2497 else
2498 {
2499 if ( cell.length() != 0 )
2500 {
2501 // Always add a space for multi line tables cells
2502 cell.append( " " );
2503 }
2504 }
2505
2506 cell.append( cellLine );
2507 }
2508
2509 ++i;
2510 if ( i == columns )
2511 {
2512 break;
2513 }
2514 }
2515 }
2516 }
2517 if ( rows == 0 )
2518 {
2519 throw new AptParseException( "no table rows" );
2520 }
2521 AptParser.this.sink.tableRows_();
2522
2523 if ( captionIndex >= 0 )
2524 {
2525 AptParser.this.sink.tableCaption();
2526 AptParser.this.doTraverseText( text, captionIndex, textLength, AptParser.this.sink );
2527 AptParser.this.sink.tableCaption_();
2528 }
2529
2530 AptParser.this.sink.table_();
2531 }
2532
2533 /**
2534 * Parse a table justification line.
2535 *
2536 * @param jline the justification line.
2537 * @param lineLength the length of the line. Must be > 2.
2538 * @return int[]
2539 * @throws AptParseException if something goes wrong.
2540 */
2541 private int[] parseJustification( String jline, int lineLength )
2542 throws AptParseException
2543 {
2544 int columns = 0;
2545
2546 for ( int i = 2 /*Skip '*--'*/; i < lineLength; ++i )
2547 {
2548 switch ( jline.charAt( i ) )
2549 {
2550 case STAR:
2551 case PLUS:
2552 case COLON:
2553 ++columns;
2554 break;
2555 default:
2556 break;
2557 }
2558 }
2559
2560 if ( columns == 0 )
2561 {
2562 throw new AptParseException( "no columns specified" );
2563 }
2564
2565 int[] justification = new int[columns];
2566 columns = 0;
2567 for ( int i = 2; i < lineLength; ++i )
2568 {
2569 switch ( jline.charAt( i ) )
2570 {
2571 case STAR:
2572 justification[columns++] = Sink.JUSTIFY_CENTER;
2573 break;
2574 case PLUS:
2575 justification[columns++] = Sink.JUSTIFY_LEFT;
2576 break;
2577 case COLON:
2578 justification[columns++] = Sink.JUSTIFY_RIGHT;
2579 break;
2580 default:
2581 break;
2582 }
2583 }
2584
2585 return justification;
2586 }
2587
2588 /**
2589 * Traverse a table row.
2590 *
2591 * @param cells The table cells.
2592 * @param headers true for header cells.
2593 * @param justification the justification for each cell.
2594 * @return boolean
2595 * @throws AptParseException if something goes wrong.
2596 */
2597 private boolean traverseRow( StringBuffer[] cells, boolean[] headers, int[] justification )
2598 throws AptParseException
2599 {
2600 // Skip empty row (a decorative line).
2601 boolean traversed = false;
2602 for ( int i = 0; i < cells.length; ++i )
2603 {
2604 if ( cells[i].length() > 0 )
2605 {
2606 traversed = true;
2607 break;
2608 }
2609 }
2610
2611 if ( traversed )
2612 {
2613 AptParser.this.sink.tableRow();
2614 for ( int i = 0; i < cells.length; ++i )
2615 {
2616 StringBuffer cell = cells[i];
2617
2618 SinkEventAttributes justif;
2619 switch ( justification[i] )
2620 {
2621 case Sink.JUSTIFY_CENTER:
2622 justif = SinkEventAttributeSet.CENTER;
2623 break;
2624 case Sink.JUSTIFY_LEFT:
2625 justif = SinkEventAttributeSet.LEFT;
2626 break;
2627 case Sink.JUSTIFY_RIGHT:
2628 justif = SinkEventAttributeSet.RIGHT;
2629 break;
2630 default:
2631 justif = SinkEventAttributeSet.LEFT;
2632 break;
2633 }
2634 SinkEventAttributeSet event = new SinkEventAttributeSet();
2635 event.addAttributes( justif );
2636
2637 if ( headers[i] )
2638 {
2639 AptParser.this.sink.tableHeaderCell( event );
2640 }
2641 else
2642 {
2643 AptParser.this.sink.tableCell( event );
2644 }
2645 if ( cell.length() > 0 )
2646 {
2647 AptParser.this.doTraverseText( cell.toString(), 0, cell.length(), AptParser.this.sink );
2648 cell.setLength( 0 );
2649 }
2650 if ( headers[i] )
2651 {
2652 AptParser.this.sink.tableHeaderCell_();
2653 }
2654 else
2655 {
2656 AptParser.this.sink.tableCell_();
2657 }
2658 }
2659 AptParser.this.sink.tableRow_();
2660 }
2661
2662 return traversed;
2663 }
2664 }
2665
2666 /** A ListItem Block. */
2667 private class ListItem
2668 extends Block
2669 {
2670 /**
2671 * Constructor.
2672 *
2673 * @param indent indent.
2674 * @param firstLine the first line.
2675 * @throws AptParseException AptParseException
2676 */
2677 public ListItem( int indent, String firstLine )
2678 throws AptParseException
2679 {
2680 super( LIST_ITEM, indent, firstLine );
2681 }
2682
2683 /** {@inheritDoc} */
2684 public void traverse()
2685 throws AptParseException
2686 {
2687 traverseText( skipLeadingBullets() );
2688 }
2689 }
2690
2691 /** A NumberedListItem Block. */
2692 private class NumberedListItem
2693 extends Block
2694 {
2695 /** numbering. */
2696 private int numbering;
2697
2698 /**
2699 * Constructor.
2700 *
2701 * @param indent indent.
2702 * @param firstLine the first line.
2703 * @param number numbering.
2704 * @throws AptParseException AptParseException
2705 */
2706 public NumberedListItem( int indent, String firstLine, int number )
2707 throws AptParseException
2708 {
2709 super( NUMBERED_LIST_ITEM, indent, firstLine );
2710 this.numbering = number;
2711 }
2712
2713 /**
2714 * getNumbering.
2715 *
2716 * @return int
2717 */
2718 public int getNumbering()
2719 {
2720 return numbering;
2721 }
2722
2723 /** {@inheritDoc} */
2724 public void traverse()
2725 throws AptParseException
2726 {
2727 traverseText( skipItemNumber() );
2728 }
2729
2730 /**
2731 * skipItemNumber.
2732 *
2733 * @return int
2734 * @throws AptParseException AptParseException
2735 */
2736 private int skipItemNumber()
2737 throws AptParseException
2738 {
2739 int i = skipSpaceFrom( 0 );
2740
2741 char prevChar = SPACE;
2742 for ( ; i < textLength; ++i )
2743 {
2744 char c = text.charAt( i );
2745 if ( c == RIGHT_SQUARE_BRACKET && prevChar == RIGHT_SQUARE_BRACKET )
2746 {
2747 break;
2748 }
2749 prevChar = c;
2750 }
2751
2752 if ( i == textLength )
2753 {
2754 throw new AptParseException( "missing '" + RIGHT_SQUARE_BRACKET + RIGHT_SQUARE_BRACKET + "'" );
2755 }
2756
2757 return skipSpaceFrom( i + 1 );
2758 }
2759 }
2760
2761 /** A DefinitionListItem Block. */
2762 private class DefinitionListItem
2763 extends Block
2764 {
2765 /**
2766 * Constructor.
2767 *
2768 * @param indent indent.
2769 * @param firstLine the first line.
2770 * @throws AptParseException AptParseException
2771 */
2772 public DefinitionListItem( int indent, String firstLine )
2773 throws AptParseException
2774 {
2775 super( DEFINITION_LIST_ITEM, indent, firstLine );
2776 }
2777
2778 /** {@inheritDoc} */
2779 public void traverse()
2780 throws AptParseException
2781 {
2782 int i = skipSpaceFrom( 0 );
2783 int j = skipFromLeftToRightBracket( i );
2784
2785 AptParser.this.sink.definedTerm();
2786 traverseText( i + 1, j );
2787 AptParser.this.sink.definedTerm_();
2788
2789 j = skipSpaceFrom( j + 1 );
2790 if ( j == textLength )
2791 {
2792 // TODO: this doesn't handle the case of a dd in a paragraph
2793 //throw new AptParseException( "no definition" );
2794 }
2795
2796 AptParser.this.sink.definition();
2797 traverseText( j );
2798 }
2799 }
2800
2801 /** A HorizontalRule Block. */
2802 private class HorizontalRule
2803 extends Block
2804 {
2805 /**
2806 * Constructor.
2807 *
2808 * @param indent indent.
2809 * @param firstLine the first line.
2810 * @throws AptParseException AptParseException
2811 */
2812 public HorizontalRule( int indent, String firstLine )
2813 throws AptParseException
2814 {
2815 super( HORIZONTAL_RULE, indent, firstLine );
2816 }
2817
2818 /** {@inheritDoc} */
2819 public void traverse()
2820 throws AptParseException
2821 {
2822 AptParser.this.sink.horizontalRule();
2823 }
2824 }
2825
2826 /** A PageBreak Block. */
2827 private class PageBreak
2828 extends Block
2829 {
2830 /**
2831 * Constructor.
2832 *
2833 * @param indent indent.
2834 * @param firstLine the first line.
2835 * @throws AptParseException AptParseException
2836 */
2837 public PageBreak( int indent, String firstLine )
2838 throws AptParseException
2839 {
2840 super( PG_BREAK, indent, firstLine );
2841 }
2842
2843 /** {@inheritDoc} */
2844 public void traverse()
2845 throws AptParseException
2846 {
2847 AptParser.this.sink.pageBreak();
2848 }
2849 }
2850
2851 /** A MacroBlock Block. */
2852 private class MacroBlock
2853 extends Block
2854 {
2855 /**
2856 * Constructor.
2857 *
2858 * @param indent indent.
2859 * @param firstLine the first line.
2860 * @throws AptParseException AptParseException
2861 */
2862 public MacroBlock( int indent, String firstLine )
2863 throws AptParseException
2864 {
2865 super( MACRO, indent );
2866
2867 text = firstLine;
2868 }
2869
2870 /** {@inheritDoc} */
2871 public void traverse()
2872 throws AptParseException
2873 {
2874 if ( isSecondParsing() )
2875 {
2876 return;
2877 }
2878
2879 final int start = text.indexOf( '{' );
2880 final int end = text.indexOf( '}' );
2881
2882 String s = text.substring( start + 1, end );
2883
2884 s = escapeForMacro( s );
2885
2886 String[] params = StringUtils.split( s, "|" );
2887
2888 String macroId = params[0];
2889
2890 Map<String, Object> parameters = new HashMap<String, Object>();
2891
2892 for ( int i = 1; i < params.length; i++ )
2893 {
2894 String[] param = StringUtils.split( params[i], "=" );
2895
2896 if ( param.length == 1 )
2897 {
2898 throw new AptParseException( "Missing 'key=value' pair for macro parameter: " + params[i] );
2899 }
2900
2901 String key = unescapeForMacro( param[0] );
2902 String value = unescapeForMacro( param[1] );
2903
2904 parameters.put( key, value );
2905 }
2906
2907 parameters.put( "sourceContent", sourceContent );
2908
2909 AptParser aptParser = new AptParser();
2910 aptParser.setSecondParsing( true );
2911 aptParser.enableLogging( getLog() );
2912 parameters.put( "parser", aptParser );
2913
2914 // getBasedir() does not work in multi-module builds, see DOXIA-373
2915 // the basedir should be injected from here, see DOXIA-224
2916 MacroRequest request = new MacroRequest( parameters, getBasedir() );
2917 try
2918 {
2919 AptParser.this.executeMacro( macroId, request, sink );
2920 }
2921 catch ( MacroExecutionException e )
2922 {
2923 throw new AptParseException( "Unable to execute macro in the APT document", e );
2924 }
2925 catch ( MacroNotFoundException e )
2926 {
2927 throw new AptParseException( "Unable to find macro used in the APT document", e );
2928 }
2929 }
2930
2931 /**
2932 * escapeForMacro
2933 *
2934 * @param s String
2935 * @return String
2936 */
2937 private String escapeForMacro( String s )
2938 {
2939 if ( s == null || s.length() < 1 )
2940 {
2941 return s;
2942 }
2943
2944 String result = s;
2945
2946 // use some outrageously out-of-place chars for text
2947 // (these are device control one/two in unicode)
2948 result = StringUtils.replace( result, "\\=", "\u0011" );
2949 result = StringUtils.replace( result, "\\|", "\u0012" );
2950
2951 return result;
2952 }
2953
2954 /**
2955 * unescapeForMacro
2956 *
2957 * @param s String
2958 * @return String
2959 */
2960 private String unescapeForMacro( String s )
2961 {
2962 if ( s == null || s.length() < 1 )
2963 {
2964 return s;
2965 }
2966
2967 String result = s;
2968
2969 result = StringUtils.replace( result, "\u0011", "=" );
2970 result = StringUtils.replace( result, "\u0012", "|" );
2971
2972 return result;
2973 }
2974 }
2975 }