blob: 271fd5b6ff3d771362791c036f9e7607496cdbba [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* 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 com.intellij.xml.util.documentation;
import com.intellij.lang.Language;
import com.intellij.lang.LanguageDocumentation;
import com.intellij.lang.documentation.DocumentationProvider;
import com.intellij.lang.documentation.DocumentationUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.XmlElementFactory;
import com.intellij.psi.impl.source.xml.SchemaPrefix;
import com.intellij.psi.meta.PsiMetaData;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.*;
import com.intellij.util.IncorrectOperationException;
import com.intellij.xml.XmlAttributeDescriptor;
import com.intellij.xml.XmlBundle;
import com.intellij.xml.XmlElementDescriptor;
import com.intellij.xml.util.ColorSampleLookupValue;
import com.intellij.xml.util.XmlUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
/**
* @author maxim
*/
public class HtmlDocumentationProvider implements DocumentationProvider {
private DocumentationProvider myStyleProvider = null;
private static DocumentationProvider ourScriptProvider;
@NonNls public static final String ELEMENT_ELEMENT_NAME = "element";
@NonNls public static final String NBSP = ": ";
@NonNls public static final String BR = "<br>";
@Override
@Nullable
public String getQuickNavigateInfo(PsiElement element, PsiElement originalElement) {
if (element instanceof SchemaPrefix) {
return ((SchemaPrefix)element).getQuickNavigateInfo();
}
return null;
}
@Override
public List<String> getUrlFor(PsiElement element, PsiElement originalElement) {
String result = getUrlForHtml(element, PsiTreeUtil.getParentOfType(originalElement, XmlTag.class, false));
DocumentationProvider styleProvider = getStyleProvider();
if (result == null && styleProvider != null) {
return styleProvider.getUrlFor(element, originalElement);
}
return result != null ? Collections.singletonList(result) : null;
}
public static String getUrlForHtml(PsiElement element, XmlTag context) {
final EntityDescriptor descriptor = findDocumentationDescriptor(element, context);
if (descriptor!=null) {
return descriptor.getHelpRef();
} else {
return null;
}
}
private static EntityDescriptor findDocumentationDescriptor(PsiElement element, XmlTag context) {
boolean isTag = true;
PsiElement nameElement = null;
String key = null;
if (element instanceof XmlElementDecl) {
nameElement = ((XmlElementDecl)element).getNameElement();
} else if (element instanceof XmlAttributeDecl) {
nameElement = ((XmlAttributeDecl)element).getNameElement();
isTag = false;
} else if (element instanceof XmlTag) {
final XmlTag xmlTag = ((XmlTag)element);
final PsiMetaData metaData = xmlTag.getMetaData();
key = (metaData!=null)?metaData.getName():null;
isTag = xmlTag.getLocalName().equals(ELEMENT_ELEMENT_NAME);
} else if (element.getParent() instanceof XmlAttributeValue) {
isTag = false;
key = ((XmlAttribute)element.getParent().getParent()).getName();
} else if (element instanceof XmlAttributeValue) {
isTag = false;
final XmlAttribute xmlAttribute = (XmlAttribute)element.getParent();
key = xmlAttribute.getName();
} else if (element instanceof XmlAttribute) {
final XmlAttribute xmlAttribute = (XmlAttribute)element;
isTag = false;
key = xmlAttribute.getName();
} else if (element instanceof XmlElement) {
nameElement = element;
isTag = !(element.getParent() instanceof XmlAttribute);
} else {
nameElement = element;
if (context == null) {
isTag = false;
}
else {
String text = element.getText();
isTag = text != null && text.startsWith(context.getName());
}
}
if (nameElement!=null) {
key = nameElement.getText();
}
key = StringUtil.notNullize(key).toLowerCase(Locale.US);
int dotIndex = key.indexOf('.');
if (dotIndex > 0) {
key = key.substring(0, dotIndex);
}
if (isTag) {
return HtmlDescriptorsTable.getTagDescriptor(key);
} else {
return getDescriptor(key, context);
}
}
private static HtmlAttributeDescriptor getDescriptor(String name, XmlTag context) {
HtmlAttributeDescriptor attributeDescriptor = HtmlDescriptorsTable.getAttributeDescriptor(name);
if (attributeDescriptor instanceof CompositeAttributeTagDescriptor) {
return ((CompositeAttributeTagDescriptor)attributeDescriptor).findHtmlAttributeInContext(context);
}
return attributeDescriptor;
}
@Override
public String generateDoc(PsiElement element, PsiElement originalElement) {
final XmlTag tag = PsiTreeUtil.getParentOfType(originalElement, XmlTag.class, false);
String result = generateDocForHtml(element, false, tag, originalElement);
DocumentationProvider styleProvider = getStyleProvider();
if (result == null && styleProvider !=null) {
result = styleProvider.generateDoc(element, originalElement);
}
if (result == null && ourScriptProvider !=null) {
result = ourScriptProvider.generateDoc(element, originalElement);
}
if (result == null && element instanceof XmlAttributeValue) {
result = generateDocForHtml(element.getParent(), false, tag, originalElement);
}
return result;
}
protected String generateDocForHtml(PsiElement element, boolean omitHtmlSpecifics, XmlTag context, PsiElement originalElement) {
final EntityDescriptor descriptor = findDocumentationDescriptor(element,context);
if (descriptor!=null) {
return generateJavaDoc(descriptor, omitHtmlSpecifics, originalElement);
}
if (element instanceof XmlEntityDecl) {
final XmlEntityDecl entityDecl = (XmlEntityDecl)element;
return new XmlDocumentationProvider().findDocRightAfterElement(element, entityDecl.getName());
}
return null;
}
private static String generateJavaDoc(EntityDescriptor descriptor, boolean omitHtmlSpecifics, PsiElement element) {
StringBuilder buf = new StringBuilder();
final boolean isTag = descriptor instanceof HtmlTagDescriptor;
if (isTag) {
DocumentationUtil.formatEntityName(XmlBundle.message("xml.javadoc.tag.name.message"),descriptor.getName(),buf);
} else {
DocumentationUtil.formatEntityName(XmlBundle.message("xml.javadoc.attribute.name.message"),descriptor.getName(),buf);
}
buf.append(XmlBundle.message("xml.javadoc.description.message")).append(NBSP).append(descriptor.getDescription()).append(BR);
if (isTag) {
final HtmlTagDescriptor tagDescriptor = (HtmlTagDescriptor)descriptor;
if (!omitHtmlSpecifics) {
boolean hasStartTag = tagDescriptor.isHasStartTag();
if (!hasStartTag) {
buf.append(XmlBundle.message("xml.javadoc.start.tag.could.be.omitted.message")).append(BR);
}
if (!tagDescriptor.isEmpty() && !tagDescriptor.isHasEndTag()) {
buf.append(XmlBundle.message("xml.javadoc.end.tag.could.be.omitted.message")).append(BR);
}
}
if (tagDescriptor.isEmpty()) {
buf.append(XmlBundle.message("xml.javadoc.is.empty.message")).append(BR);
}
} else {
final HtmlAttributeDescriptor attributeDescriptor = (HtmlAttributeDescriptor)descriptor;
buf.append(XmlBundle.message("xml.javadoc.attr.type.message", attributeDescriptor.getType())).append(BR);
if (!attributeDescriptor.isHasDefaultValue())
buf.append(XmlBundle.message("xml.javadoc.attr.default.required.message")).append(BR);
}
char dtdId = descriptor.getDtd();
boolean deprecated = dtdId == EntityDescriptor.LOOSE_DTD;
if (deprecated) {
buf.append(XmlBundle.message("xml.javadoc.deprecated.message", true)).append(BR);
}
if (dtdId == EntityDescriptor.LOOSE_DTD) {
buf.append(XmlBundle.message("xml.javadoc.defined.in.loose.dtd.message"));
}
else if (dtdId == EntityDescriptor.FRAME_DTD) {
buf.append(XmlBundle.message("xml.javadoc.defined.in.frameset.dtd.message"));
}
else {
buf.append(XmlBundle.message("xml.javadoc.defined.in.any.dtd.message"));
}
if (!isTag) {
ColorSampleLookupValue.addColorPreviewAndCodeToLookup(element,buf);
}
if (element != null) {
buf.append(XmlDocumentationProvider.generateHtmlAdditionalDocTemplate(element));
}
return buf.toString();
}
@Override
public PsiElement getDocumentationElementForLookupItem(PsiManager psiManager, Object object, PsiElement element) {
PsiElement result = createNavigationElementHTML(psiManager, object.toString(),element);
DocumentationProvider styleProvider = getStyleProvider();
if (result== null && styleProvider !=null) {
result = styleProvider.getDocumentationElementForLookupItem(psiManager, object, element);
}
if (result== null && ourScriptProvider !=null) {
result = ourScriptProvider.getDocumentationElementForLookupItem(psiManager, object, element);
}
if (result == null && object instanceof String && element != null) {
result = XmlDocumentationProvider.findDeclWithName((String)object, element);
}
return result;
}
@Override
public PsiElement getDocumentationElementForLink(PsiManager psiManager, String link, PsiElement context) {
PsiElement result = createNavigationElementHTML(psiManager, link, context);
DocumentationProvider styleProvider = getStyleProvider();
if (result== null && styleProvider !=null) {
result = styleProvider.getDocumentationElementForLink(psiManager, link, context);
}
if (result== null && ourScriptProvider !=null) {
result = ourScriptProvider.getDocumentationElementForLink(psiManager, link,context);
}
return result;
}
public PsiElement createNavigationElementHTML(PsiManager psiManager, String text, PsiElement context) {
String key = text.toLowerCase(Locale.US);
final HtmlTagDescriptor descriptor = HtmlDescriptorsTable.getTagDescriptor(key);
if (descriptor != null && !isAttributeContext(context) ) {
try {
final XmlTag tagFromText = XmlElementFactory.getInstance(psiManager.getProject()).createTagFromText("<"+ key + " xmlns=\"" + XmlUtil.XHTML_URI + "\"/>");
final XmlElementDescriptor tagDescriptor = tagFromText.getDescriptor();
return tagDescriptor != null ? tagDescriptor.getDeclaration() : null;
}
catch(IncorrectOperationException ignore) {
}
}
else {
XmlTag tagContext = findTagContext(context);
HtmlAttributeDescriptor myAttributeDescriptor = getDescriptor(key,tagContext);
if (myAttributeDescriptor != null && tagContext != null) {
XmlElementDescriptor tagDescriptor = tagContext.getDescriptor();
XmlAttributeDescriptor attributeDescriptor = tagDescriptor != null ? tagDescriptor.getAttributeDescriptor(text, tagContext): null;
return (attributeDescriptor != null)?attributeDescriptor.getDeclaration():null;
}
}
return null;
}
protected boolean isAttributeContext(PsiElement context) {
if(context instanceof XmlAttribute) return true;
if (context instanceof PsiWhiteSpace) {
PsiElement prevSibling = context.getPrevSibling();
if (prevSibling instanceof XmlAttribute)
return true;
}
return false;
}
protected XmlTag findTagContext(PsiElement context) {
if (context instanceof PsiWhiteSpace) {
PsiElement prevSibling = context.getPrevSibling();
if (prevSibling instanceof XmlTag)
return (XmlTag)prevSibling;
}
return PsiTreeUtil.getParentOfType(context,XmlTag.class,false);
}
public static void registerScriptDocumentationProvider(final DocumentationProvider provider) {
ourScriptProvider = provider;
}
@Nullable
private DocumentationProvider getStyleProvider() {
if (myStyleProvider == null) {
Language cssLanguage = Language.findLanguageByID("CSS");
if (cssLanguage != null) {
myStyleProvider = LanguageDocumentation.INSTANCE.forLanguage(cssLanguage);
}
}
return myStyleProvider;
}
}