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