| /* |
| * Copyright 2007 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.ASTNode; |
| import com.intellij.lang.LanguageParserDefinitions; |
| import com.intellij.lang.injection.MultiHostInjector; |
| import com.intellij.lang.injection.MultiHostRegistrar; |
| import com.intellij.lexer.Lexer; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.psi.PsiElement; |
| import com.intellij.psi.impl.source.xml.XmlAttributeValueImpl; |
| import com.intellij.psi.xml.XmlAttribute; |
| import com.intellij.psi.xml.XmlElementType; |
| import com.intellij.util.SmartList; |
| import org.intellij.lang.xpath.XPathTokenTypes; |
| import org.intellij.lang.xpath.xslt.XsltSupport; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.Arrays; |
| import java.util.List; |
| |
| public class XPathLanguageInjector implements MultiHostInjector { |
| private static final Key<Pair<String, TextRange[]>> CACHED_FILES = Key.create("CACHED_FILES"); |
| private static final TextRange[] EMPTY_ARRAY = new TextRange[0]; |
| |
| public XPathLanguageInjector() { |
| } |
| |
| @Nullable |
| private static TextRange[] getCachedRanges(XmlAttribute attribute) { |
| Pair<String, TextRange[]> pair; |
| if ((pair = attribute.getUserData(CACHED_FILES)) != null) { |
| if (!attribute.getValue().equals(pair.getFirst())) { |
| attribute.putUserData(CACHED_FILES, null); |
| return null; |
| } |
| } else { |
| return null; |
| } |
| return pair.getSecond(); |
| } |
| |
| static final class AVTRange extends TextRange { |
| final boolean myComplete; |
| |
| private AVTRange(int startOffset, int endOffset, boolean iscomplete) { |
| super(startOffset, endOffset); |
| myComplete = iscomplete; |
| } |
| |
| public static AVTRange create(XmlAttribute attribute, int startOffset, int endOffset, boolean iscomplete) { |
| return new AVTRange(attribute.displayToPhysical(startOffset), attribute.displayToPhysical(endOffset), iscomplete); |
| } |
| } |
| |
| @NotNull |
| private synchronized TextRange[] getInjectionRanges(final XmlAttribute attribute, XsltChecker.LanguageLevel languageLevel) { |
| final TextRange[] cachedFiles = getCachedRanges(attribute); |
| if (cachedFiles != null) { |
| return cachedFiles; |
| } |
| |
| final String value = attribute.getDisplayValue(); |
| if (value == null) return EMPTY_ARRAY; |
| |
| final TextRange[] ranges; |
| if (XsltSupport.mayBeAVT(attribute)) { |
| final List<TextRange> avtRanges = new SmartList<TextRange>(); |
| |
| int i; |
| int j = 0; |
| Lexer lexer = null; |
| while ((i = XsltSupport.getAVTOffset(value, j)) != -1) { |
| if (lexer == null) { |
| lexer = LanguageParserDefinitions.INSTANCE.forLanguage(languageLevel.getXPathVersion().getLanguage()) |
| .createLexer(attribute.getProject()); |
| } |
| |
| // "A right curly brace inside a Literal in an expression is not recognized as terminating the expression." |
| lexer.start(value, i, value.length()); |
| j = -1; |
| while (lexer.getTokenType() != null) { |
| if (lexer.getTokenType() == XPathTokenTypes.RBRACE) { |
| j = lexer.getTokenStart(); |
| break; |
| } |
| lexer.advance(); |
| } |
| |
| if (j != -1) { |
| avtRanges.add(AVTRange.create(attribute, i, j + 1, j > i + 1)); |
| } else { |
| // missing '}' error will be flagged by xpath parser |
| avtRanges.add(AVTRange.create(attribute, i, value.length(), false)); |
| break; |
| } |
| } |
| |
| if (avtRanges.size() > 0) { |
| ranges = avtRanges.toArray(new TextRange[avtRanges.size()]); |
| } else { |
| ranges = EMPTY_ARRAY; |
| } |
| } else { |
| ranges = new TextRange[]{ attribute.getValueTextRange() }; |
| } |
| |
| attribute.putUserData(CACHED_FILES, Pair.create(attribute.getValue(), ranges)); |
| |
| return ranges; |
| } |
| |
| @NotNull |
| public List<? extends Class<? extends PsiElement>> elementsToInjectIn() { |
| return Arrays.asList(XmlAttribute.class); |
| } |
| |
| public void getLanguagesToInject(@NotNull MultiHostRegistrar registrar, @NotNull PsiElement context) { |
| final XmlAttribute attribute = (XmlAttribute)context; |
| if (!XsltSupport.isXPathAttribute(attribute)) return; |
| |
| XmlAttributeValueImpl value = (XmlAttributeValueImpl)attribute.getValueElement(); |
| if (value == null) return; |
| ASTNode type = value.findChildByType(XmlElementType.XML_ENTITY_REF); |
| if (type != null) return; // workaround for inability to inject into text with entity refs (e.g. IDEA-72972) TODO: fix it |
| |
| final XsltChecker.LanguageLevel languageLevel = XsltSupport.getXsltLanguageLevel(attribute.getContainingFile()); |
| final TextRange[] ranges = getInjectionRanges(attribute, languageLevel); |
| for (TextRange range : ranges) { |
| // workaround for http://www.jetbrains.net/jira/browse/IDEA-10096 |
| TextRange rangeInsideHost; |
| String prefix; |
| if (range instanceof AVTRange) { |
| if (((AVTRange)range).myComplete) { |
| rangeInsideHost = range.shiftRight(2).grown(-2); |
| prefix = ""; |
| } |
| else { |
| // we need to keep the "'}' expected" parse error |
| rangeInsideHost = range.shiftRight(2).grown(-1); |
| prefix = "{"; |
| } |
| } |
| else { |
| rangeInsideHost = range; |
| prefix = ""; |
| } |
| if (value.getTextRange().contains(rangeInsideHost.shiftRight(value.getTextRange().getStartOffset()))) { |
| registrar.startInjecting(languageLevel.getXPathVersion().getLanguage()) |
| .addPlace(prefix, "", value, rangeInsideHost) |
| .doneInjecting(); |
| } |
| } |
| } |
| } |