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