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