2 * Copyright 2005 Sascha Weinreuter
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 org
.intellij
.lang
.xpath
.xslt
.context
;
18 import com
.intellij
.lang
.LanguageRefactoringSupport
;
19 import com
.intellij
.lang
.refactoring
.RefactoringSupportProvider
;
20 import com
.intellij
.lang
.xml
.XMLLanguage
;
21 import com
.intellij
.openapi
.diagnostic
.Logger
;
22 import com
.intellij
.openapi
.project
.Project
;
23 import com
.intellij
.openapi
.util
.SimpleFieldCache
;
24 import com
.intellij
.psi
.*;
25 import com
.intellij
.psi
.util
.CachedValue
;
26 import com
.intellij
.psi
.util
.CachedValueProvider
;
27 import com
.intellij
.psi
.util
.CachedValuesManager
;
28 import com
.intellij
.psi
.util
.PsiTreeUtil
;
29 import com
.intellij
.psi
.xml
.*;
30 import com
.intellij
.util
.IncorrectOperationException
;
31 import com
.intellij
.xml
.XmlAttributeDescriptor
;
32 import com
.intellij
.xml
.XmlElementDescriptor
;
33 import com
.intellij
.xml
.XmlNSDescriptor
;
34 import com
.intellij
.xml
.impl
.schema
.XmlElementDescriptorImpl
;
35 import com
.intellij
.xml
.impl
.schema
.XmlNSDescriptorImpl
;
36 import com
.intellij
.xml
.util
.XmlUtil
;
37 import gnu
.trove
.THashSet
;
38 import org
.intellij
.lang
.xpath
.XPathFile
;
39 import org
.intellij
.lang
.xpath
.context
.ContextProvider
;
40 import org
.intellij
.lang
.xpath
.context
.ContextType
;
41 import org
.intellij
.lang
.xpath
.context
.NamespaceContext
;
42 import org
.intellij
.lang
.xpath
.context
.VariableContext
;
43 import org
.intellij
.lang
.xpath
.context
.functions
.FunctionContext
;
44 import org
.intellij
.lang
.xpath
.psi
.XPathExpression
;
45 import org
.intellij
.lang
.xpath
.psi
.XPathType
;
46 import org
.intellij
.lang
.xpath
.validation
.inspections
.quickfix
.XPathQuickFixFactory
;
47 import org
.intellij
.lang
.xpath
.xslt
.XsltSupport
;
48 import org
.intellij
.lang
.xpath
.xslt
.associations
.FileAssociationsManager
;
49 import org
.intellij
.lang
.xpath
.xslt
.psi
.XsltElement
;
50 import org
.intellij
.lang
.xpath
.xslt
.psi
.XsltElementFactory
;
51 import org
.intellij
.lang
.xpath
.xslt
.psi
.XsltVariable
;
52 import org
.intellij
.lang
.xpath
.xslt
.psi
.XsltWithParam
;
53 import org
.intellij
.lang
.xpath
.xslt
.psi
.impl
.XsltLanguage
;
54 import org
.intellij
.lang
.xpath
.xslt
.util
.NSDeclTracker
;
55 import org
.intellij
.lang
.xpath
.xslt
.util
.QNameUtil
;
56 import org
.jetbrains
.annotations
.NotNull
;
57 import org
.jetbrains
.annotations
.Nullable
;
59 import javax
.xml
.namespace
.QName
;
62 public class XsltContextProvider
extends ContextProvider
{
63 public static final ContextType TYPE
= ContextType
.lookupOrCreate("XSLT");
65 private static final Set
<String
> IGNORED_URIS
= new THashSet
<String
>();
67 IGNORED_URIS
.add(XsltSupport
.XSLT_NS
);
68 IGNORED_URIS
.addAll(XmlUtil
.ourSchemaUrisList
);
71 private CachedValue
<ElementNames
> myNames
;
73 private static final SimpleFieldCache
<CachedValue
<ElementNames
>, XsltContextProvider
> myNamesCache
= new SimpleFieldCache
<CachedValue
<ElementNames
>, XsltContextProvider
>() {
74 protected CachedValue
<ElementNames
> compute(final XsltContextProvider xsltContextProvider
) {
75 return xsltContextProvider
.createCachedValue(xsltContextProvider
.getFile());
78 protected CachedValue
<ElementNames
> getValue(final XsltContextProvider xsltContextProvider
) {
79 return xsltContextProvider
.myNames
;
82 protected void putValue(final CachedValue
<ElementNames
> elementNamesCachedValue
, final XsltContextProvider xsltContextProvider
) {
83 xsltContextProvider
.myNames
= elementNamesCachedValue
;
87 private final SmartPsiElementPointer
<XmlElement
> myContextElement
;
88 private final FileAssociationsManager myFileAssociationsManager
;
90 protected XsltContextProvider(@NotNull XmlElement contextElement
) {
91 final Project project
= contextElement
.getProject();
92 myFileAssociationsManager
= FileAssociationsManager
.getInstance(project
);
93 myContextElement
= SmartPointerManager
.getInstance(project
).createLazyPointer(contextElement
);
94 attachTo(contextElement
);
98 public ContextType
getContextType() {
102 public PsiFile
[] getRelatedFiles(final XPathFile file
) {
104 final XmlAttribute attribute
= PsiTreeUtil
.getContextOfType(file
, XmlAttribute
.class, false);
105 assert attribute
!= null;
107 final PsiFile psiFile
= attribute
.getContainingFile();
108 assert psiFile
!= null;
110 final List
<PsiFile
> files
= new ArrayList
<PsiFile
>();
112 psiFile
.accept(new XmlRecursiveElementVisitor() {
114 public void visitXmlAttribute(XmlAttribute attribute
) {
115 final PsiFile
[] _files
= XsltSupport
.getFiles(attribute
);
116 for (PsiFile _file
: _files
) {
117 if (_file
!= file
) files
.add(_file
);
122 return files
.toArray(new PsiFile
[files
.size()]);
126 public XmlElement
getContextElement() {
127 return myContextElement
.getElement();
131 public XPathType
getExpectedType(XPathExpression expr
) {
132 final XmlTag tag
= PsiTreeUtil
.getContextOfType(expr
, XmlTag
.class, true);
133 if (tag
!= null && XsltSupport
.isXsltTag(tag
)) {
134 final XsltElement element
= XsltElementFactory
.getInstance().wrapElement(tag
, XsltElement
.class);
135 if (element
instanceof XsltVariable
) {
136 return ((XsltVariable
)element
).getType();
138 final XmlAttribute attr
= PsiTreeUtil
.getContextOfType(expr
, XmlAttribute
.class, true);
140 if (element
instanceof XsltWithParam
) {
141 final XmlAttribute nameAttr
= tag
.getAttribute("name", null);
142 if (nameAttr
!= null) {
143 final XmlAttributeValue valueElement
= nameAttr
.getValueElement();
144 if (valueElement
!= null) {
145 final PsiReference
[] references
= valueElement
.getReferences();
146 for (PsiReference reference
: references
) {
147 final PsiElement psiElement
= reference
.resolve();
148 if (psiElement
instanceof XsltVariable
) {
149 return ((XsltVariable
)psiElement
).getType();
155 final String name
= attr
.getName();
156 final String tagName
= tag
.getLocalName();
157 if ("select".equals(name
)) {
158 if ("copy-of".equals(tagName
) || "for-each".equals(tagName
) || "apply-templates".equals(tagName
)) {
159 return XPathType
.NODESET
;
160 } else if ("value-of".equals(tagName
) || "sort".equals(tagName
)) {
161 return XPathType
.STRING
;
163 return XPathType
.ANY
;
165 } else if ("test".equals(name
)) {
166 if ("if".equals(tagName
) || "when".equals(tagName
)) {
167 return XPathType
.BOOLEAN
;
169 } else if ("number".equals(name
)) {
170 if ("value".equals(tagName
)) {
171 return XPathType
.NUMBER
;
178 return XPathType
.UNKNOWN
;
182 public NamespaceContext
getNamespaceContext() {
183 return XsltNamespaceContext
.NAMESPACE_CONTEXT
;
187 public VariableContext
getVariableContext() {
188 return XsltVariableContext
.INSTANCE
;
192 public FunctionContext
getFunctionContext() {
193 return XsltFunctionContext
.INSTANCE
;
196 static class ElementNames
{
197 boolean validateNames
;
199 Set
<QName
> elementNames
= new HashSet
<QName
>();
200 Set
<QName
> attributeNames
= new HashSet
<QName
>();
202 @SuppressWarnings({ "RawUseOfParameterizedType" })
203 Set dependencies
= new HashSet();
207 public Set
<QName
> getAttributes(boolean forValidation
) {
208 final ElementNames names
= getNames(getFile());
210 return !forValidation
|| names
.validateNames ? names
.attributeNames
: null;
216 public Set
<QName
> getElements(boolean forValidation
) {
217 final ElementNames names
= getNames(getFile());
219 return !forValidation
|| names
.validateNames ? names
.elementNames
: null;
225 private ElementNames
getNames(@Nullable PsiFile file
) {
226 if (file
== null) return null;
228 return myNamesCache
.get(this).getValue();
231 private CachedValue
<ElementNames
> createCachedValue(final PsiFile file
) {
232 return CachedValuesManager
.getManager(file
.getProject()).createCachedValue(new CachedValueProvider
<ElementNames
>() {
233 public Result
<ElementNames
> compute() {
234 final ElementNames names
= new ElementNames();
235 final PsiFile
[] associations
= myFileAssociationsManager
.getAssociationsFor(file
, FileAssociationsManager
.XML_FILES
);
237 if (associations
.length
== 0) {
238 fillFromSchema(file
, names
);
240 names
.validateNames
= true;
241 //noinspection unchecked
242 names
.dependencies
.addAll(Arrays
.asList(associations
));
244 //noinspection unchecked
245 names
.dependencies
.add(myFileAssociationsManager
);
247 for (PsiFile file
: associations
) {
248 if (!(file
instanceof XmlFile
)) continue;
249 file
.accept(new XmlRecursiveElementVisitor() {
251 public void visitXmlTag(XmlTag tag
) {
252 names
.elementNames
.add(QNameUtil
.createQName(tag
));
253 super.visitXmlTag(tag
);
257 public void visitXmlAttribute(XmlAttribute attribute
) {
258 if (!attribute
.isNamespaceDeclaration()) {
259 names
.attributeNames
.add(QNameUtil
.createQName(attribute
));
261 super.visitXmlAttribute(attribute
);
266 //noinspection unchecked
267 return new Result
<ElementNames
>(names
, names
.dependencies
.toArray(new Object
[names
.dependencies
.size()]));
272 private static void fillFromSchema(PsiFile file
, ElementNames names
) {
273 if (!(file
instanceof XmlFile
)) return;
274 final XmlFile f
= (XmlFile
)file
;
275 final XmlDocument d
= f
.getDocument();
276 if (d
== null) return;
277 final XmlTag rootTag
= d
.getRootTag();
278 if (rootTag
== null) return;
280 //noinspection unchecked
281 names
.dependencies
.add(new NSDeclTracker(rootTag
));
284 final Map
<String
, String
> namespaceDeclarations
= rootTag
.getLocalNamespaceDeclarations();
285 final Collection
<String
> prefixes
= namespaceDeclarations
.keySet();
287 //noinspection unchecked
288 final Set
<XmlElementDescriptor
> history
= new THashSet
<XmlElementDescriptor
>();
290 final XmlElementFactory ef
= XmlElementFactory
.getInstance(file
.getProject());
291 int noSchemaNamespaces
= 0;
292 for (String prefix
: prefixes
) {
293 final String namespace
= namespaceDeclarations
.get(prefix
);
294 if (isIgnoredNamespace(prefix
, namespace
)) continue;
296 final XmlTag tag
= ef
.createTagFromText("<dummy-tag xmlns='" + namespace
+ "' />", XMLLanguage
.INSTANCE
);
297 final XmlDocument document
= PsiTreeUtil
.getParentOfType(tag
, XmlDocument
.class);
298 final XmlNSDescriptor rootDescriptor
= tag
.getNSDescriptor(tag
.getNamespace(), true);
299 if (rootDescriptor
== null ||
300 (rootDescriptor
instanceof XmlNSDescriptorImpl
&& ((XmlNSDescriptorImpl
)rootDescriptor
).getTag() == null) ||
301 !rootDescriptor
.getDeclaration().isPhysical())
303 final QName any
= QNameUtil
.createAnyLocalName(namespace
);
304 names
.elementNames
.add(any
);
305 names
.attributeNames
.add(any
);
306 noSchemaNamespaces
++;
310 //noinspection unchecked
311 names
.dependencies
.add(rootDescriptor
.getDescriptorFile());
313 final XmlElementDescriptor
[] e
= rootDescriptor
.getRootElementsDescriptors(document
);
314 for (XmlElementDescriptor descriptor
: e
) {
315 processElementDescriptors(descriptor
, tag
, names
, history
);
319 names
.validateNames
= names
.elementNames
.size() > noSchemaNamespaces
;
321 // final QName any = QNameUtil.createAnyLocalName("");
322 // names.elementNames.add(any);
323 // names.attributeNames.add(any);
324 } catch (IncorrectOperationException e
) {
325 Logger
.getInstance(XsltContextProvider
.class.getName()).error(e
);
329 private static boolean isIgnoredNamespace(String prefix
, String namespace
) {
330 return IGNORED_URIS
.contains(namespace
) || prefix
.length() == 0 || "xmlns".equals(prefix
);
333 private static void processElementDescriptors(XmlElementDescriptor descriptor
, XmlTag tag
, ElementNames names
, Set
<XmlElementDescriptor
> history
) {
334 if (!history
.add(descriptor
)) {
337 final String namespace
= descriptor
instanceof XmlElementDescriptorImpl
338 ?
((XmlElementDescriptorImpl
)descriptor
).getNamespace()
339 : tag
.getNamespace();
340 names
.elementNames
.add(new QName(namespace
, descriptor
.getName()));
342 final XmlAttributeDescriptor
[] attributesDescriptors
= descriptor
.getAttributesDescriptors(null);
343 for (XmlAttributeDescriptor attributesDescriptor
: attributesDescriptors
) {
344 final String localPart
= attributesDescriptor
.getName();
345 if (!"xmlns".equals(localPart
)) names
.attributeNames
.add(new QName(localPart
));
348 final XmlElementDescriptor
[] descriptors
= descriptor
.getElementsDescriptors(tag
);
349 for (XmlElementDescriptor elem
: descriptors
) {
350 processElementDescriptors(elem
, tag
, names
, history
);
355 private PsiFile
getFile() {
356 final XmlElement element
= getContextElement();
357 if (element
== null) {
360 return element
.getContainingFile().getOriginalFile();
365 public XPathQuickFixFactory
getQuickFixFactory() {
366 return XsltQuickFixFactory
.INSTANCE
;
371 public RefactoringSupportProvider
getRefactoringSupportProvider() {
372 return LanguageRefactoringSupport
.INSTANCE
.forLanguage(XsltLanguage
.INSTANCE
);