/*******************************************************************************
* Copyright (c) 2017 itemis AG and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Matthias Wienand (itemis AG) - initial API and implementation
* Tamas Miklossy (itemis AG) - minor refactorings
*
*******************************************************************************/
package org.eclipse.gef.dot.internal.language.validation;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.gef.dot.internal.language.htmllabel.DotHtmlLabelHelper;
import org.eclipse.gef.dot.internal.language.htmllabel.HtmlAttr;
import org.eclipse.gef.dot.internal.language.htmllabel.HtmlContent;
import org.eclipse.gef.dot.internal.language.htmllabel.HtmlTag;
import org.eclipse.gef.dot.internal.language.htmllabel.HtmllabelPackage;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.validation.Check;
/**
* This class contains custom validation rules.
*
* See
* https://www.eclipse.org/Xtext/documentation/303_runtime_concepts.html#validation
*/
public class DotHtmlLabelJavaValidator extends
org.eclipse.gef.dot.internal.language.validation.AbstractDotHtmlLabelJavaValidator {
/**
* Checks if the given {@link HtmlTag} is properly closed. Generates errors
* if the html's open tag does not correspond to its close tag.
*
* @param tag
* The {@link HtmlTag} to check.
*/
@Check
public void checkTagIsClosed(HtmlTag tag) {
if (!tag.getName().toUpperCase()
.equals(tag.getCloseName().toUpperCase())) {
reportRangeBasedError(
"Tag '<" + tag.getName() + ">' is not closed (expected '</"
+ tag.getName() + ">' but got '</"
+ tag.getCloseName() + ">').",
tag, HtmllabelPackage.Literals.HTML_TAG__CLOSE_NAME);
}
}
/**
* Checks if the given {@link HtmlTag} is properly closed. Generates errors
* if the html tag is self-closed where self-closing is not allowed.
*
* @param tag
* The {@link HtmlTag} to check.
*/
@Check
public void checkSelfClosingTagIsAllowed(HtmlTag tag) {
String tagNameUpperCase = tag.getName().toUpperCase();
if (tag.isSelfClosing() && DotHtmlLabelHelper.getNonSelfClosingTags()
.contains(tagNameUpperCase)) {
reportRangeBasedError(
"Tag '<" + tag.getName() + "/>' cannot be self closing.",
tag, HtmllabelPackage.Literals.HTML_TAG__NAME);
}
}
/**
* Checks if a string literal is allowed in the given {@link HtmlTag}.
* Generates errors if the html tag is not allowed to contain a string
* literal.
*
* @param tag
* The {@link HtmlTag} to check.
*/
@Check
public void checkStringLiteralIsAllowed(HtmlTag tag) {
String[] stringLiteralIsNotAllowed = { "BR", "HR", "IMG", "TABLE", "TR",
"VR" };
String tagNameUpperCase = tag.getName().toUpperCase();
if (Arrays.binarySearch(stringLiteralIsNotAllowed,
tagNameUpperCase) >= 0) {
for (HtmlContent child : tag.getChildren()) {
// TODO: verify why white spaces is stored as text
String text = child.getText();
if (text != null && !text.trim().isEmpty()) {
reportRangeBasedError(
"Tag '<" + tag.getName()
+ ">' cannot contain a string literal.",
tag, HtmllabelPackage.Literals.HTML_TAG__NAME);
}
}
}
}
/**
* Checks if the given {@link HtmlTag} is valid w.r.t. its parent (not all
* tags are allowed on all nesting levels). Generates errors when the given
* {@link HtmlTag} is not supported by Graphviz w.r.t. its parent.
*
* @param tag
* The {@link HtmlTag} to check.
*/
@Check
public void checkTagNameIsValid(HtmlTag tag) {
String tagName = tag.getName();
if (!DotHtmlLabelHelper.getAllTags().contains(tagName.toUpperCase())) {
reportRangeBasedError("Tag '<" + tagName + ">' is not supported.",
tag, HtmllabelPackage.Literals.HTML_TAG__NAME);
} else {
// find parent tag
EObject container = tag.eContainer().eContainer();
HtmlTag parent = null;
if (container instanceof HtmlTag) {
parent = (HtmlTag) container;
}
// check if tag allowed inside parent or "root" if we could not find
// a parent
String parentName = parent == null
? DotHtmlLabelHelper.getRootTagKey() : parent.getName();
Map<String, Set<String>> validTags = DotHtmlLabelHelper
.getValidTags();
if (!validTags.containsKey(parentName.toUpperCase())
|| !validTags.get(parentName.toUpperCase())
.contains(tagName.toUpperCase())) {
reportRangeBasedError(
"Tag '<" + tagName + ">' is not allowed inside '<"
+ parentName + ">', but only inside '<"
+ String.join(">', '<",
DotHtmlLabelHelper.getAllowedParents()
.get(tagName.toUpperCase()))
+ ">'.",
tag, HtmllabelPackage.Literals.HTML_TAG__NAME);
}
}
}
/**
* Checks if the given {@link HtmlAttr} is valid w.r.t. its tag (only
* certain attributes are supported by the individual tags). Generates
* errors if the {@link HtmlAttr} is not supported by Graphviz w.r.t. its
* tag.
*
* @param attr
* The {@link HtmlAttr} to check.
*/
@Check
public void checkAttributeNameIsValid(HtmlAttr attr) {
String attrName = attr.getName();
EObject container = attr.eContainer();
if (container instanceof HtmlTag) {
HtmlTag tag = (HtmlTag) container;
String tagName = tag.getName();
Map<String, Set<String>> validAttributes = DotHtmlLabelHelper
.getValidAttributes();
if (!validAttributes.containsKey(tagName.toUpperCase())
|| !validAttributes.get(tagName.toUpperCase())
.contains(attrName.toUpperCase())) {
reportRangeBasedError(
"Attribute '" + attrName + "' is not allowed inside '<"
+ tagName + ">'.",
attr, HtmllabelPackage.Literals.HTML_ATTR__NAME);
}
}
}
private void reportRangeBasedError(String message, EObject object,
EStructuralFeature feature) {
List<INode> nodes = NodeModelUtils.findNodesForFeature(object, feature);
if (nodes.size() != 1) {
throw new IllegalStateException(
"Exact 1 node is expected for the feature, but got "
+ nodes.size() + " node(s).");
}
INode node = nodes.get(0);
int offset = node.getTotalOffset();
int length = node.getLength();
String code = null;
String[] issueData = null;
getMessageAcceptor().acceptError(message, object, offset, length, code,
issueData);
}
}