1
2
3
4
5
6
7
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
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
132
133
134
135
136
137
138
139
140 DocumentType expected = new DefaultDocumentType();
141
142 expected.setElementName
143 ( "greeting"
144 );
145
146 expected.setInternalDeclarations
147 ( getInternalDeclarations()
148 );
149
150
151
152
153
154 try {
155
156 assertSameDocumentType
157 ( expected,
158 readDocument
159 ( INPUT_XML_INTERNAL_FILE,
160 true,
161 false
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
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
199
200
201
202 try {
203
204 assertSameDocumentType
205 ( expected,
206 readDocument
207 ( INPUT_XML_EXTERNAL_FILE,
208 false,
209 true
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
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
252
253
254
255 try {
256
257 assertSameDocumentType
258 ( expected,
259 readDocument
260 ( INPUT_XML_MIXED_FILE,
261 true,
262 true
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
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
356
357
358 if( expected == null ) {
359
360 if( actual == null ) {
361
362 return;
363
364 } else {
365
366 fail( "Not expecting DOCTYPE."
367 );
368
369 }
370
371 } else {
372
373
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
390
391 assertSameDTDSubset
392 ( "Internal",
393 expected.getInternalDeclarations(),
394 actual.getInternalDeclarations()
395 );
396
397
398
399 assertSameDTDSubset
400 ( "External",
401 expected.getExternalDeclarations(),
402 actual.getExternalDeclarations()
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
428
429
430 if( expected == null ) {
431
432 if( actual == null ) {
433
434 return;
435
436 } else {
437
438 fail( "Not expecting "+label+" DTD subset."
439 );
440
441 }
442
443 } else {
444
445
446
447
448 if( actual == null ) {
449
450 fail( "Expecting "+label+" DTD subset."
451 );
452
453 }
454
455
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
467
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
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848