Skip to content

Commit

Permalink
🐛 Fixed superfluous attributes detection, ✨ Analyzed text blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
PheonBest committed Apr 6, 2023
1 parent fed9970 commit 5e56b8a
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@
import org.sonar.check.Priority;
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.tree.ForEachStatement;
import org.sonar.plugins.java.api.tree.LiteralTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.sonar.plugins.java.api.tree.*;
import org.w3c.dom.*;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

Expand All @@ -33,6 +29,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

@Rule(
key = "EC53",
Expand All @@ -42,26 +39,35 @@
tags = {"bug"})
public class OptimizeImageVectorsSize extends IssuableSubscriptionVisitor {
protected final String SVG_BEGIN = "<svg";
protected final String[] SUPERFLUOUS_ATTRIBUTES = new String[]{"xmlns:dc", "xmlns:cc", "xmlns:rdv", "xmlns:svg", "xmlns", "xmlns:sodipodi", "xmlns:inkscape", "inkscape:version", "inkscape:label", "inkscape:groupmode", "sodipodi:docname"};
protected final String[] SVG_END = new String[]{"</svg", "</ svg>"};
protected final List<String> SUPERFLUOUS_ATTRIBUTES = Arrays.asList("xmlns:dc", "xmlns:cc", "xmlns:rdv", "xmlns:svg", "xmlns", "xmlns:sodipodi", "xmlns:inkscape", "inkscape:version", "inkscape:label", "inkscape:groupmode", "sodipodi:docname");
protected static final String MESSAGERULE = "Optimize your svg by using open source tools such as SVGGO, Compressor.io or SVG Cleaner";
// COMPLETE MESSAGE:
// "Optimize your svg by using open source tools such as SVGGO, Compressor.io or SVG Cleaner. The following should be avoided: The attribute value length can be reduced, e.g: id=\"filter4210\" to id=\"a\" (the same goes for xlink:href attribute). The attributes' value should be approximated: stdDeviation=\"0.57264819\" to stdDeviation=\"0.573\". Duplicated tags. Superfluous tags, e.g: <sodipodi:namedview>, <metadata>. Redundant tags. Superfluous attributes (xmlns:dc, xmlns:cc, xmlns:rdv, xmlns: svg, xmlns, xmlns:sodipodi, xmlns:inkscape, most of id attributes, version, inkscape:version, inkscape:label, inkscape:groupmode, sodipodi:docname)";

@Override
public List<Tree.Kind> nodesToVisit() {
return List.of(Tree.Kind.STRING_LITERAL);
return List.of(Tree.Kind.STRING_LITERAL, Tree.Kind.TEXT_BLOCK);
}

@Override
public void visitNode(Tree tree) {

String value = ((LiteralTree) tree).value();

// convert to lower case (not case sensitve anymore)
value = value.toLowerCase();
// trim a beginning and ending double quote (")
value = value.replaceAll("^\"|\"$", "");
// stop escaping double quotes
value = value.replaceAll("\\\\\"", "\"");;

// trim a beginning and ending double quotes (") and single quotes (')
if (tree.is(Tree.Kind.STRING_LITERAL)) {
value = value.substring(1, value.length() - 1);
} else {
// a text block begins and ends with 3 double quotes
value = value.substring(3, value.length() - 3);
}

// stop escaping double quotes (") and single quotes (')
value = value.replaceAll("\\\\\"", "\"");
value = value.replaceAll("\\\\\'", "\'");

// search for svg beginning tag
int beginIndex = value.indexOf(SVG_BEGIN);
Expand All @@ -70,6 +76,17 @@ public void visitNode(Tree tree) {
return;
}

// search for svg ending tag
int endIndex = -1;
int j = 0;
while (j < SVG_END.length && endIndex == -1) {
endIndex = value.indexOf(SVG_END[j]);
++j;
}
if (endIndex == -1) {
return; // svg is invalid or is broken into multiple strings
}

// Parse svg as xml and explore its tree
try {
// Build the doc from the XML file
Expand Down Expand Up @@ -124,17 +141,21 @@ public void visitNode(Tree tree) {
}

// Avoid superfluous attributes
// TODO: make it work with namespaces (xmlns).
for (String superfluousAttribute : SUPERFLUOUS_ATTRIBUTES) {
String expression = String.format("boolean(//@*[name()=%s])\n", superfluousAttribute);

if ((boolean) xpath.evaluate(expression, doc, XPathConstants.BOOLEAN)) {
reportIssue(tree, MESSAGERULE);
return;
// We do not search for the superfluous attributes directly in the xml as a string as the attributes could be the text inside of xml tags.
NodeList list = (NodeList) xpath.evaluate("//*", doc, XPathConstants.NODESET);
int nbNodes = list.getLength();
for (int i = 0; i < nbNodes; ++i) {
NamedNodeMap attributes = list.item(i).getAttributes();
int nbAttr = attributes.getLength();
for (j = 0; j < nbAttr; ++j) {
if (SUPERFLUOUS_ATTRIBUTES.contains(((Attr) attributes.item(j)).getName())) {
reportIssue(tree, MESSAGERULE);
return;
}
}
}
} catch (ParserConfigurationException | IOException | SAXException | XPathExpressionException e) {
throw new RuntimeException(e);

} catch (Throwable ignored) {
}
}
}
30 changes: 22 additions & 8 deletions java-plugin/src/test/files/OptimizeImageVectorsSize.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,46 @@ class TestClass {
TestClass(TestClass tc) {
}

int compliantCase() {
String compliantCase() {
return "<svg id=\"a\"></svg>";
}

int idCanBeReduced() {
String idCanBeReduced() {
return "<svg id=\"main-wrapper\"></svg>"; // Noncompliant {{Optimize your svg by using open source tools such as SVGGO, Compressor.io or SVG Cleaner}}
}

int superfluousTag() {
String superfluousTag() {
return "<svg><metadata></metadata></svg>"; // Noncompliant {{Optimize your svg by using open source tools such as SVGGO, Compressor.io or SVG Cleaner}}
}

int duplicatedTags() {
String duplicatedTags() {
return "<svg><linearGradient inkscape:collect=\"always\" xlink:href=\"#linearGradient4405\" id=\"linearGradient2917\" gradientUnits=\"userSpaceOnUse\" gradientTransform=\"matrix(19.565,0,0,19.565,-95.619,79.961)\" x1=\"11.685\" y1=\"33.646\" x2=\"10.783\" y2=\"35.870\" /><linearGradient inkscape:collect=\"always\" xlink:href=\"#linearGradient4405\" id=\"linearGradient2917\" gradientUnits=\"userSpaceOnUse\" gradientTransform=\"matrix(19.565,0,0,19.565,-95.619,79.961)\" x1=\"11.685\" y1=\"33.646\" x2=\"10.783\" y2=\"35.870\" /></svg>"; // Noncompliant {{Optimize your svg by using open source tools such as SVGGO, Compressor.io or SVG Cleaner}}
}

int unapproximatedNumericValues() {
String unapproximatedNumericValues() {
return "<svg><linearGradient inkscape:collect=\"always\" xlink:href=\"#linearGradient4405\" id=\"linearGradient2917\" gradientUnits=\"userSpaceOnUse\" gradientTransform=\"matrix(19.565616,0,0,19.565616,-95.619433,79.96141)\" x1=\"11.685\" y1=\"33.646999\" x2=\"10.783\" y2=\"35.870998\" /></svg>"; // Noncompliant {{Optimize your svg by using open source tools such as SVGGO, Compressor.io or SVG Cleaner}}
}

int superfluousAttributes1() {
return "<svg><div trucmuche=\"test\"></div></svg>"; // Noncompliant {{Optimize your svg by using open source tools such as SVGGO, Compressor.io or SVG Cleaner}}
String validAttribute() {
return "<svg><div trucmuche=\"test\"></div></svg>";
}

int superfluousAttributes2() {
String superfluousAttribute() {
return "<svg><div xmlns:dc=\"test\"></div></svg>"; // Noncompliant {{Optimize your svg by using open source tools such as SVGGO, Compressor.io or SVG Cleaner}}
}

String blockCompliantCase() {
return """
<svg id="a"></svg>
""";
}

/* can't write test as all comments are included in the text block and blocks the xml analysis
String blockIdCanBeReduced() {
return """
<svg id=\"main-wrapper\"></svg>
""";
}
*/

}

0 comments on commit 5e56b8a

Please sign in to comment.