blob: 9a7493e17639c48ae6d917f577ef64148032dcde [file] [log] [blame]
/*
* Copyright 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.lang.xpath.xslt.impl;
import com.intellij.lang.documentation.DocumentationProvider;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.light.LightElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.*;
import com.intellij.util.IncorrectOperationException;
import com.intellij.xml.util.XmlUtil;
import org.intellij.lang.xpath.completion.ElementProvider;
import org.intellij.lang.xpath.completion.FunctionLookup;
import org.intellij.lang.xpath.psi.XPathFunction;
import org.intellij.lang.xpath.xslt.XsltSupport;
import org.intellij.lang.xpath.xslt.psi.XsltElement;
import org.intellij.lang.xpath.xslt.psi.impl.XsltLanguage;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.transform.JDOMSource;
import org.jdom.xpath.XPath;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class XsltDocumentationProvider implements DocumentationProvider {
private SoftReference<Templates> myTemplates;
private SoftReference<Document> myDocument;
@Nullable
public List<String> getUrlFor(PsiElement psiElement, PsiElement psiElement1) {
if (psiElement instanceof XsltElement) return null;
final String category;
final String name;
final String tagName = getTagName(psiElement);
if (tagName != null) {
name = tagName;
category = "element";
} else if (psiElement instanceof XPathFunction) {
name = ((XPathFunction)psiElement).getName();
category = "function";
} else if (psiElement instanceof DocElement) {
name = ((DocElement)psiElement).getName();
category = ((DocElement)psiElement).getCategory();
} else {
return null;
}
try {
final Document document = getDocumentationDocument();
final XPath xPath = XPath.newInstance("//x:" + category + "[@name = '" + name + "']");
xPath.addNamespace("x", document.getRootElement().getNamespaceURI());
final Element e = (Element)xPath.selectSingleNode(document);
if (e != null) {
return Collections.singletonList(e.getParentElement().getAttributeValue("base") + e.getAttributeValue("href"));
}
} catch (Exception e) {
Logger.getInstance(getClass().getName()).error(e);
}
return null;
}
@Nullable
public String generateDoc(PsiElement psiElement, PsiElement psiElement1) {
if (psiElement instanceof DocElement) {
final DocElement element = (DocElement)psiElement;
return getDocumentation(element.getName(), element.getCategory());
}
if (psiElement instanceof XsltElement) {
final XmlTag t = ((XsltElement)psiElement).getTag();
PsiElement p = t.getPrevSibling();
while (p instanceof PsiWhiteSpace || p instanceof XmlText) {
p = p.getPrevSibling();
}
if (p instanceof XmlComment) {
final String commentText = XmlUtil.getCommentText((XmlComment)p);
return commentText != null ? commentText.replaceAll("&", "&amp;").replaceAll("<", "&lt;") : null;
} else {
return null;
}
}
final String name = getTagName(psiElement);
if (name != null) {
return getDocumentation(name, "element");
} else if (psiElement instanceof XPathFunction) {
return getDocumentation(((XPathFunction)psiElement).getName(), "function");
}
return null;
}
private static final Pattern check = Pattern.compile("x:found=\"(true|false)\"");
@Nullable
private String getDocumentation(String name, String type) {
try {
final Transformer transformer = getTemplate().newTransformer();
transformer.setParameter("element", name);
transformer.setParameter("type", type);
final StringWriter writer = new StringWriter();
transformer.transform(new JDOMSource(getDocumentationDocument()), new StreamResult(writer));
final String s = writer.toString();
final Matcher matcher = check.matcher(s);
if (matcher.find()) {
if (matcher.group(1).equals("true")) {
return s.replaceFirst("<META.+?>", "");
}
}
} catch (Exception e) {
Logger.getInstance(getClass().getName()).error(e);
}
return null;
}
@Nullable
private static String getTagName(@Nullable PsiElement psiElement1) {
XmlTag xmlTag = PsiTreeUtil.getParentOfType(psiElement1, XmlTag.class, false);
if (xmlTag != null) {
if (XsltSupport.isXsltTag(xmlTag)) {
return xmlTag.getLocalName();
}
else if (XmlUtil.ourSchemaUrisList.contains(xmlTag.getNamespace())) {
PsiFile file = xmlTag.getContainingFile();
if (file instanceof XmlFile) {
XmlTag tag = ((XmlFile)file).getRootTag();
if (tag != null && XsltSupport.XSLT_NS.equals(tag.getAttributeValue("targetNamespace"))) {
return xmlTag.getAttributeValue("name");
}
}
}
}
return null;
}
private Document getDocumentationDocument() throws IOException, JDOMException {
Document d = com.intellij.reference.SoftReference.dereference(myDocument);
if (d == null) {
d = new SAXBuilder().build(XsltSupport.class.getResource("resources/documentation.xml"));
myDocument = new SoftReference<Document>(d);
}
return d;
}
private Templates getTemplate() throws TransformerConfigurationException, IOException {
Templates t = com.intellij.reference.SoftReference.dereference(myTemplates);
if (t == null) {
t = TransformerFactory.newInstance().newTemplates(makeSource("resources/documentation.xsl"));
myTemplates = new SoftReference<Templates>(t);
}
return t;
}
private static StreamSource makeSource(String name) throws IOException {
final URL resource = XsltSupport.class.getResource(name);
return new StreamSource(resource.openStream(), resource.toExternalForm());
}
@Nullable
public PsiElement getDocumentationElementForLookupItem(PsiManager mgr, Object object, PsiElement psiElement) {
if (object instanceof String) {
if (psiElement instanceof XmlElement) {
final XmlTag tag = PsiTreeUtil.getParentOfType(psiElement, XmlTag.class);
if (tag != null && XsltSupport.XSLT_NS.equals(tag.getNamespace())) {
final String prefix = tag.getNamespacePrefix();
if (prefix.length() == 0) {
return new DocElement(mgr, psiElement, "element", (String)object);
} else if (StringUtil.startsWithConcatenation(((String)object), prefix, ":")) {
return new DocElement(mgr, psiElement, "element", ((String)object).substring(prefix.length() + 1));
}
}
}
}
if (object instanceof FunctionLookup) {
final FunctionLookup lookup = ((FunctionLookup)object);
return new DocElement(mgr, psiElement, "function", lookup.getName());
} else if (object instanceof ElementProvider) {
return ((ElementProvider)object).getElement();
} if (object instanceof XsltElement) {
return (PsiElement)object;
}
return null;
}
@Nullable
public PsiElement getDocumentationElementForLink(PsiManager mgr, String string, PsiElement psiElement) {
final String[] strings = string.split("\\$");
if (strings.length == 2) {
return new DocElement(mgr, psiElement, strings[0], strings[1]);
}
return null;
}
@Nullable
public String getQuickNavigateInfo(PsiElement element, PsiElement originalElement) {
return null;
}
static class DocElement extends LightElement implements PsiNamedElement {
private final PsiElement myElement;
private final String myCategory;
private final String myName;
public DocElement(PsiManager mgr, PsiElement element, String category, String name) {
super(mgr, XsltLanguage.INSTANCE);
myElement = element;
myCategory = category;
myName = name;
}
public String getCategory() {
return myCategory;
}
public PsiElement setName(@NotNull @NonNls String name) throws IncorrectOperationException {
throw new IncorrectOperationException("Unsupported");
}
public String getName() {
return myName;
}
public String toString() {
return "DocElement";
}
public PsiElement copy() {
return this;
}
@Override
public boolean isValid() {
return myElement != null && myElement.isValid();
}
@Nullable
public PsiFile getContainingFile() {
if (!isValid()) {
return null;
}
PsiFile file = myElement.getContainingFile();
final PsiElement context = myElement.getContext();
if (file == null && context != null) {
file = context.getContainingFile();
}
PsiFile f;
if ((f = PsiTreeUtil.getContextOfType(file, PsiFile.class, true)) instanceof XmlFile) {
return f;
}
return file;
}
}
}