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