Skip to content

Commit ba90070

Browse files
authored
Merge pull request eXist-db#4402 from evolvedbinary/feature/fn-path#0,#1
2 parents 4fa59fd + ec80946 commit ba90070

File tree

10 files changed

+731
-87
lines changed

10 files changed

+731
-87
lines changed

exist-core/src/main/java/org/exist/dom/memtree/AttrImpl.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,11 @@ public void setTextContent(final String textContent) throws DOMException {
124124

125125
@Override
126126
public Element getOwnerElement() {
127-
return (Element) document.getNode(document.attrParent[nodeNumber]);
127+
final Node node = document.getNode(document.attrParent[nodeNumber]);
128+
if (node != null && node.getNodeType() == Node.ELEMENT_NODE) {
129+
return (Element) node;
130+
}
131+
return null;
128132
}
129133

130134
@Override
@@ -149,7 +153,10 @@ public void selectAncestors(boolean includeSelf, NodeTest test, Sequence result)
149153
if (test.matches(this)) {
150154
result.add(this);
151155
}
152-
((NodeImpl)getOwnerElement()).selectAncestors(true, test, result);
156+
final ElementImpl ownerElement = (ElementImpl) getOwnerElement();
157+
if (ownerElement != null) {
158+
ownerElement.selectAncestors(true, test, result);
159+
}
153160
}
154161

155162
@Override

exist-core/src/main/java/org/exist/dom/memtree/DocumentImpl.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@ public long getDocId() {
211211
return docId;
212212
}
213213

