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