| /* |
| * 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.html.impl.util; |
| |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.psi.PsiFile; |
| import com.intellij.psi.PsiReference; |
| import com.intellij.psi.XmlRecursiveElementVisitor; |
| import com.intellij.psi.impl.source.resolve.reference.impl.providers.DependentNSReference; |
| import com.intellij.psi.impl.source.resolve.reference.impl.providers.URLReference; |
| import com.intellij.psi.xml.XmlAttribute; |
| import com.intellij.psi.xml.XmlAttributeValue; |
| import com.intellij.psi.xml.XmlFile; |
| import com.intellij.psi.xml.XmlTag; |
| import com.intellij.util.PairFunction; |
| import com.intellij.util.text.StringTokenizer; |
| import com.intellij.xml.util.HtmlUtil; |
| import gnu.trove.THashMap; |
| import gnu.trove.THashSet; |
| import org.jetbrains.annotations.Nls; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| /** |
| * @author: Fedor.Korotkov |
| */ |
| public class MicrodataUtil { |
| public static final Key<List<String>> ITEM_PROP_KEYS = Key.create("microdata.prop"); |
| public static final String ITEM_REF = "itemref"; |
| public static final String ITEM_SCOPE = "itemscope"; |
| public static final String ITEM_TYPE = "itemtype"; |
| public static final String ITEM_PROP = "itemprop"; |
| public static final String ITEM_ID = "itemid"; |
| |
| public static boolean hasScopeTag(@Nullable XmlTag tag) { |
| return findScopeTag(tag) != null; |
| } |
| |
| @Nullable |
| public static XmlTag findScopeTag(@Nullable XmlTag context) { |
| Map<String, XmlTag> id2tag = findScopesWithItemRef(context != null ? context.getContainingFile() : null); |
| XmlTag tag = context; |
| while (tag != null) { |
| if (tag != context && tag.getAttribute(ITEM_SCOPE) != null) return tag; |
| final String id = getStripedAttributeValue(tag, "id"); |
| if (id != null && id2tag.containsKey(id)) return id2tag.get(id); |
| tag = tag.getParentTag(); |
| } |
| return null; |
| } |
| |
| private static Map<String, XmlTag> findScopesWithItemRef(@Nullable PsiFile file) { |
| if (!(file instanceof XmlFile)) return Collections.emptyMap(); |
| final Map<String, XmlTag> result = new THashMap<String, XmlTag>(); |
| file.accept(new XmlRecursiveElementVisitor() { |
| @Override |
| public void visitXmlTag(final XmlTag tag) { |
| super.visitXmlTag(tag); |
| XmlAttribute refAttr = tag.getAttribute(ITEM_REF); |
| if (refAttr != null && tag.getAttribute(ITEM_SCOPE) != null) { |
| getReferencesForAttributeValue(refAttr.getValueElement(), new PairFunction<String, Integer, PsiReference>() { |
| @Nullable |
| @Override |
| public PsiReference fun(String t, Integer v) { |
| result.put(t, tag); |
| return null; |
| } |
| }); |
| } |
| } |
| }); |
| return result; |
| } |
| |
| public static List<String> extractProperties(PsiFile file, String type) { |
| final VirtualFile virtualFile = file.getVirtualFile(); |
| List<String> result = virtualFile != null ? virtualFile.getUserData(ITEM_PROP_KEYS) : null; |
| if (virtualFile != null && result == null) { |
| result = collectNames(file, type); |
| virtualFile.putUserData(ITEM_PROP_KEYS, result); |
| } |
| return result; |
| } |
| |
| private static List<String> collectNames(PsiFile file, String type) { |
| if (file instanceof XmlFile) { |
| final CollectNamesVisitor collectNamesVisitor = getVisitorByType(type); |
| file.accept(collectNamesVisitor); |
| return collectNamesVisitor.getValues(); |
| } |
| return Collections.emptyList(); |
| } |
| |
| private static CollectNamesVisitor getVisitorByType(String type) { |
| if (type.contains("schema.org")) { |
| return new CollectNamesFromSchemaOrgVisitor(); |
| } |
| return new CollectNamesByMicrodataVisitor(type); |
| } |
| |
| public static PsiReference[] getUrlReferencesForAttributeValue(final XmlAttributeValue element) { |
| return getReferencesForAttributeValue(element, new PairFunction<String, Integer, PsiReference>() { |
| @Nullable |
| @Override |
| public PsiReference fun(String token, Integer offset) { |
| if (HtmlUtil.hasHtmlPrefix(token)) { |
| final TextRange range = TextRange.from(offset, token.length()); |
| final URLReference urlReference = new URLReference(element, range, true); |
| return new DependentNSReference(element, range, urlReference, true); |
| } |
| return null; |
| } |
| }); |
| } |
| |
| public static PsiReference[] getReferencesForAttributeValue(@Nullable XmlAttributeValue element, |
| PairFunction<String, Integer, PsiReference> refFun) { |
| if (element == null) { |
| return PsiReference.EMPTY_ARRAY; |
| } |
| String text = element.getText(); |
| String urls = StringUtil.stripQuotesAroundValue(text); |
| StringTokenizer tokenizer = new StringTokenizer(urls); |
| List<PsiReference> result = new ArrayList<PsiReference>(); |
| while (tokenizer.hasMoreTokens()) { |
| String token = tokenizer.nextToken(); |
| int index = text.indexOf(token); |
| PsiReference ref = refFun.fun(token, index); |
| if (ref != null) { |
| result.add(ref); |
| } |
| } |
| return result.toArray(new PsiReference[result.size()]); |
| } |
| |
| @Nullable |
| public static String getStripedAttributeValue(@Nullable XmlTag tag, @Nls String attributeName) { |
| String value = tag != null ? tag.getAttributeValue(attributeName) : null; |
| return value != null ? StringUtil.stripQuotesAroundValue(value) : null; |
| } |
| |
| private static class CollectNamesVisitor extends XmlRecursiveElementVisitor { |
| protected final Set<String> myValues = new THashSet<String>(); |
| |
| public List<String> getValues() { |
| return new ArrayList<String>(myValues); |
| } |
| } |
| |
| public static class CollectNamesByMicrodataVisitor extends CollectNamesVisitor { |
| protected final String myType; |
| private boolean myCollecting = false; |
| |
| public CollectNamesByMicrodataVisitor(String type) { |
| myType = type; |
| } |
| |
| @Override |
| public void visitXmlTag(XmlTag tag) { |
| String value = getStripedAttributeValue(tag, ITEM_ID); |
| final boolean isTypeTag = myType.equalsIgnoreCase(value); |
| if (isTypeTag) { |
| myCollecting = true; |
| } |
| |
| if (myCollecting && "name".equalsIgnoreCase(getStripedAttributeValue(tag, ITEM_PROP))) { |
| myValues.add(tag.getValue().getTrimmedText()); |
| } |
| |
| super.visitXmlTag(tag); |
| |
| if (isTypeTag) { |
| myCollecting = false; |
| } |
| } |
| } |
| |
| public static class CollectNamesFromSchemaOrgVisitor extends CollectNamesVisitor { |
| @Override |
| public void visitXmlTag(XmlTag tag) { |
| super.visitXmlTag(tag); |
| if ("prop-nam".equalsIgnoreCase(getStripedAttributeValue(tag, "class"))) { |
| final String code = tag.getSubTagText("code"); |
| if (code != null) { |
| myValues.add(code); |
| } |
| } |
| } |
| } |
| } |