Coverage Report - org.apache.maven.doxia.module.xdoc.XdocParser
 
Classes in this File Line Coverage Branch Coverage Complexity
XdocParser
91%
173/189
91%
106/116
6,667
 
 1  
 package org.apache.maven.doxia.module.xdoc;
 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 java.io.IOException;
 23  
 import java.io.Reader;
 24  
 import java.io.StringReader;
 25  
 import java.io.StringWriter;
 26  
 import java.util.HashMap;
 27  
 import java.util.Map;
 28  
 
 29  
 import javax.swing.text.html.HTML.Attribute;
 30  
 
 31  
 import org.apache.maven.doxia.macro.MacroExecutionException;
 32  
 import org.apache.maven.doxia.macro.manager.MacroNotFoundException;
 33  
 import org.apache.maven.doxia.macro.MacroRequest;
 34  
 import org.apache.maven.doxia.parser.ParseException;
 35  
 import org.apache.maven.doxia.parser.XhtmlBaseParser;
 36  
 import org.apache.maven.doxia.sink.Sink;
 37  
 import org.apache.maven.doxia.sink.SinkEventAttributeSet;
 38  
 import org.apache.maven.doxia.util.HtmlTools;
 39  
 
 40  
 import org.codehaus.plexus.util.IOUtil;
 41  
 import org.codehaus.plexus.util.StringUtils;
 42  
 import org.codehaus.plexus.util.xml.pull.XmlPullParser;
 43  
 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
 44  
 
 45  
 /**
 46  
  * Parse an xdoc model and emit events into the specified doxia Sink.
 47  
  *
 48  
  * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
 49  
  * @version $Id: XdocParser.java 1185529 2011-10-18 08:27:44Z ltheussl $
 50  
  * @since 1.0
 51  
  * @plexus.component role="org.apache.maven.doxia.parser.Parser" role-hint="xdoc"
 52  
  */
 53  42
 public class XdocParser
 54  
     extends XhtmlBaseParser
 55  
     implements XdocMarkup
 56  
 {
 57  
     /** The source content of the input reader. Used to pass into macros. */
 58  
     private String sourceContent;
 59  
 
 60  
     /** Empty elements don't write a closing tag. */
 61  
     private boolean isEmptyElement;
 62  
 
 63  
     /** A macro name. */
 64  
     private String macroName;
 65  
 
 66  
     /** The macro parameters. */
 67  42
     private Map<String, Object> macroParameters = new HashMap<String, Object>();
 68  
 
 69  
     /** Indicates that we're inside &lt;properties&gt; or &lt;head&gt;.*/
 70  
     private boolean inHead;
 71  
 
 72  
     /** Indicates that &lt;title&gt; was called from &lt;properties&gt; or &lt;head&gt;.*/
 73  
     private boolean hasTitle;
 74  
 
 75  
     /** {@inheritDoc} */
 76  
     public void parse( Reader source, Sink sink )
 77  
         throws ParseException
 78  
     {
 79  56
         this.sourceContent = null;
 80  56
         init();
 81  
 
 82  
         try
 83  
         {
 84  56
             StringWriter contentWriter = new StringWriter();
 85  56
             IOUtil.copy( source, contentWriter );
 86  56
             sourceContent = contentWriter.toString();
 87  
         }
 88  0
         catch ( IOException ex )
 89  
         {
 90  0
             throw new ParseException( "Error reading the input source: " + ex.getMessage(), ex );
 91  
         }
 92  
         finally
 93  
         {
 94  56
             IOUtil.close( source );
 95  56
         }
 96  
 
 97  56
         Reader tmp = new StringReader( sourceContent );
 98  
 
 99  
         // leave this at default (false) until everything is properly implemented, see DOXIA-226
 100  
         //setIgnorableWhitespace( true );
 101  
 
 102  
         try
 103  
         {
 104  56
             super.parse( tmp, sink );
 105  
         }
 106  
         finally
 107  
         {
 108  56
             this.sourceContent = null;
 109  
 
 110  56
             setSecondParsing( false );
 111  56
             init();
 112  42
         }
 113  42
     }
 114  
 
 115  
     /** {@inheritDoc} */
 116  
     protected void handleStartTag( XmlPullParser parser, Sink sink )
 117  
         throws XmlPullParserException, MacroExecutionException
 118  
     {
 119  754
         isEmptyElement = parser.isEmptyElementTag();
 120  
 
 121  754
         SinkEventAttributeSet attribs = getAttributesFromParser( parser );
 122  
 
 123  754
         if ( parser.getName().equals( DOCUMENT_TAG.toString() ) )
 124  
         {
 125  
             //Do nothing
 126  18
             return;
 127  
         }
 128  736
         else if ( parser.getName().equals( HEAD.toString() ) )
 129  
         {
 130  4
             if ( !inHead ) // we might be in head from a <properties> already
 131  
             {
 132  0
                 this.inHead = true;
 133  
 
 134  0
                 sink.head( attribs );
 135  
             }
 136  
         }
 137  732
         else if ( parser.getName().equals( TITLE.toString() ) )
 138  
         {
 139  18
             if ( hasTitle )
 140  
             {
 141  2
                 getLog().warn( "<title> was already defined in <properties>, ignored <title> in <head>." );
 142  
 
 143  
                 try
 144  
                 {
 145  2
                     parser.nextText(); // ignore next text event
 146  
                 }
 147  0
                 catch ( IOException ex )
 148  
                 {
 149  0
                     throw new XmlPullParserException( "Failed to parse text", parser, ex );
 150  2
                 }
 151  
             }
 152  
             else
 153  
             {
 154  16
                 sink.title( attribs );
 155  
             }
 156  
         }
 157  714
         else if ( parser.getName().equals( AUTHOR_TAG.toString() ) )
 158  
         {
 159  14
             sink.author( attribs );
 160  
         }
 161  700
         else if ( parser.getName().equals( DATE_TAG.toString() ) )
 162  
         {
 163  6
             sink.date( attribs );
 164  
         }
 165  694
         else if ( parser.getName().equals( META.toString() ) )
 166  
         {
 167  2
             handleMetaStart( parser, sink, attribs );
 168  
         }
 169  692
         else if ( parser.getName().equals( BODY.toString() ) )
 170  
         {
 171  18
             if ( inHead )
 172  
             {
 173  16
                 sink.head_();
 174  16
                 this.inHead = false;
 175  
             }
 176  
 
 177  18
             sink.body( attribs );
 178  
         }
 179  674
         else if ( parser.getName().equals( SECTION_TAG.toString() ) )
 180  
         {
 181  26
             handleSectionStart( Sink.SECTION_LEVEL_1, sink, attribs, parser );
 182  
         }
 183  648
         else if ( parser.getName().equals( SUBSECTION_TAG.toString() ) )
 184  
         {
 185  24
             handleSectionStart( Sink.SECTION_LEVEL_2, sink, attribs, parser );
 186  
         }
 187  624
         else if ( parser.getName().equals( SOURCE_TAG.toString() ) )
 188  
         {
 189  22
             verbatim();
 190  
 
 191  22
             attribs.addAttributes( SinkEventAttributeSet.BOXED );
 192  
 
 193  22
             sink.verbatim( attribs );
 194  
         }
 195  602
         else if ( parser.getName().equals( PROPERTIES_TAG.toString() ) )
 196  
         {
 197  16
             if ( !inHead ) // we might be in head from a <head> already
 198  
             {
 199  16
                 this.inHead = true;
 200  
 
 201  16
                 sink.head( attribs );
 202  
             }
 203  
         }
 204  
 
 205  
         // ----------------------------------------------------------------------
 206  
         // Macro
 207  
         // ----------------------------------------------------------------------
 208  
 
 209  586
         else if ( parser.getName().equals( MACRO_TAG.toString() ) )
 210  
         {
 211  20
             handleMacroStart( parser );
 212  
         }
 213  566
         else if ( parser.getName().equals( PARAM.toString() ) )
 214  
         {
 215  26
             handleParamStart( parser, sink );
 216  
         }
 217  540
         else if ( !baseStartTag( parser, sink ) )
 218  
         {
 219  4
             if ( isEmptyElement )
 220  
             {
 221  2
                 handleUnknown( parser, sink, TAG_TYPE_SIMPLE );
 222  
             }
 223  
             else
 224  
             {
 225  2
                 handleUnknown( parser, sink, TAG_TYPE_START );
 226  
             }
 227  
 
 228  4
             if ( getLog().isDebugEnabled() )
 229  
             {
 230  0
                 String position = "[" + parser.getLineNumber() + ":"
 231  
                     + parser.getColumnNumber() + "]";
 232  0
                 String tag = "<" + parser.getName() + ">";
 233  
 
 234  0
                 getLog().debug( "Unrecognized xdoc tag: " + tag + " at " + position );
 235  
             }
 236  
         }
 237  724
     }
 238  
 
 239  
     /** {@inheritDoc} */
 240  
     protected void handleEndTag( XmlPullParser parser, Sink sink )
 241  
         throws XmlPullParserException, MacroExecutionException
 242  
     {
 243  732
         if ( parser.getName().equals( DOCUMENT_TAG.toString() ) )
 244  
         {
 245  
             //Do nothing
 246  18
             return;
 247  
         }
 248  714
         else if ( parser.getName().equals( HEAD.toString() ) )
 249  
         {
 250  
             //Do nothing, head is closed with BODY start.
 251  
         }
 252  710
         else if ( parser.getName().equals( BODY.toString() ) )
 253  
         {
 254  18
             consecutiveSections( 0, sink );
 255  
 
 256  18
             sink.body_();
 257  
         }
 258  692
         else if ( parser.getName().equals( TITLE.toString() ) )
 259  
         {
 260  16
             if ( !hasTitle )
 261  
             {
 262  16
                 sink.title_();
 263  16
                 this.hasTitle = true;
 264  
             }
 265  
         }
 266  676
         else if ( parser.getName().equals( AUTHOR_TAG.toString() ) )
 267  
         {
 268  14
             sink.author_();
 269  
         }
 270  662
         else if ( parser.getName().equals( DATE_TAG.toString() ) )
 271  
         {
 272  6
             sink.date_();
 273  
         }
 274  656
         else if ( parser.getName().equals( SOURCE_TAG.toString() ) )
 275  
         {
 276  22
             verbatim_();
 277  
 
 278  22
             sink.verbatim_();
 279  
         }
 280  634
         else if ( parser.getName().equals( PROPERTIES_TAG.toString() ) )
 281  
         {
 282  
             //Do nothing, head is closed with BODY start.
 283  
         }
 284  618
         else if ( parser.getName().equals( MACRO_TAG.toString() ) )
 285  
         {
 286  8
             handleMacroEnd( sink );
 287  
         }
 288  610
         else if ( parser.getName().equals( PARAM.toString() ) )
 289  
         {
 290  18
             if ( !StringUtils.isNotEmpty( macroName ) )
 291  
             {
 292  8
                 handleUnknown( parser, sink, TAG_TYPE_END );
 293  
             }
 294  
         }
 295  592
         else if ( parser.getName().equals( SECTION_TAG.toString() ) )
 296  
         {
 297  26
             consecutiveSections( 0, sink );
 298  
 
 299  26
             sink.section1_();
 300  
         }
 301  566
         else if ( parser.getName().equals( SUBSECTION_TAG.toString() ) )
 302  
         {
 303  24
             consecutiveSections( Sink.SECTION_LEVEL_1, sink );
 304  
         }
 305  542
         else if ( !baseEndTag( parser, sink ) )
 306  
         {
 307  116
             if ( !isEmptyElement )
 308  
             {
 309  2
                 handleUnknown( parser, sink, TAG_TYPE_END );
 310  
             }
 311  
         }
 312  
 
 313  712
         isEmptyElement = false;
 314  712
     }
 315  
 
 316  
     /** {@inheritDoc} */
 317  
     protected void consecutiveSections( int newLevel, Sink sink )
 318  
     {
 319  138
         closeOpenSections( newLevel, sink );
 320  138
         openMissingSections( newLevel, sink );
 321  
 
 322  138
         setSectionLevel( newLevel );
 323  138
     }
 324  
 
 325  
     /** {@inheritDoc} */
 326  
     protected void init()
 327  
     {
 328  322
         super.init();
 329  
 
 330  322
         this.isEmptyElement = false;
 331  322
         this.macroName = null;
 332  322
         this.macroParameters = null;
 333  322
         this.inHead = false;
 334  322
         this.hasTitle = false;
 335  322
     }
 336  
 
 337  
     /**
 338  
      * Close open h4, h5, h6 sections.
 339  
      */
 340  
     private void closeOpenSections( int newLevel, Sink sink )
 341  
     {
 342  282
         while ( getSectionLevel() >= newLevel )
 343  
         {
 344  144
             if ( getSectionLevel() == Sink.SECTION_LEVEL_5 )
 345  
             {
 346  8
                 sink.section5_();
 347  
             }
 348  136
             else if ( getSectionLevel() == Sink.SECTION_LEVEL_4 )
 349  
             {
 350  8
                 sink.section4_();
 351  
             }
 352  128
             else if ( getSectionLevel() == Sink.SECTION_LEVEL_3 )
 353  
             {
 354  8
                 sink.section3_();
 355  
             }
 356  120
             else if ( getSectionLevel() == Sink.SECTION_LEVEL_2 )
 357  
             {
 358  26
                 sink.section2_();
 359  
             }
 360  
 
 361  144
             setSectionLevel( getSectionLevel() - 1 );
 362  
         }
 363  138
     }
 364  
 
 365  
     private void handleMacroEnd( Sink sink )
 366  
             throws MacroExecutionException
 367  
     {
 368  8
         if ( !isSecondParsing() )
 369  
         {
 370  6
             if ( StringUtils.isNotEmpty( macroName ) )
 371  
             {
 372  
                 // TODO handles specific macro attributes
 373  6
                 macroParameters.put( "sourceContent", sourceContent );
 374  6
                 XdocParser xdocParser = new XdocParser();
 375  6
                 xdocParser.setSecondParsing( true );
 376  6
                 macroParameters.put( "parser", xdocParser );
 377  
 
 378  6
                 MacroRequest request = new MacroRequest( macroParameters, getBasedir() );
 379  
 
 380  
                 try
 381  
                 {
 382  6
                     executeMacro( macroName, request, sink );
 383  2
                 } catch ( MacroNotFoundException me )
 384  
                 {
 385  2
                     throw new MacroExecutionException( "Macro not found: " + macroName, me );
 386  4
                 }
 387  
             }
 388  
         }
 389  
 
 390  
         // Reinit macro
 391  6
         macroName = null;
 392  6
         macroParameters = null;
 393  6
     }
 394  
 
 395  
     private void handleMacroStart( XmlPullParser parser )
 396  
             throws MacroExecutionException
 397  
     {
 398  20
         if ( !isSecondParsing() )
 399  
         {
 400  18
             macroName = parser.getAttributeValue( null, Attribute.NAME.toString() );
 401  
 
 402  18
             if ( macroParameters == null )
 403  
             {
 404  18
                 macroParameters = new HashMap<String, Object>();
 405  
             }
 406  
 
 407  18
             if ( StringUtils.isEmpty( macroName ) )
 408  
             {
 409  4
                 throw new MacroExecutionException( "The '" + Attribute.NAME.toString()
 410  
                         + "' attribute for the '" + MACRO_TAG.toString() + "' tag is required." );
 411  
             }
 412  
         }
 413  16
     }
 414  
 
 415  
     private void handleMetaStart( XmlPullParser parser, Sink sink, SinkEventAttributeSet attribs )
 416  
     {
 417  2
         String name = parser.getAttributeValue( null, Attribute.NAME.toString() );
 418  2
         String content = parser.getAttributeValue( null, Attribute.CONTENT.toString() );
 419  
 
 420  2
         if ( "author".equals( name ) )
 421  
         {
 422  0
             sink.author( null );
 423  0
             sink.text( content );
 424  0
             sink.author_();
 425  
         }
 426  2
         else if ( "date".equals( name ) )
 427  
         {
 428  0
             sink.date( null );
 429  0
             sink.text( content );
 430  0
             sink.date_();
 431  
         }
 432  
         else
 433  
         {
 434  2
             sink.unknown( "meta", new Object[] {new Integer( TAG_TYPE_SIMPLE )}, attribs );
 435  
         }
 436  2
     }
 437  
 
 438  
     private void handleParamStart( XmlPullParser parser, Sink sink )
 439  
             throws MacroExecutionException
 440  
     {
 441  26
         if ( !isSecondParsing() )
 442  
         {
 443  20
             if ( StringUtils.isNotEmpty( macroName ) )
 444  
             {
 445  18
                 String paramName = parser.getAttributeValue( null, Attribute.NAME.toString() );
 446  18
                 String paramValue = parser.getAttributeValue( null,
 447  
                         Attribute.VALUE.toString() );
 448  
 
 449  18
                 if ( StringUtils.isEmpty( paramName ) || StringUtils.isEmpty( paramValue ) )
 450  
                 {
 451  8
                     throw new MacroExecutionException( "'" + Attribute.NAME.toString()
 452  
                             + "' and '" + Attribute.VALUE.toString() + "' attributes for the '" + PARAM.toString()
 453  
                             + "' tag are required inside the '" + MACRO_TAG.toString() + "' tag." );
 454  
                 }
 455  
 
 456  10
                 macroParameters.put( paramName, paramValue );
 457  10
             }
 458  
             else
 459  
             {
 460  
                 // param tag from non-macro object, see MSITE-288
 461  2
                 handleUnknown( parser, sink, TAG_TYPE_START );
 462  
             }
 463  
         }
 464  18
     }
 465  
 
 466  
     private void handleSectionStart( int level, Sink sink, SinkEventAttributeSet attribs, XmlPullParser parser )
 467  
     {
 468  50
         consecutiveSections( level, sink );
 469  
 
 470  50
         Object id = attribs.getAttribute( Attribute.ID.toString() );
 471  
 
 472  50
         if ( id != null )
 473  
         {
 474  6
             sink.anchor( id.toString() );
 475  6
             sink.anchor_();
 476  
         }
 477  
 
 478  50
         sink.section( level, attribs );
 479  50
         sink.sectionTitle( level, null );
 480  50
         sink.text( HtmlTools.unescapeHTML( parser.getAttributeValue( null, Attribute.NAME.toString() ) ) );
 481  50
         sink.sectionTitle_( level );
 482  50
     }
 483  
 
 484  
     /**
 485  
      * Open missing h4, h5, h6 sections.
 486  
      */
 487  
     private void openMissingSections( int newLevel, Sink sink )
 488  
     {
 489  144
         while ( getSectionLevel() < newLevel - 1 )
 490  
         {
 491  6
             setSectionLevel( getSectionLevel() + 1 );
 492  
 
 493  6
             if ( getSectionLevel() == Sink.SECTION_LEVEL_5 )
 494  
             {
 495  0
                 sink.section5();
 496  
             }
 497  6
             else if ( getSectionLevel() == Sink.SECTION_LEVEL_4 )
 498  
             {
 499  2
                 sink.section4();
 500  
             }
 501  4
             else if ( getSectionLevel() == Sink.SECTION_LEVEL_3 )
 502  
             {
 503  2
                 sink.section3();
 504  
             }
 505  2
             else if ( getSectionLevel() == Sink.SECTION_LEVEL_2 )
 506  
             {
 507  2
                 sink.section2();
 508  
             }
 509  
         }
 510  138
     }
 511  
 }