From: <bo...@us...> - 2007-04-14 16:18:34
|
Revision: 185 http://xmlunit.svn.sourceforge.net/xmlunit/?rev=185&view=rev Author: bodewig Date: 2007-04-14 09:18:35 -0700 (Sat, 14 Apr 2007) Log Message: ----------- complete user's guide Modified Paths: -------------- trunk/xmlunit/src/user-guide/XMLUnit-Java.xml Modified: trunk/xmlunit/src/user-guide/XMLUnit-Java.xml =================================================================== --- trunk/xmlunit/src/user-guide/XMLUnit-Java.xml 2007-04-14 16:16:11 UTC (rev 184) +++ trunk/xmlunit/src/user-guide/XMLUnit-Java.xml 2007-04-14 16:18:35 UTC (rev 185) @@ -39,7 +39,7 @@ </revhistory> </articleinfo> - <section><title>A Tour of XMLUnit</title> + <section id="A Tour of XMLUnit"><title>A Tour of XMLUnit</title> <para>This first section contains a tour through XMLUnit's features, the next sections will cover them in more detail.</para> @@ -49,7 +49,7 @@ XMLUnit, but not the only one. XMLUnit's features can be fully used without any dependency on JUnit at all.</para> - <section><title>What is XMLUnit?</title> + <section id="What is XMLUnit?"><title>What is XMLUnit?</title> <para>XMLUnit enables JUnit-style assertions to be made about the content and structure of XML<footnote id="more on @@ -71,7 +71,7 @@ generate and the message that the system actually generated. And that was the beginning of XMLUnit.</para> </section> - <section><title>Quick tour</title> + <section id="Quick Tour"><title>Quick tour</title> <para>XMLUnit provides a single JUnit extension class, <literal>XMLTestCase</literal>, and a set of supporting classes @@ -103,7 +103,7 @@ class).</para> </section> - <section><title>Glossary</title> + <section id="Glossary"><title>Glossary</title> <para>As with many projects some words in XMLUnit have particular meanings so here is a quick overview. A @@ -126,7 +126,7 @@ unrecoverable differences</emphasis> between them.</para> </section> - <section id="configuring-intro"><title>Configuring XMLUnit</title> + <section id="Configuring XMLUnit"><title>Configuring XMLUnit</title> <para>There are many Java XML parsers available, and XMLUnit should work with any JAXP compliant parser library, such as @@ -174,7 +174,8 @@ </section> - <section><title>Writing XML comparison tests</title> + <section id="Writing XML comparison tests"> + <title>Writing XML comparison tests</title> <para>Let's say we have two pieces of XML that we wish to compare and assert that they are equal. We could write a simple @@ -191,7 +192,7 @@ String myControlXML = "<msg><uuid>0x00435A8C</uuid></msg>"; String myTestXML = "<msg><localId>2376</localId></msg>"; assertXMLEqual("Comparing test xml to control xml", - myControlXML, myTestXML); + myControlXML, myTestXML); } }]]></programlisting></example> @@ -232,9 +233,9 @@ "<struct><boolean>false</boolean><int>3</int></struct>"; Diff myDiff = new Diff(myControlXML, myTestXML); assertTrue("XML similar " + myDiff.toString(), - myDiff.similar()); + myDiff.similar()); assertTrue("XML identical " + myDiff.toString(), - myDiff.identical()); + myDiff.identical()); }]]></programlisting></example> <para>This test fails as two pieces of XML are similar but not @@ -318,7 +319,7 @@ Diff myDiff = new Diff(myControlXML, myTestXML); myDiff.overrideDifferenceListener(myDifferenceListener); assertTrue("test XML matches control skeleton XML", - myDiff.similar()); + myDiff.similar()); }]]></programlisting></example> <para>The <literal>DifferenceEngine</literal> class generates @@ -376,7 +377,7 @@ </section> - <section id="transform-intro"> + <section id="Comparing XML Transformations"> <title>Comparing XML Transformations</title> <para>XMLUnit can test XSL transformations at a high level using @@ -422,7 +423,7 @@ </section> - <section><title>Validation Tests</title> + <section id="Validation Tests"><title>Validation Tests</title> <para>XML parsers that validate a piece of XML against a DTD are common, however they rely on a DTD reference being present in @@ -450,11 +451,11 @@ <para>Starting with XMLUnit 1.1, the <literal>Validator</literal> class can also validate against one or more XML Schema definitions. See <xref - linkend="validating-schema"/> for details.</para> + linkend="XML Schema Validation"/> for details.</para> </section> - <section><title>Xpath Tests</title> + <section id="Xpath Tests"><title>Xpath Tests</title> <para>One of the strengths of XML is the ability to programmatically extract specific parts of a document using @@ -557,7 +558,7 @@ </section> - <section id="intro-nodetest"> + <section id="Testing by Tree Walking"> <title>Testing by Tree Walking</title> <para>The DOM specification allows a <literal>Document</literal> @@ -657,9 +658,10 @@ </section> </section> - <section id="Using"><title>Using XMLUnit</title> + <section id="Using XMLUnit"> + <title>Using XMLUnit</title> - <section id="requirements"> + <section id="Requirements"> <title>Requirements</title> <para>XMLUnit requires a JAXP compliant XML parser virtually @@ -691,7 +693,7 @@ <literal>XMLAssert</literal> classes.</para> </section> - <section id="using-plain"> + <section id="Basic Usage"> <title>Basic Usage</title> <para>XMLUnit consists of a few classes all living in the @@ -705,7 +707,8 @@ found in the more specific sections later in this document.</para> - <section><title>Comparing Pieces of XML</title> + <section id="Basic: Comparing Pieces of XML"> + <title>Comparing Pieces of XML</title> <para>Heart and soul of XMLUnit's comparison engine is <literal>DifferenceEngine</literal> but most of the time you @@ -717,11 +720,11 @@ of options on the <literal>XMLUnit</literal> class.</para> <para>More information is available in <xref - linkend="Comparing"/>.</para> + linkend="Comparing Pieces of XML"/>.</para> </section> - <section><title>Validating</title> + <section id="Basic: Validating"><title>Validating</title> <para>All validation happens in the <literal>Validator</literal> class. The default is to @@ -733,11 +736,11 @@ affect validation.</para> <para>More information is available in <xref - linkend="Validating"/>.</para> + linkend="Validating XML Documents"/>.</para> </section> - <section><title>XSLT Transformations</title> + <section id="XSLT Transformations"><title>XSLT Transformations</title> <para>The <literal>Transform</literal> class provides an easy to use layer on top of JAXP's transformations. An instance of @@ -751,7 +754,7 @@ There is no detailed sections on transformations since they are really only a different way to create input for the rest of XMLUnit's machinery. Examples can be found in <xref - linkend="transform-intro"/>.</para> + linkend="Comparing XML Transformations"/>.</para> <para>It is possible to provide a custom <literal>javax.xml.transform.URIResolver</literal> via the @@ -761,7 +764,7 @@ <literal>XMLUnit.getTransformerFactory</literal>.</para> </section> - <section> + <section id="XPath Engine"> <title>XPath Engine</title> <para>The central piece of XMLUnit's XPath support is the @@ -794,10 +797,10 @@ <literal>String</literal>s.</para> <para>More information is available in <xref - linkend="XPath"/>.</para> + linkend="XPath Tests"/>.</para> </section> - <section> + <section id="Basic: DOM Tree Walking"> <title>DOM Tree Walking</title> <para>To test pieces of XML by traversing the DOM tree you use @@ -809,11 +812,11 @@ own.</para> <para>More information is available in <xref - linkend="NodeTest"/>.</para> + linkend="DOM Tree Walking"/>.</para> </section> </section> - <section id="junit3"> + <section id="Using XMLUnit With JUnit 3.x"> <title>Using XMLUnit With JUnit 3.x</title> <para>Initially XMLUnit was tightly coupled to JUnit and the @@ -839,7 +842,7 @@ </section> - <section id="common-config"> + <section id="Common Configuration Options"> <title>Common Configuration Options</title> <section id="JAXP"> @@ -852,7 +855,7 @@ since the versions shipping with some JDKs are known to contain serious bugs.</para> - <para>As described in <xref linkend="configuring-intro"/> + <para>As described in <xref linkend="Configuring XMLUnit"/> there are two main approaches to choose the XML parser of XSLT transformer: System properties and setters in the <literal>XMLUnit</literal> class.</para> @@ -888,7 +891,7 @@ </section> - <section id="entityresolver"> + <section id="EntityResolver"> <title><literal>EntityResolver</literal></title> <para>You can provide a custom @@ -900,7 +903,7 @@ <literal>setControlEntityResolver</literal> as well.</para> </section> - <section id="element-content-whitespace"> + <section id="Basic: Element Content Whitespace"> <title>Element Content Whitespace</title> <para>Element content whitespace - also known as ignorable @@ -925,12 +928,12 @@ validation by default. It does have an effect when comparing pieces of XML, though, since the same flag is used for a different purpose as well in that case. See <xref - linkend="comparing-config"/> for more details.</para> + linkend="Whitespace Handling"/> for more details.</para> </section> </section> - <section id="input-choices"> + <section id="Providing Input to XMLUnit"> <title>Providing Input to XMLUnit</title> <para>Most methods in XMLUnit that expect a piece of XML as @@ -990,9 +993,10 @@ </section> - <section id="Comparing"><title>Comparing Pieces of XML</title> + <section id="Comparing Pieces of XML"> + <title>Comparing Pieces of XML</title> - <section id="comparing-basics"> + <section id="The Difference Engine"> <title>The Difference Engine</title> <para>At the center of XMLUnit's support for comparisons is the @@ -1002,7 +1006,7 @@ classes (see <xref linkend="Diff"/>).</para> <para>The <literal>DifferenceEngine</literal> walks two trees of - DOM <literal>Node</literal>s, the control and the test tree and + DOM <literal>Node</literal>s, the control and the test tree, and compares the nodes. Whenever it detects a difference, it sends a message to a configured <literal>DifferenceListener</literal> (see <xref linkend="DifferenceListener"/>) and asks a @@ -1025,15 +1029,16 @@ <para>A <literal>Difference</literal> can be recoverable; recoverable <literal>Difference</literal>s make the - <literal>Diff</literal> class consider the two pieces of XML - similar while non-recoverable <literal>Difference</literal>s - render the two pieces different.</para> + <literal>Diff</literal> class consider two pieces of XML similar + while non-recoverable <literal>Difference</literal>s render the + two pieces different.</para> - <para>The following types of <literal>Difference</literal>s are - currently detected (the first two columns refer to the - <literal>DifferenceConstants</literal> class):</para> + <para>The types of <literal>Difference</literal>s that are + currently detected are listed in <xref linkend="docleveldiff"/> + to <xref linkend="otherdiff"/> (the first two columns refer to + the <literal>DifferenceConstants</literal> class).</para> - <table frame="all" rules="all" pgwide="1"> + <table frame="all" rules="all" pgwide="1" id="docleveldiff"> <title>Document level <literal>Difference</literal>s detected by <literal>DifferenceEngine</literal></title> <tgroup cols="4"> @@ -1046,7 +1051,7 @@ <row> <entry><literal>ID</literal></entry> <entry><literal>Constant</literal></entry> - <entry><literal>revoverable</literal></entry> + <entry><literal>recoverable</literal></entry> <entry align="center">Description</entry> </row> </thead> @@ -1068,16 +1073,16 @@ root element.</entry> </row> <row> + <entry><literal>DOCTYPE_PUBLIC_ID_ID</literal></entry> <entry><literal>DOCTYPE_PUBLIC_ID</literal></entry> - <entry><literal>DOCTYPE_PUBLIC_ID</literal></entry> <entry><literal>false</literal></entry> <entry>Both pieces of XML contain a DOCTYPE declaration but the declarations specify different PUBLIC identifiers.</entry> </row> <row> + <entry><literal>DOCTYPE_SYSTEM_ID_ID</literal></entry> <entry><literal>DOCTYPE_SYSTEM_ID</literal></entry> - <entry><literal>DOCTYPE_SYSTEM_ID</literal></entry> <entry><literal>true</literal></entry> <entry>Both pieces of XML contain a DOCTYPE declaration but the declarations specify different SYSTEM @@ -1088,7 +1093,10 @@ <entry><literal>NODE_TYPE</literal></entry> <entry><literal>false</literal></entry> <entry>The test piece of XML contains a different type - of node than was expected.</entry> + of node than was expected. This type of difference will + also occur if either the root control or test + <literal>Node</literal> is <literal>null</literal> while + the other is not.</entry> </row> <row> <entry><literal>NAMESPACE_PREFIX_ID</literal></entry> @@ -1108,7 +1116,7 @@ </tgroup> </table> - <table frame="all" rules="all" pgwide="1"> + <table frame="all" rules="all" pgwide="1" id="elementleveldiff"> <title>Element level <literal>Difference</literal>s detected by <literal>DifferenceEngine</literal></title> <tgroup cols="4"> @@ -1121,7 +1129,7 @@ <row> <entry><literal>ID</literal></entry> <entry><literal>Constant</literal></entry> - <entry><literal>revoverable</literal></entry> + <entry><literal>recoverable</literal></entry> <entry align="center">Description</entry> </row> </thead> @@ -1168,13 +1176,20 @@ <entry><literal>ATTR_SEQUENCE</literal></entry> <entry><literal>true</literal></entry> <entry>The attributes on an element appear in different - order in the two pieces of XML.</entry> + order<footnote><para>Note that the order of attributes + is not significant in XML, different parsers may return + attributes in a different order even if parsing the same + XML document. There is an option to turn this check off + - see <xref linkend="Comparing: Configuration"/> - but it is on + by default for backwards compatibility + reasons</para></footnote> in the two pieces of + XML.</entry> </row> </tbody> </tgroup> </table> - <table frame="all" rules="all" pgwide="1"> + <table frame="all" rules="all" pgwide="1" id="attributeleveldiff"> <title>Attribute level <literal>Difference</literal>s detected by <literal>DifferenceEngine</literal></title> @@ -1188,7 +1203,7 @@ <row> <entry><literal>ID</literal></entry> <entry><literal>Constant</literal></entry> - <entry><literal>revoverable</literal></entry> + <entry><literal>recoverable</literal></entry> <entry align="center">Description</entry> </row> </thead> @@ -1198,10 +1213,14 @@ <entry><literal>ATTR_VALUE_EXPLICITLY_SPECIFIED_ID</literal></entry> <entry><literal>ATTR_VALUE_EXPLICITLY_SPECIFIED</literal></entry> <entry><literal>true</literal></entry> - <entry>An attribute that is not required and has a - default value according to the content model of the - element in question has been specified explicitly in one - piece of XML but not in the other.</entry> + <entry>An attribute that has a default value according + to the content model of the element in question has been + specified explicitly in one piece of XML but not in the + other.<footnote><para>In order for this difference to be + detected the parser must have been in validating mode + when the piece of XML was parsed and the DTD or XML + Schema must have been available.</para></footnote></entry> + </row> <row> <entry><literal>ATTR_NAME_NOT_FOUND_ID</literal></entry> @@ -1221,7 +1240,7 @@ </tgroup> </table> - <table frame="all" rules="all" pgwide="1"> + <table frame="all" rules="all" pgwide="1" id="otherdiff"> <title>Other <literal>Difference</literal>s detected by <literal>DifferenceEngine</literal></title> @@ -1235,7 +1254,7 @@ <row> <entry><literal>ID</literal></entry> <entry><literal>Constant</literal></entry> - <entry><literal>revoverable</literal></entry> + <entry><literal>recoverable</literal></entry> <entry align="center">Description</entry> </row> </thead> @@ -1281,12 +1300,556 @@ </tgroup> </table> - <para>Note that some of the listed differences may be ignored by + <para>Note that some of the differences listed may be ignored by the <literal>DifferenceEngine</literal> if certain configuration options have been specified. See <xref - linkend="comparing-config"/> for details.</para> + linkend="Comparing: Configuration"/> for details.</para> + + <para><literal>DifferenceEngine</literal> passes differences + found around as instances of the <literal>Difference</literal> + class. In addition to the type of of difference this class also + holds information on the nodes that have been found to be + different. The nodes are described by + <literal>NodeDetail</literal> instances that encapsulate the DOM + <literal>Node</literal> instance as well as the XPath expression + that locates the <literal>Node</literal> inside the given piece + of XML. <literal>NodeDetail</literal> also contains a "value" + that provides more information on the actual values that have + been found to be different, the concrete interpretation depends + on the type of difference as can be seen in <xref + linkend="diffvalue"/>.</para> + + <table frame="all" rules="all" pgwide="0" id="diffvalue"> + <title>Contents of <literal>NodeDetail.getValue()</literal> + for <literal>Difference</literal>s</title> + + <tgroup cols="2"> + <colspec colname="id" align="center"/> + <colspec colname="value" align="left"/> + + <thead> + <row> + <entry><literal>Difference.getId()</literal></entry> + <entry align="center"><literal>NodeDetail.getValue()</literal></entry> + </row> + </thead> + + <tbody> + <row> + <entry><literal>HAS_DOCTYPE_DECLARATION_ID</literal></entry> + <entry><literal>"not null"</literal> if the document has + a DOCTYPE declaration, <literal>"null"</literal> + otherwise.</entry> + </row> + <row> + <entry><literal>DOCTYPE_NAME_ID</literal></entry> + <entry>The name of the root element.</entry> + </row> + <row> + <entry><literal>DOCTYPE_PUBLIC_ID</literal></entry> + <entry>The PUBLIC identifier.</entry> + </row> + <row> + <entry><literal>DOCTYPE_SYSTEM_ID</literal></entry> + <entry>The SYSTEM identifier.</entry> + </row> + <row> + <entry><literal>NODE_TYPE_ID</literal></entry> + <entry>If one node was absent: <literal>"not + null"</literal> if the node exists, + <literal>"null"</literal> otherwise. If the node types + differ the value will be a string-ified version of + <literal>org.w3c.dom.Node.getNodeType()</literal>.</entry> + </row> + <row> + <entry><literal>NAMESPACE_PREFIX_ID</literal></entry> + <entry>The Namespace prefix.</entry> + </row> + <row> + <entry><literal>NAMESPACE_URI_ID</literal></entry> + <entry>The Namespace URI.</entry> + </row> + <row> + <entry><literal>ELEMENT_TAG_NAME_ID</literal></entry> + <entry>The tag name with any Namespace information + stripped.</entry> + </row> + <row> + <entry><literal>ELEMENT_NUM_ATTRIBUTES_ID</literal></entry> + <entry>The number of attributes present turned into a + <literal>String</literal>.</entry> + </row> + <row> + <entry><literal>HAS_CHILD_NODES_ID</literal></entry> + <entry><literal>"true"</literal> if the element has + child nodes, <literal>"false"</literal> + otherwise.</entry> + </row> + <row> + <entry><literal>CHILD_NODELIST_LENGTH_ID</literal></entry> + <entry>The number of child nodes present turned into a + <literal>String</literal>.</entry> + </row> + <row> + <entry><literal>CHILD_NODELIST_SEQUENCE_ID</literal></entry> + <entry>The sequence number of this child node turned into a + <literal>String</literal>.</entry> + </row> + <row> + <entry><literal>ATTR_SEQUENCE_ID</literal></entry> + <entry>The attribute's name.</entry> + </row> + <row> + <entry><literal>ATTR_VALUE_EXPLICITLY_SPECIFIED_ID</literal></entry> + <entry><literal>"true"</literal> if the attribute has + been specified, <literal>"false"</literal> + otherwise.</entry> + </row> + <row> + <entry><literal>ATTR_NAME_NOT_FOUND_ID</literal></entry> + <entry>The attribute's name or <literal>null</literal>.</entry> + </row> + <row> + <entry><literal>ATTR_VALUE_ID</literal></entry> + <entry>The attribute's value.</entry> + </row> + <row> + <entry><literal>COMMENT_VALUE_ID</literal></entry> + <entry>The actual comment.</entry> + </row> + <row> + <entry><literal>PROCESSING_INSTRUCTION_TARGET_ID</literal></entry> + <entry>The processing instruction's target.</entry> + </row> + <row> + <entry><literal>PROCESSING_INSTRUCTION_DATA_ID</literal></entry> + <entry>The processing instruction's data.</entry> + </row> + + <row> + <entry><literal>CDATA_VALUE_ID</literal></entry> + <entry>The content of the CDATA section.</entry> + </row> + <row> + <entry><literal>TEXT_VALUE_ID</literal></entry> + <entry>The actual text.</entry> + </row> + </tbody> + </tgroup> + </table> + + <para>As said in the first paragraph you won't deal with + <literal>DifferenceEngine</literal> directly in most cases. In + cases where <literal>Diff</literal> or + <literal>DetailedDiff</literal> don't provide what you need + you'd create an instance of <literal>DifferenceEngine</literal> + passing a <literal>ComparisonController</literal> in the + constructor and invoke <literal>compare</literal> with your DOM + trees to compare as well as a + <literal>DifferenceListener</literal> and + <literal>ElementQualifier</literal>. The listener will be + called on any differences while the <literal>control</literal> + method is executing.</para> + + <example> + <title>Using <literal>DifferenceEngine</literal> + Directly</title> + <programlisting language="Java"><![CDATA[ +class MyDifferenceListener implements DifferenceListener { + private boolean calledFlag = false; + public boolean called() { return calledFlag; } + + public int differenceFound(Difference difference) { + calledFlag = true; + return RETURN_ACCEPT_DIFFERENCE; + } + + public void skippedComparison(Node control, Node test) { + } +} + +DifferenceEngine engine = new DifferenceEngine(myComparisonController); +MyDifferenceListener listener = new MyDifferenceListener(); +engine.compare(controlNode, testNode, listener, + myElementQualifier); +System.err.println("There have been " + + (listener.called() ? "" : "no ") + + "differences."); +]]></programlisting></example> </section> + <section id="ComparisonController"> + <title><literal>ComparisonController</literal></title> + + <para>The <literal>ComparisonController</literal>'s job is to + decide whether a comparison should be halted after a difference + has been found. Its interface is:</para> + + <programlisting language="Java"><![CDATA[ + /** + * Determine whether a Difference that the listener has been notified of + * should halt further XML comparison. Default behaviour for a Diff + * instance is to halt if the Difference is not recoverable. + * @see Difference#isRecoverable + * @param afterDifference the last Difference passed to <code>differenceFound</code> + * @return true to halt further comparison, false otherwise + */ + boolean haltComparison(Difference afterDifference); +]]></programlisting> + + <para>Whenever a difference has been detected by the + <literal>DifferenceEngine</literal> the + <literal>haltComparison</literal> method will be called + immediately after the <literal>DifferenceListener</literal> has + been informed of the difference. This is true no matter what + type of <literal>Difference</literal> has been found or which + value the <literal>DifferenceListener</literal> has + returned.</para> + + <para>The only implementations of + <literal>ComparisonController</literal> that ship with XMLUnit + are <literal>Diff</literal> and <literal>DetailedDiff</literal>, + see <xref linkend="Diff"/> for details about them.</para> + + <para>A <literal>ComparisonController</literal> that halted the + comparison on any non-recoverable difference could be + implemented as:</para> + + <example> + <title>A Simple + <literal>ComparisonController</literal></title> + <programlisting language="Java"><![CDATA[ +public class HaltOnNonRecoverable implements ComparisonController { + public boolean haltComparison(Difference afterDifference) { + return !afterDifference.isRecoverable(); + } +} +]]></programlisting></example> + </section> + + <section id="DifferenceListener"> + <title><literal>DifferenceListener</literal></title> + + <para><literal>DifferenceListener</literal> contains two + callback methods that are invoked by the + <literal>DifferenceEngine</literal> when differences are + detected:</para> + + <programlisting language="Java"><![CDATA[ + /** + * Receive notification that 2 nodes are different. + * @param difference a Difference instance as defined in {@link + * DifferenceConstants DifferenceConstants} describing the cause + * of the difference and containing the detail of the nodes that + * differ + * @return int one of the RETURN_... constants describing how this + * difference was interpreted + */ + int differenceFound(Difference difference); + + /** + * Receive notification that a comparison between 2 nodes has been skipped + * because the node types are not comparable by the DifferenceEngine + * @param control the control node being compared + * @param test the test node being compared + * @see DifferenceEngine + */ + void skippedComparison(Node control, Node test); +]]></programlisting> + + <para><literal>differenceFound</literal> is invoked by + <literal>DifferenceEngine</literal> as soon as a difference has + been detected. The return value of that method is completely + ignored by <literal>DifferenceEngine</literal>, it becomes + important when used together with <literal>Diff</literal>, + though (see <xref linkend="Diff"/>). The return value should be + one of the three constants defined in the the + <literal>DifferenceListener</literal> interface:</para> + + <programlisting language="Java"><![CDATA[ + /** + * Standard return value for the <code>differenceFound</code> method. + * Indicates that the <code>Difference</code> is interpreted as defined + * in {@link DifferenceConstants DifferenceConstants}. + */ + int RETURN_ACCEPT_DIFFERENCE; + /** + * Override return value for the <code>differenceFound</code> method. + * Indicates that the nodes identified as being different should be + * interpreted as being identical. + */ + int RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL; + /** + * Override return value for the <code>differenceFound</code> method. + * Indicates that the nodes identified as being different should be + * interpreted as being similar. + */ + int RETURN_IGNORE_DIFFERENCE_NODES_SIMILAR; +]]></programlisting> + + <para>The <literal>skippedComparison</literal> method is + invoked if the <literal>DifferenceEngine</literal> encounters + two <literal>Node</literal>s it cannot compare. Before invoking + <literal>skippedComparison</literal> + <literal>DifferenceEngine</literal> will have invoked + <literal>differenceFound</literal> with a + <literal>Difference</literal> of type + <literal>NODE_TYPE</literal>.</para> + + <para>A custom <literal>DifferenceListener</literal> that + ignored any DOCTYPE related differences could be written + as:</para> + + <example> + <title>A <literal>DifferenceListener</literal> that Ignores + DOCTYPE Differences</title> + <programlisting language="Java"><![CDATA[ +public class IgnoreDoctype implements DifferenceListener { + private static final int[] IGNORE = new int[] { + DifferenceConstants.HAS_DOCTYPE_DECLARATION_ID, + DifferenceConstants.DOCTYPE_NAME_ID, + DifferenceConstants.DOCTYPE_PUBLIC_ID_ID, + DifferenceConstants.DOCTYPE_SYSTEM_ID_ID + }; + + static { + Arrays.sort(IGNORE); + } + + public int differenceFound(Difference difference) { + return Arrays.binarySearch(IGNORE, difference.getId()) >= 0 + ? RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL + : RETURN_ACCEPT_DIFFERENCE; + } + + public void skippedComparison(Node control, Node test) { + } +} +]]></programlisting></example> + + <para>Apart from <literal>Diff</literal> and + <literal>DetailedDiff</literal> XMLUnit ships with an additional + implementation of <literal>DifferenceListener</literal>.</para> + + <section id="IgnoreTextAndAttributeValuesDifferenceListener"> + <title><literal>IgnoreTextAndAttributeValuesDifferenceListener</literal></title> + + <para><literal>IgnoreTextAndAttributeValuesDifferenceListener</literal> + doesn't do anything in <literal>skippedComparison</literal>. + It "downgrades" <literal>Difference</literal>s of type + <literal>ATTR_VALUE</literal>, + <literal>ATTR_VALUE_EXPLICITLY_SPECIFIED</literal> and + <literal>TEXT_VALUE</literal> to recoverable + differences.</para> + + <para>This means if instances of + <literal>IgnoreTextAndAttributeValuesDifferenceListener</literal> + are used together with <literal>Diff</literal> then two pieces + of XML will be considered similar if they have the same basic + structure. They are not considered identical, though.</para> + + <para>Note that the list of ignored differences doesn't cover + all textual differences. You should configure XMLUnit to + ignore comments and whitespace and to consider CDATA sections + and text nodes to be the same (see <xref + linkend="Comparing: Configuration"/>) in order to cover + <literal>COMMENT_VALUE</literal> and + <literal>CDATA_VALUE</literal> as well.</para> + + </section> + </section> + + <section id="ElementQualifier"> + <title><literal>ElementQualifier</literal></title> + + <para>When <literal>DifferenceEngine</literal> encounters a list + of DOM <literal>Element</literal>s as children of another + <literal>Element</literal> it will ask the configured + <literal>ElementQualifier</literal> which + <literal>Element</literal> of the control piece of XML should be + compared to which of the test piece. Its contract is:</para> + + <programlisting language="Java"><![CDATA[ + /** + * Determine whether two elements are comparable + * @param control an Element from the control XML NodeList + * @param test an Element from the test XML NodeList + * @return true if the elements are comparable, false otherwise + */ + boolean qualifyForComparison(Element control, Element test); +]]></programlisting> + + <para>For any given <literal>Element</literal> in the control + piece of XML <literal>DifferenceEngine</literal> will cycle + through the corresponding list of <literal>Element</literal>s in + the test piece of XML until + <literal>qualifyForComparison</literal> has returned + <literal>true</literal> or the test document is + exhausted.</para> + + <para>When using <literal>DifferenceEngine</literal> or + <literal>Diff</literal> it is completely legal to set the + <literal>ElementQualifier</literal> to <literal>null</literal>. + In this case any kind of <literal>Node</literal> is compared to + the test <literal>Node</literal> that appears at the same + position in the sequence.</para> + + <example id="eq-nodelist-example"> + <title>Example Nodes for <literal>ElementQualifier</literal> + (the comments are not part of the example)</title> + <programlisting language="XML"><![CDATA[ +<!-- control piece of XML --> +<parent> + <child1/> <!-- control node 1 --> + <child2/> <!-- control node 2 --> + <child2 foo="bar">xyzzy</child2> <!-- control node 3 --> + <child2 foo="baz"/> <!-- control node 4 --> +</parent> + +<!-- test piece of XML --> +<parent> + <child2 foo="baz"/> <!-- test node 1 --> + <child1/> <!-- test node 2 --> + <child2>xyzzy</child2> <!-- test node 3 --> + <child2 foo="bar"/> <!-- test node 4 --> +</parent> +]]></programlisting></example> + + <para>Taking <xref linkend="eq-nodelist-example"/> without any + <literal>ElementQualifier</literal> + <literal>DifferenceEngine</literal> will compare control node + <literal>n</literal> to test node <literal>n</literal> for + <literal>n</literal> between 1 and 4. In many cases this is + exactly what is desired, but sometimes + <literal><![CDATA[<a><b/><c/></a>]]></literal> should be similar + to <literal><![CDATA[<a><c/><b/></a>]]></literal> because the + order of elements doesn't matter - this is when you'd use a + different <literal>ElementQualifier</literal>. XMLUnit ships + with several implementations.</para> + + <section id="ElementNameQualifier"> + <title><literal>ElementNameQualifier</literal></title> + + <para>Only <literal>Element</literal>s with the same name - + and Namespace URI if present - qualify.</para> + + <para>In <xref linkend="eq-nodelist-example"/> this means + control node 1 will be compared to test node 2. Then control + node 2 will be compared to test node 3 because + <literal>DifferenceEngine</literal> will start to search for + the matching test <literal>Element</literal> at the second + test node, the same sequence number the control node is at. + Control node 3 is compared to test node 3 as well and control + node 4 to test node 4.</para> + </section> + + <section id="ElementNameAndAttributeQualifier"> + <title><literal>ElementNameAndAttributeQualifier</literal></title> + + <para>Only <literal>Element</literal>s with the same name - + and Namespace URI if present - as well as the same values for + all attributes given in + <literal>ElementNameAndAttributeQualifier</literal>'s + constructor qualify.</para> + + <para>Let's say <literal>"foo"</literal> has been passed to + <literal>ElementNameAndAttributeQualifier</literal>'s + constructor when looking at <xref + linkend="eq-nodelist-example"/>. This again means control + node 1 will be compared to test node 2 since they do have the + same name and no value at all for attribute + <literal>"foo"</literal>. Then control node 2 will be + compared to test node 3 - again, no value for + <literal>"foo"</literal>. Control node 3 is compared to test + node 4 as they have the same value <literal>"bar"</literal>. + Finally control node 4 is compared to test node 1; here + <literal>DifferenceEngine</literal> searches from the + beginning of the test node list after test node 4 didn't + match.</para> + + <para>There are three constructors in + <literal>ElementNameAndAttributeQualifier</literal>. The + no-arg constructor creates an instance that compares all + attributes while the others will compare a single attribute or + a given subset of all attributes.</para> + </section> + + <section id="ElementNameAndTextQualifier"> + <title><literal>ElementNameAndTextQualifier</literal></title> + + <para>Only <literal>Element</literal>s with the same name - + and Namespace URI if present - as well as the same text + content nested into them qualify.</para> + + <para>In <xref linkend="eq-nodelist-example"/> this means + control node 1 will be compared to test node 2 since they both + don't have any nested text at all. Then control node 2 will + be compared to test node 4. Control node 3 is compared to + test node 3 since they have the same nested text and control + node 4 to test node 4.</para> + </section> + + <section id="MultiLevelElementNameAndTextQualifier"> + <title><literal>org.custommonkey.xmlunit.examples.MultiLevelElementNameAndTextQualifier</literal></title> + + <para>All <literal>ElementQualifier</literal>s seen so far + only looked at the <literal>Element</literal>s themselves and + not at the structure nested into them at a deeper level. A + frequent user question has been which + <literal>ElementQualifier</literal> should be used if the + pieces of XML in <xref linkend="htmltable"/> should be + considered similar.</para> + + <example id="htmltable"> + <title>Example for + <literal>MultiLevelElementNameAndTextQualifier</literal> + (the comments are not part of the example)</title> + <programlisting language="HTML"><![CDATA[ +<!-- control --> +<table> + <tr> <!-- control row 1 --> + <td>foo</td> + </tr> + <tr> <!-- control row 2 --> + <td>bar</td> + </tr> +</table> + +<!-- test --> +<table> + <tr> <!-- test row 1 --> + <td>bar</td> + </tr> + <tr> <!-- test row 2 --> + <td>foo</td> + </tr> +</table> +]]></programlisting></example> + + <para>At first glance + <literal>ElementNameAndTextQualifier</literal> should work but + it doesn't. When <literal>DifferenceEngine</literal> + processed the children of <literal>table</literal> it would + compare control row 1 to test row 1 since both + <literal>tr</literal> elements have the same name and both + have no textual content at all.</para> + + <para>What is needed in this case is an + <literal>ElementQualifier</literal> that looks at the element's + name, as well as the name of the first child element and the + text nested into that first child element. This is what + <literal>MultiLevelElementNameAndTextQualifier</literal> does. + <literal>MultiLevelElementNameAndTextQualifier</literal>'s + constructor expects a single argument which is the nesting + level of the expected text. If you use an argument of 1, + <literal>MultiLevelElementNameAndTextQualifier</literal> is + identical to <literal>ElementNameAndTextQualifier</literal>. + In <xref linkend="htmltable"/> a value of 2 would be + needed.</para> + </section> + + </section> + <section id="Diff"> <title><literal>Diff</literal> and <literal>DetailedDiff</literal></title> @@ -1301,34 +1864,319 @@ is what <literal>Diff</literal> does) and finding all differences between them (this is what <literal>DetailedDiff</literal> does).</para> - </section> - <section id="ElementQualifier"> - <title><literal>ElementQualifier</literal></title> - </section> + <para><literal>DetailedDiff</literal> is a subclass of + <literal>Diff</literal> and can only be constructed by creating + a <literal>Diff</literal> instance first.</para> - <section id="DifferenceListener"> - <title><literal>DifferenceListener</literal></title> - </section> + <para>The major difference between them is their implementation + of the <literal>ComparisonController</literal> interface: + <literal>DetailedDiff</literal> will never stop the comparison + since it wants to collect all differences. + <literal>Diff</literal> in turn will halt the comparison as soon + as the first <literal>Difference</literal> is found that is not + recoverable. In addition <literal>DetailedDiff</literal> + collects all <literal>Difference</literal>s in a list and + provides access to it.</para> - <section id="ComparisonController"> - <title><literal>ComparisonController</literal></title> + <para>By default <literal>Diff</literal> will consider two + pieces of XML as identical if no differences have been found at + all, similar if all differences that have been found have been + recoverable (see <xref linkend="docleveldiff"/> to <xref + linkend="otherdiff"/>) and different as soon as any + non-recoverable difference has been found.</para> + + <para>It is possible to specify a + <literal>DifferenceListener</literal> to <literal>Diff</literal> + using the <literal>overrideDifferenceListener</literal> method. + In this case each <literal>Difference</literal> will be + evaluated by the passed in + <literal>DifferenceListener</literal>. By returning + <literal>RETURN_IGNORE_DIFFERENCE_NODES_IDENTICAL</literal> the + custom listener can make <literal>Diff</literal> ignore the + difference completely. Likewise any + <literal>Difference</literal> for which the custom listener + returns + <literal>RETURN_IGNORE_DIFFERENCE_NODES_SIMILAR</literal> will + be treated as if the <literal>Difference</literal> was + recoverable.</para> + + <para>There are several overloads of the <literal>Diff</literal> + constructor that allow you to specify your piece of XML in many + ways. There are overloads that accept additional + <literal>DifferenceEngine</literal> and + <literal>ElementQualifier</literal> arguments. Passing in a + <literal>DifferenceEngine</literal> of your own is the only way + to use a <literal>ComparisonController</literal> other than + <literal>Diff</literal>.</para> + + <para>Note that <literal>Diff</literal> and + <literal>DetailedDiff</literal> use + <literal>ElementNameQualifier</literal> as their default + <literal>ElementQualifier</literal>. This is different from + <literal>DifferenceEngine</literal> which defaults to no + <literal>ElementQualifier</literal> at all.</para> + + <para>To use a custom <literal>ElementQualifier</literal> you + can also use the <literal>overrideElementQualifier</literal> + method. Use this with an argument of <literal>null</literal> to + unset the default <literal>ElementQualifier</literal> as + well.</para> + + <para>To compare two pieces of XML you'd create a + <literal>Diff</literal> instance from those two pieces and + invoke <literal>identical</literal> to check that there have + been no differences at all and <literal>similar</literal> to + check that any difference, if any, has been recoverable. If the + pieces are identical they are also similar. Likewise if they + are not similar they can't be identical either.</para> + + <example> + <title>Comparing Two Pieces of XML Using + <literal>Diff</literal></title> + <programlisting language="Java"><![CDATA[ +Diff d = new Diff("<a><b/><c/></a>", "<a><c/><b/></a>"); +assertFalse(d.identical()); // CHILD_NODELIST_SEQUENCE Difference +assertTrue(d.similar()); +]]></programlisting></example> + + <para>The result of the comparison is cached in + <literal>Diff</literal>, repeated invocations of + <literal>identical</literal> or <literal>similar</literal> will + not reevaluate the pieces of XML.</para> + + <para><literal>DetailedDiff</literal> provides only a single + constructor that expects a <literal>Diff</literal> as argument. + Don't use <literal>DetailedDiff</literal> if all you need to + know is whether two pieces of XML are identical/similar - use + <literal>Diff</literal> directly since its short-cut + <literal>ComparisonController</literal> implementation will save + time in this case.</para> + + <example> + <title>Finding All Differences Using + <literal>DetailedDiff</literal></title> + <programlisting language="Java"><![CDATA[ +Diff d = new Diff("<a><b/><c/></a>", "<a><c/><b/></a>"); +DetailedDiff dd = new DetailedDiff(d); +dd.overrideElementQualifier(null); +assertFalse(dd.similar()); +List l = dd.getAllDifferences(); +assertEquals(2, l.size()); // expected <b/> but was <c/> and vice versa +]]></programlisting></example> + </section> - <section id="comparing-junit3"> + <section id="Comparing: JUnit 3"> <title>JUnit 3.x Convenience Methods</title> + + <para><literal>XMLAssert</literal> and + <literal>XMLTestCase</literal> contain quite a few overloads of + methods for comparing two pieces of XML.</para> + + <para>The method's names use the word <literal>Equal</literal> + to mean the same as <literal>similar</literal> in the + <literal>Diff</literal> class (or throughout this guide). So + <literal>assertXMLEqual</literal> will assert that only + recoverable differences have been encountered where + <literal>assertXMLNotEqual</literal> asserts that some + differences have been non-recoverable. + <literal>assertXMLIdentical</literal> asserts that there haven't + been any differences at all while + <literal>assertXMLNotIdentical</literal> asserts that there have + been differences (recoverable or not).</para> + + <para>Most of the overloads of <literal>assertXMLEqual</literal> + just provide different means to specify the pieces of XML as + <literal>String</literal>s, <literal>InputSource</literal>s, + <literal>Reader</literal>s<footnote><para>See <xref + linkend="Providing Input to XMLUnit"/> for some advice on choosing your input + format.</para></footnote> or <literal>Document</literal>s. For each + method there is a version that takes an additional + <literal>err</literal> argument which is used to create the + message if the assertion fails.</para> + + <para>If you don't need any control over the + <literal>ElementQualifier</literal> or + <literal>DifferenceListener</literal> used by + <literal>Diff</literal> these methods will save some boilerplate + code. If <literal>CONTROL</literal> and <literal>TEST</literal> + are pieces of XML represented as one of the supported inputs + then</para> + + <programlisting language="Java"><![CDATA[ +Diff d = new Diff(CONTROL, TEST); +assertTrue("expected pieces to be similar, " + d.toString(), + d.similar()); +]]></programlisting> + + <para>and</para> + + <programlisting language="Java"><![CDATA[ +assertXMLEqual("expected pieces to be similar", CONTROL, TEST); +]]></programlisting> + + <para>are equivalent.</para> + + <para>If you need more control over the <literal>Diff</literal> + instance there is a version of <literal>assertXMLEqual</literal> + (and <literal>assertXMLIdentical</literal>) that accepts a + <literal>Diff</literal> instance as its argument as well as a + <literal>boolean</literal> indicating whether you expect the + <literal>Diff</literal> to be <literal>similar</literal> + (<literal>identical</literal>) or not.</para> + + <para><literal>XMLTestCase</literal> contains a couple of + <literal>compareXML</literal> methods that really are only + shortcuts to <literal>Diff</literal>'s constructors.</para> + + <para>There is no way to use <literal>DifferenceEngine</literal> + or <literal>DetailedDiff</literal> directly via the convenience + methods.</para> + </section> - <section id="comparing-config"> + <section id="Comparing: Configuration"> <title>Configuration Options</title> + + <para>Unless you are using <literal>Document</literal> or + <literal>DOMSource</literal> overrides when specifying your + pieces of XML, XMLUnit will use the configured XML parsers (see + <xref linkend="JAXP"/>) and <literal>EntityResolver</literal>s + (see <xref linkend="EntityResolver"/>). There configuration + options to use different settings for the control and test + pieces of XML.</para> + + <para>In addition some of the other configuration settings may + lead to XMLUnit using the configured XSLT transformer (see <xref + linkend="JAXP"/>) under the covers.</para> + + <section id="Whitespace Handling"> + <title>Whitespace Handling</title> + + <para>Two different configuration options affect how XMLUnit + treats whitespace in comparisons:</para> + + <itemizedlist> + + <listitem>Element Content Whitespace (see <xref + linkend="Basic: Element Content Whitespace"/>) + + <para>If XMLUnit has been configured to ignore element + content whitespace it will trim any text nodes found by + the parser. This means that there won't appear to be any + textual content in element <literal><foo></literal> + for the following example. If you don't set + <literal>XMLUnit.setIgnoreWhitespace</literal> there would + be textual content consisting of a new line + character.</para> + + <programlisting language="XML"><![CDATA[ +<foo> +</foo> +]]></programlisting> + + <para>At the same time the following two + <literal><foo></literal> elements will be considered + identical if the option has been enabled, though.</para> + + <programlisting language="XML"><![CDATA[ +<foo>bar</foo> +<foo> bar </foo> +]]></programlisting> + + <para>When this option is set to <literal>true</literal>, + <literal>Diff</literal> will use the XSLT transformer + under the covers.</para> + </listitem> + + <listitem>"Normalizing" Whitespace + + <para>If you set + <literal>XMLUnit.setNormalizeWhitespace</literal> to true + then XMLUnit will replace any kind of whitespace found in + character content with a SPACE character and collapse + consecutive whitespace characters to a single SPACE. It + will also trim the resulting character content on both + ends.</para> + + <para>The following two <literal><foo></literal> + elements will be considered identical if the option has + been set:</para> + + <programlisting language="XML"><![CDATA[ +<foo>bar baz</foo> +<foo> bar + baz</foo> +]]></programlisting> + + <para>Note that this is not related to "normalizing" the + document as a whole (see <xref + linkend="Normalizing Documents"/>).</para> + + </listitem> + + </itemizedlist> + </section> + + <section id="Normalizing Documents"> + <title>"Normalizing" <literal>Document</literal>s</title> + + <para>"Normalize" in this context corresponds to the + <literal>normalize</literal> method in DOM's + <literal>Document</literal> class. It is the process of + merging adjacent <literal>Text</literal> nodes and is not + related to "normalizing whitespace" as described in the + previous section.</para> + + <para>Usually you don't need to care about this option since + the XML parser is required to normalize the + <literal>Document</literal> when creating it. The only reason + you may want to change the option via + <literal>XMLUnit.setNormalize</literal> is that your + <literal>Document</literal> instances have not been created by + an XML parser but rather been put together in memory using the + DOM API directly.</para> + </section> + + <section id="Ignoring Comments"> + <title>Ignoring Comments</title> + + <para>Using <literal>XMLUnit.setIgnoreComments</literal> you + can make XMLUnit's difference engine ignore comments + completely.</para> + + <para>When this option is set to <literal>true</literal>, + <literal>Diff</literal> will use the XSLT transformer under + the covers.</para> + </section> + + <section id="Treating CDATA Sections and Text Nodes Alike"> + <title>Treating CDATA Sections and Text Nodes Alike</title> + + <para>It is not always necessary to know whether a text has + been put into a CDATA section or not. Using + <literal>XMLUnit.setIgnoreDiffBetweenTextAndCDATA</literal> + you can make XMLUnit consider the following two pieces of XML + identical:</para> + + <programlisting language="XML"><![CDATA[ +<foo><bar></foo> +]]></programlisting> + <programlisting language="XML"> +<foo><![CDATA[<bar>]]></foo> +</programlisting> + </section> + </section> </section> - <section id="Validating"><title>Validating XML documents</title> + <section id="Validating XML Documents"> + <title>Validating XML Documents</title> - <section id="validating-basics"> - <title>The <literal>Validator</literal> class</title> + <section id="The Validator Class"> + <title>The <literal>Validator</literal> Class</title> <para>The <literal>Validator</literal> class encapsulates XMLUnit's validation support. It will use the @@ -1346,7 +2194,7 @@ multiple) Schema(s) as well. Schema validation requires an XML parser that supports it, of course.</para> - <section id="validating-dtd"> + <section id="DTD Validation"> <title>DTD Validation</title> </section> @@ -1357,12 +2205,12 @@ object using one of the single argument constructors.</para> <example> - <title>Validating against the DTD defined in + <title>Validating Against the DTD Defined in <literal>DOCTYPE</literal></title> <programlisting language="Java"><![CDATA[ InputSource is = new InputSource(new FileInputStream(myXmlDocument)); Validator v = new Validator(is); -bool isValid = v.isValid(); +boolean isValid = v.isValid(); ]]></programlisting></example> <para>If the piece of XML doesn't contain any @@ -1379,14 +2227,14 @@ can be resolved by your parser.</para> <example> - <title>Validating a piece of XML that doesn't contain a + <title>Validating a Piece of XML that doesn't Contain a <literal>DOCTYPE</literal></title> <programlisting language="Java"><![CDATA[ InputSource is = new InputSource(new FileInputStream(myXmlDocument)); Validator v = new Validator(is, - (new File(myDTD)).toURI().toURL(), + (new File(myDTD)).toURI().toURL().toString(), myPublicId); -bool isValid = v.isValid(); +boolean isValid = v.isValid(); ]]></programlisting></example> <para>If the piece of XML already has the correct @@ -1401,12 +2249,12 @@ <literal>systemId</literal>. <example> - <title>Validating against a local DTD</title> + <title>Validating Against a Local DTD</title> <programlisting language="Java"><![CDATA[ InputSource is = new InputSource(new FileInputStream(myXmlDocument)); Validator v = new Validator(is, - (new File(myDTD)).toURI().toURL()); -bool isValid = v.isValid(); + (new File(myDTD)).toURI().toURL().toString()); +boolean isValid = v.isValid(); ]]></programlisting></example> </listitem> @@ -1416,22 +2264,22 @@ Validator. <para>This approach would allow you to use an OASIS - catalog<footnote><ulink - url="http://www.oasis-open.org/committees/download.php/14809/xml-catalogs.html">http://www.oasis-open.org/committees/download.php/14809/xml-catalogs.html</ulink></footnote> + catalog<footnote><para><ulink + url="http://www.oasis-open.org/committees/download.php/14809/xml-catalogs.html">http://www.oasis-open.org/committees/download.php/14809/xml-catalogs.html</ulink></para></footnote> in conjunction with the Apache XML Resolver - library<footnote><ulink - url="http://xml.apache.org/commons/components/resolver/index.html">http://xml.apache.org/commons/components/resolver/index.html</ulink></footnote> + library<footnote><para><ulink + url="http://xml.apache.org/commons/components/resolver/index.html">http://xml.apache.org/commons/components/resolver/index.html</ulink></para></footnote> to resolve the DTD location as well as the location of any ... [truncated message content] |