001package org.apache.maven.doxia.module.xdoc;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *   http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.File;
023import java.io.FileFilter;
024import java.io.FileReader;
025import java.io.Reader;
026import java.io.Writer;
027
028import java.util.Iterator;
029import java.util.regex.Pattern;
030
031import org.apache.maven.doxia.parser.AbstractParserTest;
032import org.apache.maven.doxia.parser.ParseException;
033import org.apache.maven.doxia.parser.Parser;
034import org.apache.maven.doxia.sink.Sink;
035import org.apache.maven.doxia.sink.SinkEventAttributeSet;
036import org.apache.maven.doxia.sink.SinkEventElement;
037import org.apache.maven.doxia.sink.SinkEventTestingSink;
038
039import org.codehaus.plexus.util.IOUtil;
040
041/**
042 * @author <a href="mailto:jason@maven.org">Jason van Zyl</a>
043 * @author <a href="mailto:evenisse@codehaus.org">Emmanuel Venisse</a>
044 * @version $Id$
045 * @since 1.0
046 */
047public class XdocParserTest
048    extends AbstractParserTest
049{
050    private XdocParser parser;
051
052    @Override
053    protected void setUp()
054        throws Exception
055    {
056        super.setUp();
057
058        parser = (XdocParser) lookup( Parser.ROLE, "xdoc" );
059
060        // AbstractXmlParser.CachedFileEntityResolver downloads DTD/XSD files in ${java.io.tmpdir}
061        // Be sure to delete them
062        String tmpDir = System.getProperty( "java.io.tmpdir" );
063
064        final Pattern xsdFilePattern = Pattern.compile( "(xdoc\\-.*|xml)\\.xsd" );
065
066        File[] xsdFiles = new File( tmpDir ).listFiles( new FileFilter()
067        {
068
069            public boolean accept( File pathname )
070            {
071                return pathname.isFile() && xsdFilePattern.matcher( pathname.getName() ).matches();
072            }
073        } );
074
075        for ( File xsdFile : xsdFiles )
076        {
077            xsdFile.delete();
078        }
079
080        /* FileUtils 3.0.10 is about 5-8 times slower than File.listFiles() + regexp */
081//        String includes = "xdoc-*.xsd, xml.xsd";
082//        List<File> tmpFiles = FileUtils.getFiles( new File( tmpDir ), includes, null, true );
083//        for ( File tmpFile  : tmpFiles )
084//        {
085//            tmpFile.delete();
086//        }
087    }
088
089    /** {@inheritDoc} */
090    protected String outputExtension()
091    {
092        return "xml";
093    }
094
095    /** {@inheritDoc} */
096    protected Parser createParser()
097    {
098        return parser;
099    }
100
101    /** @throws Exception  */
102    public void testSnippetMacro()
103        throws Exception
104    {
105        Writer output = null;
106        Reader reader = null;
107
108        try
109        {
110            output = getTestWriter( "macro" );
111            reader = getTestReader( "macro" );
112
113            Sink sink = new XdocSink( output );
114            createParser().parse( reader, sink );
115            sink.close();
116        }
117        finally
118        {
119            IOUtil.close( output );
120            IOUtil.close( reader );
121        }
122
123        File f = getTestFile( getBasedir(), outputBaseDir() + getOutputDir() + "macro.xml" );
124        assertTrue( "The file " + f.getAbsolutePath() + " was not created", f.exists() );
125
126        String content;
127        try
128        {
129            reader = new FileReader( f );
130            content = IOUtil.toString( reader );
131        }
132        finally
133        {
134            IOUtil.close( reader );
135        }
136
137        assertTrue( content.indexOf( "&lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;" ) != -1 );
138    }
139
140    /** @throws Exception  */
141    public void testTocMacro()
142        throws Exception
143    {
144        Writer output = null;
145        Reader reader = null;
146
147        try
148        {
149            output = getTestWriter( "toc" );
150            reader = getTestReader( "toc" );
151
152            Sink sink = new XdocSink( output );
153            createParser().parse( reader, sink );
154            sink.close();
155        }
156        finally
157        {
158            IOUtil.close( output );
159            IOUtil.close( reader );
160        }
161
162        File f = getTestFile( getBasedir(), outputBaseDir() + getOutputDir() + "toc.xml" );
163        assertTrue( "The file " + f.getAbsolutePath() + " was not created", f.exists() );
164
165        String content;
166        try
167        {
168            reader = new FileReader( f );
169            content = IOUtil.toString( reader );
170        }
171        finally
172        {
173            IOUtil.close( reader );
174        }
175
176        // No section, only subsection 1 and 2
177        assertTrue( content.indexOf( "<a href=\"#Section_11\">Section 11</a>" ) != -1 );
178        assertTrue( content.indexOf( "<a href=\"#Section_1211\">Section 1211</a>" ) == -1 );
179    }
180
181    /** @throws Exception  */
182    public void testHeadEventsList()
183        throws Exception
184    {
185        String text = "<document>"
186                + "<properties><title>title</title>"
187                + "<!-- Test comment: DOXIA-312 -->"
188                + "<author email=\"a@b.c\">John Doe</author></properties>"
189                + "<head>"
190                + "<meta name=\"security\" content=\"low\"/>"
191                + "<base href=\"http://maven.apache.org/\"/>"
192                + "</head>"
193                + "<body></body></document>";
194
195        SinkEventTestingSink sink = new SinkEventTestingSink();
196
197        parser.parse( text, sink );
198
199        Iterator<SinkEventElement> it = sink.getEventList().iterator();
200
201        assertEquals( it, "head", "title", "text", "title_", "comment", "author", "text", "author_" );
202
203        SinkEventElement unknown = it.next();
204        assertEquals( "unknown", unknown.getName() );
205        assertEquals( "meta", unknown.getArgs()[0] );
206
207        unknown = it.next();
208        assertEquals( "unknown", unknown.getName() );
209        assertEquals( "base", unknown.getArgs()[0] );
210
211        assertEquals( it, "head_", "body", "body_" );
212        assertFalse( it.hasNext() );
213
214        // DOXIA-359
215        text = "<document>"
216                + "<properties><title>properties title</title></properties>"
217                + "<head><title>head title</title></head>"
218                + "<body></body></document>";
219
220        sink.reset();
221        parser.parse( text, sink );
222
223        it = sink.getEventList().iterator();
224
225        assertEquals( it, "head", "title" );
226
227        SinkEventElement title = it.next();
228        assertEquals( "text", title.getName() );
229        assertEquals( "properties title", title.getArgs()[0] );
230
231        assertEquals( it, "title_", "head_", "body",  "body_" );
232        assertFalse( it.hasNext() );
233    }
234
235    /** @throws Exception  */
236    public void testDocumentBodyEventsList()
237        throws Exception
238    {
239        String text = "<document><body></body></document>";
240
241        SinkEventTestingSink sink = new SinkEventTestingSink();
242
243        parser.parse( text, sink );
244
245        Iterator<SinkEventElement> it = sink.getEventList().iterator();
246
247        assertEquals( it, "body", "body_" );
248        assertFalse( it.hasNext() );
249    }
250
251    /** @throws Exception  */
252    public void testSectionEventsList()
253        throws Exception
254    {
255        String text = "<section name=\"sec 1\"><subsection name=\"sub 1\"></subsection></section>";
256
257        SinkEventTestingSink sink = new SinkEventTestingSink();
258
259        parser.parse( text, sink );
260
261        Iterator<SinkEventElement> it = sink.getEventList().iterator();
262
263        assertEquals( it, "section1", "sectionTitle1", "text", "sectionTitle1_", "section2", "sectionTitle2", "text",
264                      "sectionTitle2_", "section2_", "section1_" );
265        assertFalse( it.hasNext() );
266    }
267
268    /** @throws Exception  */
269    public void testSectionAttributes()
270        throws Exception
271    {
272        // DOXIA-448
273        String text = "<section name=\"section name\" class=\"foo\" id=\"bar\"></section>";
274
275        SinkEventTestingSink sink = new SinkEventTestingSink();
276
277        parser.parse( text, sink );
278
279        Iterator<SinkEventElement> it = sink.getEventList().iterator();
280
281        assertEquals( it, "anchor", "anchor_" );
282
283        SinkEventElement next = it.next();
284        assertEquals( "section1", next.getName() );
285        SinkEventAttributeSet set = (SinkEventAttributeSet) next.getArgs()[0];
286        assertEquals( 3, set.getAttributeCount() );
287        assertTrue( set.containsAttribute( "name", "section name" ) );
288        assertTrue( set.containsAttribute( "class", "foo" ) );
289        assertTrue( set.containsAttribute( "id", "bar" ) );
290
291        next = it.next();
292        assertEquals( "sectionTitle1", next.getName() );
293        assertNull( (SinkEventAttributeSet) next.getArgs()[0] );
294
295        assertEquals( it, "text", "sectionTitle1_", "section1_" );
296        assertFalse( it.hasNext() );
297    }
298
299    /** @throws Exception  */
300    public void testNestedSectionsEventsList()
301        throws Exception
302    {
303        // DOXIA-241
304        String text = "<section name=\"section\"><h6>h6</h6><subsection name=\"subsection\"></subsection></section>";
305
306        SinkEventTestingSink sink = new SinkEventTestingSink();
307
308        parser.parse( text, sink );
309
310        Iterator<SinkEventElement> it = sink.getEventList().iterator();
311
312        assertEquals( it, "section1", "sectionTitle1", "text", "sectionTitle1_", "section2", "section3", "section4",
313                      "section5", "sectionTitle5", "text", "sectionTitle5_", "section5_", "section4_", "section3_",
314                      "section2_", "section2", "sectionTitle2", "text", "sectionTitle2_", "section2_", "section1_" );
315        assertFalse( it.hasNext() );
316    }
317
318    /** @throws Exception  */
319    public void testSourceEventsList()
320        throws Exception
321    {
322        String text = "<source><a href=\"what.html\">what</a></source>";
323
324        SinkEventTestingSink sink = new SinkEventTestingSink();
325
326        parser.parse( text, sink );
327
328        Iterator<SinkEventElement> it = sink.getEventList().iterator();
329        assertEquals( it, "verbatim", "link", "text", "link_", "verbatim_" );
330        assertFalse( it.hasNext() );
331
332        text = "<source><![CDATA[<a href=\"what.html\">what</a>]]></source>";
333        sink.reset();
334        parser.parse( text, sink );
335
336        it = sink.getEventList().iterator();
337        assertEquals( it, "verbatim", "text", "verbatim_" );
338        assertFalse( it.hasNext() );
339
340        text = "<source><![CDATA[<source>what</source>]]></source>";
341        sink.reset();
342        parser.parse( text, sink );
343
344        it = sink.getEventList().iterator();
345        assertEquals( it, "verbatim", "text", "verbatim_" );
346        assertFalse( it.hasNext() );
347    }
348
349    /** @throws Exception  */
350    public void testSourceContainingDTD()
351        throws Exception
352    {
353        String text = "<source><![CDATA[" +
354                          "<!DOCTYPE web-app PUBLIC " +
355                          "\"-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN\"" +
356                          " \"http://java.sun.com/j2ee/dtds/web-app_2.2.dtd\">" +
357                      "]]></source>";
358
359        SinkEventTestingSink sink = new SinkEventTestingSink();
360
361        parser.parse( text, sink );
362
363        Iterator<SinkEventElement> it = sink.getEventList().iterator();
364        assertEquals( it, "verbatim", "text", "verbatim_" );
365        assertFalse( it.hasNext() );
366
367    }
368
369    /** @throws Exception  */
370    public void testPreEOL()
371        throws Exception
372    {
373        // test EOLs within <source>: the sink MUST receive a text event for the EOL
374        String text = "<source><a href=\"what.html\">what</a>" + EOL
375                + "<a href=\"what.html\">what</a></source>";
376
377        SinkEventTestingSink sink = new SinkEventTestingSink();
378
379        parser.parse( text, sink );
380
381        Iterator<SinkEventElement> it = sink.getEventList().iterator();
382
383        assertEquals( it, "verbatim", "link", "text", "link_", "text", "link", "text", "link_", "verbatim_" );
384    }
385
386    /**
387     * Test section with ids.
388     *
389     * @throws java.lang.Exception if any.
390     */
391    public void testSectionIdAnchor()
392        throws Exception
393    {
394        String text = "<section name=\"test\" id=\"test-id\">This is a test."
395                + "<subsection name=\"sub-test\" id=\"sub-id\">Sub-section</subsection></section>";
396
397        SinkEventTestingSink sink = new SinkEventTestingSink();
398
399        parser.parse( text, sink );
400
401        Iterator<SinkEventElement> it = sink.getEventList().iterator();
402
403        assertEquals( it.next(), "anchor", "test-id" );
404
405        assertEquals( it, "anchor_", "section1", "sectionTitle1", "text", "sectionTitle1_", "text" );
406
407        assertEquals( it.next(), "anchor", "sub-id" );
408
409        assertEquals( it, "anchor_", "section2", "sectionTitle2", "text", "sectionTitle2_", "text", "section2_",
410                      "section1_" );
411        assertFalse( it.hasNext() );
412    }
413
414    /**
415     * Test script block.
416     *
417     * @throws java.lang.Exception if any.
418     */
419    public void testJavaScript()
420        throws Exception
421    {
422        String text = "<script type=\"text/javascript\"><![CDATA[alert(\"Hello!\");]]></script>";
423
424        SinkEventTestingSink sink = new SinkEventTestingSink();
425
426        parser.parse( text, sink );
427
428        Iterator<SinkEventElement> it = sink.getEventList().iterator();
429        assertEquals( it, "unknown", "unknown", "unknown" );
430        assertFalse( it.hasNext() );
431    }
432
433    /**
434     * Test unknown tags.
435     *
436     * @throws java.lang.Exception if any.
437     */
438    public void testUnknown()
439        throws Exception
440    {
441        String text = "<applet><param name=\"name\" value=\"value\"/><unknown/></applet>";
442
443        SinkEventTestingSink sink = new SinkEventTestingSink();
444
445        parser.parse( text, sink );
446
447        Iterator<SinkEventElement> it = sink.getEventList().iterator();
448        assertEquals( it, "unknown", "unknown", "unknown", "unknown", "unknown" );
449        assertFalse( it.hasNext() );
450    }
451
452    /**
453     * Test invalid macro tags.
454     */
455    public void testMacroExceptions()
456    {
457        SinkEventTestingSink sink = new SinkEventTestingSink();
458        assertParseException( sink, "<macro/>" );
459        assertParseException( sink, "<macro name=\"\"/>" );
460        assertParseException( sink, "<macro name=\"name\"><param name=\"\" value=\"value\"/></macro>" );
461        assertParseException( sink, "<macro name=\"name\"><param name=\"name\" value=\"\"/></macro>" );
462        assertParseException( sink, "<macro name=\"name\"><param value=\"value\"/></macro>" );
463        assertParseException( sink, "<macro name=\"name\"><param name=\"name\"/></macro>" );
464        assertParseException( sink, "<macro name=\"unknown\"></macro>" );
465    }
466
467    private void assertParseException( Sink sink, String text )
468    {
469        try
470        {
471            parser.parse( text, sink );
472
473            fail( "Should not be parseable: '" + text + "'" );
474        }
475        catch ( ParseException ex )
476        {
477            assertNotNull( ex );
478        }
479    }
480
481    /** @throws Exception  */
482    public void testEntities()
483        throws Exception
484    {
485        final String text = "<!DOCTYPE test [<!ENTITY foo \"&#x159;\"><!ENTITY tritPos  \"&#x1d7ed;\">]>"
486                + "<section name=\"&amp;&foo;&tritPos;\"><p>&amp;&foo;&tritPos;</p></section>";
487
488        SinkEventTestingSink sink = new SinkEventTestingSink();
489
490        parser.setValidate( false );
491        parser.parse( text, sink );
492
493        Iterator<SinkEventElement> it = sink.getEventList().iterator();
494
495        assertEquals( it, "section1", "sectionTitle1" );
496
497        assertEquals( it.next(), "text", "&\u0159\uD835\uDFED" );
498
499        assertEquals( it, "sectionTitle1_", "paragraph" );
500
501        assertEquals( it.next(), "text", "&" );
502
503        assertEquals( it.next(), "text", "\u0159" );
504
505        assertEquals( it.next(), "text", "\uD835\uDFED" );
506
507        assertEquals( it, "paragraph_", "section1_" );
508        assertFalse( it.hasNext() );
509    }
510    
511    public void testStyleWithCData() throws Exception
512    {
513        // DOXIA-449
514        final String text = "<style type=\"text/css\">\n" + 
515                        "<![CDATA[\n" + 
516                        "h2 {\n" + 
517                        "font-size: 50px;\n" + 
518                        "}\n" + 
519                        "]]>\n" + 
520                        "</style>"; 
521        
522        SinkEventTestingSink sink = new SinkEventTestingSink();
523
524        parser.setValidate( false );
525        parser.parse( text, sink );
526        
527        Iterator<SinkEventElement> it = sink.getEventList().iterator();
528        SinkEventElement styleElm = it.next(); 
529        assertEquals( "unknown", styleElm.getName() );
530        assertEquals( "style", styleElm.getArgs()[0] );
531        SinkEventElement cdataElm = it.next(); 
532        assertEquals( "unknown", cdataElm.getName() );
533        assertEquals( "CDATA", cdataElm.getArgs()[0] );
534        SinkEventElement styleElm_ = it.next(); 
535        assertEquals( "unknown", styleElm_.getName() );
536        assertEquals( "style", styleElm_.getArgs()[0] );
537        assertFalse( it.hasNext() );
538    }
539}