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