blob: 43c05804151952b5c9759e18677c508257500741 [file] [log] [blame]
/*
* Copyright 2002-2005 Sascha Weinreuter
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.intellij.plugins.xpathView.support.jaxen;
import com.intellij.lang.xml.XMLLanguage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.xml.*;
import com.intellij.xml.XmlAttributeDescriptor;
import org.intellij.plugins.xpathView.util.MyPsiUtil;
import org.jaxen.DefaultNavigator;
import org.jaxen.FunctionCallException;
import org.jaxen.UnsupportedAxisException;
import org.jaxen.XPath;
import org.jaxen.saxpath.SAXPathException;
import org.jetbrains.annotations.NotNull;
import java.util.Collections;
import java.util.Iterator;
/**
* <p>Adapter class for IDEA's PSI-tree to Jaxen.</p>
* Not all of the required functionality is implemented yet. See the TODO comments...
*/
public class PsiDocumentNavigator extends DefaultNavigator {
private static final Logger LOG = Logger.getInstance("org.intellij.plugins.xpathView.support.jaxen.PsiDocumentNavigator");
private final XmlFile file;
public PsiDocumentNavigator(XmlFile file) {
this.file = file;
}
public Iterator getChildAxisIterator(Object contextNode) throws UnsupportedAxisException {
if (LOG.isDebugEnabled()) {
LOG.debug("enter: getChildAxisIterator " + contextNode);
}
if (!(contextNode instanceof XmlElement)) {
return Collections.emptyList().iterator();
}
return new PsiChildAxisIterator(contextNode);
}
public Iterator getParentAxisIterator(Object contextNode) {
if (LOG.isDebugEnabled()) {
LOG.debug("enter: getParentAxisIterator " + contextNode);
}
if (!(contextNode instanceof XmlElement)) {
return Collections.emptyList().iterator();
}
return new NodeIterator((XmlElement)contextNode) {
protected PsiElement getFirstNode(PsiElement n) {
while (n != null) {
n = n.getParent();
if (n instanceof XmlTag) {
return n;
}
}
return null;
}
protected PsiElement getNextNode(PsiElement n) {
return null;
}
};
}
public Iterator getNamespaceAxisIterator(Object contextNode) throws UnsupportedAxisException {
if (LOG.isDebugEnabled()) {
LOG.debug("enter: getNamespaceAxisIterator()");
}
// TODO
return super.getNamespaceAxisIterator(contextNode);
}
public Object getDocumentNode(Object contextNode) {
LOG.debug("enter: getDocumentNode");
if (contextNode instanceof XmlDocument) {
return contextNode;
}
while (contextNode instanceof PsiElement) {
if (contextNode instanceof XmlDocument) {
return contextNode;
}
contextNode = ((PsiElement)contextNode).getParent();
}
return null;
}
public String translateNamespacePrefixToUri(String prefix, Object element) {
if (LOG.isDebugEnabled()) {
LOG.debug("enter: translateNamespacePrefixToUri()");
}
if (isElement(element)) {
return ((XmlTag)element).getNamespaceByPrefix(prefix);
}
return super.translateNamespacePrefixToUri(prefix, element);
}
public String getProcessingInstructionTarget(Object obj) {
LOG.debug("enter: getProcessingInstructionTarget");
LOG.assertTrue(obj instanceof XmlProcessingInstruction);
XmlProcessingInstruction pi = (XmlProcessingInstruction)obj;
return getProcessingInstructionTarget(pi);
}
public static String getProcessingInstructionTarget(XmlProcessingInstruction pi) {
final PsiElement[] children = pi.getChildren();
LOG.assertTrue(children[1] instanceof XmlToken && ((XmlToken)children[1]).getTokenType() == XmlTokenType.XML_NAME, "Unknown PI structure");
String text = children[1].getText();
int i;
for (i=0; i<text.length() && text.charAt(i) == ' ';) i++; // skip
final int pos = text.indexOf(' ', i);
if (pos != -1) {
text = text.substring(i, pos);
} else {
text = text.substring(i);
}
if (LOG.isDebugEnabled()) {
LOG.debug("Target: " + text);
}
return text;
}
@NotNull
public String getProcessingInstructionData(Object obj) {
LOG.debug("enter: getProcessingInstructionData");
LOG.assertTrue(obj instanceof XmlProcessingInstruction);
XmlProcessingInstruction pi = (XmlProcessingInstruction)obj;
int targetLength = getProcessingInstructionTarget(obj).length();
int piLength= pi.getText().length();
final String s = pi.getText().substring(2 + targetLength, piLength - 2).trim();
if (LOG.isDebugEnabled()) {
LOG.debug("Data: " + s);
}
return s;
}
public Object getParentNode(Object contextNode) throws UnsupportedAxisException {
return ((PsiElement)contextNode).getParent();
}
public Object getDocument(String url) throws FunctionCallException {
LOG.debug("enter: getDocument: " + url);
final VirtualFile virtualFile = VfsUtil.findRelativeFile(url, file.getVirtualFile());
if (virtualFile != null) {
LOG.debug("document() -> VirtualFile = " + virtualFile.getPath());
final PsiFile file = this.file.getManager().findFile(virtualFile);
if (file instanceof XmlFile) {
return ((XmlFile)file).getDocument();
}
}
return null;
}
public Iterator getAttributeAxisIterator(Object contextNode) {
if (isElement(contextNode)) {
return new AttributeIterator((XmlElement)contextNode);
} else {
return Collections.emptyList().iterator();
}
}
public String getElementNamespaceUri(Object element) {
LOG.assertTrue(element instanceof XmlTag);
final XmlTag context = (XmlTag)element;
final String namespaceUri = context.getNamespace();
if (!MyPsiUtil.isInDeclaredNamespace(context, namespaceUri, context.getNamespacePrefix())) {
if (LOG.isDebugEnabled()) {
LOG.debug("getElementNamespaceUri: not returning implicit namespace uri: " + namespaceUri);
}
return "";
}
if (LOG.isDebugEnabled()) {
LOG.debug("enter: getElementNamespaceUri: " + namespaceUri);
}
return namespaceUri;
}
public String getElementName(Object element) {
LOG.assertTrue(element instanceof XmlTag);
final String name = ((XmlTag)element).getLocalName();
if (LOG.isDebugEnabled()) {
LOG.debug("getElementName: " + name);
}
return name;
}
public String getElementQName(Object element) {
LOG.assertTrue(element instanceof XmlTag);
if (LOG.isDebugEnabled()) {
LOG.debug("enter: getElementQName: " + ((XmlTag)element).getName());
}
return ((XmlTag)element).getName();
}
public String getAttributeNamespaceUri(Object attr) {
LOG.assertTrue(attr instanceof XmlAttribute);
final XmlAttribute attribute = ((XmlAttribute)attr);
final String name = attribute.getName();
if (name.indexOf(':') == -1) return "";
final String uri = attribute.getNamespace();
if (!MyPsiUtil.isInDeclaredNamespace(attribute.getParent(), uri, MyPsiUtil.getAttributePrefix(attribute))) {
LOG.info("getElementNamespaceUri: not returning implicit attribute-namespace uri: " + uri);
return "";
}
if (LOG.isDebugEnabled()) {
LOG.debug("getAttributeNamespaceUri: " + uri);
}
return uri;
}
public String getAttributeName(Object attr) {
LOG.assertTrue(attr instanceof XmlAttribute);
final String name = ((XmlAttribute)attr).getLocalName();
if (LOG.isDebugEnabled()) {
LOG.debug("getAttributeName: " + name);
}
return name;
}
public String getAttributeQName(Object attr) {
LOG.assertTrue(attr instanceof XmlAttribute);
if (LOG.isDebugEnabled()) {
LOG.debug("enter: getAttributeQName");
}
return ((XmlAttribute)attr).getName();
}
public boolean isDocument(Object object) {
final boolean b = object instanceof XmlDocument;
if (LOG.isDebugEnabled()) {
LOG.debug("enter: isDocument(): " + object + " = " + b);
}
return b;
}
public boolean isElement(Object object) {
final boolean b = object instanceof XmlTag;
if (LOG.isDebugEnabled()) {
LOG.debug("enter: isElement(): " + object + " = " + b);
}
return b && isSupportedElement((XmlTag)object);
}
private static boolean isSupportedElement(XmlTag object) {
// optimization: all tags from XML language are supported, but some from other languages (JSP, see IDEADEV-37939) are not
return object.getLanguage() == XMLLanguage.INSTANCE || MyPsiUtil.findNameElement(object) != null;
}
public boolean isAttribute(Object object) {
final boolean b = object instanceof XmlAttribute;
if (LOG.isDebugEnabled()) {
LOG.debug("enter: isAttribute(): " + object + " = " + b);
}
return b;
}
public boolean isNamespace(Object object) {
if (LOG.isDebugEnabled()) {
LOG.debug("enter: isNamespace(): " + object);
}
// TODO: implement when namespace axis is supported
return false;
}
public boolean isComment(Object object) {
final boolean b = object instanceof XmlComment;
if (LOG.isDebugEnabled()) {
LOG.debug("enter: isComment():" + object + " = " + b);
}
return b;
}
public boolean isText(Object object) {
final boolean b;
if (object instanceof PsiWhiteSpace) {
b = ((PsiWhiteSpace)object).getParent() instanceof XmlText;
} else {
b = object instanceof XmlText;
}
if (LOG.isDebugEnabled()) {
LOG.debug("enter: isText():" + object + " = " + b);
}
return b;
}
public boolean isProcessingInstruction(Object object) {
final boolean b = object instanceof XmlProcessingInstruction;
if (LOG.isDebugEnabled()) {
LOG.debug("enter: isProcessingInstruction(): " + object + " = " + b);
}
return b;
}
@NotNull
public String getCommentStringValue(Object comment) {
LOG.assertTrue(comment instanceof XmlComment);
if (LOG.isDebugEnabled()) {
LOG.debug("enter: getCommentStringValue()");
}
PsiElement c = (PsiElement)comment;
final PsiElement[] children = c.getChildren();
for (PsiElement child : children) {
if (child instanceof XmlToken && ((XmlToken)child).getTokenType() == XmlTokenType.XML_COMMENT_CHARACTERS) {
return child.getText();
}
}
return "";
}
@NotNull
public String getElementStringValue(Object element) {
LOG.assertTrue(element instanceof XmlTag);
if (LOG.isDebugEnabled()) {
LOG.debug("enter: getElementStringValue()");
}
final TextCollector collector = new TextCollector();
((XmlTag)element).accept(collector);
return collector.getText();
}
@NotNull
public String getAttributeStringValue(Object attr) {
LOG.assertTrue(attr instanceof XmlAttribute);
return StringUtil.notNullize(((XmlAttribute)attr).getValue());
}
public String getNamespaceStringValue(Object ns) {
if (LOG.isDebugEnabled()) {
LOG.debug("enter: getNamespaceStringValue");
LOG.debug("ns = " + ns);
}
// TODO: implement when namespace axis is supported
return null;
}
public String getNamespacePrefix(Object ns) {
if (LOG.isDebugEnabled()) {
LOG.debug("enter: getNamespacePrefix");
LOG.debug("ns = " + ns);
}
// TODO: implement when namespace axis is supported
return null;
}
@NotNull
public String getTextStringValue(Object txt) {
LOG.debug("enter: getTextStringValue");
if (txt instanceof XmlText) {
return ((XmlText)txt).getValue();
}
return txt instanceof PsiElement ? ((PsiElement)txt).getText() : txt.toString();
}
public XPath parseXPath(String xpath) throws SAXPathException {
return new PsiXPath(file, xpath);
}
public Object getElementById(Object object, final String elementId) {
if (LOG.isDebugEnabled()) {
LOG.debug("enter: getElementById: " + object + " -- " + elementId);
}
final XmlTag rootTag = ((XmlFile)((XmlElement)object).getContainingFile()).getRootTag();
if (rootTag == null) {
return null;
}
final Ref<XmlTag> ref = new Ref<XmlTag>();
rootTag.accept(new XmlRecursiveElementVisitor() {
@Override
public void visitElement(PsiElement element) {
if (ref.get() == null) {
super.visitElement(element);
}
}
@Override
public void visitXmlAttribute(XmlAttribute attribute) {
final XmlAttributeDescriptor descriptor = attribute.getDescriptor();
final String value = attribute.getValue();
if ((value != null &&
(descriptor != null && descriptor.hasIdType()))) {
if (elementId.equals(value)) {
ref.set(attribute.getParent());
}
}
}
});
return ref.get();
}
static class TextCollector extends XmlRecursiveElementVisitor {
private final StringBuffer builder = new StringBuffer();
@Override
public void visitXmlText(XmlText text) {
builder.append(text.getValue());
}
public String getText() {
return builder.toString();
}
}
}