214+
public boolean isExplicitlyCreated() {
215+
return explicitlyCreated;
216+
}
217+
214218
public int addNode(final short kind, final short level, final QName qname) {
215219
if(nodeKind == null) {
216220
init();
@@ -1056,7 +1060,9 @@ protected void copyTo(NodeImpl node, final DocumentBuilderReceiver receiver, fin
10561060
nextNode = (NodeImpl) node.getFirstChild();
10571061
}
10581062
while(nextNode == null) {
1059-
copyEndNode(node, receiver);
1063+
if (node != null) {
1064+
copyEndNode(node, receiver);
1065+
}
10601066
if((top != null) && (top.nodeNumber == node.nodeNumber)) {
10611067
break;
10621068
}
@@ -1065,7 +1071,9 @@ protected void copyTo(NodeImpl node, final DocumentBuilderReceiver receiver, fin
10651071
if(nextNode == null) {
10661072
node = (NodeImpl) node.getParentNode();
10671073
if((node == null) || ((top != null) && (top.nodeNumber == node.nodeNumber))) {
1068-
copyEndNode(node, receiver);
1074+
if (node != null) {
1075+
copyEndNode(node, receiver);
1076+
}
10691077
break;
10701078
}
10711079
}
@@ -1304,7 +1312,9 @@ public void streamTo(final Serializer serializer, NodeImpl node, final Receiver
13041312
if(nextNode == null) {
13051313
node = (NodeImpl) node.getParentNode();
13061314
if((node == null) || ((top != null) && (top.nodeNumber == node.nodeNumber))) {
1307-
endNode(node, receiver);
1315+
if (node != null) {
1316+
endNode(node, receiver);
1317+
}
13081318
break;
13091319
}
13101320
}

exist-core/src/main/java/org/exist/dom/memtree/NodeImpl.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -270,13 +270,23 @@ public short getNodeType() {
270270
@Override
271271
public Node getParentNode() {
272272
int next = document.next[nodeNumber];
273-
while(next > nodeNumber) {
273+
while (next > nodeNumber) {
274274
next = document.next[next];
275275
}
276-
if(next < 0) {
276+
if (next < 0) {
277277
return null;
278278
}
279-
return document.getNode(next);
279+
final NodeImpl parent = document.getNode(next);
280+
if (parent.getNodeType() == DOCUMENT_NODE && !((DocumentImpl) parent).isExplicitlyCreated()) {
281+
/*
282+
All nodes in the MemTree will return an Owner document due to how the MemTree is implemented,
283+
however the explicitlyCreated flag tells us whether there "really" was a Document Node or not.
284+
See https://github.com/eXist-db/exist/issues/1463
285+
*/
286+
return null;
287+
} else {
288+
return parent;
289+
}
280290
}
281291

282292
public Node selectParentNode() {

exist-core/src/main/java/org/exist/dom/memtree/SAXAdapter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public void endDocument() throws SAXException {
6868

6969
@Override
7070
public void startDocument() throws SAXException {
71-
builder.startDocument();
71+
builder.startDocument(true);
7272
if(replaceAttributeFlag) {
7373
builder.setReplaceAttributeFlag(replaceAttributeFlag);
7474
}

exist-core/src/main/java/org/exist/xquery/Function.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ private Expression checkArgumentType(Expression argument, @Nullable final Sequen
305305
//Because () is seen as a node
306306
(argType.getCardinality().isSuperCardinalityOrEqualOf(Cardinality.EMPTY_SEQUENCE) && returnType == Type.NODE))) {
307307
LOG.debug(ExpressionDumper.dump(argument));
308-
throw new XPathException(this, Messages.getMessage(Error.FUNC_PARAM_TYPE_STATIC,
308+
throw new XPathException(this, ErrorCodes.XPTY0004, Messages.getMessage(Error.FUNC_PARAM_TYPE_STATIC,
309309
String.valueOf(argPosition), mySignature, argType.toString(), Type.getTypeName(returnType)));
310310
}
311311
}

exist-core/src/main/java/org/exist/xquery/functions/fn/FnModule.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ public class FnModule extends AbstractInternalModule {
171171
new FunctionDef(FunNumber.signatures[1], FunNumber.class),
172172
new FunctionDef(FunOneOrMore.signature, FunOneOrMore.class),
173173
new FunctionDef(FnOuterMost.FNS_OUTERMOST, FnOuterMost.class),
174+
new FunctionDef(FunPath.FS_PATH_SIGNATURES[0], FunPath.class),
175+
new FunctionDef(FunPath.FS_PATH_SIGNATURES[1], FunPath.class),
174176
new FunctionDef(FunPosition.signature, FunPosition.class),
175177
new FunctionDef(FunQName.signature, FunQName.class),
176178
new FunctionDef(FunRemove.signature, FunRemove.class),
Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
/*
2+
* eXist-db Open Source Native XML Database
3+
* Copyright (C) 2001 The eXist-db Authors
4+
*
5+
6+
* http://www.exist-db.org
7+
*
8+
* This library is free software; you can redistribute it and/or
9+
* modify it under the terms of the GNU Lesser General Public
10+
* License as published by the Free Software Foundation; either
11+
* version 2.1 of the License, or (at your option) any later version.
12+
*
13+
* This library is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16+
* Lesser General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Lesser General Public
19+
* License along with this library; if not, write to the Free Software
20+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21+
*/
22+
23+
package org.exist.xquery.functions.fn;
24+
25+
import org.exist.Namespaces;
26+
import org.exist.dom.INode;
27+
import org.exist.dom.QName;
28+
import org.exist.xquery.*;
29+
import org.exist.xquery.value.*;
30+
import org.w3c.dom.*;
31+
32+
import javax.annotation.Nullable;
33+
import java.util.LinkedList;
34+
import java.util.List;
35+
36+
import static org.exist.xquery.FunctionDSL.functionSignature;
37+
38+
public class FunPath extends BasicFunction {
39+
40+
private static final QName FN_PATH_NAME = new QName("path", Function.BUILTIN_FUNCTION_NS);
41+
private static final String FN_PATH_DESCRIPTION =
42+
"Returns a path expression that can be used to select the supplied node " +
43+
"relative to the root of its containing document.";
44+
private static final FunctionReturnSequenceType FN_PATH_RETURN = new FunctionReturnSequenceType(
45+
Type.STRING, Cardinality.ZERO_OR_ONE, "The path expression, or any empty sequence");
46+
47+
public static final FunctionSignature[] FS_PATH_SIGNATURES = {
48+
functionSignature(FunPath.FN_PATH_NAME, FunPath.FN_PATH_DESCRIPTION, FunPath.FN_PATH_RETURN),
49+
functionSignature(FunPath.FN_PATH_NAME, FunPath.FN_PATH_DESCRIPTION, FunPath.FN_PATH_RETURN,
50+
new FunctionParameterSequenceType("node", Type.NODE, Cardinality.ZERO_OR_ONE, "The node for which to calculate a path expression"))
51+
};
52+
53+
public FunPath(final XQueryContext context, final FunctionSignature signature) {
54+
super(context, signature);
55+
}
56+
57+
@Override
58+
public Sequence eval(final Sequence[] args, final Sequence contextSequence) throws XPathException {
59+
final Sequence result;
60+
final Sequence sequence;
61+
62+
// The behavior of the function if the argument is omitted is exactly
63+
// the same as if the context item (.) had been passed as the argument.
64+
if (getArgumentCount() == 0) {
65+
if (contextSequence != null) {
66+
sequence = contextSequence;
67+
} else {
68+
sequence = Sequence.EMPTY_SEQUENCE;
69+
}
70+
} else {
71+
sequence = args[0];
72+
}
73+
74+
if (sequence.isEmpty()) {
75+
// If $arg is the empty sequence, the function returns the empty sequence.
76+
result = Sequence.EMPTY_SEQUENCE;
77+
} else {
78+
final Item item = sequence.itemAt(0);
79+
if (item.getType() == Type.DOCUMENT) {
80+
// If $arg is a document node, the function returns the string "/".
81+
result = new StringValue("/");
82+
} else if (Type.subTypeOf(item.getType(), Type.NODE)) {
83+
// For an element node, Q{uri}local[position], where uri is the
84+
// namespace URI of the node name or the empty string if the
85+
// node is in no namespace, local is the local part of the node
86+
// name, and position is an integer representing the position
87+
// of the selected node among its like-named siblings.
88+
final NodeValue nodeValue = (NodeValue) item;
89+
final Node node = nodeValue.getNode();
90+
final LinkedList<String> pathValues = new LinkedList<>();
91+
getPathValues(node, pathValues);
92+
if ((node.getOwnerDocument() == null ||
93+
node.getOwnerDocument().getDocumentElement() == null ||
94+
(node.getOwnerDocument() instanceof org.exist.dom.memtree.DocumentImpl &&
95+
!((org.exist.dom.memtree.DocumentImpl) node.getOwnerDocument()).isExplicitlyCreated()))) {
96+
// This string is prefixed by "Q{http://www.w3.org/2005/xpath-functions}root()"
97+
// if the root node is not a document node.
98+
pathValues.remove(0);
99+
result = new StringValue(String.format("Q{%s}root()", Namespaces.XPATH_FUNCTIONS_NS) + String.join("", pathValues));
100+
} else {
101+
result = new StringValue(String.join("", pathValues));
102+
}
103+
} else {
104+
// If the context item is not a node, type error [err:XPTY0004].
105+
throw new XPathException(this, ErrorCodes.XPTY0004, "Item is not a document or node; got '" + Type.getTypeName(item.getType()) + "'", sequence);
106+
}
107+
}
108+
return result;
109+
}
110+
111+
/**
112+
* Gets the position of a specified node among its like-named siblings.
113+
*
114+
* @param node the node whose position to get
115+
* @return the position of the specified node, or zero if this method
116+
* failed to determine the position of the specified node
117+
*/
118+
private static int getNodePosition(final Node node) {
119+
int position = 1;
120+
Node siblingNode = node.getPreviousSibling();
121+
while (siblingNode != null) {
122+
if (siblingNode.getNodeName().equals(node.getNodeName())) {
123+
++position;
124+
}
125+
siblingNode = siblingNode.getPreviousSibling();
126+
}
127+
return position;
128+
}
129+
130+
/**
131+
* Gets the path values of a specified node.
132+
*
133+
* @param node the node whose path values to get
134+
* @param values the path values
135+
*/
136+
private static void getPathValues(final Node node, final List<String> values) {
137+
@Nullable Node parent = node.getParentNode();
138+
139+
final StringBuilder value = new StringBuilder();
140+
141+
switch (node.getNodeType()) {
142+
case Node.ATTRIBUTE_NODE:
143+
// For an attribute node, if the node is in no namespace,
144+
// @local, where local is the local part of the node name.
145+
// Otherwise, @Q{uri}local, where uri is the namespace URI of
146+
// the node name, and local is the local part of the node name.
147+
value.append('/');
148+
if (node.getNamespaceURI() != null) {
149+
value.append(String.format("@Q{%s}", node.getNamespaceURI()));
150+
} else {
151+
value.append('@');
152+
}
153+
value.append(node.getLocalName());
154+
155+
// attributes have an owner element - not a parent node!
156+
parent = ((Attr) node).getOwnerElement();
157+
break;
158+
159+
case Node.TEXT_NODE:
160+
// For a text node: text()[position] where position is an integer
161+
// representing the position of the selected node among its text
162+
// node siblings
163+
final int textNodePosition = getNodePosition(node);
164+
if (textNodePosition > 0) {
165+
value.append(String.format("/text()[%d]", textNodePosition));
166+
}
167+
break;
168+
169+
case Node.COMMENT_NODE:
170+
// For a comment node: comment()[position] where position is an
171+
// integer representing the position of the selected node among
172+
// its comment node siblings.
173+
final int commentNodePosition = getNodePosition(node);
174+
if (commentNodePosition > 0) {
175+
value.append(String.format("/comment()[%d]", commentNodePosition));
176+
}
177+
break;
178+
179+
case Node.PROCESSING_INSTRUCTION_NODE:
180+
// For a processing-instruction node: processing-instruction(local)[position]
181+
// where local is the name of the processing instruction node and position is
182+
// an integer representing the position of the selected node among its
183+
// like-named processing-instruction node siblings.
184+
int processingInstructionNodePosition = getNodePosition(node);
185+
if (processingInstructionNodePosition > 0) {
186+
value.append(String.format("/processing-instruction(%s)[%d]", node.getNodeName(), processingInstructionNodePosition));
187+
}
188+
break;
189+
190+
case INode.NAMESPACE_NODE:
191+
// For a namespace node: If the namespace node has a name: namespace::prefix,
192+
// where prefix is the local part of the name of the namespace node
193+
// (which represents the namespace prefix). If the namespace node
194+
// has no name (that is, it represents the default namespace):
195+
// namespace::*[Q{http://www.w3.org/2005/xpath-functions}local-name()=""]
196+
if (node.getNamespaceURI() != null) {
197+
value.append(String.format("namespace::{%s}", node.getLocalName()));
198+
} else {
199+
value.append("namespace::*[Q{http://www.w3.org/2005/xpath-functions}local-name()=\"\"]");
200+
}
201+
break;
202+
203+
default:
204+
if (node.getLocalName() != null) {
205+
// For an element node, Q{uri}local[position], where uri is the
206+
// namespace URI of the node name or the empty string if the
207+
// node is in no namespace, local is the local part of the node
208+
// name, and position is an integer representing the position
209+
// of the selected node among its like-named siblings.
210+
final int nodePosition = getNodePosition(node);
211+
value.append((node.getOwnerDocument() != null && node.getOwnerDocument().getDocumentElement() != null) ? "/Q" : "Q");
212+
value.append(((INode) node).getQName().toURIQualifiedName());
213+
if (nodePosition > 0) {
214+
value.append(String.format("[%d]", nodePosition));
215+
}
216+
}
217+
break;
218+
}
219+
220+
if (parent != null) {
221+
getPathValues(parent, values);
222+
}
223+
224+
if (value.toString().length() > 0) {
225+
values.add(value.toString());
226+
}
227+
}
228+
}

0 commit comments

Comments
 (0)