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