update copyright
[fedora-idea.git] / xml / impl / src / com / intellij / xml / util / HtmlUtil.java
blob413e9b71f448df658b76b927a52a22afd1b5dea3
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.xml.util;
18 import com.intellij.codeInspection.InspectionProfile;
19 import com.intellij.codeInspection.ex.LocalInspectionToolWrapper;
20 import com.intellij.codeInspection.htmlInspections.HtmlUnknownAttributeInspection;
21 import com.intellij.codeInspection.htmlInspections.HtmlUnknownTagInspection;
22 import com.intellij.codeInspection.htmlInspections.XmlEntitiesInspection;
23 import com.intellij.lang.Language;
24 import com.intellij.lang.html.HTMLLanguage;
25 import com.intellij.lang.xhtml.XHTMLLanguage;
26 import com.intellij.openapi.editor.Editor;
27 import com.intellij.openapi.util.Ref;
28 import com.intellij.openapi.vfs.CharsetToolkit;
29 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
30 import com.intellij.psi.FileViewProvider;
31 import com.intellij.psi.PsiElement;
32 import com.intellij.psi.PsiFile;
33 import com.intellij.psi.html.HtmlTag;
34 import com.intellij.psi.impl.source.parsing.xml.HtmlBuilderDriver;
35 import com.intellij.psi.impl.source.parsing.xml.XmlBuilder;
36 import com.intellij.psi.impl.source.xml.XmlAttributeImpl;
37 import com.intellij.psi.templateLanguages.TemplateLanguageFileViewProvider;
38 import com.intellij.psi.templateLanguages.TemplateLanguageUtil;
39 import com.intellij.psi.util.PsiTreeUtil;
40 import com.intellij.psi.xml.XmlDocument;
41 import com.intellij.psi.xml.XmlElement;
42 import com.intellij.psi.xml.XmlFile;
43 import com.intellij.psi.xml.XmlTag;
44 import com.intellij.util.ArrayUtil;
45 import com.intellij.xml.XmlAttributeDescriptor;
46 import com.intellij.xml.XmlElementDescriptor;
47 import com.intellij.xml.impl.schema.XmlAttributeDescriptorImpl;
48 import com.intellij.xml.impl.schema.XmlElementDescriptorImpl;
49 import com.intellij.xml.util.documentation.HtmlDescriptorsTable;
50 import gnu.trove.THashSet;
51 import org.apache.commons.collections.Bag;
52 import org.apache.commons.collections.bag.HashBag;
53 import org.jetbrains.annotations.NonNls;
54 import org.jetbrains.annotations.NotNull;
55 import org.jetbrains.annotations.Nullable;
57 import java.nio.charset.Charset;
58 import java.util.Arrays;
59 import java.util.List;
60 import java.util.Set;
61 import java.util.StringTokenizer;
63 /**
64 * @author Maxim.Mossienko
66 public class HtmlUtil {
67 @NonNls private static final String JSFC = "jsfc";
68 @NonNls private static final String CHARSET_PREFIX = "charset=";
70 private HtmlUtil() {}
71 @NonNls private static final String[] EMPTY_TAGS = {
72 "base","hr","meta","link","frame","br","basefont","param","img","area","input","isindex","col"
74 private static final Set<String> EMPTY_TAGS_MAP = new THashSet<String>();
75 @NonNls private static final String[] OPTIONAL_END_TAGS = {
76 //"html",
77 "head",
78 //"body",
79 "p", "li", "dd", "dt", "thead", "tfoot", "tbody", "colgroup", "tr", "th", "td", "option", "embed", "noembed"
81 private static final Set<String> OPTIONAL_END_TAGS_MAP = new THashSet<String>();
83 @NonNls private static final String[] BLOCK_TAGS = { "p", "h1", "h2", "h3", "h4", "h5", "h6", "ul", "ol", "dir", "menu", "pre",
84 "dl", "div", "center", "noscript", "noframes", "blockquote", "form", "isindex", "hr", "table", "fieldset", "address",
85 // nonexplicitly specified
86 "map",
87 // flow elements
88 "body", "object", "applet", "ins", "del", "dd", "li", "button", "th", "td", "iframe","comment","nobr"
91 // flow elements are block or inline, so they shuld not close <p> for example
92 @NonNls private static final String[] POSSIBLY_INLINE_TAGS = { "object", "applet", "ins", "del", "button", "nobr" };
94 private static final Set<String> BLOCK_TAGS_MAP = new THashSet<String>();
96 @NonNls private static final String[] INLINE_ELEMENTS_CONTAINER = { "p", "h1", "h2", "h3", "h4", "h5", "h6", "pre", "dt" };
97 private static final Set<String> INLINE_ELEMENTS_CONTAINER_MAP = new THashSet<String>();
99 @NonNls private static final String[] EMPTY_ATTRS = { "nowrap", "compact", "disabled", "readonly", "selected", "multiple", "nohref", "ismap", "declare", "noshade", "checked" };
100 private static final Set<String> EMPTY_ATTRS_MAP = new THashSet<String>();
102 private static final Set<String> POSSIBLY_INLINE_TAGS_MAP = new THashSet<String>();
104 static {
105 EMPTY_TAGS_MAP.addAll(Arrays.asList(EMPTY_TAGS));
106 EMPTY_ATTRS_MAP.addAll(Arrays.asList(EMPTY_ATTRS));
107 OPTIONAL_END_TAGS_MAP.addAll(Arrays.asList(OPTIONAL_END_TAGS));
108 BLOCK_TAGS_MAP.addAll(Arrays.asList(BLOCK_TAGS));
109 INLINE_ELEMENTS_CONTAINER_MAP.addAll(Arrays.asList(INLINE_ELEMENTS_CONTAINER));
110 POSSIBLY_INLINE_TAGS_MAP.addAll(Arrays.asList(POSSIBLY_INLINE_TAGS));
113 public static boolean isSingleHtmlTag(String tagName) {
114 return EMPTY_TAGS_MAP.contains(tagName.toLowerCase());
117 public static boolean isSingleHtmlTagL(String tagName) {
118 return EMPTY_TAGS_MAP.contains(tagName);
121 public static boolean isOptionalEndForHtmlTag(String tagName) {
122 return OPTIONAL_END_TAGS_MAP.contains(tagName.toLowerCase());
125 public static boolean isOptionalEndForHtmlTagL(String tagName) {
126 return OPTIONAL_END_TAGS_MAP.contains(tagName);
129 public static boolean isSingleHtmlAttribute(String attrName) {
130 return EMPTY_ATTRS_MAP.contains(attrName.toLowerCase());
133 public static boolean isHtmlBlockTag(String tagName) {
134 return BLOCK_TAGS_MAP.contains(tagName.toLowerCase());
137 public static boolean isPossiblyInlineTag(String tagName) {
138 return POSSIBLY_INLINE_TAGS_MAP.contains(tagName);
141 public static boolean isHtmlBlockTagL(String tagName) {
142 return BLOCK_TAGS_MAP.contains(tagName);
145 public static boolean isInlineTagContainer(String tagName) {
146 return INLINE_ELEMENTS_CONTAINER_MAP.contains(tagName.toLowerCase());
149 public static boolean isInlineTagContainerL(String tagName) {
150 return INLINE_ELEMENTS_CONTAINER_MAP.contains(tagName);
153 public static void addHtmlSpecificCompletions(final XmlElementDescriptor descriptor,
154 final XmlTag element,
155 final List<XmlElementDescriptor> variants) {
156 // add html block completions for tags with optional ends!
157 String name = descriptor.getName(element);
159 if (name != null && isOptionalEndForHtmlTag(name)) {
160 PsiElement parent = element.getParent();
162 if (parent!=null) {
163 // we need grand parent since completion already uses parent's descriptor
164 parent = parent.getParent();
167 if (parent instanceof HtmlTag) {
168 final XmlElementDescriptor parentDescriptor = ((HtmlTag)parent).getDescriptor();
170 if (parentDescriptor!=descriptor && parentDescriptor!=null) {
171 for (final XmlElementDescriptor elementsDescriptor : parentDescriptor.getElementsDescriptors((XmlTag)parent)) {
172 if (isHtmlBlockTag(elementsDescriptor.getName())) {
173 variants.add(elementsDescriptor);
181 @Nullable
182 public static XmlDocument getRealXmlDocument(@Nullable XmlDocument doc) {
183 if (doc == null) return null;
184 final PsiFile containingFile = doc.getContainingFile();
186 final PsiFile templateFile = TemplateLanguageUtil.getTemplateFile(containingFile);
187 if (templateFile instanceof XmlFile) {
188 return ((XmlFile)templateFile).getDocument();
190 return doc;
193 public static String[] getHtmlTagNames() {
194 return HtmlDescriptorsTable.getHtmlTagNames();
197 public static XmlAttributeDescriptor[] getCustomAttributeDescriptors(XmlElement context) {
198 String entitiesString = getEntitiesString(context, XmlEntitiesInspection.UNKNOWN_ATTRIBUTE);
199 if (entitiesString == null) return XmlAttributeDescriptor.EMPTY;
201 StringTokenizer tokenizer = new StringTokenizer(entitiesString,",");
202 XmlAttributeDescriptor[] descriptors = new XmlAttributeDescriptor[tokenizer.countTokens()];
203 int index = 0;
205 while(tokenizer.hasMoreElements()) {
206 final String customName = tokenizer.nextToken();
207 if (customName.length() == 0) continue;
209 descriptors[index++] = new XmlAttributeDescriptorImpl() {
210 public String getName(PsiElement context) {
211 return customName;
214 public String getName() {
215 return customName;
220 return descriptors;
223 public static XmlElementDescriptor[] getCustomTagDescriptors(XmlElement context) {
224 String entitiesString = getEntitiesString(context, XmlEntitiesInspection.UNKNOWN_TAG);
225 if (entitiesString == null) return XmlElementDescriptor.EMPTY_ARRAY;
227 StringTokenizer tokenizer = new StringTokenizer(entitiesString, ",");
228 XmlElementDescriptor[] descriptors = new XmlElementDescriptor[tokenizer.countTokens()];
229 int index = 0;
231 while(tokenizer.hasMoreElements()) {
232 final String tagName = tokenizer.nextToken();
233 if (tagName.length() == 0) continue;
235 descriptors[index++] = new XmlElementDescriptorImpl(context instanceof XmlTag ? (XmlTag)context:null) {
236 public String getName(PsiElement context) {
237 return tagName;
240 public String getDefaultName() {
241 return tagName;
244 public boolean allowElementsFromNamespace(final String namespace, final XmlTag context) {
245 return true;
250 return descriptors;
253 @Nullable
254 public static String getEntitiesString(XmlElement context, int type) {
255 if (context == null) return null;
256 PsiFile containingFile = context.getContainingFile().getOriginalFile();
258 final InspectionProfile profile = InspectionProjectProfileManager.getInstance(context.getProject()).getInspectionProfile();
260 switch(type) {
261 case XmlEntitiesInspection.UNKNOWN_TAG:
262 LocalInspectionToolWrapper wrapper = (LocalInspectionToolWrapper) profile.getInspectionTool(HtmlUnknownTagInspection.TAG_SHORT_NAME,
263 containingFile);
264 HtmlUnknownTagInspection unknownTagInspection = wrapper != null ? (HtmlUnknownTagInspection) wrapper.getTool() : null;
265 if (unknownTagInspection != null) {
266 return unknownTagInspection.getAdditionalEntries();
268 break;
269 case XmlEntitiesInspection.UNKNOWN_ATTRIBUTE:
270 LocalInspectionToolWrapper wrapper1 = (LocalInspectionToolWrapper) profile.getInspectionTool(HtmlUnknownAttributeInspection.ATTRIBUTE_SHORT_NAME,
271 containingFile);
272 HtmlUnknownAttributeInspection unknownAttributeInspection = wrapper1 != null ? (HtmlUnknownAttributeInspection) wrapper1.getTool() : null;
273 if (unknownAttributeInspection != null) {
274 return unknownAttributeInspection.getAdditionalEntries();
276 break;
279 return null;
282 public static XmlAttributeDescriptor[] appendHtmlSpecificAttributeCompletions(final XmlTag declarationTag,
283 XmlAttributeDescriptor[] descriptors,
284 final XmlAttributeImpl context) {
285 if (declarationTag instanceof HtmlTag) {
286 descriptors = ArrayUtil.mergeArrays(
287 descriptors,
288 getCustomAttributeDescriptors(context),
289 XmlAttributeDescriptor.class
291 return descriptors;
294 if (declarationTag.getPrefixByNamespace(XmlUtil.JSF_HTML_URI) != null &&
295 declarationTag.getNSDescriptor(XmlUtil.XHTML_URI, true) != null &&
296 !XmlUtil.JSP_URI.equals(declarationTag.getNamespace())) {
298 descriptors = ArrayUtil.append(
299 descriptors,
300 new XmlAttributeDescriptorImpl() {
301 public String getName(PsiElement context) {
302 return JSFC;
305 public String getName() {
306 return JSFC;
311 return descriptors;
314 private static class TerminateException extends RuntimeException {
315 private static final TerminateException INSTANCE = new TerminateException();
317 public static Charset detectCharsetFromMetaHttpEquiv(@NotNull String content) {
318 final Ref<String> charsetNameRef = new Ref<String>();
319 try {
320 new HtmlBuilderDriver(content).build(new XmlBuilder() {
321 @NonNls final Bag inTag = new HashBag();
322 boolean metHttpEquiv = false;
324 public void doctype(@Nullable final CharSequence publicId, @Nullable final CharSequence systemId, final int startOffset, final int endOffset) {
327 public ProcessingOrder startTag(final CharSequence localName, final String namespace, final int startoffset, final int endoffset,
328 final int headerEndOffset) {
329 @NonNls String name = localName.toString().toLowerCase();
330 inTag.add(name);
331 if (!inTag.contains("head") && !"html".equals(name)) terminate();
332 return ProcessingOrder.TAGS_AND_ATTRIBUTES;
335 private void terminate() {
336 throw TerminateException.INSTANCE;
339 public void endTag(final CharSequence localName, final String namespace, final int startoffset, final int endoffset) {
340 @NonNls final String name = localName.toString().toLowerCase();
341 if ("meta".equals(name) && metHttpEquiv && contentAttributeValue != null) {
342 int start = contentAttributeValue.indexOf(CHARSET_PREFIX);
343 if (start == -1) return;
344 start += CHARSET_PREFIX.length();
345 int end = contentAttributeValue.indexOf(';', start);
346 if (end == -1) end = contentAttributeValue.length();
347 String charsetName = contentAttributeValue.substring(start, end);
348 charsetNameRef.set(charsetName);
349 terminate();
351 if ("head".equals(name)) {
352 terminate();
354 inTag.remove(name);
355 metHttpEquiv = false;
356 contentAttributeValue = null;
359 private String contentAttributeValue;
360 public void attribute(final CharSequence localName, final CharSequence v, final int startoffset, final int endoffset) {
361 @NonNls final String name = localName.toString().toLowerCase();
362 if (inTag.contains("meta")) {
363 @NonNls String value = v.toString().toLowerCase();
364 if (name.equals("http-equiv")) {
365 metHttpEquiv |= value.equals("content-type");
367 if (name.equals("content")) {
368 contentAttributeValue = value;
373 public void textElement(final CharSequence display, final CharSequence physical, final int startoffset, final int endoffset) {
376 public void entityRef(final CharSequence ref, final int startOffset, final int endOffset) {
379 public void error(String message, int startOffset, int endOffset) {
383 catch (TerminateException e) {
384 //ignore
386 catch (Exception e) {
387 // some weird things can happen, like unbalanaced tree
390 String name = charsetNameRef.get();
391 return CharsetToolkit.forName(name);
394 public static boolean isTagWithoutAttributes(@NonNls String tagName) {
395 return tagName != null && "br".equalsIgnoreCase(tagName);
398 public static boolean hasHtml(PsiFile file) {
399 if (isHtmlFile(file)) return true;
400 if (file.getViewProvider() instanceof TemplateLanguageFileViewProvider) return true;
401 return false;
404 private static boolean isHtmlFile(final PsiFile file) {
405 final Language language = file.getLanguage();
406 if (language == HTMLLanguage.INSTANCE || language == XHTMLLanguage.INSTANCE) {
407 return true;
409 return false;
412 public static boolean isHtmlTagContainingFile(PsiElement element) {
413 if (element == null) {
414 return false;
416 final PsiFile containingFile = element.getContainingFile();
417 if (containingFile != null) {
418 final XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class, false);
419 if (tag instanceof HtmlTag) {
420 return true;
422 else {
423 final FileViewProvider provider = containingFile.getViewProvider();
424 Language language;
425 if (provider instanceof TemplateLanguageFileViewProvider) {
426 language = ((TemplateLanguageFileViewProvider)provider).getTemplateDataLanguage();
428 else {
429 language = provider.getBaseLanguage();
432 return language == XHTMLLanguage.INSTANCE;
435 return false;
438 public static boolean isHtmlTagContainingFile(final Editor editor, final PsiFile file) {
439 if (editor == null || file == null || !(file instanceof XmlFile)) {
440 return false;
442 final int offset = editor.getCaretModel().getOffset();
443 final PsiElement element = file.findElementAt(offset);
444 return isHtmlTagContainingFile(element);