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