mirror of
https://git.huckle.dev/Huckles-Minecraft-Archive/jpexs-decompiler.git
synced 2026-06-10 04:03:42 +00:00
#1585 SVG import - support for style tag (CSS)
This commit is contained in:
@@ -12,7 +12,8 @@
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library.
|
||||
* License along with this library.
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.importers.svg;
|
||||
|
||||
import com.jpexs.decompiler.flash.ReadOnlyTagList;
|
||||
@@ -24,6 +25,9 @@ import com.jpexs.decompiler.flash.exporters.modes.ShapeExportMode;
|
||||
import com.jpexs.decompiler.flash.exporters.settings.ShapeExportSettings;
|
||||
import com.jpexs.decompiler.flash.exporters.shape.BitmapExporter;
|
||||
import com.jpexs.decompiler.flash.importers.ShapeImporter;
|
||||
import com.jpexs.decompiler.flash.importers.svg.css.CssParseException;
|
||||
import com.jpexs.decompiler.flash.importers.svg.css.CssParser;
|
||||
import com.jpexs.decompiler.flash.importers.svg.css.CssSelectorToXPath;
|
||||
import com.jpexs.decompiler.flash.tags.DefineShape4Tag;
|
||||
import com.jpexs.decompiler.flash.tags.ExportAssetsTag;
|
||||
import com.jpexs.decompiler.flash.tags.Tag;
|
||||
@@ -66,8 +70,13 @@ import javax.imageio.ImageIO;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NamedNodeMap;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
@@ -211,6 +220,55 @@ public class SvgImporter {
|
||||
}
|
||||
}
|
||||
|
||||
private void processStyle(Element element) {
|
||||
String styleSheet = element.getTextContent().trim();
|
||||
CssParser cssParser = new CssParser(styleSheet);
|
||||
CssSelectorToXPath selectorToXPath = new CssSelectorToXPath();
|
||||
Document doc = element.getOwnerDocument();
|
||||
try {
|
||||
cssParser.styleshet();
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
for (int i = 0; i < cssParser.getCountRulesets(); i++) {
|
||||
String selector = cssParser.getSelector(i);
|
||||
String xPathExpression = selectorToXPath.css2xpath(selector);
|
||||
try {
|
||||
NodeList nodeList = (NodeList) xPath.compile(xPathExpression).evaluate(doc, XPathConstants.NODESET);
|
||||
for (int j = 0; j < nodeList.getLength(); j++) {
|
||||
Node node = nodeList.item(j);
|
||||
NamedNodeMap attrs = node.getAttributes();
|
||||
Node styleAttr = attrs.getNamedItem("ffdec-style");
|
||||
if (styleAttr != null) {
|
||||
styleAttr.setNodeValue(styleAttr.getNodeValue() + ";" + "{" + cssParser.getSpecifity(i) + "}" + cssParser.getDeclarations(i));
|
||||
attrs.setNamedItem(styleAttr);
|
||||
} else {
|
||||
Node styleNode = doc.createAttribute("ffdec-style");
|
||||
styleNode.setNodeValue("{" + cssParser.getSpecifity(i) + "}" + cssParser.getDeclarations(i));
|
||||
attrs.setNamedItem(styleNode);
|
||||
}
|
||||
}
|
||||
} catch (XPathExpressionException ex) {
|
||||
Logger.getLogger(SvgImporter.class.getName()).log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
} catch (IOException | CssParseException ex) {
|
||||
showWarning("CannotParseCSSStyle", "Cannot parse CSS style: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void processDefs(Element element) {
|
||||
for (int i = 0; i < element.getChildNodes().getLength(); i++) {
|
||||
Node childNode = element.getChildNodes().item(i);
|
||||
if (childNode instanceof Element) {
|
||||
Element childElement = (Element) childNode;
|
||||
String tagName = childElement.getTagName();
|
||||
if ("style".equals(tagName)) {
|
||||
processStyle(childElement);
|
||||
} else if ("defs".equals(tagName)) {
|
||||
processDefs(childElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private void processSvgObject(Map<String, Element> idMap, int shapeNum, SHAPEWITHSTYLE shapes, Element element, Matrix transform, SvgStyle style) {
|
||||
for (int i = 0; i < element.getChildNodes().getLength(); i++) {
|
||||
Node childNode = element.getChildNodes().item(i);
|
||||
@@ -220,7 +278,9 @@ public class SvgImporter {
|
||||
SvgStyle newStyle = new SvgStyle(this, idMap, childElement);
|
||||
Matrix m = Matrix.parseSvgMatrix(childElement.getAttribute("transform"), 1, 1);
|
||||
Matrix m2 = m == null ? transform : transform.concatenate(m);
|
||||
Matrix m2 = m == null ? transform : transform.concatenate(m);
|
||||
if ("style".equals(tagName)) {
|
||||
processStyle(childElement);
|
||||
} else if ("g".equals(tagName)) {
|
||||
processSvgObject(idMap, shapeNum, shapes, childElement, m2, newStyle);
|
||||
} else if ("path".equals(tagName)) {
|
||||
processPath(shapeNum, shapes, childElement, m2, newStyle);
|
||||
@@ -236,7 +296,9 @@ public class SvgImporter {
|
||||
processPolyline(shapeNum, shapes, childElement, m2, newStyle);
|
||||
} else if ("polygon".equals(tagName)) {
|
||||
processPolygon(shapeNum, shapes, childElement, m2, newStyle);
|
||||
processPolygon(shapeNum, shapes, childElement, m2, newStyle);
|
||||
} else if ("defs".equals(tagName)) {
|
||||
processDefs(childElement);
|
||||
} else if ("title".equals(tagName) || "desc".equals(tagName)
|
||||
|| "radialGradient".equals(tagName) || "linearGradient".equals(tagName)) {
|
||||
// ignore
|
||||
} else {
|
||||
@@ -1035,6 +1097,7 @@ public class SvgImporter {
|
||||
|
||||
//Stub for w3 test. TODO: refactor and move to test directory. It's here because of easy access - compiling single file
|
||||
private static void svgTest(String name) throws IOException, InterruptedException {
|
||||
System.err.println("running test " + name);
|
||||
if (!new File(name + ".original.svg").exists()) {
|
||||
URL svgUrl = new URL("http://www.w3.org/Graphics/SVG/Test/20061213/svggen/" + name + ".svg");
|
||||
byte[] svgData = Helper.readStream(svgUrl.openStream());
|
||||
@@ -1048,7 +1111,7 @@ public class SvgImporter {
|
||||
String svgDataS = Helper.readTextFile(name + ".orig.svg");
|
||||
SWF swf = new SWF();
|
||||
DefineShape4Tag st = new DefineShape4Tag(swf);
|
||||
DefineShape4Tag st = new DefineShape4Tag(swf);
|
||||
st = (DefineShape4Tag) (new SvgImporter().importSvg(st, svgDataS, false));
|
||||
swf.addTag(st);
|
||||
SerializableImage si = new SerializableImage(480, 360, BufferedImage.TYPE_4BYTE_ABGR);
|
||||
BitmapExporter.export(swf, st.shapes, Color.yellow, si, new Matrix(), new Matrix(), null);
|
||||
@@ -1301,13 +1364,13 @@ public class SvgImporter {
|
||||
// svgTest("struct-use-01-t");
|
||||
// svgTest("struct-use-03-t");
|
||||
// svgTest("struct-use-05-b");
|
||||
// svgTest("struct-use-05-b");
|
||||
// svgTest("styling-css-01-b");
|
||||
// svgTest("styling-css-02-b");
|
||||
// svgTest("styling-css-03-b");
|
||||
svgTest("styling-css-01-b");
|
||||
svgTest("styling-css-02-b");
|
||||
svgTest("styling-css-03-b");
|
||||
svgTest("styling-css-04-f");
|
||||
// svgTest("styling-css-05-b");
|
||||
// svgTest("styling-css-06-b");
|
||||
// svgTest("styling-css-06-b");
|
||||
svgTest("styling-inherit-01-b");
|
||||
// svgTest("styling-pres-01-t");
|
||||
// svgTest("text-align-01-b");
|
||||
// svgTest("text-align-02-b");
|
||||
@@ -1341,7 +1404,7 @@ public class SvgImporter {
|
||||
// svgTest("text-tspan-01-b");
|
||||
// svgTest("text-ws-01-t");
|
||||
// svgTest("text-ws-02-t");
|
||||
// svgTest("text-ws-02-t");
|
||||
// svgTest("types-basicDOM-01-b");
|
||||
}
|
||||
|
||||
private void applyFillGradients(SvgFill fill, FILLSTYLE fillStyle, RECT bounds, StyleChangeRecord scr, Matrix transform, int shapeNum, SvgStyle style) {
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library.
|
||||
* License along with this library.
|
||||
*/
|
||||
package com.jpexs.decompiler.flash.importers.svg;
|
||||
|
||||
import com.jpexs.decompiler.flash.importers.ShapeImporter;
|
||||
@@ -21,6 +22,8 @@ import com.jpexs.helpers.Helper;
|
||||
import java.awt.Color;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -56,9 +59,42 @@ class SvgStyle {
|
||||
|
||||
private Map<String, String> getStyleAttributeValues(Element element) {
|
||||
// todo: cache
|
||||
|
||||
Map<String, String> styleValues = new HashMap<>();
|
||||
String styleStr = "";
|
||||
if (element.hasAttribute("ffdec-style")) {
|
||||
styleStr += element.getAttribute("ffdec-style");
|
||||
}
|
||||
if (element.hasAttribute("style")) {
|
||||
if (element.hasAttribute("style")) {
|
||||
styleStr += "{1000}" + element.getAttribute("style");
|
||||
}
|
||||
|
||||
if (!styleStr.isEmpty()) {
|
||||
String[] rulesBySpec = styleStr.split("\\{");
|
||||
List<String> rulesBySpecList = Arrays.asList(rulesBySpec);
|
||||
List<String> rulesBySpecListUnordered = new ArrayList<>(rulesBySpecList);
|
||||
rulesBySpecList.sort(new Comparator<String>() {
|
||||
@Override
|
||||
public int compare(String o1, String o2) {
|
||||
if (o1.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
if (o2.isEmpty()) {
|
||||
return 1;
|
||||
}
|
||||
int r1 = Integer.parseInt(o1.substring(0, o1.indexOf("}")));
|
||||
int r2 = Integer.parseInt(o2.substring(0, o2.indexOf("}")));
|
||||
if (r1 == r2) {
|
||||
return rulesBySpecListUnordered.indexOf(r1) - rulesBySpecListUnordered.indexOf(r2);
|
||||
}
|
||||
return r1 - r2;
|
||||
}
|
||||
});
|
||||
for (int i = 0; i < rulesBySpecList.size(); i++) {
|
||||
rulesBySpecList.set(i, rulesBySpecList.get(i).substring(rulesBySpecList.get(i).indexOf("}") + 1));
|
||||
}
|
||||
styleStr = String.join(";", rulesBySpecList) + ";";
|
||||
String[] styleDefs = styleStr.split(";");
|
||||
for (String styleDef : styleDefs) {
|
||||
if (!styleDef.contains(":")) {
|
||||
continue;
|
||||
@@ -75,7 +111,6 @@ class SvgStyle {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return styleValues;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
||||
package com.jpexs.decompiler.flash.importers.svg.css;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class CssParseException extends Exception {
|
||||
public CssParseException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
public CssParseException() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.jpexs.decompiler.flash.importers.svg.css;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class CssParsedSymbol {
|
||||
public String value;
|
||||
public CssSymbolType type;
|
||||
|
||||
public CssParsedSymbol(String value, CssSymbolType type) {
|
||||
this.value = value;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public boolean isType(String... ss){
|
||||
if(type == CssSymbolType.OTHER){
|
||||
for(String s:ss){
|
||||
if(s.equals(value)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isType(CssSymbolType... types){
|
||||
for(CssSymbolType type:types){
|
||||
if(this.type == type){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return type.toString() + ": " + value;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,477 @@
|
||||
package com.jpexs.decompiler.flash.importers.svg.css;
|
||||
|
||||
import com.jpexs.helpers.Reference;
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
*
|
||||
* CSS Stylesheet parser. Based on https://www.w3.org/TR/CSS21/grammar.html
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public class CssParser {
|
||||
|
||||
private final CssLexer lexer;
|
||||
private final String s;
|
||||
|
||||
private final List<String> selectors = new ArrayList<>();
|
||||
private final List<String> declarations = new ArrayList<>();
|
||||
|
||||
private final List<List<String>> propNames = new ArrayList<>();
|
||||
private final List<List<String>> propValues = new ArrayList<>();
|
||||
|
||||
private final List<Integer> specifities = new ArrayList<>();
|
||||
|
||||
public CssParser(String s) {
|
||||
this.s = s;
|
||||
this.lexer = new CssLexer(new StringReader(s));
|
||||
}
|
||||
|
||||
public void styleshet() throws IOException, CssParseException {
|
||||
CssParsedSymbol symb = lex();
|
||||
if (symb.type == CssSymbolType.CHARSET_SYM) {
|
||||
expect(CssSymbolType.STRING);
|
||||
expect(";");
|
||||
symb = lex();
|
||||
}
|
||||
while (symb.isType(CssSymbolType.S, CssSymbolType.CDO, CssSymbolType.CDC)) {
|
||||
symb = lex();
|
||||
}
|
||||
while (symb.type == CssSymbolType.IMPORT_SYM) {
|
||||
sstar();
|
||||
expect(CssSymbolType.STRING, CssSymbolType.URI);
|
||||
sstar();
|
||||
symb = lex();
|
||||
if (!symb.isType(";")) {
|
||||
lexer.pushback(symb);
|
||||
media_list();
|
||||
expect(";");
|
||||
}
|
||||
sstar();
|
||||
symb = lex();
|
||||
while (symb.isType(CssSymbolType.CDO, CssSymbolType.CDC)) {
|
||||
sstar();
|
||||
symb = lex();
|
||||
}
|
||||
}
|
||||
|
||||
while (symb.type != CssSymbolType.EOF) {
|
||||
if (symb.type == CssSymbolType.MEDIA_SYM) {
|
||||
lexer.pushback(symb);
|
||||
media();
|
||||
} else if (symb.type == CssSymbolType.PAGE_SYM) {
|
||||
lexer.pushback(symb);
|
||||
List<String> propNames = new ArrayList<>();
|
||||
List<String> propValues = new ArrayList<>();
|
||||
page(propNames, propValues);
|
||||
} else {
|
||||
lexer.pushback(symb);
|
||||
if (!ruleset()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
symb = lex();
|
||||
while (symb.isType(CssSymbolType.CDO, CssSymbolType.CDC)) {
|
||||
sstar();
|
||||
symb = lex();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean ruleset() throws IOException, CssParseException {
|
||||
int posSelectorStart = lexer.getPos();
|
||||
int specifity = selector();
|
||||
if (specifity == -1) {
|
||||
return false;
|
||||
}
|
||||
CssParsedSymbol symb = lex();
|
||||
while (symb.isType(",")) {
|
||||
sstar();
|
||||
specifity += selector();
|
||||
symb = lex();
|
||||
}
|
||||
expect(symb, "{");
|
||||
|
||||
int posSelectorEnd = lexer.getPos() - 1;
|
||||
String selectorStr = s.substring(posSelectorStart, posSelectorEnd).trim();
|
||||
selectors.add(selectorStr);
|
||||
specifities.add(specifity);
|
||||
|
||||
int declarationsStart = lexer.getPos();
|
||||
Reference<String> propName = new Reference<>("");
|
||||
Reference<String> propValue = new Reference<>("");
|
||||
|
||||
List<String> propNames = new ArrayList<>();
|
||||
List<String> propValues = new ArrayList<>();
|
||||
|
||||
sstar();
|
||||
symb = lex();
|
||||
if (symb.type == CssSymbolType.IDENT) {
|
||||
lexer.pushback(symb);
|
||||
declaration(propName, propValue);
|
||||
propNames.add(propName.getVal());
|
||||
propValues.add(propValue.getVal());
|
||||
symb = lex();
|
||||
}
|
||||
while (symb.isType(";")) {
|
||||
sstar();
|
||||
symb = lex();
|
||||
if (symb.type == CssSymbolType.IDENT) {
|
||||
lexer.pushback(symb);
|
||||
declaration(propName, propValue);
|
||||
propNames.add(propName.getVal());
|
||||
propValues.add(propValue.getVal());
|
||||
symb = lex();
|
||||
}
|
||||
}
|
||||
expect(symb, "}");
|
||||
int declarationsEnd = lexer.getPos() - 1;
|
||||
String declaration = s.substring(declarationsStart, declarationsEnd);
|
||||
declarations.add(declaration);
|
||||
|
||||
this.propNames.add(propNames);
|
||||
this.propValues.add(propValues);
|
||||
|
||||
sstar();
|
||||
return true;
|
||||
}
|
||||
|
||||
private int selector() throws IOException, CssParseException {
|
||||
int specifity = simple_selector();
|
||||
if (specifity == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
CssParsedSymbol symb = lex();
|
||||
if (symb.type == CssSymbolType.S) {
|
||||
while (symb.type == CssSymbolType.S) {
|
||||
symb = lex();
|
||||
}
|
||||
if (symb.isType("+", ">")) {
|
||||
sstar();
|
||||
specifity += selector();
|
||||
} else {
|
||||
lexer.pushback(symb);
|
||||
if (symb.isType("*", ".", "[", ":") || symb.isType(CssSymbolType.IDENT, CssSymbolType.HASH)) {
|
||||
specifity += selector();
|
||||
}
|
||||
}
|
||||
} else if (symb.isType("+", ">")) {
|
||||
sstar();
|
||||
specifity += selector();
|
||||
} else {
|
||||
lexer.pushback(symb);
|
||||
}
|
||||
return specifity;
|
||||
}
|
||||
|
||||
private int simple_selector() throws IOException, CssParseException {
|
||||
CssParsedSymbol symb = lex();
|
||||
int specifity = 0;
|
||||
if (symb.isType(CssSymbolType.IDENT) || symb.isType("*")) {
|
||||
if (symb.isType(CssSymbolType.IDENT)) {
|
||||
specifity += 1;
|
||||
}
|
||||
while (true) {
|
||||
symb = lex();
|
||||
if (symb.type == CssSymbolType.HASH) {
|
||||
specifity += 100;
|
||||
} else if (symb.isType(".")) {
|
||||
expect(CssSymbolType.IDENT);
|
||||
specifity += 10;
|
||||
} else if (symb.isType("[")) {
|
||||
lexer.pushback(symb);
|
||||
attrib();
|
||||
specifity += 10;
|
||||
} else if (symb.isType(":")) {
|
||||
lexer.pushback(symb);
|
||||
pseudo();
|
||||
specifity += 10;
|
||||
} else {
|
||||
lexer.pushback(symb);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
int count = 0;
|
||||
while (true) {
|
||||
if (symb.type == CssSymbolType.HASH) {
|
||||
specifity += 100;
|
||||
count++;
|
||||
} else if (symb.isType(".")) {
|
||||
expect(CssSymbolType.IDENT);
|
||||
specifity += 10;
|
||||
count++;
|
||||
} else if (symb.isType("[")) {
|
||||
lexer.pushback(symb);
|
||||
attrib();
|
||||
specifity += 10;
|
||||
count++;
|
||||
} else if (symb.isType(":")) {
|
||||
lexer.pushback(symb);
|
||||
pseudo();
|
||||
specifity += 10;
|
||||
count++;
|
||||
} else {
|
||||
if (count == 0) {
|
||||
lexer.pushback(symb);
|
||||
return -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
symb = lex();
|
||||
}
|
||||
lexer.pushback(symb);
|
||||
}
|
||||
return specifity;
|
||||
}
|
||||
|
||||
private void pseudo() throws IOException, CssParseException {
|
||||
expect(":");
|
||||
CssParsedSymbol symb = lex();
|
||||
if (symb.type == CssSymbolType.IDENT) {
|
||||
//okay
|
||||
} else if (symb.type == CssSymbolType.FUNCTION) {
|
||||
sstar();
|
||||
symb = lex();
|
||||
if (symb.type == CssSymbolType.IDENT) {
|
||||
sstar();
|
||||
symb = lex();
|
||||
}
|
||||
expect(symb, ")");
|
||||
}
|
||||
}
|
||||
|
||||
private void attrib() throws IOException, CssParseException {
|
||||
expect("[");
|
||||
sstar();
|
||||
expect(CssSymbolType.IDENT);
|
||||
sstar();
|
||||
CssParsedSymbol symb = lex();
|
||||
if (symb.isType(CssSymbolType.INCLUDES, CssSymbolType.DASHMATCH) || symb.isType("=")) {
|
||||
sstar();
|
||||
expect(CssSymbolType.IDENT, CssSymbolType.STRING);
|
||||
sstar();
|
||||
symb = lex();
|
||||
}
|
||||
expect(symb, "]");
|
||||
|
||||
}
|
||||
|
||||
private void page(List<String> propNames, List<String> propValues) throws IOException, CssParseException {
|
||||
expect(CssSymbolType.PAGE_SYM);
|
||||
sstar();
|
||||
CssParsedSymbol symb = lex();
|
||||
if (symb.isType(":")) { //pseudo_page
|
||||
expect(CssSymbolType.IDENT);
|
||||
sstar();
|
||||
symb = lex();
|
||||
}
|
||||
expect(symb, "{");
|
||||
sstar();
|
||||
Reference<String> propName = new Reference<>("");
|
||||
Reference<String> propValue = new Reference<>("");
|
||||
declaration(propName, propValue);
|
||||
propNames.add(propName.getVal());
|
||||
propValues.add(propValue.getVal());
|
||||
symb = lex();
|
||||
while (symb.isType(";")) {
|
||||
sstar();
|
||||
symb = lex();
|
||||
if (!symb.isType(";", "}")) {
|
||||
declaration(propName, propValue);
|
||||
propNames.add(propName.getVal());
|
||||
propValues.add(propValue.getVal());
|
||||
}
|
||||
}
|
||||
expect(symb, "}");
|
||||
|
||||
}
|
||||
|
||||
private void declaration(Reference<String> propName, Reference<String> propValue) throws IOException, CssParseException {
|
||||
CssParsedSymbol symb = lex();
|
||||
expect(symb, CssSymbolType.IDENT);
|
||||
propName.setVal(symb.value);
|
||||
sstar();
|
||||
expect(":");
|
||||
sstar();
|
||||
lexer.startBuffer();
|
||||
expr();
|
||||
symb = lex();
|
||||
if (symb.type == CssSymbolType.IMPORTANT_SYM) {
|
||||
sstar();
|
||||
} else {
|
||||
lexer.pushback(symb);
|
||||
}
|
||||
propValue.setVal(lexer.getAndClearBuffer());
|
||||
}
|
||||
|
||||
private void expr() throws IOException, CssParseException {
|
||||
term(true);
|
||||
while (true) {
|
||||
CssParsedSymbol symb = lex();
|
||||
if (symb.isType("/", ",")) {
|
||||
sstar();
|
||||
term(true);
|
||||
} else {
|
||||
lexer.pushback(symb);
|
||||
if (!term(false)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private boolean term(boolean required) throws IOException, CssParseException {
|
||||
CssParsedSymbol symb = lex();
|
||||
if (symb.isType("-", "+")) {
|
||||
symb = lex();
|
||||
}
|
||||
if (symb.isType(CssSymbolType.NUMBER, CssSymbolType.PERCENTAGE,
|
||||
CssSymbolType.LENGTH, CssSymbolType.EMS, CssSymbolType.EXS,
|
||||
CssSymbolType.ANGLE, CssSymbolType.TIME, CssSymbolType.FREQ)) {
|
||||
sstar();
|
||||
} else if (symb.isType(CssSymbolType.STRING, CssSymbolType.IDENT, CssSymbolType.URI)) {
|
||||
sstar();
|
||||
} else if (symb.type == CssSymbolType.HASH) {
|
||||
sstar();
|
||||
} else if (symb.type == CssSymbolType.FUNCTION) {
|
||||
sstar();
|
||||
expr();
|
||||
expect(")");
|
||||
sstar();
|
||||
} else {
|
||||
lexer.pushback(symb);
|
||||
if (required) {
|
||||
throw new CssParseException();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void media() throws IOException, CssParseException {
|
||||
expect(CssSymbolType.MEDIA_SYM);
|
||||
sstar();
|
||||
media_list();
|
||||
expect("{");
|
||||
sstar();
|
||||
CssParsedSymbol symb = lex();
|
||||
while (!symb.isType("}")) {
|
||||
if (!ruleset()) {
|
||||
break;
|
||||
}
|
||||
symb = lex();
|
||||
}
|
||||
sstar();
|
||||
}
|
||||
|
||||
private void medium() throws IOException, CssParseException {
|
||||
expect(CssSymbolType.IDENT);
|
||||
sstar();
|
||||
}
|
||||
|
||||
private void media_list() throws IOException, CssParseException {
|
||||
medium();
|
||||
CssParsedSymbol symb = lex();
|
||||
while (symb.isType(",")) {
|
||||
sstar();
|
||||
medium();
|
||||
}
|
||||
lexer.pushback(symb);
|
||||
}
|
||||
|
||||
private void sstar() throws IOException {
|
||||
CssParsedSymbol symb = lex();
|
||||
while (symb.type == CssSymbolType.S) {
|
||||
symb = lex();
|
||||
}
|
||||
lexer.pushback(symb);
|
||||
}
|
||||
|
||||
private CssParsedSymbol lex() throws IOException {
|
||||
CssParsedSymbol v = lexer.lex();
|
||||
//System.err.println("" + v);
|
||||
return v;
|
||||
}
|
||||
|
||||
private void expect(String s) throws IOException, CssParseException {
|
||||
CssParsedSymbol symb = lex();
|
||||
if (symb.type != CssSymbolType.OTHER) {
|
||||
throw new CssParseException(s + " expected but " + symb + " found");
|
||||
}
|
||||
if (!s.equals(symb.value)) {
|
||||
throw new CssParseException(s + " expected but " + symb + " found");
|
||||
}
|
||||
}
|
||||
|
||||
private void expect(CssParsedSymbol symb, String s) throws IOException, CssParseException {
|
||||
if (symb.type != CssSymbolType.OTHER) {
|
||||
throw new CssParseException(s + " expected but " + symb + " found");
|
||||
}
|
||||
if (!s.equals(symb.value)) {
|
||||
throw new CssParseException(s + " expected but " + symb.value + " found");
|
||||
}
|
||||
}
|
||||
|
||||
private void expect(CssParsedSymbol symb, CssSymbolType... types) throws IOException, CssParseException {
|
||||
List<String> toPrint = new ArrayList<>();
|
||||
for (CssSymbolType type : types) {
|
||||
toPrint.add(type.toString());
|
||||
if (symb.type == type) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new CssParseException(String.join(",", toPrint) + " expected but " + symb + " found");
|
||||
}
|
||||
|
||||
private void expect(CssSymbolType... types) throws IOException, CssParseException {
|
||||
List<String> toPrint = new ArrayList<>();
|
||||
CssParsedSymbol symb = lex();
|
||||
|
||||
for (CssSymbolType type : types) {
|
||||
toPrint.add(type.toString());
|
||||
if (symb.type == type) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw new CssParseException(String.join(",", toPrint) + " expected but " + symb + " found");
|
||||
}
|
||||
|
||||
public int getCountRulesets() {
|
||||
return declarations.size();
|
||||
}
|
||||
|
||||
public String getSelector(int index) {
|
||||
return selectors.get(index);
|
||||
}
|
||||
|
||||
public String getDeclarations(int index) {
|
||||
return declarations.get(index);
|
||||
}
|
||||
|
||||
public int getPropertyCount(int index) {
|
||||
return propNames.get(index).size();
|
||||
}
|
||||
|
||||
public String getPropertyName(int ruleSetIndex, int propertyIndex) {
|
||||
return propNames.get(ruleSetIndex).get(propertyIndex);
|
||||
}
|
||||
|
||||
public String getPropertyValue(int ruleSetIndex, int propertyIndex) {
|
||||
return propValues.get(ruleSetIndex).get(propertyIndex);
|
||||
}
|
||||
|
||||
public int getSpecifity(int index) {
|
||||
return specifities.get(index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,686 @@
|
||||
package com.jpexs.decompiler.flash.importers.svg.css;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*
|
||||
* Based on implementation https://github.com/css2xpath/css2xpath
|
||||
*
|
||||
*/
|
||||
public class CssSelectorToXPath {
|
||||
|
||||
private String xpath_to_lower(String s) {
|
||||
return "translate("
|
||||
+ (s == null ? "normalize-space()" : s)
|
||||
+ ", 'ABCDEFGHJIKLMNOPQRSTUVWXYZ'"
|
||||
+ ", 'abcdefghjiklmnopqrstuvwxyz')";
|
||||
}
|
||||
|
||||
private String xpath_ends_with(String s1, String s2) {
|
||||
return "substring(" + s1 + ","
|
||||
+ "string-length(" + s1 + ")-string-length(" + s2 + ")+1)=" + s2;
|
||||
}
|
||||
|
||||
private String xpath_url(String s) {
|
||||
return "substring-before(concat(substring-after("
|
||||
+ (s == null ? xpath_url_attrs : s) + ",\"://\"),\"?\"),\"?\")";
|
||||
}
|
||||
|
||||
private String xpath_url_path(String s) {
|
||||
return "substring-after(" + (s == null || s.isEmpty() ? xpath_url_attrs : s) + ",\"/\")";
|
||||
}
|
||||
|
||||
private String xpath_url_domain(String s) {
|
||||
return "substring-before(concat(substring-after("
|
||||
+ (s == null || s.isEmpty() ? xpath_url_attrs : s) + ",\"://\"),\"/\"),\"/\")";
|
||||
}
|
||||
private final String xpath_url_attrs = "@href|@src";
|
||||
private final String xpath_lower_case = xpath_to_lower(null);
|
||||
private final String xpath_ns_uri = "ancestor-or-self::*[last()]/@url";
|
||||
private final String xpath_ns_path = xpath_url_path(xpath_url(xpath_ns_uri));
|
||||
private final String xpath_has_protocal = "(starts-with(" + xpath_url_attrs + ",\"http://\") or starts-with(" + xpath_url_attrs + ",\"https://\"))";
|
||||
private final String xpath_is_internal = "starts-with(" + xpath_url(null) + "," + xpath_url_domain(xpath_ns_uri) + ") or " + xpath_ends_with(xpath_url_domain(null), xpath_url_domain(xpath_ns_uri));
|
||||
private final String xpath_is_local = "(" + xpath_has_protocal + " and starts-with(" + xpath_url(null) + "," + xpath_url(xpath_ns_uri) + "))";
|
||||
private final String xpath_is_path = "starts-with(" + xpath_url_attrs + ",\"/\")";
|
||||
private final String xpath_is_local_path = "starts-with(" + xpath_url_path(null) + "," + xpath_ns_path + ")";
|
||||
private final String xpath_normalize_space = "normalize-space()";
|
||||
private final String xpath_internal = "[not(" + xpath_has_protocal + ") or " + xpath_is_internal + "]";
|
||||
private final String xpath_external = "[" + xpath_has_protocal + " and not(" + xpath_is_internal + ")]";
|
||||
private final char escape_literal = (char) 30;
|
||||
private final char escape_parens = (char) 31;
|
||||
private final String regex_string_literal = "(\"[^\"\\x1E]*\"|'[^'\\x1E]*'|=\\s*[^\\s\\]\\'\\\"]+)"; // /g
|
||||
private final String regex_escaped_literal = "['\"]?(\\x1E+)['\"]?"; // /g;
|
||||
private final String regex_css_wrap_pseudo = "(\\x1F\\)|[^\\)])\\:(first|limit|last|gt|lt|eq|nth)([^\\-]|$)"; // bez g
|
||||
private final String regex_specal_chars = "[\\x1C-\\x1F]+"; // /g;
|
||||
private final String regex_first_axis = "^([\\s\\(\\x1F]*)(\\.?[^\\.\\/\\(]{1,2}[a-z]*:*)";
|
||||
private final String regex_filter_prefix = "(^|\\/|\\:)\\["; // /g;
|
||||
private final String regex_attr_prefix = "([^\\(\\[\\/\\|\\s\\x1F])\\@"; // /g;
|
||||
private final String regex_nth_equation = "^([-0-9]*)n.*?([0-9]*)$"; //bez g
|
||||
private final String css_combinators_regex = "\\s*(!?[+>~,^ ])\\s*(\\.?\\/+|[a-z\\-]+::)?([a-z\\-]+\\()?((and\\s*|or\\s*|mod\\s*)?[^+>~,\\s'\"\\]\\|\\^\\$\\!\\<\\=\\x1C-\\x1F]+)?"; // /g;
|
||||
|
||||
// Check if string is numeric
|
||||
private boolean isNumeric(String s) {
|
||||
try {
|
||||
Integer.parseInt(s);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private String css_combinators_callback(String match, String operator, String axis, String func, String literal, String exclude, int offset, String orig) {
|
||||
String prefix = ""; // If we can, we'll prefix a '.'
|
||||
|
||||
// XPath operators can look like node-name selectors
|
||||
// Detect false positive for " and", " or", " mod"
|
||||
if (operator.equals(" ") && exclude != null) {
|
||||
return match;
|
||||
}
|
||||
|
||||
if (axis == null) {
|
||||
// Only allow node-selecting XPath functions
|
||||
// Detect false positive for " + count(...)", " count(...)", " > position()", etc.
|
||||
if (func != null && (!func.equals("node(") && !func.equals("text(") && !func.equals("comment("))) {
|
||||
return null;
|
||||
} else if (literal == null) {
|
||||
literal = func;
|
||||
} // Handle case " + text()", " > comment()", etc. where "func" is our "literal"
|
||||
|
||||
// XPath math operators match some CSS combinators
|
||||
// Detect false positive for " + 1", " > 1", etc.
|
||||
if (isNumeric(literal)) {
|
||||
return match;
|
||||
}
|
||||
|
||||
if (orig.length() <= offset - 1) {
|
||||
prefix = ".";
|
||||
} else {
|
||||
char prevChar = orig.charAt(offset - 1);
|
||||
|
||||
if (prevChar == '('
|
||||
|| prevChar == '|'
|
||||
|| prevChar == ':') {
|
||||
prefix = ".";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return if we don't have a selector to follow the axis
|
||||
if (literal == null) {
|
||||
if (offset + match.length() == orig.length()) {
|
||||
literal = "*";
|
||||
} else {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
|
||||
switch (operator) {
|
||||
case " ":
|
||||
return "//" + literal;
|
||||
case ">":
|
||||
return "/" + literal;
|
||||
case "+":
|
||||
return prefix + "/following-sibling::*[1]/self::" + literal;
|
||||
case "~":
|
||||
return prefix + "/following-sibling::" + literal;
|
||||
case ",":
|
||||
if (axis == null) {
|
||||
|
||||
}
|
||||
axis = ".//";
|
||||
return "|" + axis + literal;
|
||||
case "^": // first child
|
||||
return "/child::*[1]/self::" + literal;
|
||||
case "!^": // last child
|
||||
return "/child::*[last()]/self::" + literal;
|
||||
case "! ": // ancestor-or-self
|
||||
return "/ancestor-or-self::" + literal;
|
||||
case "!>": // direct parent
|
||||
return "/parent::" + literal;
|
||||
case "!+": // adjacent preceding sibling
|
||||
return "/preceding-sibling::*[1]/self::" + literal;
|
||||
case "!~": // preceding sibling
|
||||
return "/preceding-sibling::" + literal;
|
||||
// case '~~'
|
||||
// return '/following-sibling::*/self::|'+selectorStart(orig, offset)+'/preceding-sibling::*/self::'+literal;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private final String css_attributes_regex = "\\[([^\\@\\|\\*\\=\\^\\~\\$\\!\\(\\/\\s\\x1C-\\x1F]+)\\s*(([\\|\\*\\~\\^\\$\\!]?)=?\\s*(\\x1E+))?\\]"; // /g;
|
||||
|
||||
private String css_attributes_callback(String str, String attr, String comp, String op, String val, int offset, String orig) {
|
||||
String axis = "";
|
||||
//String prevChar = offset == 0 ? "" : "" + orig.charAt(offset - 1);
|
||||
|
||||
/*
|
||||
if (prevChar === '/' || // found after an axis shortcut ("/", "//", etc.)
|
||||
prevChar === ':') // found after an axis ("self::", "parent::", etc.)
|
||||
axis = '*';*/
|
||||
if (op == null) {
|
||||
op = "";
|
||||
}
|
||||
|
||||
switch (op) {
|
||||
case "!":
|
||||
return axis + "[not(@" + attr + ") or @" + attr + "!=\"" + val + "\"]";
|
||||
case "$":
|
||||
return axis + "[substring(@" + attr + ",string-length(@" + attr + ")-(string-length(\"" + val + "'\")-1))=\"" + val + "\"]";
|
||||
case "^":
|
||||
return axis + "[starts-with(@" + attr + ",\"" + val + "\")]";
|
||||
case "~":
|
||||
return axis + "[contains(concat(\" \",normalize-space(@" + attr + "),\" \"),concat(\" \",\"" + val + "\",\" \"))]";
|
||||
case "*":
|
||||
return axis + "[contains(@" + attr + ",\"" + val + "\")]";
|
||||
case "|":
|
||||
return axis + "[@" + attr + "=\"" + val + "\" or starts-with(@" + attr + ",concat(\"" + val + "\",\"-\"))]";
|
||||
default:
|
||||
if (comp == null) {
|
||||
if (attr.charAt(attr.length() - 1) == '(' || attr.matches("^[0-9]+$") || attr.indexOf(':') != -1) {
|
||||
return str;
|
||||
}
|
||||
return axis + "[@" + attr + "]";
|
||||
} else {
|
||||
return axis + "[@" + attr + "=\"" + val + "\"]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final String css_pseudo_classes_regex = ":([a-z\\-]+)(\\((\\x1F+)(([^\\x1F]+(\\3\\x1F+)?)*)(\\3\\)))?"; // /g;
|
||||
|
||||
private String css_pseudo_classes_callback(String match, String name, String g1, String g2, String arg, String g3, String g4, String g5, int offset, String orig) {
|
||||
if ((offset - 2 >= 0) && orig.charAt(offset - 1) == ':' && orig.charAt(offset - 2) != ':') {
|
||||
// XPath "axis::node-name" will match
|
||||
// Detect false positive ":node-name"
|
||||
return match;
|
||||
}
|
||||
|
||||
if ("odd".equals(name) || "even".equals(name)) {
|
||||
arg = name;
|
||||
name = "nth-of-type";
|
||||
}
|
||||
|
||||
switch (name) { // name.toLowerCase()?
|
||||
case "after":
|
||||
return "[count(" + css2xpath("preceding::" + arg, true) + ") > 0]";
|
||||
case "after-sibling":
|
||||
return "[count(" + css2xpath("preceding-sibling::" + arg, true) + ") > 0]";
|
||||
case "before":
|
||||
return "[count(" + css2xpath("following::" + arg, true) + ") > 0]";
|
||||
case "before-sibling":
|
||||
return "[count(" + css2xpath("following-sibling::" + arg, true) + ") > 0]";
|
||||
case "checked":
|
||||
return "[@selected or @checked]";
|
||||
case "contains":
|
||||
return "[contains(" + xpath_normalize_space + "," + arg + ")]";
|
||||
case "icontains":
|
||||
return "[contains(" + xpath_lower_case + "," + xpath_to_lower(arg) + ")]";
|
||||
case "empty":
|
||||
return "[not(*) and not(normalize-space())]";
|
||||
case "enabled":
|
||||
case "disabled":
|
||||
return "[@" + name + "]";
|
||||
case "first-child":
|
||||
return "[not(preceding-sibling::*)]";
|
||||
case "first":
|
||||
case "limit":
|
||||
case "first-of-type":
|
||||
if (arg != null) {
|
||||
return "[position()<=" + arg + "]";
|
||||
}
|
||||
return "[1]";
|
||||
case "gt":
|
||||
// Position starts at 0 for consistency with Sizzle selectors
|
||||
return "[position()>" + (Integer.parseInt(arg, 10) + 1) + "]";
|
||||
case "lt":
|
||||
// Position starts at 0 for consistency with Sizzle selectors
|
||||
return "[position()<" + (Integer.parseInt(arg, 10) + 1) + "]";
|
||||
case "last-child":
|
||||
return "[not(following-sibling::*)]";
|
||||
case "only-child":
|
||||
return "[not(preceding-sibling::*) and not(following-sibling::*)]";
|
||||
case "only-of-type":
|
||||
return "[not(preceding-sibling::*[name()=name(self::node())]) and not(following-sibling::*[name()=name(self::node())])]";
|
||||
case "nth-child":
|
||||
if (isNumeric(arg)) {
|
||||
return "[(count(preceding-sibling::*)+1) = " + arg + "]";
|
||||
}
|
||||
switch (arg) {
|
||||
case "even":
|
||||
return "[(count(preceding-sibling::*)+1) mod 2=0]";
|
||||
case "odd":
|
||||
return "[(count(preceding-sibling::*)+1) mod 2=1]";
|
||||
default:
|
||||
String[] a = (arg == null || arg.isEmpty() ? "0" : arg).replaceAll(regex_nth_equation, "$1+$2").split("\\+");
|
||||
String a0;
|
||||
if (a.length < 1 || a[0].isEmpty()) {
|
||||
a0 = "1";
|
||||
} else {
|
||||
a0 = a[0];
|
||||
}
|
||||
String a1;
|
||||
if (a.length < 2 || a[1].isEmpty()) {
|
||||
a1 = "0";
|
||||
} else {
|
||||
a1 = a[1];
|
||||
}
|
||||
return "[(count(preceding-sibling::*)+1)>=" + a1 + " and ((count(preceding-sibling::*)+1)-" + a1 + ") mod " + a0 + "=0]";
|
||||
}
|
||||
case "nth-of-type":
|
||||
if (isNumeric(arg)) {
|
||||
return "[" + arg + "]";
|
||||
}
|
||||
switch (arg) {
|
||||
case "odd":
|
||||
return "[position() mod 2=1]";
|
||||
case "even":
|
||||
return "[position() mod 2=0 and position()>=0]";
|
||||
default:
|
||||
String[] a = (arg == null || arg.isEmpty() ? "0" : arg).replaceAll(regex_nth_equation, "$1+$2").split("\\+");
|
||||
String a0;
|
||||
if (a.length < 1 || a[0].isEmpty()) {
|
||||
a0 = "1";
|
||||
} else {
|
||||
a0 = a[0];
|
||||
}
|
||||
String a1;
|
||||
if (a.length < 2 || a[1].isEmpty()) {
|
||||
a1 = "0";
|
||||
} else {
|
||||
a1 = a[1];
|
||||
}
|
||||
return "[position()>=" + a1 + " and (position()-" + a1 + ") mod " + a0 + "=0]";
|
||||
}
|
||||
case "eq":
|
||||
case "nth":
|
||||
// Position starts at 0 for consistency with Sizzle selectors
|
||||
if (isNumeric(arg)) {
|
||||
return "[" + (Integer.parseInt(arg, 10) + 1) + "]";
|
||||
}
|
||||
|
||||
return "[1]";
|
||||
case "text":
|
||||
return "[@type=\"text\"]";
|
||||
case "istarts-with":
|
||||
return "[starts-with(" + xpath_lower_case + "," + xpath_to_lower(arg) + ")]";
|
||||
case "starts-with":
|
||||
return "[starts-with(" + xpath_normalize_space + "," + arg + ")]";
|
||||
case "iends-with":
|
||||
return "[" + xpath_ends_with(xpath_lower_case, xpath_to_lower(arg)) + "]";
|
||||
case "ends-with":
|
||||
return "[" + xpath_ends_with(xpath_normalize_space, arg) + "]";
|
||||
case "has":
|
||||
String xpath1 = prependAxis(css2xpath(arg, true), ".//");
|
||||
|
||||
return "[count(" + xpath1 + ") > 0]";
|
||||
case "has-sibling":
|
||||
String xpath2 = css2xpath("preceding-sibling::" + arg, true);
|
||||
|
||||
return "[count(" + xpath2 + ") > 0 or count(following-sibling::" + xpath2.substring(19) + ") > 0]";
|
||||
case "has-parent":
|
||||
return "[count(" + css2xpath("parent::" + arg, true) + ") > 0]";
|
||||
case "has-ancestor":
|
||||
return "[count(" + css2xpath("ancestor::" + arg, true) + ") > 0]";
|
||||
case "last":
|
||||
case "last-of-type":
|
||||
if (arg != null) {
|
||||
return "[position()>last()-" + arg + "]";
|
||||
}
|
||||
return "[last()]";
|
||||
case "selected": // Sizzle: "(option) elements that are currently selected"
|
||||
return "[local-name()=\"option\" and @selected]";
|
||||
case "skip":
|
||||
case "skip-first":
|
||||
return "[position()>" + arg + "]";
|
||||
case "skip-last":
|
||||
if (arg != null) {
|
||||
return "[last()-position()>=" + arg + "]";
|
||||
}
|
||||
return "[position()<last()]";
|
||||
case "root":
|
||||
return "/ancestor::[last()]";
|
||||
case "range":
|
||||
String arr[] = arg.split(",");
|
||||
|
||||
return "[" + arr[0] + "<=position() and position()<=" + arr[1] + "]";
|
||||
case "input": // Sizzle: "input, button, select, and textarea are all considered to be input elements."
|
||||
return "[local-name()=\"input\" or local-name()=\"button\" or local-name()=\"select\" or local-name()=\"textarea\"]";
|
||||
case "internal":
|
||||
return xpath_internal;
|
||||
case "external":
|
||||
return xpath_external;
|
||||
case "http":
|
||||
case "https":
|
||||
case "mailto":
|
||||
case "javascript":
|
||||
return "[starts-with(@href,concat(\"" + name + "\",\":\"))]";
|
||||
case "domain":
|
||||
return "[(string-length(" + xpath_url_domain(null) + ")=0 and contains(" + xpath_url_domain(xpath_ns_uri) + "," + arg + ")) or contains(" + xpath_url_domain(null) + "," + arg + ")]";
|
||||
case "path":
|
||||
return "[starts-with(" + xpath_url_path(null) + ",substring-after(\"" + arg + "\",\"/\"))]";
|
||||
case "not":
|
||||
String xpath3 = css2xpath(arg, true);
|
||||
|
||||
if (xpath3.charAt(0) == '[') {
|
||||
xpath3 = "self::node()" + xpath3;
|
||||
}
|
||||
return "[not(" + xpath3 + ")]";
|
||||
case "target":
|
||||
return "[starts-with(@href, \"#\")]";
|
||||
/*case "root":
|
||||
return "ancestor-or-self::*[last()]";*/ //FIXME?? Duplicated case label
|
||||
/* case 'active':
|
||||
case 'focus':
|
||||
case 'hover':
|
||||
case 'link':
|
||||
case 'visited':
|
||||
return '';*/
|
||||
case "lang":
|
||||
return "[@lang=\"" + arg + "\"]";
|
||||
case "read-only":
|
||||
case "read-write":
|
||||
return "[@" + name.replace("-", "") + "]";
|
||||
case "valid":
|
||||
case "required":
|
||||
case "in-range":
|
||||
case "out-of-range":
|
||||
return "[@" + name + "]";
|
||||
default:
|
||||
return "[@_pseudo_" + name + "]"; //JPEXS - to disable such notes
|
||||
}
|
||||
}
|
||||
|
||||
private String css_ids_classes_regex = "(#|\\.)([^\\#\\@\\.\\/\\(\\[\\)\\]\\|\\:\\s\\+\\>\\<\\'\\\"\\x1D-\\x1F]+)"; // /g;
|
||||
|
||||
private String css_ids_classes_callback(String str, String op, String val, int offset, String orig) {
|
||||
String axis = "";
|
||||
/* var prevChar = orig.charAt(offset-1);
|
||||
if (prevChar.length === 0 ||
|
||||
prevChar === '/' ||
|
||||
prevChar === '(')
|
||||
axis = '*';
|
||||
else if (prevChar === ':')
|
||||
axis = 'node()';*/
|
||||
if ("#".equals(op)) {
|
||||
return axis + "[@id=\"" + val + "\"]";
|
||||
}
|
||||
return axis + "[contains(concat(\" \",normalize-space(@class),\" \"),\" " + val + " \")]";
|
||||
}
|
||||
|
||||
// Prepend descendant-or-self if no other axis is specified
|
||||
private String prependAxis(String s, String axis) {
|
||||
Pattern pat = Pattern.compile(regex_first_axis);
|
||||
StringBuffer buf = new StringBuffer();
|
||||
Matcher mat = pat.matcher(s);
|
||||
while (mat.find()) {
|
||||
String start = mat.group(1);
|
||||
String literal = mat.group(2);
|
||||
if (literal.length() >= 2 && "::".equals(literal.substring(literal.length() - 2))) {
|
||||
mat.appendReplacement(buf, mat.group());
|
||||
} else {
|
||||
if (literal.charAt(0) == '[') {
|
||||
axis += "*";
|
||||
}
|
||||
mat.appendReplacement(buf, start + axis + literal);
|
||||
}
|
||||
|
||||
}
|
||||
mat.appendTail(buf);
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
// Find the begining of the selector, starting at i and working backwards
|
||||
private int selectorStart(String s, int i) {
|
||||
int depth = 0;
|
||||
int offset = 0;
|
||||
|
||||
while (i-- > 0) {
|
||||
switch (s.charAt(i)) {
|
||||
case ' ':
|
||||
case escape_parens:
|
||||
offset++;
|
||||
break;
|
||||
case '[':
|
||||
case '(':
|
||||
depth--;
|
||||
|
||||
if (depth < 0) {
|
||||
return ++i + offset;
|
||||
}
|
||||
break;
|
||||
case ']':
|
||||
case ')':
|
||||
depth++;
|
||||
break;
|
||||
case ',':
|
||||
case '|':
|
||||
if (depth == 0) {
|
||||
return ++i + offset;
|
||||
}
|
||||
default:
|
||||
offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Append escape "char" to "open" or "close"
|
||||
private String escapeChar(String s, String open, String close, char chr) {
|
||||
Pattern pat = Pattern.compile("[\\" + open + "\\" + close + "]");
|
||||
StringBuffer buf = new StringBuffer();
|
||||
Matcher mat = pat.matcher(s);
|
||||
int depth = 0;
|
||||
while (mat.find()) {
|
||||
if (open.equals(mat.group())) {
|
||||
depth++;
|
||||
}
|
||||
if (open.equals(mat.group())) {
|
||||
mat.appendReplacement(buf, mat.group() + repeat("" + chr, depth));
|
||||
} else {
|
||||
mat.appendReplacement(buf, repeat("" + chr, depth--) + mat.group());
|
||||
}
|
||||
}
|
||||
mat.appendTail(buf);
|
||||
return buf.toString();
|
||||
/*int depth = 0;
|
||||
|
||||
return s.replace(new RegExp('[\\' + open + '\\' + close + ']', 'g'), function (a) {
|
||||
if (a === open) {
|
||||
depth++;
|
||||
}
|
||||
|
||||
if (a === open) {
|
||||
return a + repeat(chr, depth);
|
||||
} else {
|
||||
return repeat(chr, depth--) + a;
|
||||
}
|
||||
})*/
|
||||
}
|
||||
|
||||
private String repeat(String str, int num) {
|
||||
String result = "";
|
||||
|
||||
while (true) {
|
||||
if ((num & 1) == 1) {
|
||||
result += str;
|
||||
}
|
||||
num >>>= 1;
|
||||
|
||||
if (num <= 0) {
|
||||
break;
|
||||
}
|
||||
str += str;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static interface ReplaceCallBack {
|
||||
|
||||
public String run(Matcher mat, String s);
|
||||
}
|
||||
|
||||
private String replace(String s, String regexp, ReplaceCallBack callback) {
|
||||
Pattern pat = Pattern.compile(regexp);
|
||||
StringBuffer sb = new StringBuffer();
|
||||
Matcher m = pat.matcher(s);
|
||||
while (m.find()) {
|
||||
m.appendReplacement(sb, callback.run(m, s));
|
||||
}
|
||||
m.appendTail(sb);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private int search(String s, String regexp) {
|
||||
Pattern pat = Pattern.compile(regexp);
|
||||
Matcher m = pat.matcher(s);
|
||||
if (m.find()) {
|
||||
return m.start();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public String css2xpath(String s) {
|
||||
return css2xpath(s, false);
|
||||
}
|
||||
|
||||
public String css2xpath(String s, boolean nested) {
|
||||
if (nested == true) {
|
||||
// Replace :pseudo-classes
|
||||
final String s1 = s;
|
||||
s = replace(s, css_pseudo_classes_regex, new ReplaceCallBack() {
|
||||
@Override
|
||||
public String run(Matcher mat, String s) {
|
||||
return css_pseudo_classes_callback(mat.group(), mat.group(1), mat.group(1), mat.group(2), mat.group(3), mat.group(4), mat.group(5), mat.group(6), mat.start(), s);
|
||||
}
|
||||
});
|
||||
s = replace(s, css_pseudo_classes_regex, new ReplaceCallBack() {
|
||||
@Override
|
||||
public String run(Matcher mat, String s) {
|
||||
return css_pseudo_classes_callback(mat.group(), mat.group(1), mat.group(2), mat.group(3), mat.group(4),
|
||||
mat.group(5), mat.group(6), mat.group(7), mat.start(), s);
|
||||
}
|
||||
});
|
||||
|
||||
// Replace #ids and .classes
|
||||
s = replace(s, css_ids_classes_regex, new ReplaceCallBack() {
|
||||
@Override
|
||||
public String run(Matcher mat, String s) {
|
||||
return css_ids_classes_callback(mat.group(), mat.group(1), mat.group(2), mat.start(), s);
|
||||
}
|
||||
});
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// Tag open and close parenthesis pairs (for RegExp searches)
|
||||
s = escapeChar(s, "(", ")", escape_parens);
|
||||
|
||||
// Remove and save any string literals
|
||||
List<String> literals = new ArrayList<>();
|
||||
|
||||
s = replace(s, regex_string_literal, new ReplaceCallBack() {
|
||||
@Override
|
||||
public String run(Matcher mat, String fullS) {
|
||||
String s = mat.group();
|
||||
String a = mat.group(1);
|
||||
|
||||
if (a.charAt(0) == '=') {
|
||||
a = a.substring(1).trim();
|
||||
|
||||
if (isNumeric(a)) {
|
||||
return s;
|
||||
}
|
||||
} else {
|
||||
a = a.substring(1, a.length() - 1);
|
||||
}
|
||||
|
||||
literals.add(a);
|
||||
return repeat("" + escape_literal, literals.size());
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// Replace CSS combinators (" ", "+", ">", "~", ",") and reverse combinators ("!", "!+", "!>", "!~")
|
||||
s = replace(s, css_combinators_regex, new ReplaceCallBack() {
|
||||
@Override
|
||||
public String run(Matcher mat, String s) {
|
||||
return css_combinators_callback(mat.group(), mat.group(1), mat.group(2), mat.group(3), mat.group(4), mat.group(5), mat.start(), s);
|
||||
}
|
||||
});
|
||||
|
||||
// Replace CSS attribute filters
|
||||
s = replace(s, css_attributes_regex, new ReplaceCallBack() {
|
||||
@Override
|
||||
public String run(Matcher mat, String s) {
|
||||
return css_attributes_callback(mat.group(), mat.group(1), mat.group(2), mat.group(3), mat.group(4), mat.start(), s);
|
||||
}
|
||||
});
|
||||
|
||||
// Wrap certain :pseudo-classes in parens (to collect node-sets)
|
||||
while (true) {
|
||||
int index = search(s, regex_css_wrap_pseudo);
|
||||
|
||||
if (index == -1) {
|
||||
break;
|
||||
}
|
||||
index = s.indexOf(':', index);
|
||||
int start = selectorStart(s, index);
|
||||
|
||||
s = s.substring(0, start)
|
||||
+ '(' + s.substring(start, index) + ')'
|
||||
+ s.substring(index);
|
||||
}
|
||||
|
||||
// Replace :pseudo-classes
|
||||
s = replace(s, css_pseudo_classes_regex, new ReplaceCallBack() {
|
||||
@Override
|
||||
public String run(Matcher mat, String s) {
|
||||
return css_pseudo_classes_callback(mat.group(), mat.group(1), mat.group(2), mat.group(3), mat.group(4), mat.group(5), mat.group(6), mat.group(7), mat.start(), s);
|
||||
}
|
||||
});
|
||||
|
||||
// Replace #ids and .classes
|
||||
s = replace(s, css_ids_classes_regex, new ReplaceCallBack() {
|
||||
@Override
|
||||
public String run(Matcher mat, String s) {
|
||||
return css_ids_classes_callback(mat.group(), mat.group(1), mat.group(2), mat.start(), s);
|
||||
}
|
||||
});
|
||||
|
||||
// Restore the saved string literals
|
||||
s = replace(s, regex_escaped_literal, new ReplaceCallBack() {
|
||||
@Override
|
||||
public String run(Matcher mat, String a) {
|
||||
String str = literals.get(mat.group(1).length() - 1);
|
||||
|
||||
return "\"" + str + "\"";
|
||||
}
|
||||
});
|
||||
|
||||
// Remove any special characters
|
||||
s = s.replaceAll(regex_specal_chars, "");
|
||||
|
||||
// add * to stand-alone filters
|
||||
s = s.replaceAll(regex_filter_prefix, "$1*[");
|
||||
|
||||
// add "/" between @attribute selectors
|
||||
s = s.replaceAll(regex_attr_prefix, "$1/@");
|
||||
|
||||
/*
|
||||
Combine multiple filters?
|
||||
|
||||
s = escapeChar(s, '[', ']', filter_char);
|
||||
s = s.replace(/(\x1D+)\]\[\1(.+?[^\x1D])\1\]/g, ' and ($2)$1]')
|
||||
*/
|
||||
s = prependAxis(s, ".//"); // prepend ".//" axis to begining of CSS selector
|
||||
return s;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.jpexs.decompiler.flash.importers.svg.css;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author JPEXS
|
||||
*/
|
||||
public enum CssSymbolType {
|
||||
S,
|
||||
CDO,
|
||||
CDC,
|
||||
INCLUDES,
|
||||
DASHMATCH,
|
||||
STRING,
|
||||
BAD_STRING,
|
||||
IDENT,
|
||||
HASH,
|
||||
IMPORT_SYM,
|
||||
PAGE_SYM,
|
||||
MEDIA_SYM,
|
||||
CHARSET_SYM,
|
||||
IMPORTANT_SYM,
|
||||
EMS,
|
||||
EXS,
|
||||
LENGTH,
|
||||
ANGLE,
|
||||
TIME,
|
||||
FREQ,
|
||||
DIMENSION,
|
||||
PERCENTAGE,
|
||||
NUMBER,
|
||||
URI,
|
||||
BAD_URI,
|
||||
FUNCTION,
|
||||
EOF,
|
||||
OTHER
|
||||
}
|
||||
Reference in New Issue
Block a user