ef1d24cbe1ae2d53f3409e89f6c17c2fa4a1974f
[idea/community.git] / plugins / xpath / xpath-view / src / org / intellij / plugins / xpathView / support / jaxen / PsiDocumentNavigator.java
1 /*
2  * Copyright 2002-2005 Sascha Weinreuter
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.intellij.plugins.xpathView.support.jaxen;
17
18 import com.intellij.lang.xml.XMLLanguage;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.util.Ref;
21 import com.intellij.openapi.util.text.StringUtil;
22 import com.intellij.openapi.vfs.VfsUtilCore;
23 import com.intellij.openapi.vfs.VirtualFile;
24 import com.intellij.psi.PsiElement;
25 import com.intellij.psi.PsiFile;
26 import com.intellij.psi.PsiWhiteSpace;
27 import com.intellij.psi.XmlRecursiveElementVisitor;
28 import com.intellij.psi.xml.*;
29 import com.intellij.xml.XmlAttributeDescriptor;
30 import org.intellij.plugins.xpathView.util.MyPsiUtil;
31 import org.jaxen.DefaultNavigator;
32 import org.jaxen.FunctionCallException;
33 import org.jaxen.UnsupportedAxisException;
34 import org.jaxen.XPath;
35 import org.jaxen.saxpath.SAXPathException;
36 import org.jetbrains.annotations.NotNull;
37
38 import java.util.Collections;
39 import java.util.Iterator;
40
41 /**
42  * <p>Adapter class for IDEA's PSI-tree to Jaxen.</p>
43  * Not all of the required functionality is implemented yet. See the TODO comments...
44  */
45 public class PsiDocumentNavigator extends DefaultNavigator {
46
47     private static final Logger LOG = Logger.getInstance("org.intellij.plugins.xpathView.support.jaxen.PsiDocumentNavigator");
48     private final XmlFile file;
49
50     public PsiDocumentNavigator(XmlFile file) {
51         this.file = file;
52     }
53
54     @Override
55     public Iterator getChildAxisIterator(Object contextNode) throws UnsupportedAxisException {
56         if (!(contextNode instanceof XmlElement)) {
57             return Collections.emptyList().iterator();
58         }
59         return new PsiChildAxisIterator(contextNode);
60     }
61
62
63     @Override
64     public Iterator getParentAxisIterator(Object contextNode) {
65         if (!(contextNode instanceof XmlElement)) {
66             return Collections.emptyList().iterator();
67         }
68
69         return new NodeIterator((XmlElement)contextNode) {
70             @Override
71             protected PsiElement getFirstNode(PsiElement n) {
72                 while (n != null) {
73                     n = n.getParent();
74                     if (n instanceof XmlTag) {
75                         return n;
76                     }
77                 }
78                 return null;
79             }
80
81             @Override
82             protected PsiElement getNextNode(PsiElement n) {
83                 return null;
84             }
85         };
86     }
87
88
89   @Override
90   public Object getDocumentNode(Object contextNode) {
91         if (contextNode instanceof XmlDocument) {
92             return contextNode;
93         }
94
95         while (contextNode instanceof PsiElement) {
96             if (contextNode instanceof XmlDocument) {
97                 return contextNode;
98             }
99             contextNode = ((PsiElement)contextNode).getParent();
100         }
101
102         return null;
103     }
104
105     @Override
106     public String translateNamespacePrefixToUri(String prefix, Object element) {
107         if (isElement(element)) {
108             return ((XmlTag)element).getNamespaceByPrefix(prefix);
109         }
110         return super.translateNamespacePrefixToUri(prefix, element);
111     }
112
113     @Override
114     public String getProcessingInstructionTarget(Object obj) {
115         LOG.assertTrue(obj instanceof XmlProcessingInstruction);
116
117         XmlProcessingInstruction pi = (XmlProcessingInstruction)obj;
118         return getProcessingInstructionTarget(pi);
119     }
120
121     public static String getProcessingInstructionTarget(XmlProcessingInstruction pi) {
122         final PsiElement[] children = pi.getChildren();
123         LOG.assertTrue(children[1] instanceof XmlToken && ((XmlToken)children[1]).getTokenType() == XmlTokenType.XML_NAME, "Unknown PI structure");
124
125         String text = children[1].getText();
126         int i;
127         for (i=0; i<text.length() && text.charAt(i) == ' ';) i++; // skip
128         final int pos = text.indexOf(' ', i);
129         if (pos != -1) {
130             text = text.substring(i, pos);
131         } else {
132             text = text.substring(i);
133         }
134
135         return text;
136     }
137
138     @Override
139     @NotNull
140     public String getProcessingInstructionData(Object obj) {
141         LOG.assertTrue(obj instanceof XmlProcessingInstruction);
142
143         XmlProcessingInstruction pi = (XmlProcessingInstruction)obj;
144         int targetLength = getProcessingInstructionTarget(obj).length();
145         int piLength= pi.getText().length();
146         return pi.getText().substring(2 + targetLength, piLength - 2).trim();
147     }
148
149     @Override
150     public Object getParentNode(Object contextNode) throws UnsupportedAxisException {
151         return ((PsiElement)contextNode).getParent();
152     }
153
154     @Override
155     public Object getDocument(String url) throws FunctionCallException {
156         final VirtualFile virtualFile = VfsUtilCore.findRelativeFile(url, file.getVirtualFile());
157         if (virtualFile != null) {
158             final PsiFile file = this.file.getManager().findFile(virtualFile);
159             if (file instanceof XmlFile) {
160                 return ((XmlFile)file).getDocument();
161             }
162         }
163         return null;
164     }
165
166     @Override
167     public Iterator getAttributeAxisIterator(Object contextNode) {
168         if (isElement(contextNode)) {
169             return new AttributeIterator((XmlElement)contextNode);
170         } else {
171             return Collections.emptyList().iterator();
172         }
173     }
174
175     @Override
176     public String getElementNamespaceUri(Object element) {
177         LOG.assertTrue(element instanceof XmlTag);
178
179         final XmlTag context = (XmlTag)element;
180         final String namespaceUri = context.getNamespace();
181         if (!MyPsiUtil.isInDeclaredNamespace(context, namespaceUri, context.getNamespacePrefix())) {
182           return "";
183         }
184         return namespaceUri;
185     }
186
187     @Override
188     public String getElementName(Object element) {
189         LOG.assertTrue(element instanceof XmlTag);
190         return ((XmlTag)element).getLocalName();
191     }
192
193     @Override
194     public String getElementQName(Object element) {
195         LOG.assertTrue(element instanceof XmlTag);
196         return ((XmlTag)element).getName();
197     }
198
199     @Override
200     public String getAttributeNamespaceUri(Object attr) {
201         LOG.assertTrue(attr instanceof XmlAttribute);
202
203         final XmlAttribute attribute = ((XmlAttribute)attr);
204         final String name = attribute.getName();
205         if (name.indexOf(':') == -1) return "";
206
207         final String uri = attribute.getNamespace();
208         if (!MyPsiUtil.isInDeclaredNamespace(attribute.getParent(), uri, MyPsiUtil.getAttributePrefix(attribute))) {
209             LOG.info("getElementNamespaceUri: not returning implicit attribute-namespace uri: " + uri);
210             return "";
211         }
212         return uri;
213     }
214
215     @Override
216     public String getAttributeName(Object attr) {
217         LOG.assertTrue(attr instanceof XmlAttribute);
218         return ((XmlAttribute)attr).getLocalName();
219     }
220
221     @Override
222     public String getAttributeQName(Object attr) {
223         LOG.assertTrue(attr instanceof XmlAttribute);
224         return ((XmlAttribute)attr).getName();
225     }
226
227     @Override
228     public boolean isDocument(Object object) {
229         return object instanceof XmlDocument;
230     }
231
232     @Override
233     public boolean isElement(Object object) {
234         return object instanceof XmlTag && isSupportedElement((XmlTag)object);
235     }
236
237     private static boolean isSupportedElement(XmlTag object) {
238         // optimization: all tags from XML language are supported, but some from other languages (JSP, see IDEADEV-37939) are not
239         return object.getLanguage() == XMLLanguage.INSTANCE || MyPsiUtil.findNameElement(object) != null;
240     }
241
242     @Override
243     public boolean isAttribute(Object object) {
244         return object instanceof XmlAttribute;
245     }
246
247     @Override
248     public boolean isNamespace(Object object) {
249         // TODO: implement when namespace axis is supported
250         return false;
251     }
252
253     @Override
254     public boolean isComment(Object object) {
255         return object instanceof XmlComment;
256     }
257
258     @Override
259     public boolean isText(Object object) {
260         return object instanceof PsiWhiteSpace ? ((PsiWhiteSpace)object).getParent() instanceof XmlText : object instanceof XmlText;
261     }
262
263     @Override
264     public boolean isProcessingInstruction(Object object) {
265         return object instanceof XmlProcessingInstruction;
266     }
267
268     @Override
269     @NotNull
270     public String getCommentStringValue(Object comment) {
271         LOG.assertTrue(comment instanceof XmlComment);
272
273         PsiElement c = (PsiElement)comment;
274         final PsiElement[] children = c.getChildren();
275         for (PsiElement child : children) {
276             if (child instanceof XmlToken && ((XmlToken)child).getTokenType() == XmlTokenType.XML_COMMENT_CHARACTERS) {
277                 return child.getText();
278             }
279         }
280         return "";
281     }
282
283     @Override
284     @NotNull
285     public String getElementStringValue(Object element) {
286         LOG.assertTrue(element instanceof XmlTag);
287
288         final TextCollector collector = new TextCollector();
289         ((XmlTag)element).accept(collector);
290         return collector.getText();
291     }
292
293     @Override
294     @NotNull
295     public String getAttributeStringValue(Object attr) {
296         LOG.assertTrue(attr instanceof XmlAttribute);
297         return StringUtil.notNullize(((XmlAttribute)attr).getValue());
298     }
299
300     @Override
301     public String getNamespaceStringValue(Object ns) {
302         // TODO: implement when namespace axis is supported
303         return null;
304     }
305
306     @Override
307     public String getNamespacePrefix(Object ns) {
308         // TODO: implement when namespace axis is supported
309         return null;
310     }
311
312     @Override
313     @NotNull
314     public String getTextStringValue(Object txt) {
315
316         if (txt instanceof XmlText) {
317           return ((XmlText)txt).getValue();
318         }
319         return txt instanceof PsiElement ? ((PsiElement)txt).getText() : txt.toString();
320     }
321
322     @Override
323     public XPath parseXPath(String xpath) throws SAXPathException {
324         return new PsiXPath(file, xpath);
325     }
326
327     @Override
328     public Object getElementById(Object object, final String elementId) {
329       final XmlTag rootTag = ((XmlFile)((XmlElement)object).getContainingFile()).getRootTag();
330       if (rootTag == null) {
331         return null;
332       }
333
334       final Ref<XmlTag> ref = new Ref<>();
335       rootTag.accept(new XmlRecursiveElementVisitor() {
336         @Override
337         public void visitElement(PsiElement element) {
338           if (ref.get() == null) {
339             super.visitElement(element);
340           }
341         }
342
343         @Override
344         public void visitXmlAttribute(XmlAttribute attribute) {
345           final XmlAttributeDescriptor descriptor = attribute.getDescriptor();
346           final String value = attribute.getValue();
347           if ((value != null &&
348                (descriptor != null && descriptor.hasIdType()))) {
349             if (elementId.equals(value)) {
350               ref.set(attribute.getParent());
351             }
352           }
353         }
354       });
355       return ref.get();
356     }
357
358     static class TextCollector extends XmlRecursiveElementVisitor {
359         private final StringBuffer builder = new StringBuffer();
360
361         @Override
362         public void visitXmlText(XmlText text) {
363             builder.append(text.getValue());
364         }
365
366         public String getText() {
367             return builder.toString();
368         }
369     }
370 }