001: /* Copyright 2002-2005 Elliotte Rusty Harold
002:
003: This library is free software; you can redistribute it and/or modify
004: it under the terms of version 2.1 of the GNU Lesser General Public
005: License as published by the Free Software Foundation.
006:
007: This library is distributed in the hope that it will be useful,
008: but WITHOUT ANY WARRANTY; without even the implied warranty of
009: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
010: GNU Lesser General Public License for more details.
011:
012: You should have received a copy of the GNU Lesser General Public
013: License along with this library; if not, write to the
014: Free Software Foundation, Inc., 59 Temple Place, Suite 330,
015: Boston, MA 02111-1307 USA
016:
017: You can contact Elliotte Rusty Harold by sending e-mail to
018: elharo@metalab.unc.edu. Please include the word "XOM" in the
019: subject line. The XOM home page is located at http://www.xom.nu/
020: */
021:
022: package nu.xom.tests;
023:
024: import junit.framework.ComparisonFailure;
025: import junit.framework.TestCase;
026: import nu.xom.Attribute;
027: import nu.xom.Comment;
028: import nu.xom.DocType;
029: import nu.xom.Document;
030: import nu.xom.Element;
031: import nu.xom.Namespace;
032: import nu.xom.Node;
033: import nu.xom.ProcessingInstruction;
034: import nu.xom.Text;
035:
036: /**
037: * <p>
038: * Provides utility methods to compare nodes for deep equality in an
039: * infoset sense.
040: * </p>
041: *
042: * @author Elliotte Rusty Harold
043: * @version 1.1a2
044: *
045: */
046: public class XOMTestCase extends TestCase {
047:
048: /**
049: * <p>
050: * Create a new <code>XOMTestCase</code> with the specified name.
051: * </p>
052: */
053: public XOMTestCase(String name) {
054: super (name);
055: }
056:
057: /**
058: * <p>
059: * Asserts that two text nodes are equal. Text nodes are considered
060: * equal if they are identical char by char, or if both are null.
061: * Unicode and whitespace normalization is not performed before
062: * comparison. If the two nodes are not equal, a
063: * <code>ComparisonFailure</code> is thrown.
064: * </p>
065: *
066: * @param expected the text the test should produce
067: * @param actual the text the test does produce
068: *
069: * @throws ComparisonFailure if the text nodes are not equal
070: */
071: public static void assertEquals(Text expected, Text actual) {
072: assertEquals(null, expected, actual);
073: }
074:
075: /**
076: * <p>
077: * Asserts that two text nodes are equal. Text nodes are considered
078: * equal if they are identical char by char, or if both are null.
079: * Unicode and whitespace normalization is not performed before
080: * comparison. If the two nodes are not equal, a
081: * <code>ComparisonFailure</code> is thrown with the given
082: * message.
083: * </p>
084: *
085: * @param message printed if the texts are not equal
086: * @param expected the text the test should produce
087: * @param actual the text the test does produce
088: *
089: * @throws ComparisonFailure if the text nodes are not equal
090: */
091: public static void assertEquals(String message, Text expected,
092: Text actual) {
093:
094: if (actual == expected)
095: return;
096: nullCheck(message, expected, actual);
097:
098: assertEquals(message, expected.getValue(), actual.getValue());
099: }
100:
101: private static void nullCheck(String message, Node expected,
102: Node actual) {
103:
104: if (expected == null) {
105: throw new ComparisonFailure(message, null, actual.toXML());
106: } else if (actual == null) {
107: throw new ComparisonFailure(message, expected.toXML(), null);
108: }
109:
110: }
111:
112: /**
113: * <p>
114: * Asserts that two attribute nodes are equal.
115: * Attribute nodes are considered equal if their
116: * qualified names, namespace URIs, and values
117: * are equal. The type is not considered because it tends not to
118: * survive a roundtrip. If the two nodes are not equal, a
119: * <code>ComparisonFailure</code> is thrown.
120: * </p>
121: *
122: * <p>
123: * There is special handling for the <code>xml:base</code>
124: * attribute. In order to facilitate comparison between relative
125: * and absolute URIs, two <code>xml:base</code> attributes are
126: * considered equal if one might be a relative form of the other.
127: * </p>
128: *
129: * @param expected the attribute the test should produce
130: * @param actual the attribute the test does produce
131: *
132: * @throws ComparisonFailure if the sttributes are not equal
133: */
134: public static void assertEquals(Attribute expected, Attribute actual) {
135: assertEquals(null, expected, actual);
136: }
137:
138: /**
139: * <p>
140: * Asserts that two attribute nodes are equal.
141: * Attribute nodes are considered equal if their
142: * qualified names, namespace URIs, and values
143: * are equal. The type is not considered because this tends not to
144: * survive a roundtrip. If the two nodes are not equal, a
145: * <code>ComparisonFailure</code> is thrown with the given
146: * message.
147: * </p>
148: *
149: * <p>
150: * There is special handling for the <code>xml:base</code>
151: * attribute. In order to facilitate comparison between relative and
152: * absolute URIs, two <code>xml:base</code> attributes are
153: * considered equal if one might be a relative form of the other.
154: * </p>
155: *
156: * @param message printed if the attributes are not equal
157: * @param expected the attribute the test should produce
158: * @param actual the attribute the test does produce
159: *
160: * @throws ComparisonFailure if the attributes are not equal
161: */
162: public static void assertEquals(String message, Attribute expected,
163: Attribute actual) {
164:
165: if (actual == expected)
166: return;
167: nullCheck(message, expected, actual);
168:
169: String value1 = expected.getValue();
170: String value2 = actual.getValue();
171: if ("xml:base".equals(expected.getQualifiedName())) {
172: // handle possibility that one is relative and other is not
173: if (value1.equals(value2))
174: return;
175: if (value1.startsWith("../")) {
176: assertTrue(message, value2
177: .endsWith(value1.substring(2)));
178: } else {
179: assertTrue(message, value1.endsWith('/' + value2)
180: || value2.endsWith('/' + value1));
181: }
182: } else {
183: assertEquals(message, value1, value2);
184: assertEquals(message, expected.getLocalName(), actual
185: .getLocalName());
186: assertEquals(message, expected.getQualifiedName(), actual
187: .getQualifiedName());
188: assertEquals(message, expected.getNamespaceURI(), actual
189: .getNamespaceURI());
190: }
191:
192: }
193:
194: /**
195: * <p>
196: * Asserts that two <code>DocType</code> nodes are equal.
197: * <code>DocType</code> nodes are considered equal if their
198: * root element names, public IDs, and system IDs
199: * are equal. The internal DTD subsets are not considered.
200: * If the two nodes are not equal, a
201: * <code>ComparisonFailure</code> is thrown.
202: * </p>
203: *
204: * @param expected the DOCTYPE declaration the test should produce
205: * @param actual the DOCTYPE declaration the test does produce
206: *
207: * @throws ComparisonFailure if the document type declarations
208: * are not equal
209: */
210: public static void assertEquals(DocType expected, DocType actual) {
211: assertEquals(null, expected, actual);
212: }
213:
214: /**
215: * <p>
216: * Asserts that two <code>DocType</code> nodes are equal.
217: * <code>DocType</code> nodes are considered equal if their
218: * root element name, public ID, and system ID
219: * are equal. The internal DTD subsets are not considered.
220: * If the two nodes are not equal, a
221: * <code>ComparisonFailure</code> is thrown with the given
222: * message.
223: * </p>
224: *
225: * @param message printed if the DOCTYPE declarations are not equal
226: * @param expected the DOCTYPE declaration the test should produce
227: * @param actual the DOCTYPE declaration the test does produce
228: *
229: * @throws ComparisonFailure if the document type declarations
230: * are not equal
231: *
232: */
233: public static void assertEquals(String message, DocType expected,
234: DocType actual) {
235:
236: if (actual == expected)
237: return;
238: nullCheck(message, expected, actual);
239:
240: assertEquals(message, expected.getPublicID(), actual
241: .getPublicID());
242: assertEquals(message, expected.getSystemID(), actual
243: .getSystemID());
244: assertEquals(message, expected.getRootElementName(), actual
245: .getRootElementName());
246: }
247:
248: /**
249: * <p>
250: * Asserts that two element nodes are equal.
251: * Element nodes are considered equal if their
252: * qualified names, namespace URI, attributes,
253: * declared namespaces, and children
254: * are equal. Consecutive text node children are coalesced
255: * before the comparison is made. If the two nodes are not equal,
256: * a <code>ComparisonFailure</code> is thrown.
257: * </p>
258: *
259: * @param expected the element the test should produce
260: * @param actual the element the test does produce
261: *
262: * @throws ComparisonFailure if the elements are not equal
263: */
264: public static void assertEquals(Element expected, Element actual) {
265: assertEquals(null, expected, actual);
266:
267: }
268:
269: /**
270: * <p>
271: * Asserts that two element nodes are equal.
272: * Element nodes are considered equal if their
273: * qualified names, namespace URI, attributes,
274: * declared namespaces, and children
275: * are equal. Consecutive text node children are coalesced
276: * before the comparison is made. If the two nodes are not equal,
277: * a <code>ComparisonFailure</code> is thrown with the given
278: * message.
279: * </p>
280: *
281: * @param message printed if the elements are not equal
282: * @param expected the element the test should produce
283: * @param actual the element the test does produce
284: *
285: * @throws ComparisonFailure if the elements are not equal
286: */
287: public static void assertEquals(String message, Element expected,
288: Element actual) {
289:
290: if (actual == expected)
291: return;
292: nullCheck(message, expected, actual);
293:
294: assertEquals(message, expected.getLocalName(), actual
295: .getLocalName());
296: assertEquals(message, expected.getNamespacePrefix(), actual
297: .getNamespacePrefix());
298: assertEquals(message, expected.getNamespaceURI(), actual
299: .getNamespaceURI());
300:
301: assertEquals(message, expected.getAttributeCount(), actual
302: .getAttributeCount());
303:
304: for (int i = 0; i < expected.getAttributeCount(); i++) {
305: Attribute att1 = expected.getAttribute(i);
306: Attribute att2 = actual.getAttribute(att1.getLocalName(),
307: att1.getNamespaceURI());
308: assertNotNull(message, att2);
309: assertEquals(message, att1, att2);
310: }
311:
312: // Check declared namespaces by listing all the prefixes
313: // on element1 and making sure element2 gives the same value
314: // for those prefixes, and vice versa. This is necessary
315: // to handle a few weird cases that arise in XInclude
316: // when prefixes are declared multiple times, to account for
317: // the fact that some serializers may drop redundant
318: // namespace declarations.
319: for (int i = 0; i < expected.getNamespaceDeclarationCount(); i++) {
320: String prefix1 = expected.getNamespacePrefix(i);
321: String uri1 = expected.getNamespaceURI(prefix1);
322: assertNotNull(message, actual.getNamespaceURI(prefix1));
323: assertEquals(message, uri1, actual.getNamespaceURI(prefix1));
324: }
325: for (int i = 0; i < actual.getNamespaceDeclarationCount(); i++) {
326: String prefix1 = actual.getNamespacePrefix(i);
327: String uri1 = actual.getNamespaceURI(prefix1);
328: assertNotNull(message, expected.getNamespaceURI(prefix1));
329: assertEquals(message, uri1, expected
330: .getNamespaceURI(prefix1));
331: }
332:
333: compareChildren(message, expected, actual);
334:
335: }
336:
337: private static boolean hasAdjacentTextNodes(Element element) {
338:
339: boolean previousWasText = false;
340: int count = element.getChildCount();
341: for (int i = 0; i < count; i++) {
342: Node child = element.getChild(i);
343: if (child instanceof Text) {
344: if (previousWasText)
345: return true;
346: else
347: previousWasText = true;
348: } else {
349: previousWasText = false;
350: }
351: }
352: return false;
353:
354: }
355:
356: private static void compareChildren(String message,
357: Element expected, Element actual) {
358:
359: Element expectedCopy = expected;
360: Element actualCopy = actual;
361: if (hasAdjacentTextNodes(expected)) {
362: expectedCopy = combineTextNodes(expected);
363: }
364: if (hasAdjacentTextNodes(actual)) {
365: actualCopy = combineTextNodes(actual);
366: }
367:
368: int count = expectedCopy.getChildCount();
369: assertEquals(message, count, actualCopy.getChildCount());
370: int nonTextNodes = count;
371: for (int i = 0; i < count; i++) {
372: Node child1 = expectedCopy.getChild(i);
373: // could remove this instanceof Test by having combineTextNodes
374: // set a list of text indices
375: if (child1 instanceof Text) {
376: nonTextNodes--;
377: Node child2 = actualCopy.getChild(i);
378: assertEquals(message, child1, child2);
379: }
380: }
381:
382: // now compare everything that isn't text using the original
383: // element objects
384: for (int i = 0; i < nonTextNodes; i++) {
385: Node expectedChild = getNonTextNode(expected, i);
386: Node actualChild = getNonTextNode(actual, i);
387: assertEquals(message, expectedChild, actualChild);
388: }
389:
390: }
391:
392: private static Node getNonTextNode(Element element, int index) {
393:
394: int nonTextCount = 0;
395: int count = element.getChildCount();
396: for (int i = 0; i < count; i++) {
397: Node child = element.getChild(i);
398: if (!(child instanceof Text)) {
399: if (nonTextCount == index)
400: return child;
401: nonTextCount++;
402: }
403: }
404: throw new RuntimeException(
405: "Bug in XOMTestCase: this statement should not be reachable");
406:
407: }
408:
409: /* We only need to make an element that has the combined text
410: * nodes, and something as a child placeholder.
411: * It does need to have the other pieces.
412: */
413: private static Element combineTextNodes(Element element) {
414:
415: Element stub = new Element("a");
416: Comment stubc = new Comment("c");
417: StringBuffer sb = new StringBuffer();
418: int count = element.getChildCount();
419: for (int i = 0; i < count; i++) {
420: Node child = element.getChild(i);
421: if (child instanceof Text) {
422: sb.setLength(0);
423: do {
424: sb.append(child.getValue());
425: i++;
426: if (i == count) {
427: break;
428: }
429: child = element.getChild(i);
430: } while (child instanceof Text);
431: i--;
432: stub.appendChild(sb.toString());
433: } else {
434: stub.appendChild(stubc.copy());
435: }
436: }
437: return stub;
438:
439: }
440:
441: /**
442: * <p>
443: * Asserts that two document nodes are equal.
444: * Document nodes are considered equal if their
445: * children are equal. If the two nodes are not equal,
446: * a <code>ComparisonFailure</code> is thrown.
447: * </p>
448: *
449: * @param expected the document the test should produce
450: * @param actual the document the test does produce
451: *
452: * @throws ComparisonFailure if the documents are not equal
453: */
454: public static void assertEquals(Document expected, Document actual) {
455: assertEquals(null, expected, actual);
456: }
457:
458: /**
459: * <p>
460: * Asserts that two document nodes are equal.
461: * Document nodes are considered equal if their
462: * children are equal. If the two nodes are not equal,
463: * a <code>ComparisonFailure</code> is thrown with the given
464: * message.
465: * </p>
466: *
467: * @param message printed if the documents are not equal
468: * @param expected the document the test should produce
469: * @param actual the document the test does produce
470: *
471: * @throws ComparisonFailure if the documents are not equal
472: */
473: public static void assertEquals(String message, Document expected,
474: Document actual) {
475:
476: if (actual == expected)
477: return;
478: nullCheck(message, expected, actual);
479:
480: assertEquals(message, expected.getChildCount(), actual
481: .getChildCount());
482: for (int i = 0; i < actual.getChildCount(); i++) {
483: Node child1 = expected.getChild(i);
484: Node child2 = actual.getChild(i);
485: assertEquals(message, child1, child2);
486: }
487:
488: }
489:
490: /**
491: * <p>
492: * Asserts that two comment nodes are equal. Comment nodes are
493: * considered equal if they are identical char by char, or if both
494: * are null. Unicode and whitespace normalization is not performed
495: * before comparison. If the two nodes are not equal, a
496: * <code>ComparisonFailure</code> is thrown.
497: * </p>
498: *
499: * @param expected the comment the test should produce
500: * @param actual the comment the test does produce
501: *
502: * @throws ComparisonFailure if the comments are not equal
503: */
504: public static void assertEquals(Comment expected, Comment actual) {
505: assertEquals(null, expected, actual);
506: }
507:
508: /**
509: * <p>
510: * Asserts that two comment nodes are equal. Comment nodes are considered
511: * equal if they are identical char by char, or if both are null.
512: * Unicode and whitespace normalization is not performed before
513: * comparison. If the two nodes are not equal, a
514: * <code>ComparisonFailure</code> is thrown with the given
515: * message.
516: * </p>
517: *
518: * @param message printed if the comments are not equal
519: * @param expected the comment the test should produce
520: * @param actual the comment the test does produce
521: *
522: * @throws ComparisonFailure if the comments are not equal
523: */
524: public static void assertEquals(String message, Comment expected,
525: Comment actual) {
526:
527: if (actual == expected)
528: return;
529: nullCheck(message, expected, actual);
530: assertEquals(message, expected.getValue(), actual.getValue());
531:
532: }
533:
534: /**
535: * <p>
536: * Asserts that two processing instruction nodes are equal.
537: * Processing instruction nodes are considered
538: * equal if they have the same target and the same value.
539: * If the two nodes are not equal, a
540: * <code>ComparisonFailure</code> is thrown.
541: * </p>
542: *
543: * @param expected the processing instruction the test should produce
544: * @param actual the processing instruction the test does produce
545: *
546: * @throws ComparisonFailure if the processing instructions
547: * are not equal
548: */
549: public static void assertEquals(ProcessingInstruction expected,
550: ProcessingInstruction actual) {
551: assertEquals(null, expected, actual);
552: }
553:
554: /**
555: * <p>
556: * Asserts that two processing instruction nodes are equal.
557: * Processing instruction nodes are considered
558: * equal if they have the same target and the same value.
559: * If the two nodes are not equal, a
560: * <code>ComparisonFailure</code> is thrown with the given
561: * message.
562: * </p>
563: *
564: * @param message printed if the processing instructions are
565: * not equal
566: * @param expected the processing instruction the test
567: * should produce
568: * @param actual the processing instruction the test does produce
569: *
570: * @throws ComparisonFailure if the processing instructions
571: * are not equal
572: */
573: public static void assertEquals(String message,
574: ProcessingInstruction expected, ProcessingInstruction actual) {
575:
576: if (actual == expected)
577: return;
578: nullCheck(message, expected, actual);
579:
580: assertEquals(message, expected.getValue(), actual.getValue());
581: assertEquals(message, expected.getTarget(), actual.getTarget());
582:
583: }
584:
585: /**
586: * <p>
587: * Asserts that two namespace nodes are equal.
588: * Namespace nodes are considered
589: * equal if they have the same prefix and the same URI.
590: * If the two nodes are not equal, a
591: * <code>ComparisonFailure</code> is thrown with the given
592: * message.
593: * </p>
594: *
595: * @param message printed if the namespaces are not equal
596: * @param expected the namespace the test should produce
597: * @param actual the namespace the test does produce
598: *
599: * @throws ComparisonFailure if the namespaces are not equal
600: */
601: public static void assertEquals(String message, Namespace expected,
602: Namespace actual) {
603:
604: if (actual == expected)
605: return;
606: nullCheck(message, expected, actual);
607:
608: assertEquals(message, expected.getValue(), actual.getValue());
609: assertEquals(message, expected.getPrefix(), actual.getPrefix());
610:
611: }
612:
613: /**
614: * <p>
615: * Asserts that two nodes are equal. If the two nodes are not
616: * equal a <code>ComparisonFailure</code> is thrown.
617: * The subclass is not considered. The basic XOM class
618: * is considered, but the subclass is not. For example,
619: * a <code>Text</code> object can be equal to an object that
620: * is an <code>HTMLText</code>, but it can never be equal to
621: * a <code>Comment</code>.
622: * </p>
623: *
624: * @param expected the node the test should produce
625: * @param actual the node the test does produce
626: *
627: * @throws ComparisonFailure if the nodes are not equal
628: */
629: public static void assertEquals(Node expected, Node actual) {
630: assertEquals(null, expected, actual);
631: }
632:
633: /**
634: * <p>
635: * Asserts that two nodes are equal. If the two nodes are not
636: * equal a <code>ComparisonFailure</code> is thrown with the given
637: * message. The subclass is not considered. The basic XOM class
638: * is considered, but the subclass is not. For example,
639: * a <code>Text</code> object can be equal to an an
640: * <code>HTMLText</code> object, but it can never be equal to
641: * a <code>Comment</code>.
642: * </p>
643: *
644: * @param message printed if the nodes are not equal
645: * @param expected the node the test should produce
646: * @param actual the node the test does produce
647: *
648: * @throws ComparisonFailure if the nodes are not equal
649: */
650: public static void assertEquals(String message, Node expected,
651: Node actual) {
652:
653: if (actual == expected)
654: return;
655: nullCheck(message, expected, actual);
656:
657: try {
658: if (expected instanceof Document) {
659: assertEquals(message, (Document) expected,
660: (Document) actual);
661: } else if (expected instanceof Element) {
662: assertEquals(message, (Element) expected,
663: (Element) actual);
664: } else if (expected instanceof Text) {
665: assertEquals(message, (Text) expected, (Text) actual);
666: } else if (expected instanceof DocType) {
667: assertEquals(message, (DocType) expected,
668: (DocType) actual);
669: } else if (expected instanceof Comment) {
670: assertEquals(message, (Comment) expected,
671: (Comment) actual);
672: } else if (expected instanceof ProcessingInstruction) {
673: assertEquals(message, (ProcessingInstruction) expected,
674: (ProcessingInstruction) actual);
675: } else if (expected instanceof Attribute) {
676: assertEquals(message, (Attribute) expected,
677: (Attribute) actual);
678: } else if (expected instanceof Namespace) {
679: assertEquals(message, (Namespace) expected,
680: (Namespace) actual);
681: } else {
682: throw new IllegalArgumentException(
683: "Unexpected node type "
684: + expected.getClass().getName());
685: }
686: } catch (ClassCastException ex) {
687: throw new ComparisonFailure(message
688: + "; Mismatched node types: "
689: + expected.getClass().getName() + " != "
690: + actual.getClass().getName(), expected.toXML(),
691: actual.toXML());
692: }
693:
694: }
695:
696: }
|