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