From 2cba9bbff0108939b298952638cc0a6e543a45e5 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Sun, 4 Oct 2026 16:51:26 +0100 Subject: [PATCH] Let valueEquals match a copy() against its original complex_values_equal walks both trees until their content runs out. A value made by copy() is rooted at a document, so its walk ends on ENDDOC, while an element in a document ends on END. The two tokens differ, so x.copy().valueEquals(x) was false for every complex value. Treat either finish token as the end of the content. Found by POI's TestXWPFNumbering.testCompareAbstractNum, which matches a copied CTAbstractNum against the numbering's own. Co-Authored-By: Claude Opus 5.5 --- .../impl/values/XmlValueComparison.java | 7 +++++++ .../xmlobject/detailed/ValueEqualsTest.java | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/main/java/org/apache/xmlbeans/impl/values/XmlValueComparison.java b/src/main/java/org/apache/xmlbeans/impl/values/XmlValueComparison.java index 4e7e5904e..db60a2dfc 100644 --- a/src/main/java/org/apache/xmlbeans/impl/values/XmlValueComparison.java +++ b/src/main/java/org/apache/xmlbeans/impl/values/XmlValueComparison.java @@ -143,6 +143,13 @@ private static boolean content_equal(XmlCursor a, XmlCursor b, boolean mixed) { TokenType tokenA = skip_ignorable(a, mixed); TokenType tokenB = skip_ignorable(b, mixed); + // a value rooted at a document - such as one made by copy() - ends + // in ENDDOC where an element ends in END: either way, it has run + // out of content + if (tokenA.isFinish() || tokenB.isFinish()) { + return tokenA.isFinish() && tokenB.isFinish(); + } + if (tokenA != tokenB) { return false; } diff --git a/src/test/java/xmlobject/detailed/ValueEqualsTest.java b/src/test/java/xmlobject/detailed/ValueEqualsTest.java index f8ef962a8..82d68a3bd 100755 --- a/src/test/java/xmlobject/detailed/ValueEqualsTest.java +++ b/src/test/java/xmlobject/detailed/ValueEqualsTest.java @@ -95,6 +95,27 @@ void testNestedElementDifference() throws Exception { .valueEquals(clm3.getCarLocationMessage().getEventStatusArray(0))); } + @Test + void testCopyEqualsOriginal() throws Exception { + CarLocationMessageDocument clm = CarLocationMessageDocument.Factory.parse(CLM); + + // copy() roots the copy at a document, the original is an element in one + CarLocationMessageDocument.CarLocationMessage msg = clm.getCarLocationMessage(); + XmlObject copy = msg.copy(); + assertTrue(copy.valueEquals(msg)); + assertTrue(msg.valueEquals(copy)); + + XmlObject eventStatus = msg.getEventStatusArray(0); + XmlObject eventStatusCopy = eventStatus.copy(); + assertTrue(eventStatusCopy.valueEquals(eventStatus)); + assertTrue(eventStatus.valueEquals(eventStatusCopy)); + + CarLocationMessageDocument other = + CarLocationMessageDocument.Factory.parse(CLM.replace("DALLAS", "AUSTIN")); + assertFalse(copy.valueEquals(other.getCarLocationMessage())); + assertFalse(other.getCarLocationMessage().valueEquals(copy)); + } + @Test void testAttributesCompared() throws Exception { CarLocationMessageDocument clm1 = CarLocationMessageDocument.Factory.parse(CLM);