1   
2   
3   
4   
5   
6   
7   
8   package org.dom4j.io;
9   
10  import junit.framework.AssertionFailedError;
11  
12  import junit.textui.TestRunner;
13  
14  import java.io.FileInputStream;
15  import java.io.IOException;
16  import java.io.InputStream;
17  import java.util.ArrayList;
18  import java.util.Iterator;
19  import java.util.List;
20  
21  import org.dom4j.AbstractTestCase;
22  import org.dom4j.Document;
23  import org.dom4j.DocumentType;
24  import org.dom4j.dtd.AttributeDecl;
25  import org.dom4j.dtd.ElementDecl;
26  import org.dom4j.dtd.ExternalEntityDecl;
27  import org.dom4j.dtd.InternalEntityDecl;
28  import org.dom4j.tree.DefaultDocumentType;
29  
30  import org.xml.sax.EntityResolver;
31  import org.xml.sax.InputSource;
32  import org.xml.sax.SAXException;
33  
34  /***
35   * Tests the DocType functionality.
36   * 
37   * <p>
38   * Incorporated additional test cases for optional processing of the internal
39   * and external DTD subsets. The "external" and "mixed" tests both <strong>fail
40   * </strong> due to a reported bug. See http://tinyurl.com/4dzyq
41   * </p>
42   * 
43   * @author <a href="mailto:jstrachan@apache.org">James Strachan </a>
44   * @version $Revision: 1.4 $
45   */
46  public class DTDTest extends AbstractTestCase {
47      /***
48       * Input XML file to read <code>xml/dtd/internal.xml</code>- document
49       * using internal DTD subset, but no external DTD subset.
50       */
51      private static final String XML_INTERNAL_FILE = "xml/dtd/internal.xml";
52  
53      /***
54       * Input XML file to read <code>xml/dtd/external.xml</code>- document
55       * using external DTD subset, but no internal DTD subset. The external
56       * entity should be locatable by either PUBLIC or SYSTEM identifier. The
57       * testing harness should use an appropriate EntityResolver to locate the
58       * external entity as a local resource (no internet access).
59       */
60      private static final String XML_EXTERNAL_FILE = "xml/dtd/external.xml";
61  
62      /***
63       * Input XML file to read <code>xml/dtd/mixed.xml</code>- document using
64       * both an internal and an external DTD subset. The external entity should
65       * be locatable by either PUBLIC or SYSTEM identifier. The testing harness
66       * should use an appropriate EntityResolver to locate the external entity as
67       * a local resource (no internet access).
68       */
69      private static final String XML_MIXED = "xml/dtd/mixed.xml";
70  
71      /***
72       * Input XML file to for {@linkEntityResolver}
73       * <code>xml/dtd/sample.dtd</code>- the external entity providing the
74       * external DTD subset for test cases that need one. The SYSTEM identifier
75       * for this external entity is given by {@link#DTD_SYSTEM_ID}.
76       */
77      private static final String DTD_FILE = "xml/dtd/sample.dtd";
78  
79      /***
80       * The PUBLIC identifier, which is <code>-//dom4j//DTD sample</code>, for
81       * the external entity providing DTD for tests.
82       */
83      protected static final String DTD_PUBLICID = "-//dom4j//DTD sample";
84  
85      /***
86       * The SYSTEM identifier, which is <code>sample.dtd</code>, for the
87       * external entity providing DTD for tests.
88       */
89      protected static final String DTD_SYSTEM_ID = "sample.dtd";
90  
91      public static void main(String[] args) {
92          TestRunner.run(DTDTest.class);
93      }
94  
95      
96      
97  
98      /***
99       * Test verifies correct identification of the internal DTD subset and
100      * correct non-presence of the external DTD subset.
101      * 
102      * @throws Exception
103      *             DOCUMENT ME!
104      */
105     public void testInternalDTDSubset() throws Exception {
106         
107 
108 
109 
110 
111 
112 
113 
114 
115         DocumentType expected = new DefaultDocumentType();
116 
117         expected.setElementName("greeting");
118 
119         expected.setInternalDeclarations(getInternalDeclarations());
120 
121         
122 
123 
124 
125         try {
126             assertSameDocumentType(expected, readDocument(
127                     XML_INTERNAL_FILE, true, false).getDocType());
128         } catch (AssertionFailedError ex) {
129             throw ex;
130         } catch (Throwable t) {
131             fail("Not expecting: " + t);
132         }
133     }
134 
135     /***
136      * Test verifies correct identification of the external DTD subset and
137      * correct non-presence of the internal DTD subset.
138      */
139     public void testExternalDTDSubset() {
140         
141 
142 
143         DocumentType expected = new DefaultDocumentType("another-greeting",
144                 null, DTD_SYSTEM_ID);
145 
146         expected.setExternalDeclarations(getExternalDeclarations());
147 
148         
149 
150 
151 
152         try {
153             assertSameDocumentType(expected, readDocument(
154                     XML_EXTERNAL_FILE, false, true).getDocType());
155         } catch (AssertionFailedError ex) {
156             throw ex;
157         } catch (Throwable t) {
158             fail("Not expecting: " + t);
159         }
160     }
161 
162     /***
163      * Test verifies correct identification of the internal and external DTD
164      * subsets.
165      */
166     public void testMixedDTDSubset() {
167         
168 
169 
170         DocumentType expected = new DefaultDocumentType("another-greeting",
171                 null, DTD_SYSTEM_ID);
172 
173         expected.setInternalDeclarations(getInternalDeclarations());
174 
175         expected.setExternalDeclarations(getExternalDeclarations());
176 
177         
178 
179 
180 
181         try {
182             assertSameDocumentType(expected, readDocument(XML_MIXED,
183                     true, true).getDocType());
184         } catch (AssertionFailedError ex) {
185             throw ex;
186         } catch (Throwable t) {
187             fail("Not expecting: " + t);
188         }
189     }
190 
191     
192     
193 
194     /***
195      * Test helper method returns a {@link List}of DTD declarations that
196      * represents the expected internal DTD subset (for the tests that use an
197      * internal DTD subset).
198      * 
199      * <p>
200      * Note: The declarations returned by this method MUST agree those actually
201      * declared in {@link #XML_INTERNAL_FILE}and {@link
202      * #XML_MIXED}.
203      * </p>
204      * 
205      * <p>
206      * </p>
207      * 
208      * @return DOCUMENT ME!
209      */
210     protected List getInternalDeclarations() {
211         List decls = new ArrayList();
212 
213         decls.add(new ElementDecl("greeting", "(#PCDATA)"));
214 
215         decls.add(new AttributeDecl("greeting", "foo", "ID", "#IMPLIED", null));
216 
217         decls.add(new InternalEntityDecl("%boolean", "( true | false )"));
218 
219         return decls;
220     }
221 
222     /***
223      * Test helper method returns a {@link List}of DTD declarations that
224      * represents the expected external DTD subset (for the tests that use an
225      * external DTD subset).
226      * 
227      * @return DOCUMENT ME!
228      */
229     protected List getExternalDeclarations() {
230         List decls = new ArrayList();
231 
232         decls.add(new ElementDecl("another-greeting", "(#PCDATA)"));
233 
234         return decls;
235     }
236 
237     /***
238      * Test helper method compares the expected and actual {@link DocumentType}
239      * objects, including their internal and external DTD subsets.
240      * 
241      * <p>
242      * </p>
243      * 
244      * @param expected
245      *            DOCUMENT ME!
246      * @param actual
247      *            DOCUMENT ME!
248      */
249     protected void assertSameDocumentType(DocumentType expected,
250             DocumentType actual) {
251         
252 
253 
254         if (expected == null) {
255             if (actual == null) {
256                 return; 
257             } else {
258                 fail("Not expecting DOCTYPE.");
259             }
260         } else {
261             
262 
263 
264             if (actual == null) {
265                 fail("Expecting DOCTYPE");
266             }
267 
268             log("Expected DocumentType:\n" + expected.toString());
269 
270             log("Actual DocumentType:\n" + actual.toString());
271 
272             
273             assertSameDTDSubset("Internal", expected.getInternalDeclarations(),
274                     actual.getInternalDeclarations());
275 
276             
277             assertSameDTDSubset("External", expected.getExternalDeclarations(),
278                     actual.getExternalDeclarations());
279         }
280     }
281 
282     /***
283      * Test helper method compares an expected set of DTD declarations with an
284      * actual set of DTD declarations. This method should be invoked seperately
285      * for the internal DTD subset and the external DTD subset. The declarations
286      * must occur in their logical ordering. See <a
287      * href="http://tinyurl.com/5jhd8">Lexical Handler </a> for conformance
288      * criteria.
289      * 
290      * @param txt
291      *            DOCUMENT ME!
292      * @param expected
293      *            DOCUMENT ME!
294      * @param actual
295      *            DOCUMENT ME!
296      * 
297      * @throws AssertionError
298      *             DOCUMENT ME!
299      */
300     protected void assertSameDTDSubset(String txt, List expected, List actual) {
301         
302 
303 
304         if (expected == null) {
305             if (actual == null) {
306                 return; 
307             } else {
308                 fail("Not expecting " + txt + " DTD subset.");
309             }
310         } else {
311             
312 
313 
314             if (actual == null) {
315                 fail("Expecting " + txt + " DTD subset.");
316             }
317 
318             
319 
320 
321             assertEquals(txt + " DTD subset has correct #of declarations"
322                     + ": expected=[" + expected.toString() + "]" + ", actual=["
323                     + actual.toString() + "]", expected.size(), actual.size());
324 
325             
326 
327 
328 
329             Iterator itr1 = expected.iterator();
330 
331             Iterator itr2 = actual.iterator();
332 
333             while (itr1.hasNext()) {
334                 Object obj1 = itr1.next();
335 
336                 Object obj2 = itr2.next();
337 
338                 assertEquals(txt + " DTD subset: Same type of declaration",
339                         obj1.getClass().getName(), obj2.getClass().getName());
340 
341                 if (obj1 instanceof AttributeDecl) {
342                     assertSameDecl((AttributeDecl) obj1, (AttributeDecl) obj2);
343                 } else if (obj1 instanceof ElementDecl) {
344                     assertSameDecl((ElementDecl) obj1, (ElementDecl) obj2);
345                 } else if (obj1 instanceof InternalEntityDecl) {
346                     assertSameDecl((InternalEntityDecl) obj1,
347                             (InternalEntityDecl) obj2);
348                 } else if (obj1 instanceof ExternalEntityDecl) {
349                     assertSameDecl((ExternalEntityDecl) obj1,
350                             (ExternalEntityDecl) obj2);
351                 } else {
352                     throw new AssertionError("Unexpected declaration type: "
353                             + obj1.getClass());
354                 }
355             }
356         }
357     }
358 
359     /***
360      * Test helper method compares an expected and an actual {@link
361      * AttributeDecl}.
362      * 
363      * @param expected
364      *            DOCUMENT ME!
365      * @param actual
366      *            DOCUMENT ME!
367      */
368     public void assertSameDecl(AttributeDecl expected, AttributeDecl actual) {
369         assertEquals("attributeName is correct", expected.getAttributeName(),
370                 actual.getAttributeName());
371 
372         assertEquals("elementName is correct", expected.getElementName(),
373                 actual.getElementName());
374 
375         assertEquals("type is correct", expected.getType(), actual.getType());
376 
377         assertEquals("value is not correct", expected.getValue(), actual
378                 .getValue());
379 
380         assertEquals("valueDefault is correct", expected.getValueDefault(),
381                 actual.getValueDefault());
382 
383         assertEquals("toString() is correct", expected.toString(), actual
384                 .toString());
385     }
386 
387     /***
388      * Test helper method compares an expected and an actual {@link
389      * ElementDecl}.
390      * 
391      * @param expected
392      *            DOCUMENT ME!
393      * @param actual
394      *            DOCUMENT ME!
395      */
396     protected void assertSameDecl(ElementDecl expected, ElementDecl actual) {
397         assertEquals("name is correct", expected.getName(), actual.getName());
398 
399         assertEquals("model is not correct", expected.getModel(), actual
400                 .getModel());
401 
402         assertEquals("toString() is correct", expected.toString(), actual
403                 .toString());
404     }
405 
406     /***
407      * Test helper method compares an expected and an actual {@link
408      * InternalEntityDecl}.
409      * 
410      * @param expected
411      *            DOCUMENT ME!
412      * @param actual
413      *            DOCUMENT ME!
414      */
415     protected void assertSameDecl(InternalEntityDecl expected,
416             InternalEntityDecl actual) {
417         assertEquals("name is correct", expected.getName(), actual.getName());
418 
419         assertEquals("value is not correct", expected.getValue(), actual
420                 .getValue());
421 
422         assertEquals("toString() is correct", expected.toString(), actual
423                 .toString());
424     }
425 
426     /***
427      * Test helper method compares an expected and an actual {@link
428      * ExternalEntityDecl}.
429      * 
430      * @param expected
431      *            DOCUMENT ME!
432      * @param actual
433      *            DOCUMENT ME!
434      */
435     protected void assertSameDecl(ExternalEntityDecl expected,
436             ExternalEntityDecl actual) {
437         assertEquals("name is correct", expected.getName(), actual.getName());
438 
439         assertEquals("publicID is correct", expected.getPublicID(), actual
440                 .getPublicID());
441 
442         assertEquals("systemID is correct", expected.getSystemID(), actual
443                 .getSystemID());
444 
445         assertEquals("toString() is correct", expected.toString(), actual
446                 .toString());
447     }
448 
449     /***
450      * Helper method reads a local resource and parses it as an XML document.
451      * The internal and external DTD subsets are optionally retained by the
452      * parser and exposed via the {@link DocumentType}object on the returned
453      * {@link Document}. The parser is configured with an {@link
454      * EntityResolver}that knows how to find the local resource identified by
455      * {@link #DTD_FILE}whose SYSTEM identifier is given by {@link
456      * #DTD_SYSTEM_ID}.
457      * 
458      * @param resourceName
459      *            DOCUMENT ME!
460      * @param includeInternal
461      *            DOCUMENT ME!
462      * @param includeExternal
463      *            DOCUMENT ME!
464      * 
465      * @return DOCUMENT ME!
466      * 
467      * @throws Exception
468      *             DOCUMENT ME!
469      */
470     protected Document readDocument(String resourceName,
471             boolean includeInternal, boolean includeExternal) throws Exception {
472         SAXReader reader = new SAXReader();
473 
474         reader.setIncludeInternalDTDDeclarations(includeInternal);
475 
476         reader.setIncludeExternalDTDDeclarations(includeExternal);
477 
478         reader.setEntityResolver(new MyEntityResolver(DTD_FILE,
479                 DTD_PUBLICID, DTD_SYSTEM_ID));
480 
481         return getDocument(resourceName, reader);
482     }
483 
484     /***
485      * Provides a resolver for the local test DTD resource.
486      */
487     protected static class MyEntityResolver implements EntityResolver {
488         private String resourceName;
489 
490         private String pubId;
491 
492         private String sysId;
493 
494         public MyEntityResolver(String localResourceName, String publicId,
495                 String systemId) {
496             resourceName = localResourceName;
497 
498             sysId = systemId;
499         }
500 
501         public InputSource resolveEntity(String publicId, String systemId)
502                 throws SAXException, IOException {
503             if (pubId != null) {
504                 if (pubId.equals(publicId)) {
505                     return new InputSource(getInputStream(resourceName));
506                 }
507             }
508 
509             if (sysId.equals(systemId)) {
510                 return new InputSource(getInputStream(resourceName));
511             } else {
512                 return null;
513             }
514         }
515 
516         /***
517          * Returns an {@link InputStream}that will read from the indicated
518          * local resource.
519          * 
520          * @param localResourceName
521          *            DOCUMENT ME!
522          * 
523          * @return DOCUMENT ME!
524          * 
525          * @throws IOException
526          *             DOCUMENT ME!
527          */
528         protected InputStream getInputStream(String localResourceName)
529                 throws IOException {
530             InputStream is = new FileInputStream(localResourceName);
531 
532             return is;
533         }
534     }
535 }
536 
537 
538 
539 
540 
541 
542 
543 
544 
545 
546 
547 
548 
549 
550 
551 
552 
553 
554 
555 
556 
557 
558 
559 
560 
561 
562 
563 
564 
565 
566 
567 
568 
569 
570 
571 
572