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