Skip to content

Commit e2bf08c

Browse files
authored
Merge pull request #3605 from line-o/fix/root-node-handling
Fix root node handling and implicit context in functions
2 parents c63f20a + bc53cca commit e2bf08c

File tree

15 files changed

+639
-107
lines changed

15 files changed

+639
-107
lines changed

exist-core/pom.xml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,13 @@
456456
<artifactId>junit</artifactId>
457457
<scope>compile</scope>
458458
<!-- scope>test</scope --> <!-- not just test scope, as needed for org.exist.test -->
459+
<exclusions>
460+
<exclusion>
461+
<!-- we ourselves have a test scoped dependency on a newer hamcrest... see below! -->
462+
<groupId>org.hamcrest</groupId>
463+
<artifactId>hamcrest-core</artifactId>
464+
</exclusion>
465+
</exclusions>
459466
</dependency>
460467
<dependency>
461468
<groupId>org.hamcrest</groupId>
@@ -484,12 +491,10 @@
484491
<dependency>
485492
<groupId>org.xmlunit</groupId>
486493
<artifactId>xmlunit-core</artifactId>
487-
<scope>test</scope>
488494
</dependency>
489495
<dependency>
490496
<groupId>org.xmlunit</groupId>
491497
<artifactId>xmlunit-matchers</artifactId>
492-
<scope>test</scope>
493498
</dependency>
494499
<dependency>
495500
<groupId>org.xmlunit</groupId>
@@ -665,6 +670,7 @@
665670
<exclude>src/test/java/org/exist/storage/txn/ReusableTxnTest.java</exclude>
666671
<exclude>src/test/java/org/exist/storage/txn/TransactionManagerTestHelper.java</exclude>
667672
<exclude>src/test/java/org/exist/storage/txn/TxnTest.java</exclude>
673+
<exclude>src/main/java/org/exist/test/DiffMatcher.java</exclude>
668674
<exclude>src/main/java/org/exist/util/CollectionOfArrayIterator.java</exclude>
669675
<exclude>src/test/java/org/exist/util/CollectionOfArrayIteratorTest.java</exclude>
670676
<exclude>src/main/java/org/exist/xquery/Cardinality.java</exclude>
@@ -802,6 +808,7 @@ The original license statement is also included below.]]></preamble>
802808
<include>src/test/java/org/exist/storage/txn/ReusableTxnTest.java</include>
803809
<include>src/test/java/org/exist/storage/txn/TransactionManagerTestHelper.java</include>
804810
<include>src/test/java/org/exist/storage/txn/TxnTest.java</include>
811+
<include>src/main/java/org/exist/test/DiffMatcher.java</include>
805812
<include>src/main/java/org/exist/util/CollectionOfArrayIterator.java</include>
806813
<include>src/test/java/org/exist/util/CollectionOfArrayIteratorTest.java</include>
807814
<include>src/main/java/org/exist/xquery/Cardinality.java</include>

exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,11 @@ functionBody throws XPathException
521521
:
522522
( LCURLY RCURLY ) => l:LCURLY^ RCURLY!
523523
{ #functionBody= #(#l, #(#[PARENTHESIZED, "Parenthesized"], null)); }
524-
| LCURLY^ expr RCURLY!
524+
| LCURLY^
525+
{ lexer.inFunctionBody = true; }
526+
expr
527+
{ lexer.inFunctionBody = false; }
528+
RCURLY!
525529
;
526530
527531
returnType throws XPathException:
@@ -1059,8 +1063,20 @@ unaryExpr throws XPathException
10591063
;
10601064
10611065
valueExpr throws XPathException
1066+
{ Boolean inFunctionBodyState = lexer.inFunctionBody; }
10621067
:
1063-
pathExpr (BANG^ pathExpr)*
1068+
pathExpr (
1069+
BANG^
1070+
{
1071+
// simple map operator might add new document context
1072+
lexer.inFunctionBody = false;
1073+
}
1074+
pathExpr
1075+
{
1076+
// reset state
1077+
lexer.inFunctionBody = inFunctionBodyState;
1078+
}
1079+
)*
10641080
|
10651081
extensionExpr
10661082
;
@@ -1111,15 +1127,33 @@ pathExpr throws XPathException
11111127
relativePathExpr
11121128
|
11131129
( SLASH relativePathExpr )
1114-
=> SLASH relPath:relativePathExpr
1115-
{ #pathExpr= #(#[ABSOLUTE_SLASH, "AbsoluteSlash"], #relPath); }
1130+
=> s1:SLASH relPath:relativePathExpr
1131+
{
1132+
if (lexer.inFunctionBody) {
1133+
throw new XPathException(#s1.getLine(), #s1.getColumn(), ErrorCodes.XPDY0002,
1134+
"Leading '/' selects nothing, ContextItem is absent in function body");
1135+
}
1136+
#pathExpr= #(#[ABSOLUTE_SLASH, "AbsoluteSlash"], #relPath);
1137+
}
11161138
// lone slash
11171139
|
1118-
SLASH
1119-
{ #pathExpr= #[ABSOLUTE_SLASH, "AbsoluteSlash"]; }
1140+
s2:SLASH
1141+
{
1142+
if (lexer.inFunctionBody) {
1143+
throw new XPathException(#s2.getLine(), #s2.getColumn(), ErrorCodes.XPDY0002,
1144+
"Leading '/' selects nothing, ContextItem is absent in function body");
1145+
}
1146+
#pathExpr= #[ABSOLUTE_SLASH, "AbsoluteSlash"];
1147+
}
11201148
|
1121-
DSLASH relPath2:relativePathExpr
1122-
{ #pathExpr= #(#[ABSOLUTE_DSLASH, "AbsoluteSlashSlash"], #relPath2); }
1149+
ds:DSLASH relPath2:relativePathExpr
1150+
{
1151+
if (lexer.inFunctionBody) {
1152+
throw new XPathException(#ds.getLine(), #ds.getColumn(), ErrorCodes.XPDY0002,
1153+
"Leading '//' selects nothing, ContextItem is absent in function body");
1154+
}
1155+
#pathExpr= #(#[ABSOLUTE_DSLASH, "AbsoluteSlashSlash"], #relPath2);
1156+
}
11231157
;
11241158
11251159
relativePathExpr throws XPathException
@@ -2188,6 +2222,7 @@ options {
21882222
protected boolean inStringConstructor = false;
21892223
protected boolean inElementContent= false;
21902224
protected boolean inAttributeContent= false;
2225+
protected boolean inFunctionBody= false;
21912226
protected char attrDelimChar = '"';
21922227
protected boolean inComment= false;
21932228
protected boolean inPragma = false;
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/*
2+
* Copyright (C) 2014, Evolved Binary Ltd
3+
*
4+
* This file was originally ported from FusionDB to eXist-db by
5+
* Evolved Binary, for the benefit of the eXist-db Open Source community.
6+
* Only the ported code as it appears in this file, at the time that
7+
* it was contributed to eXist-db, was re-licensed under The GNU
8+
* Lesser General Public License v2.1 only for use in eXist-db.
9+
*
10+
* This license grant applies only to a snapshot of the code as it
11+
* appeared when ported, it does not offer or infer any rights to either
12+
* updates of this source code or access to the original source code.
13+
*
14+
* The GNU Lesser General Public License v2.1 only license follows.
15+
*
16+
* ---------------------------------------------------------------------
17+
*
18+
* Copyright (C) 2014, Evolved Binary Ltd
19+
*
20+
* This library is free software; you can redistribute it and/or
21+
* modify it under the terms of the GNU Lesser General Public
22+
* License as published by the Free Software Foundation; version 2.1.
23+
*
24+
* This library is distributed in the hope that it will be useful,
25+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
26+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
27+
* Lesser General Public License for more details.
28+
*
29+
* You should have received a copy of the GNU Lesser General Public
30+
* License along with this library; if not, write to the Free Software
31+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
32+
*/
33+
package org.exist.test;
34+
35+
import org.exist.xquery.value.Item;
36+
import org.exist.xquery.value.NodeValue;
37+
import org.exist.xquery.value.Sequence;
38+
import org.hamcrest.Description;
39+
import org.hamcrest.DiagnosingMatcher;
40+
import org.w3c.dom.Node;
41+
import org.xmlunit.builder.DiffBuilder;
42+
import org.xmlunit.builder.Input;
43+
import org.xmlunit.diff.Diff;
44+
import org.xmlunit.util.Convert;
45+
46+
import javax.xml.transform.Source;
47+
48+
/**
49+
* Implementation of a Hamcrest Matcher
50+
* which will compare XML nodes.
51+
*
52+
* @author <a href="mailto:[email protected]">Adam Retter</a>
53+
*/
54+
public class DiffMatcher extends DiagnosingMatcher<Sequence> {
55+
private final Source expectedSource;
56+
private final boolean identical;
57+
58+
private DiffMatcher(final Source expectedSource) {
59+
this(expectedSource, false);
60+
}
61+
62+
private DiffMatcher(final Source expectedSource, final boolean identical) {
63+
this.expectedSource = expectedSource;
64+
this.identical = identical;
65+
}
66+
67+
/**
68+
* Compares that the XML sources are similar.
69+
*
70+
* In this context "similar" is defined by {@link DiffBuilder#checkForSimilar()}.
71+
*
72+
* @param expectedSource the expected XML
73+
*
74+
* @return The Hamcrest Matcher
75+
*/
76+
public static DiffMatcher hasSimilarXml(final Source expectedSource) {
77+
return new DiffMatcher(expectedSource);
78+
}
79+
80+
/**
81+
* Compares that the XML sources are identical.
82+
*
83+
* In this context "similar" is defined by {@link DiffBuilder#checkForIdentical()} ()}.
84+
*
85+
* @param expectedSource the expected XML
86+
*
87+
* @return The Hamcrest Matcher
88+
*/
89+
public static DiffMatcher hasIdenticalXml(final Source expectedSource) {
90+
return new DiffMatcher(expectedSource, true);
91+
}
92+
93+
@Override
94+
public boolean matches(final Object item, final Description mismatch) {
95+
if (item == null) {
96+
mismatch.appendText("null");
97+
return false;
98+
}
99+
100+
final Item actualItem;
101+
if (item instanceof NodeValue) {
102+
actualItem = (NodeValue) item;
103+
104+
} else if (item instanceof Sequence) {
105+
final Sequence actual = ((Sequence) item);
106+
107+
if (actual.getItemCount() != 1) {
108+
mismatch.appendText("Sequence does not contain 1 item");
109+
return false;
110+
}
111+
112+
actualItem = actual.itemAt(0);
113+
if (!(actualItem instanceof NodeValue)) {
114+
mismatch.appendText("Sequence does not contain a Node");
115+
return false;
116+
}
117+
} else {
118+
mismatch.appendText("is not a Node");
119+
return false;
120+
}
121+
122+
final Source actualSource = Input.fromNode((org.w3c.dom.Node) actualItem).build();
123+
124+
DiffBuilder diffBuilder = DiffBuilder.compare(expectedSource)
125+
.withTest(actualSource);
126+
if (identical) {
127+
diffBuilder = diffBuilder.checkForIdentical();
128+
} else {
129+
diffBuilder = diffBuilder.checkForSimilar();
130+
}
131+
132+
final Diff diff = diffBuilder.build();
133+
if (diff.hasDifferences()) {
134+
mismatch.appendText("differences: " + diff.toString());
135+
return false;
136+
}
137+
138+
return true;
139+
}
140+
141+
@Override
142+
public void describeTo(final Description description) {
143+
description
144+
.appendText("nodes match ")
145+
.appendValue(expectedSource);
146+
}
147+
148+
/**
149+
* Creates an Document Source form an XML String.
150+
*
151+
* @param str a string representation of XML.
152+
*
153+
* @return a Document Source.
154+
*/
155+
public static Source docSource(final String str) {
156+
return Input.fromString(str).build();
157+
}
158+
159+
/**
160+
* Creates an Element Source form an XML String.
161+
*
162+
* @param str a string representation of XML.
163+
*
164+
* @return an Element Source.
165+
*/
166+
public static Source elemSource(final String str) {
167+
final Node documentNode = Convert.toNode(docSource(str));
168+
final Node firstElement = documentNode.getFirstChild();
169+
return Input.fromNode(firstElement).build();
170+
}
171+
}

0 commit comments

Comments
 (0)