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
+
1
23
package org .exist .xquery .functions .fn ;
2
24
25
+ import org .exist .Namespaces ;
26
+ import org .exist .dom .INode ;
3
27
import org .exist .dom .QName ;
4
28
import org .exist .xquery .*;
5
29
import org .exist .xquery .value .*;
30
+ import org .w3c .dom .*;
31
+
32
+ import java .util .LinkedList ;
33
+ import java .util .List ;
6
34
7
35
import static org .exist .xquery .FunctionDSL .functionSignature ;
8
36
@@ -27,6 +55,172 @@ public FunPath(final XQueryContext context, final FunctionSignature signature) {
27
55
28
56
@ Override
29
57
public Sequence eval (final Sequence [] args , final Sequence contextSequence ) throws XPathException {
30
- return Sequence .EMPTY_SEQUENCE ;
58
+ final Sequence result ;
59
+ final Sequence sequence ;
60
+
61
+ // The behavior of the function if the argument is omitted is exactly
62
+ // the same as if the context item (.) had been passed as the argument.
63
+ if (getArgumentCount () == 0 ) {
64
+ if (contextSequence != null ) {
65
+ sequence = contextSequence ;
66
+ } else {
67
+ sequence = Sequence .EMPTY_SEQUENCE ;
68
+ }
69
+ } else {
70
+ sequence = args [0 ];
71
+ }
72
+
73
+ if (sequence .isEmpty ()) {
74
+ // If $arg is the empty sequence, the function returns the empty sequence.
75
+ result = Sequence .EMPTY_SEQUENCE ;
76
+ } else {
77
+ final Item item = sequence .itemAt (0 );
78
+ if (item .getType () == Type .DOCUMENT ) {
79
+ // If $arg is a document node, the function returns the string "/".
80
+ result = new StringValue ("/" );
81
+ } else if (item .getType () == Type .ELEMENT ||
82
+ item .getType () == Type .ATTRIBUTE ||
83
+ item .getType () == Type .TEXT ) {
84
+ // For an element node, Q{uri}local[position], where uri is the
85
+ // namespace URI of the node name or the empty string if the
86
+ // node is in no namespace, local is the local part of the node
87
+ // name, and position is an integer representing the position
88
+ // of the selected node among its like-named siblings.
89
+ final NodeValue nodeValue = (NodeValue ) item ;
90
+ final Node node = nodeValue .getNode ();
91
+ final LinkedList <String > pathValues = new LinkedList <>();
92
+ getPathValues (node , pathValues );
93
+ if ((node .getOwnerDocument () == null ||
94
+ node .getOwnerDocument ().getDocumentElement () == null ||
95
+ (node .getOwnerDocument () instanceof org .exist .dom .memtree .DocumentImpl &&
96
+ !((org .exist .dom .memtree .DocumentImpl ) node .getOwnerDocument ()).isExplicitlyCreated ()))) {
97
+ // This string is prefixed by "Q{http://www.w3.org/2005/xpath-functions}root()"
98
+ // if the root node is not a document node.
99
+ pathValues .remove (0 );
100
+ result = new StringValue (String .format ("Q{%s}root()" , Namespaces .XPATH_FUNCTIONS_NS ) + String .join ("" , pathValues ));
101
+ } else {
102
+ result = new StringValue (String .join ("" , pathValues ));
103
+ }
104
+ } else {
105
+ // If the context item is not a node, type error [err:XPTY0004].
106
+ throw new XPathException (this , ErrorCodes .XPTY0004 , "Item is not a document or node; got '" + item + "'" , sequence );
107
+ }
108
+ }
109
+ return result ;
110
+ }
111
+
112
+ /**
113
+ * Gets the position of a specified node among its like-named siblings.
114
+ *
115
+ * @param node the node whose position to get
116
+ * @return the position of the specified node, or zero if this method
117
+ * failed to determine the position of the specified node
118
+ */
119
+ private static int getNodePosition (final Node node ) {
120
+ int position = 1 ;
121
+ Node siblingNode = node .getPreviousSibling ();
122
+ while (siblingNode != null ) {
123
+ if (siblingNode != null && siblingNode .getNodeName ().equals (node .getNodeName ())) {
124
+ ++position ;
125
+ }
126
+ siblingNode = siblingNode .getPreviousSibling ();
127
+ }
128
+ return position ;
129
+ }
130
+
131
+ /**
132
+ * Gets the path values of a specified node.
133
+ *
134
+ * @param node the node whose path values to get
135
+ * @param values the path values
136
+ */
137
+ private static void getPathValues (Node node , final List <String > values ) {
138
+ // get the owner element, if the specified node is an attribute node
139
+ Node attributeNode ;
140
+ if (node .getNodeType () == Type .ATTRIBUTE ) {
141
+ attributeNode = node ;
142
+ node = ((Attr ) node ).getOwnerElement ();
143
+ } else {
144
+ attributeNode = null ;
145
+ }
146
+
147
+ final StringBuilder value = new StringBuilder ();
148
+ final StringBuilder attributeValue = new StringBuilder ();
149
+ if (node .getNodeType () == Type .TEXT ) {
150
+ // For a text node: text()[position] where position is an integer
151
+ // representing the position of the selected node among its text
152
+ // node siblings
153
+ final int nodePosition = getNodePosition (node );
154
+ if (nodePosition > 0 ) {
155
+ value .append (String .format ("/text()[%d]" , nodePosition ));
156
+ }
157
+ } else if (node .getNodeType () == Type .COMMENT ) {
158
+ // For a comment node: comment()[position] where position is an
159
+ // integer representing the position of the selected node among
160
+ // its comment node siblings.
161
+ final int nodePosition = getNodePosition (node );
162
+ if (nodePosition > 0 ) {
163
+ value .append (String .format ("/comment()[%d]" , nodePosition ));
164
+ }
165
+ } else if (node .getNodeType () == Type .PROCESSING_INSTRUCTION ) {
166
+ // For a processing-instruction node: processing-instruction(local)[position]
167
+ // where local is the name of the processing instruction node and position is
168
+ // an integer representing the position of the selected node among its
169
+ // like-named processing-instruction node siblings.
170
+ final int nodePosition = getNodePosition (node );
171
+ if (nodePosition > 0 ) {
172
+ value .append (String .format ("/processing-instruction(%s)[%d]" , node .getNodeName (), nodePosition ));
173
+ }
174
+ } else if (node .getNodeType () == Type .NAMESPACE ) {
175
+ // For a namespace node: If the namespace node has a name: namespace::prefix,
176
+ // where prefix is the local part of the name of the namespace node
177
+ // (which represents the namespace prefix). If the namespace node
178
+ // has no name (that is, it represents the default namespace):
179
+ // namespace::*[Q{http://www.w3.org/2005/xpath-functions}local-name()=""]
180
+ if (node .getNamespaceURI () != null ) {
181
+ value .append (String .format ("namespace::{%s}" , node .getLocalName ()));
182
+ } else {
183
+ value .append ("namespace::*[Q{http://www.w3.org/2005/xpath-functions}local-name()=\" \" ]" );
184
+ }
185
+ } else if (node .getLocalName () != null ) {
186
+ // For an element node, Q{uri}local[position], where uri is the
187
+ // namespace URI of the node name or the empty string if the
188
+ // node is in no namespace, local is the local part of the node
189
+ // name, and position is an integer representing the position
190
+ // of the selected node among its like-named siblings.
191
+ final int nodePosition = getNodePosition (node );
192
+ value .append ((node .getOwnerDocument () != null && node .getOwnerDocument ().getDocumentElement () != null ) ? "/Q" : "Q" );
193
+ value .append (((INode ) node ).getQName ().toURIQualifiedName ());
194
+ if (nodePosition > 0 ) {
195
+ value .append (String .format ("[%d]" , nodePosition ));
196
+ }
197
+ }
198
+
199
+ // For an attribute node, if the node is in no namespace,
200
+ // @local, where local is the local part of the node name.
201
+ // Otherwise, @Q{uri}local, where uri is the namespace URI of
202
+ // the node name, and local is the local part of the node name.
203
+ if (attributeNode != null ) {
204
+ attributeValue .append ('/' );
205
+ if (attributeNode .getNamespaceURI () != null ) {
206
+ attributeValue .append (String .format ("@Q{%s}" , attributeNode .getNamespaceURI ()));
207
+ } else {
208
+ attributeValue .append ('@' );
209
+ }
210
+ attributeValue .append (attributeNode .getLocalName ());
211
+ node = attributeNode ;
212
+ }
213
+
214
+ if (node .getParentNode () != null ) {
215
+ getPathValues (node .getParentNode (), values );
216
+ }
217
+
218
+ if (value .toString ().length () > 0 ) {
219
+ values .add (value .toString ());
220
+ }
221
+
222
+ if (attributeValue .toString ().length () > 0 ) {
223
+ values .add (attributeValue .toString ());
224
+ }
31
225
}
32
226
}
0 commit comments