private void checkDuplicateAttribute(XmlTag tag, final XmlAttribute attribute) { if (skipValidation(tag)) { return; } final XmlAttribute[] attributes = tag.getAttributes(); final PsiFile containingFile = tag.getContainingFile(); final XmlExtension extension = containingFile instanceof XmlFile ? XmlExtension.getExtension(containingFile) : XmlExtension.DEFAULT_EXTENSION; for (XmlAttribute tagAttribute : attributes) { ProgressManager.checkCanceled(); if (attribute != tagAttribute && Comparing.strEqual(attribute.getName(), tagAttribute.getName())) { final String localName = attribute.getLocalName(); if (extension.canBeDuplicated(tagAttribute)) continue; // multiple import attributes are allowed in jsp directive HighlightInfo highlightInfo = HighlightInfo.createHighlightInfo( getTagProblemInfoType(tag), XmlChildRole.ATTRIBUTE_NAME_FINDER.findChild( SourceTreeToPsiMap.psiElementToTree(attribute)), XmlErrorMessages.message("duplicate.attribute", localName)); addToResults(highlightInfo); IntentionAction intentionAction = new RemoveAttributeIntentionFix(localName, attribute); QuickFixAction.registerQuickFixAction(highlightInfo, intentionAction); } } }
@NotNull public XPathType getExpectedType(XPathExpression expr) { final XmlTag tag = PsiTreeUtil.getContextOfType(expr, XmlTag.class, true); if (tag != null && XsltSupport.isXsltTag(tag)) { final XsltElement element = XsltElementFactory.getInstance().wrapElement(tag, XsltElement.class); if (element instanceof XsltVariable) { return ((XsltVariable) element).getType(); } else { final XmlAttribute attr = PsiTreeUtil.getContextOfType(expr, XmlAttribute.class, true); if (attr != null) { if (element instanceof XsltWithParam) { final XmlAttribute nameAttr = tag.getAttribute("name", null); if (nameAttr != null) { final XmlAttributeValue valueElement = nameAttr.getValueElement(); if (valueElement != null) { final PsiReference[] references = valueElement.getReferences(); for (PsiReference reference : references) { final PsiElement psiElement = reference.resolve(); if (psiElement instanceof XsltVariable) { return ((XsltVariable) psiElement).getType(); } } } } } else { final String name = attr.getName(); return getTypeForTag(tag, name); } } } } return XPathType.UNKNOWN; }
@NotNull public PsiReference[] getReferencesByElement( @NotNull PsiElement element, @NotNull final ProcessingContext context) { boolean soft = myDefaultSoft; if (element instanceof XmlAttributeValue) { final XmlAttribute xmlAttribute = (XmlAttribute) element.getParent(); if (element.getTextLength() < 2) { return PsiReference.EMPTY_ARRAY; } final XmlTag tag = xmlAttribute.getParent(); String value = null; String bundle = tag.getAttributeValue("bundle"); if ("key".equals(xmlAttribute.getName())) { value = xmlAttribute.getValue(); } else if ("groupKey".equals(xmlAttribute.getName())) { value = xmlAttribute.getValue(); final String groupBundle = tag.getAttributeValue("groupBundle"); if (groupBundle != null) { bundle = groupBundle; } } if (value != null) { return new PsiReference[] { new PropertyReference(value, xmlAttribute.getValueElement(), bundle, soft) { @Override protected List<PropertiesFile> retrievePropertyFilesByBundleName( String bundleName, PsiElement element) { final Project project = element.getProject(); return PropertiesReferenceManager.getInstance(project) .findPropertiesFiles( GlobalSearchScope.projectScope(project), bundleName, BundleNameEvaluator.DEFAULT); } } }; } } return PsiReference.EMPTY_ARRAY; }
@NotNull public Map<String, String> getLocalNamespaceDeclarations() { Map<String, String> namespaces = new THashMap<String, String>(); for (final XmlAttribute attribute : getAttributes()) { if (!attribute.isNamespaceDeclaration() || attribute.getValue() == null) continue; // xmlns -> "", xmlns:a -> a final String localName = attribute.getLocalName(); namespaces.put(localName.equals(attribute.getName()) ? "" : localName, attribute.getValue()); } return namespaces; }
protected void checkAttribute( @NotNull final XmlAttribute attribute, @NotNull final ProblemsHolder holder, final boolean isOnTheFly) { final XmlTag tag = attribute.getParent(); if (tag instanceof HtmlTag) { XmlElementDescriptor elementDescriptor = tag.getDescriptor(); if (elementDescriptor == null || elementDescriptor instanceof AnyXmlElementDescriptor) { return; } XmlAttributeDescriptor attributeDescriptor = elementDescriptor.getAttributeDescriptor(attribute); final String name = attribute.getName(); if (attributeDescriptor == null && !attribute.isNamespaceDeclaration()) { if (!XmlUtil.attributeFromTemplateFramework(name, tag) && (!isCustomValuesEnabled() || !isCustomValue(name))) { final ASTNode node = attribute.getNode(); assert node != null; final PsiElement nameElement = XmlChildRole.ATTRIBUTE_NAME_FINDER.findChild(node).getPsi(); boolean maySwitchToHtml5 = HtmlUtil.isCustomHtml5Attribute(name) && !HtmlUtil.hasNonHtml5Doctype(tag); LocalQuickFix[] quickfixes = new LocalQuickFix[maySwitchToHtml5 ? 3 : 2]; quickfixes[0] = new AddCustomTagOrAttributeIntentionAction( getShortName(), name, XmlEntitiesInspection.UNKNOWN_ATTRIBUTE); quickfixes[1] = new RemoveAttributeIntentionAction(name); if (maySwitchToHtml5) { quickfixes[2] = new SwitchToHtml5WithHighPriorityAction(); } holder.registerProblem( nameElement, XmlErrorMessages.message("attribute.is.not.allowed.here", name), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, quickfixes); } } } }
@Override public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException { if (!FileModificationService.getInstance().preparePsiElementsForWrite(element)) return; final XmlAttribute attr = (XmlAttribute) element.getParent(); final String name = attr.getName(); final XmlAttributeDescriptor descriptor = attr.getDescriptor(); LOG.assertTrue(descriptor != null); String value = attr.getValue(); final PsiElement declaration = descriptor.getDeclaration(); if (declaration instanceof PsiField) { final PsiType fieldType = ((PsiField) declaration).getType(); final PsiType itemType = JavaGenericsUtil.getCollectionItemType(fieldType, declaration.getResolveScope()); if (itemType != null) { final String typeNode = itemType.getPresentableText(); JavaFxPsiUtil.insertImportWhenNeeded( (XmlFile) attr.getContainingFile(), typeNode, itemType.getCanonicalText()); final String[] vals = value.split(","); value = StringUtil.join( vals, new Function<String, String>() { @Override public String fun(String s) { return "<" + typeNode + " " + FxmlConstants.FX_VALUE + "=\"" + s.trim() + "\"/>"; } }, "\n"); } } final XmlTag childTag = XmlElementFactory.getInstance(project) .createTagFromText("<" + name + ">" + value + "</" + name + ">"); attr.getParent().add(childTag); attr.delete(); }
@Nullable private BidirectionalMap<String, String> computeNamespaceMap(PsiElement parent) { BidirectionalMap<String, String> map = null; if (hasNamespaceDeclarations()) { map = new BidirectionalMap<String, String>(); final XmlAttribute[] attributes = getAttributes(); for (final XmlAttribute attribute : attributes) { if (attribute.isNamespaceDeclaration()) { final String name = attribute.getName(); int splitIndex = name.indexOf(':'); final String value = getRealNs(attribute.getValue()); if (value != null) { if (splitIndex < 0) { map.put("", value); } else { map.put(XmlUtil.findLocalNameByQualifiedName(name), value); } } } } } if (parent instanceof XmlDocument) { final XmlExtension extension = XmlExtension.getExtensionByElement(parent); if (extension != null) { final String[][] defaultNamespace = extension.getNamespacesFromDocument((XmlDocument) parent, map != null); if (defaultNamespace != null) { if (map == null) { map = new BidirectionalMap<String, String>(); } for (final String[] prefix2ns : defaultNamespace) { map.put(prefix2ns[0], getRealNs(prefix2ns[1])); } } } } return map; }
private static void inlineSingleTag( XmlTag includeTag, XmlTag includeParentTag, XmlTag layoutRootTag) { final Map<String, String> attributesToAdd = new HashMap<String, String>(); for (XmlAttribute attribute : includeTag.getAttributes()) { final String namespace = attribute.getNamespace(); if (SdkConstants.NS_RESOURCES.equals(namespace)) { attributesToAdd.put(attribute.getLocalName(), attribute.getValue()); } } final XmlTag newTag = (XmlTag) includeTag.replace(layoutRootTag.copy()); final List<XmlAttribute> toDelete = new ArrayList<XmlAttribute>(); for (XmlAttribute attribute : newTag.getAttributes()) { if (attribute.isNamespaceDeclaration()) { final String localName = attribute.getLocalName(); final String prefix = localName.equals(attribute.getName()) ? "" : localName; final String namespace = attribute.getValue(); if (includeParentTag != null && namespace.equals(includeParentTag.getNamespaceByPrefix(prefix))) { toDelete.add(attribute); } } } for (XmlAttribute attribute : toDelete) { attribute.delete(); } for (Map.Entry<String, String> entry : attributesToAdd.entrySet()) { final String localName = entry.getKey(); final String value = entry.getValue(); newTag.setAttribute(localName, SdkConstants.NS_RESOURCES, value); } CodeStyleManager.getInstance(newTag.getManager()).reformat(newTag); }
@Override public void annotate(@NotNull final PsiElement element, @NotNull AnnotationHolder holder) { final PsiFile containingFile = element.getContainingFile(); if (!JavaFxFileTypeFactory.isFxml(containingFile)) return; if (element instanceof XmlAttributeValue) { final String value = ((XmlAttributeValue) element).getValue(); if (!JavaFxPsiUtil.isExpressionBinding(value) && !JavaFxPsiUtil.isIncorrectExpressionBinding(value)) { final PsiReference[] references = element.getReferences(); for (PsiReference reference : references) { if (reference instanceof JavaFxColorReference) { attachColorIcon(element, holder, StringUtil.unquoteString(element.getText())); continue; } final PsiElement resolve = reference.resolve(); if (resolve instanceof PsiMember) { if (!JavaFxPsiUtil.isVisibleInFxml((PsiMember) resolve)) { final String symbolPresentation = "'" + SymbolPresentationUtil.getSymbolPresentableText(resolve) + "'"; final Annotation annotation = holder.createErrorAnnotation( element, symbolPresentation + (resolve instanceof PsiClass ? " should be public" : " should be public or annotated with @FXML")); if (!(resolve instanceof PsiClass)) { annotation.registerUniversalFix( new AddAnnotationFix( JavaFxCommonNames.JAVAFX_FXML_ANNOTATION, (PsiMember) resolve, ArrayUtil.EMPTY_STRING_ARRAY), null, null); } } } } } } else if (element instanceof XmlAttribute) { final XmlAttribute attribute = (XmlAttribute) element; final String attributeName = attribute.getName(); if (!FxmlConstants.FX_BUILT_IN_ATTRIBUTES.contains(attributeName) && !attribute.isNamespaceDeclaration() && JavaFxPsiUtil.isReadOnly(attributeName, attribute.getParent())) { holder.createErrorAnnotation( element.getNavigationElement(), "Property '" + attributeName + "' is read-only"); } if (FxmlConstants.SOURCE.equals(attributeName)) { final XmlAttributeValue valueElement = attribute.getValueElement(); if (valueElement != null) { final XmlTag xmlTag = attribute.getParent(); if (xmlTag != null) { final XmlTag referencedTag = JavaFxBuiltInTagDescriptor.getReferencedTag(xmlTag); if (referencedTag != null) { if (referencedTag.getTextOffset() > xmlTag.getTextOffset()) { holder.createErrorAnnotation( valueElement.getValueTextRange(), valueElement.getValue() + " not found"); } else if (xmlTag.getParentTag() == referencedTag.getParentTag()) { final Annotation annotation = holder.createErrorAnnotation( valueElement.getValueTextRange(), "Duplicate child added"); annotation.registerFix( new JavaFxWrapWithDefineIntention(referencedTag, valueElement.getValue())); } } } } } } else if (element instanceof XmlTag) { if (FxmlConstants.FX_SCRIPT.equals(((XmlTag) element).getName())) { final XmlTagValue tagValue = ((XmlTag) element).getValue(); if (!StringUtil.isEmptyOrSpaces(tagValue.getText())) { final List<String> langs = JavaFxPsiUtil.parseInjectedLanguages((XmlFile) element.getContainingFile()); if (langs.isEmpty()) { final ASTNode openTag = element.getNode().findChildByType(XmlTokenType.XML_NAME); final Annotation annotation = holder.createErrorAnnotation( openTag != null ? openTag.getPsi() : element, "Page language not specified."); annotation.registerFix(new JavaFxInjectPageLanguageIntention()); } } } } }
private void checkAttribute(XmlAttribute attribute) { XmlTag tag = attribute.getParent(); final String name = attribute.getName(); PsiElement prevLeaf = PsiTreeUtil.prevLeaf(attribute); if (!(prevLeaf instanceof PsiWhiteSpace)) { TextRange textRange = attribute.getTextRange(); addToResults( HighlightInfo.createHighlightInfo( tag instanceof HtmlTag ? HighlightInfoType.WARNING : HighlightInfoType.ERROR, textRange.getStartOffset(), textRange.getStartOffset(), XmlErrorMessages.message("attribute.should.be.preceded.with.space"))); } if (attribute.isNamespaceDeclaration()) { checkReferences(attribute.getValueElement()); return; } final String namespace = attribute.getNamespace(); if (XmlUtil.XML_SCHEMA_INSTANCE_URI.equals(namespace)) { checkReferences(attribute.getValueElement()); return; } XmlElementDescriptor elementDescriptor = tag.getDescriptor(); if (elementDescriptor == null || elementDescriptor instanceof AnyXmlElementDescriptor || ourDoJaxpTesting) { return; } XmlAttributeDescriptor attributeDescriptor = elementDescriptor.getAttributeDescriptor(attribute); if (attributeDescriptor == null) { if (!XmlUtil.attributeFromTemplateFramework(name, tag)) { final String localizedMessage = XmlErrorMessages.message("attribute.is.not.allowed.here", name); final HighlightInfo highlightInfo = reportAttributeProblem(tag, name, attribute, localizedMessage); if (highlightInfo != null) { final XmlFile xmlFile = (XmlFile) tag.getContainingFile(); if (xmlFile != null) { XmlExtension.getExtension(xmlFile).createAddAttributeFix(attribute, highlightInfo); } } } } else { checkDuplicateAttribute(tag, attribute); if (tag instanceof HtmlTag && attribute.getValueElement() == null && !HtmlUtil.isSingleHtmlAttribute(name)) { final String localizedMessage = XmlErrorMessages.message("empty.attribute.is.not.allowed", name); reportAttributeProblem(tag, name, attribute, localizedMessage); } // we skip resolve of attribute references since there is separate check when taking attribute // descriptors PsiReference[] attrRefs = attribute.getReferences(); doCheckRefs(attribute, attrRefs, attribute.getNamespacePrefix().length() > 0 ? 2 : 1); } }
@Nullable public static String doExtractStyle( @NotNull Module module, @NotNull final XmlTag viewTag, final boolean addStyleAttributeToTag, @Nullable MyTestConfig testConfig) { final PsiFile file = viewTag.getContainingFile(); if (file == null) { return null; } final String dialogTitle = AndroidBundle.message("android.extract.style.title"); final String fileName = AndroidResourceUtil.getDefaultResourceFileName(ResourceType.STYLE); assert fileName != null; final List<String> dirNames = Arrays.asList(ResourceFolderType.VALUES.getName()); final List<XmlAttribute> extractableAttributes = getExtractableAttributes(viewTag); final Project project = module.getProject(); if (extractableAttributes.size() == 0) { AndroidUtils.reportError( project, "The tag doesn't contain any attributes that can be extracted", dialogTitle); return null; } final LayoutViewElement viewElement = getLayoutViewElement(viewTag); assert viewElement != null; final ResourceValue parentStyleVlaue = viewElement.getStyle().getValue(); final String parentStyle; boolean supportImplicitParent = false; if (parentStyleVlaue != null) { parentStyle = parentStyleVlaue.getResourceName(); if (!ResourceType.STYLE.getName().equals(parentStyleVlaue.getResourceType()) || parentStyle == null || parentStyle.length() == 0) { AndroidUtils.reportError( project, "Invalid parent style reference " + parentStyleVlaue.toString(), dialogTitle); return null; } supportImplicitParent = parentStyleVlaue.getPackage() == null; } else { parentStyle = null; } final String styleName; final List<XmlAttribute> styledAttributes; final Module chosenModule; if (testConfig == null) { final ExtractStyleDialog dialog = new ExtractStyleDialog( module, fileName, supportImplicitParent ? parentStyle : null, dirNames, extractableAttributes); dialog.setTitle(dialogTitle); dialog.show(); if (!dialog.isOK()) { return null; } chosenModule = dialog.getChosenModule(); assert chosenModule != null; styledAttributes = dialog.getStyledAttributes(); styleName = dialog.getStyleName(); } else { testConfig.validate(extractableAttributes); chosenModule = testConfig.getModule(); styleName = testConfig.getStyleName(); final Set<String> attrsToExtract = new HashSet<String>(Arrays.asList(testConfig.getAttributesToExtract())); styledAttributes = new ArrayList<XmlAttribute>(); for (XmlAttribute attribute : extractableAttributes) { if (attrsToExtract.contains(attribute.getName())) { styledAttributes.add(attribute); } } } final boolean[] success = {false}; final boolean finalSupportImplicitParent = supportImplicitParent; new WriteCommandAction(project, "Extract Android Style '" + styleName + "'", file) { @Override protected void run(final Result result) throws Throwable { final List<XmlAttribute> attributesToDelete = new ArrayList<XmlAttribute>(); if (!AndroidResourceUtil.createValueResource( chosenModule, styleName, ResourceType.STYLE, fileName, dirNames, new Processor<ResourceElement>() { @Override public boolean process(ResourceElement element) { assert element instanceof Style; final Style style = (Style) element; for (XmlAttribute attribute : styledAttributes) { if (SdkConstants.NS_RESOURCES.equals(attribute.getNamespace())) { final StyleItem item = style.addItem(); item.getName().setStringValue("android:" + attribute.getLocalName()); item.setStringValue(attribute.getValue()); attributesToDelete.add(attribute); } } if (parentStyleVlaue != null && (!finalSupportImplicitParent || !styleName.startsWith(parentStyle + "."))) { final String aPackage = parentStyleVlaue.getPackage(); style .getParentStyle() .setStringValue((aPackage != null ? aPackage + ":" : "") + parentStyle); } return true; } })) { return; } ApplicationManager.getApplication() .runWriteAction( new Runnable() { @Override public void run() { for (XmlAttribute attribute : attributesToDelete) { attribute.delete(); } if (addStyleAttributeToTag) { final LayoutViewElement viewElement = getLayoutViewElement(viewTag); assert viewElement != null; viewElement.getStyle().setStringValue("@style/" + styleName); } } }); success[0] = true; } @Override protected UndoConfirmationPolicy getUndoConfirmationPolicy() { return UndoConfirmationPolicy.REQUEST_CONFIRMATION; } }.execute(); return success[0] ? styleName : null; }
@Override public XmlAttributeDescriptor getAttributeDescriptor(XmlAttribute attribute) { return getAttributeDescriptor(attribute.getName(), attribute.getParent()); }