1   /*
2    * Copyright 2001-2004 (C) MetaStuff, Ltd. All Rights Reserved.
3    * 
4    * This software is open source. 
5    * See the bottom of this file for the licence.
6    * 
7    * $Id: TestAelfred.java,v 1.3 2004/06/25 08:03:49 maartenc Exp $
8    */
9   
10  package org.dom4j.io;
11  
12  import java.io.IOException;
13  import java.io.InputStream;
14  import java.io.FileInputStream;
15  import java.net.URL;
16  
17  import java.util.Iterator;
18  import java.util.List;
19  import java.util.ArrayList;
20  
21  import junit.framework.*;
22  import junit.textui.TestRunner;
23  
24  import org.dom4j.*;
25  import org.dom4j.dtd.*;
26  import org.dom4j.tree.*;
27  
28  import org.xml.sax.InputSource;
29  import org.xml.sax.EntityResolver;
30  import org.xml.sax.SAXException;
31  
32  /*** Tests the DocType functionality
33    *
34    * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
35    * @version $Revision: 1.3 $
36  
37    * Incorporated additional test cases for optional processing of the
38    * internal and external DTD subsets.  The "external" and "mixed"
39    * tests both <strong>fail</strong> due to a reported bug.  See
40  
41  <a href="http://sourceforge.net/tracker/index.php?func=detail&aid=909349&group_id=16035&atid=116035">
42  
43  [ 909349 ] can not distinguish the external DTD subset.
44  
45  </a>
46  
47    */
48  
49  public class TestAelfred extends AbstractTestCase {
50  
51      /***
52       * Input XML file to read <code>xml/dtd/internal.xml</code> -
53       * document using internal DTD subset, but no external DTD subset.
54       */
55  
56      protected static String INPUT_XML_INTERNAL_FILE = "xml/dtd/internal.xml";
57      
58      /***
59       * Input XML file to read <code>xml/dtd/external.xml</code> -
60       * document using external DTD subset, but no internal DTD subset.
61       * The external entity should be locatable by either PUBLIC or
62       * SYSTEM identifier.  The testing harness should use an
63       * appropriate EntityResolver to locate the external entity as a
64       * local resource (no internet access).
65       */
66  
67      protected static String INPUT_XML_EXTERNAL_FILE = "xml/dtd/external.xml";
68      
69      /***
70       * Input XML file to read <code>xml/dtd/mixed.xml</code> -
71       * document using both an internal and an external DTD subset.
72       * The external entity should be locatable by either PUBLIC or
73       * SYSTEM identifier.  The testing harness should use an
74       * appropriate EntityResolver to locate the external entity as a
75       * local resource (no internet access).
76       */
77  
78      protected static String INPUT_XML_MIXED_FILE = "xml/dtd/mixed.xml";
79  
80      /***
81       * Input XML file to for {@link EntityResolver}
82       * <code>xml/dtd/sample.dtd</code> - the external entity providing
83       * the external DTD subset for test cases that need one.  The
84       * SYSTEM identifier for this external entity is given by {@link
85       * #INPUT_DTD_SYSTEMID}.
86       */
87  
88      protected static String INPUT_DTD_FILE = "xml/dtd/sample.dtd";
89  
90      /***
91       * The PUBLIC identifier, which is
92  
93         <code>-//dom4j//DTD sample</code>
94  
95       * , for the external entity providing DTD for tests.
96       */
97  
98      protected static String INPUT_DTD_PUBLICID = "-//dom4j//DTD sample";
99  
100     /***
101      * The SYSTEM identifier, which is <code>sample.dtd</code>, for
102      * the external entity providing DTD for tests.
103      */
104 
105     protected static String INPUT_DTD_SYSTEMID = "sample.dtd";
106     
107     public static void main( String[] args ) {
108         TestRunner.run( suite() );
109     }
110     
111     public static Test suite() {
112         return new TestSuite( TestAelfred.class );
113     }
114     
115     public TestAelfred(String name) {
116         super(name);
117     }
118 
119     // Test case(s)
120     //-------------------------------------------------------------------------                    
121 
122     /***
123      * Test verifies correct identification of the internal DTD subset
124      * and correct non-presence of the external DTD subset.
125      */
126 
127     public void testInternalDTDSubset()
128 	throws Exception
129     {
130 
131 	/* Setup the expected DocumentType.
132 	 *
133 	 * @todo dom4j should expose a DefaultDocumentType constructor
134 	 * that accepts only the elementName property.  This is used
135 	 * when only an internal DTD subset is being provided via the
136 	 * <!DOCTYPE foo [...]> syntax, in which case there is neither
137 	 * a SYSTEM nor PUBLIC identifier.
138 	 */
139 
140 	DocumentType expected = new DefaultDocumentType();
141 
142 	expected.setElementName
143 	    ( "greeting"
144 	      );
145 
146 	expected.setInternalDeclarations
147 	    ( getInternalDeclarations()
148 	      );
149 
150 	/* Parse the test XML document and compare the expected and
151 	 * actual DOCTYPEs.
152 	 */
153 
154 	try {
155 
156 	    assertSameDocumentType
157 		( expected,
158 		  readDocument
159 		  ( INPUT_XML_INTERNAL_FILE,
160 		    true,		// internal decls.
161 		    false		// external decls.
162 		    ).getDocType()
163 		  );
164 
165 	}
166 
167 	catch( AssertionFailedError ex ) { throw ex; }
168 
169 	catch( Throwable t ) {
170 	    fail( "Not expecting: " + t	);
171 
172 	}
173 
174     }
175 
176 
177     /***
178      * Test verifies correct identification of the external DTD subset
179      * and correct non-presence of the internal DTD subset.
180      */
181 
182     public void testExternalDTDSubset()
183     {
184 
185 	/* Setup the expected DocumentType.
186 	 */
187 
188 	DocumentType expected = new DefaultDocumentType
189 	    ( "another-greeting",
190 	      null,
191 	      INPUT_DTD_SYSTEMID
192 	      );
193 
194 	expected.setExternalDeclarations
195 	    ( getExternalDeclarations()
196 	      );
197 
198 	/* Parse the test XML document and compare the expected and
199 	 * actual DOCTYPEs.
200 	 */
201 
202 	try {
203 	    
204 	    assertSameDocumentType
205 		( expected,
206 		  readDocument
207 		  ( INPUT_XML_EXTERNAL_FILE,
208 		    false,		// internal decls.
209 		    true		// external decls.
210 		    ).getDocType()
211 		  );
212 
213 	}
214 	
215 	catch( AssertionFailedError ex ) { throw ex; }
216 
217 	catch( Throwable t ) {
218 
219 	    fail( "Not expecting: " + t
220 		  );
221 
222 	}
223 
224     }
225 
226     /***
227      * Test verifies correct identification of the internal and
228      * external DTD subsets.
229      */
230 
231     public void testMixedDTDSubset()
232     {
233 
234 	/* Setup the expected DocumentType.
235 	 */
236 
237 	DocumentType expected = new DefaultDocumentType
238 	    ( "another-greeting",
239 	      null,
240 	      INPUT_DTD_SYSTEMID
241 	      );
242 
243 	expected.setInternalDeclarations
244 	    ( getInternalDeclarations()
245 	      );
246 
247 	expected.setExternalDeclarations
248 	    ( getExternalDeclarations()
249 	      );
250 
251 	/* Parse the test XML document and compare the expected and
252 	 * actual DOCTYPEs.
253 	 */
254 
255 	try {
256 	    
257 	    assertSameDocumentType
258 		( expected,
259 		  readDocument
260 		  ( INPUT_XML_MIXED_FILE,
261 		    true,		// internal decls.
262 		    true		// external decls.
263 		    ).getDocType()
264 		  );
265 
266 	}
267 
268 	catch( AssertionFailedError ex ) { throw ex; }
269 
270 	catch( Throwable t ) {
271 
272 	    fail( "Not expecting: " + t
273 		  );
274 
275 	}
276 	
277     }
278 
279 
280     // Implementation methods
281     //-------------------------------------------------------------------------                    
282 
283     /***
284      * Test helper method returns a {@link List} of DTD declarations
285      * that represents the expected internal DTD subset (for the tests
286      * that use an internal DTD subset).<p>
287      *
288      * Note: The declarations returned by this method MUST agree those
289      * actually declared in {@link #INPUT_XML_INTERNAL_FILE} and
290      * {@link #INPUT_XML_MIXED_FILE}.<p>
291      */
292 
293     protected List getInternalDeclarations()
294     {
295 
296 	List decls = new ArrayList();
297 
298 	decls.add
299 	    ( new ElementDecl
300 	      ( "greeting", "(#PCDATA)*"
301 		)
302 	      );
303 	
304 	decls.add
305 	    ( new AttributeDecl
306 	      ( "greeting",
307 		"foo",
308 		"ID",
309 		"#IMPLIED",
310 		null
311 		)
312 	      );
313 
314 	decls.add
315 	    ( new InternalEntityDecl
316 	      ( "%boolean",
317 		"( true | false )"
318 		)
319 	      );
320 
321 	return decls;
322 
323     }
324 
325 
326     /***
327      * Test helper method returns a {@link List} of DTD declarations
328      * that represents the expected external DTD subset (for the tests
329      * that use an external DTD subset).
330      */
331 
332     protected List getExternalDeclarations()
333     {
334 
335 	List decls = new ArrayList();
336 
337 	decls.add( new ElementDecl( "another-greeting", "(#PCDATA)*" ) );
338 
339 	return decls;
340 
341     }
342 
343     /***
344      * Test helper method compares the expected and actual {@link
345      * DocumentType} objects, including their internal and external
346      * DTD subsets.<p>
347      */
348 
349     protected void assertSameDocumentType
350 	( DocumentType expected,
351 	  DocumentType actual
352 	  )
353     {
354 
355 	/* Nothing expected?
356 	 */
357 
358 	if( expected == null ) {
359 
360 	    if( actual == null ) {
361 
362 		return; // Nothing found.
363 
364 	    } else {
365 
366 		fail( "Not expecting DOCTYPE."
367 		      );
368 
369 	    }
370 		
371 	} else {
372 
373 	    /* Something expected.
374 	     */
375 
376 	    if( actual == null ) {
377 
378 		fail( "Expecting DOCTYPE"
379 		      );
380 
381 	    }
382 
383 	    log( "Expected DocumentType:\n"+expected.toString()
384 		 );
385 	    
386 	    log( "Actual DocumentType:\n"+actual.toString()
387 		  );
388 
389 	    // Check the internal DTD subset.
390 	    
391 	    assertSameDTDSubset
392 		( "Internal",
393 		  expected.getInternalDeclarations(), // expected
394 		  actual.getInternalDeclarations() // actual
395 		  );
396 	    
397 	    // Check the external DTD subset.
398 	    
399 	    assertSameDTDSubset
400 		( "External",
401 		  expected.getExternalDeclarations(), // expected
402 		  actual.getExternalDeclarations() // actual
403 		  );
404 
405 	}
406 	
407     }
408 
409     /***
410      * Test helper method compares an expected set of DTD declarations
411      * with an actual set of DTD declarations.  This method should be
412      * invoked seperately for the internal DTD subset and the external
413      * DTD subset.  The declarations must occur in their logical
414      * ordering.
415 
416      See <a href="http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html#startDTD(java.lang.String,%20java.lang.String,%20java.lang.String)"> Lexical Handler</a> for conformance criteria.
417 
418      */
419 
420     protected void assertSameDTDSubset
421 	( String label,
422 	  List expected,
423 	  List actual
424 	  )
425     {
426 
427 	/* Nothing expected?
428 	 */
429 
430 	if( expected == null ) {
431 
432 	    if( actual == null ) {
433 
434 		return; // Nothing found.
435 
436 	    } else {
437 
438 		fail( "Not expecting "+label+" DTD subset."
439 		      );
440 
441 	    }
442 		
443 	} else {
444 
445 	    /* Something expected.
446 	     */
447 
448 	    if( actual == null ) {
449 
450 		fail( "Expecting "+label+" DTD subset."
451 		      );
452 
453 	    }
454 
455 	    /* Correct #of declarations found?
456 	     */
457 	    
458 	    assertEquals
459 		( label+" DTD subset has correct #of declarations"+
460 		  ": expected=["+expected.toString()+"]"+
461 		  ", actual=["+actual.toString()+"]",
462 		  expected.size(),
463 		  actual.size()
464 		  );
465 
466 	    /* Check order, type, and values of each declaration.
467 	     * Serialization tests are done separately.
468 	     */
469 
470 	    Iterator itr1 = expected.iterator();
471 
472 	    Iterator itr2 = actual.iterator();
473 
474 	    while( itr1.hasNext() ) {
475 
476 		Object obj1 = itr1.next();
477 		
478 		Object obj2 = itr2.next();
479 
480 		assertEquals
481 		    ( label+" DTD subset: Same type of declaration",
482 		      obj1.getClass().getName(),
483 		      obj2.getClass().getName()
484 		      );
485 
486 		if( obj1 instanceof AttributeDecl ) {
487 
488 		    assertSameDecl
489 			( (AttributeDecl)obj1,
490 			  (AttributeDecl)obj2
491 			  );
492 
493 		} else if( obj1 instanceof ElementDecl ) {
494 
495 		    assertSameDecl
496 			( (ElementDecl)obj1,
497 			  (ElementDecl)obj2
498 			  );
499 
500 		} else if( obj1 instanceof InternalEntityDecl ) {
501 
502 		    assertSameDecl
503 			( (InternalEntityDecl)obj1,
504 			  (InternalEntityDecl)obj2
505 			  );
506 
507 		} else if( obj1 instanceof ExternalEntityDecl ) {
508 
509 		    assertSameDecl
510 			( (ExternalEntityDecl)obj1,
511 			  (ExternalEntityDecl)obj2
512 			  );
513 
514 		} else {
515 
516 		    throw new AssertionError
517 			( "Unexpected declaration type: "+obj1.getClass()
518 			  );
519 
520 		}
521 
522 	    }
523 
524 	}
525 
526     }
527 
528     /***
529      * Test helper method compares an expected and an actual {@link
530      * AttributeDecl}.
531      */
532 
533     protected void assertSameDecl
534 	( AttributeDecl expected,
535 	  AttributeDecl actual
536 	  )
537     {
538 	
539 	assertEquals
540 	    ( "attributeName is correct",
541 	      expected.getAttributeName(),
542 	      actual.getAttributeName()
543 	      );
544 		    
545 	assertEquals
546 	    ( "elementName is correct",
547 	      expected.getElementName(),
548 	      actual.getElementName()
549 	      );
550 	
551 	assertEquals
552 	    ( "type is correct",
553 	      expected.getType(),
554 	      actual.getType()
555 	      );
556 	
557 	assertEquals
558 	    ( "value is correct",
559 	      expected.getValue(),
560 	      actual.getValue()
561 	      );
562 	
563 	assertEquals
564 	    ( "valueDefault is correct",
565 	      expected.getValueDefault(),
566 	      actual.getValueDefault()
567 	      );
568 	
569 	assertEquals
570 	    ( "toString() is correct",
571 	      expected.toString(),
572 	      actual.toString()
573 	      );
574 
575     }
576 
577     /***
578      * Test helper method compares an expected and an actual {@link
579      * ElementDecl}.
580      */
581 
582     protected void assertSameDecl
583 	( ElementDecl expected,
584 	  ElementDecl actual
585 	  )
586     {
587 
588 	assertEquals
589 	    ( "name is correct",
590 	      expected.getName(),
591 	      actual.getName()
592 	      );
593 	
594 	assertEquals
595 	    ( "model is correct",
596 	      expected.getModel(),
597 	      actual.getModel()
598 	      );
599 	
600 	assertEquals
601 	    ( "toString() is correct",
602 	      expected.toString(),
603 	      actual.toString()
604 	      );
605 	
606     }
607 
608     /***
609      * Test helper method compares an expected and an actual {@link
610      * InternalEntityDecl}.
611      */
612 
613     protected void assertSameDecl
614 	( InternalEntityDecl expected,
615 	  InternalEntityDecl actual
616 	  )
617     {
618 	
619 	assertEquals
620 	    ( "name is correct",
621 	      expected.getName(),
622 	      actual.getName()
623 	      );
624 
625 	assertEquals
626 	    ( "value is correct",
627 	      expected.getValue(),
628 	      actual.getValue()
629 	      );
630 
631 	assertEquals
632 	    ( "toString() is correct",
633 	      expected.toString(),
634 	      actual.toString()
635 	      );
636 
637     }
638 
639     /***
640      * Test helper method compares an expected and an actual {@link
641      * ExternalEntityDecl}.
642      */
643 
644     protected void assertSameDecl
645 	( ExternalEntityDecl expected,
646 	  ExternalEntityDecl actual
647 	  )
648     {
649 	
650 	assertEquals
651 	    ( "name is correct",
652 	      expected.getName(),
653 	      actual.getName()
654 	      );
655 
656 	assertEquals
657 	    ( "publicID is correct",
658 	      expected.getPublicID(),
659 	      actual.getPublicID()
660 	      );
661 
662 	assertEquals
663 	    ( "systemID is correct",
664 	      expected.getSystemID(),
665 	      actual.getSystemID()
666 	      );
667 
668 	assertEquals
669 	    ( "toString() is correct",
670 	      expected.toString(),
671 	      actual.toString()
672 	      );
673 
674     }
675 
676     /***
677      * Helper method reads a local resource and parses it as an XML
678      * document.  The internal and external DTD subsets are optionally
679      * retained by the parser and exposed via the {@link DocumentType}
680      * object on the returned {@link Document}.  The parser is
681      * configured with an {@link EntityResolver} that knows how to
682      * find the local resource identified by {@link #INPUT_DTD_FILE}
683      * whose SYSTEM identifier is given by {@link #INPUT_DTD_SYSTEMID}.
684      */
685 
686     protected Document readDocument
687 	( String resourceName,
688 	  boolean includeInternalDTDDeclarations,
689 	  boolean includeExternalDTDDeclarations
690 	  )
691 	throws Exception
692     {
693 
694         SAXReader reader = new SAXReader("org.dom4j.io.aelfred2.SAXDriver");
695 
696         reader.setIncludeInternalDTDDeclarations
697 	    ( includeInternalDTDDeclarations
698 	      );
699 
700         reader.setIncludeExternalDTDDeclarations
701 	    ( includeExternalDTDDeclarations
702 	      );
703 
704 	reader.setEntityResolver
705 	    ( new MyEntityResolver
706 	      ( INPUT_DTD_FILE,
707 		INPUT_DTD_PUBLICID,
708 		INPUT_DTD_SYSTEMID
709 		)
710 	      );
711 
712         URL res = getClass().getResource("/" + resourceName);
713         return reader.read( res );
714 
715     }
716 
717     /***
718      * Provides a resolver for the local test DTD resource.
719      */
720 
721     protected static class MyEntityResolver
722 	implements EntityResolver
723     {
724 
725 	String m_localResourceName;
726 	String m_publicId;
727 	String m_systemId;
728 
729 	public MyEntityResolver
730 	    ( String localResourceName,
731 	      String publicId,
732 	      String systemId
733 	      )
734 	{
735 
736 	    m_localResourceName = localResourceName;
737 						
738 	    m_systemId = systemId;
739 
740 	}	      
741 
742 	public InputSource resolveEntity
743 	    ( String publicId,
744 	      String systemId
745 	      )
746 	    throws SAXException,
747 		   IOException
748 	{
749 
750 	    if( m_publicId != null ) {
751 
752 		if( m_publicId.equals( publicId ) ) {
753 
754 		    return new InputSource
755 			( getInputStream
756 			  ( m_localResourceName
757 			    )
758 			  );
759 
760 		}
761 
762 	    }
763 
764 	    if( m_systemId.equals( systemId ) ) {
765 		
766 		return new InputSource
767 		    ( getInputStream
768 		      ( m_localResourceName
769 			)
770 		      );
771 		
772 	    } else {
773 		
774 		return null;
775 		
776 	    }
777 	    
778 	}
779 
780 	/***
781 	 * Returns an {@link InputStream} that will read from the
782 	 * indicated local resource.
783 	 */
784 
785 	protected InputStream getInputStream
786 	    ( String localResourceName
787 	      )
788 	    throws IOException
789 	{
790 
791 	    InputStream is = new FileInputStream
792 		( localResourceName
793 		  );
794 
795 	    return is;
796 
797 	}
798 
799     }
800 
801 }
802 
803 
804 
805 /*
806  * Redistribution and use of this software and associated documentation
807  * ("Software"), with or without modification, are permitted provided
808  * that the following conditions are met:
809  *
810  * 1. Redistributions of source code must retain copyright
811  *    statements and notices.  Redistributions must also contain a
812  *    copy of this document.
813  *
814  * 2. Redistributions in binary form must reproduce the
815  *    above copyright notice, this list of conditions and the
816  *    following disclaimer in the documentation and/or other
817  *    materials provided with the distribution.
818  *
819  * 3. The name "DOM4J" must not be used to endorse or promote
820  *    products derived from this Software without prior written
821  *    permission of MetaStuff, Ltd.  For written permission,
822  *    please contact dom4j-info@metastuff.com.
823  *
824  * 4. Products derived from this Software may not be called "DOM4J"
825  *    nor may "DOM4J" appear in their names without prior written
826  *    permission of MetaStuff, Ltd. DOM4J is a registered
827  *    trademark of MetaStuff, Ltd.
828  *
829  * 5. Due credit should be given to the DOM4J Project - 
830  *    http://www.dom4j.org
831  *
832  * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS
833  * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
834  * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
835  * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
836  * METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
837  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
838  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
839  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
840  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
841  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
842  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
843  * OF THE POSSIBILITY OF SUCH DAMAGE.
844  *
845  * Copyright 2001-2004 (C) MetaStuff, Ltd. All Rights Reserved.
846  *
847  * $Id: TestAelfred.java,v 1.3 2004/06/25 08:03:49 maartenc Exp $
848  */