aboutsummaryrefslogtreecommitdiff
path: root/src/main/model/css/CssParser.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/model/css/CssParser.java')
-rw-r--r--src/main/model/css/CssParser.java368
1 files changed, 220 insertions, 148 deletions
diff --git a/src/main/model/css/CssParser.java b/src/main/model/css/CssParser.java
index 8d57bdc..25b6752 100644
--- a/src/main/model/css/CssParser.java
+++ b/src/main/model/css/CssParser.java
@@ -14,13 +14,6 @@ import java.util.*;
* ATTRIBUTE ::= 'color' | 'text' | ...
* VALUE ::= ??? idk lol
*/
-
-/**
- * This class assumes that it is getting _valid CSS_: that is, the style between two tags
- * of a style block, or the raw content of a .css file.
- * Making sure this assumption holds is extremely important for program robustness.
- * We do not check for validity, i.e. throw any exceptions - the driving principle of web standards is to "fail softly".
- */
public class CssParser {
/**
@@ -32,156 +25,60 @@ public class CssParser {
SINGLE_QUOTES, DOUBLE_QUOTES, // VALUE::SINGLE_QUOTES, VALUE::DOUBLE_QUOTES
}
+ // essentially the csstree type, only we don't need it to be a tree
+ private ArrayList<Pair<String, ArrayList<Pair<String, String>>>> result;
+ // a bunch of useful buffers: optimizations in the future could likely come from tweaking these
+ // note that i know nothing about data structure performance: but i'm pretty sure that Strings
+ // are _not_ the right tool for the job here, lol
+ private String currentSelector;
+ private ArrayList<Pair<String, String>> currentRule;
+ private String currentProperty;
+ private String currentValue;
+ // important for quote escapes
+ private char previousChar;
+
+ private ParserState state;
+
+ /// Initialize all buffers to default values
+ public CssParser() {
+ result = new ArrayList<>();
+ currentSelector = "";
+ currentRule = new ArrayList<>();
+ currentProperty = "";
+ currentValue = "";
+ previousChar = '\0';
+
+ // We safely assume to start by reading a selector.
+ state = ParserState.SELECTORS;
+ }
+
/**
- * Parses a (valid) CSS file in a left-to-right, leftmost-derivation style.
+ * Parses a (valid) CSS file in a left-to-right, leftmost-derivation style. No additional lookup is needed,
+ * however we do keep a previousChar value for dealing with (annoying) escaped quotes.
* It should be fast - I'd say something about time complexity if I knew anything about time complexity.
* No guarantees are made about invalid CSS files. Also, no guarantees are made about valid CSS files, lol.
+ * <br>
+ * REQUIRES: A valid CSS file, as a raw String.
+ * MODIFIES: this
+ * EFFECTS: Returns a parsed CSS representation as several nested ArrayLists and Pairs of Strings.
*/
- public static ArrayList<Pair<String, ArrayList<Pair<String, String>>>> parseLL(String input) {
-
- // parser buffers
- // essentially the CssTree type
- var result = new ArrayList<Pair<String, ArrayList<Pair<String, String>>>>();
- var currentSelector = "";
- var currentRule = new ArrayList<Pair<String, String>>();
- var currentProperty = "";
- var currentValue = "";
- var previousChar = '\0';
-
- // We safely assume to start by reading a selector.
- ParserState state = ParserState.SELECTORS;
+ public ArrayList<Pair<String, ArrayList<Pair<String, String>>>> parseCSS(String input) {
for (char c : input.toCharArray()) {
// System.out.print(state);
// System.out.println(" " + c);
switch (state) {
- case SELECTORS:
- switch (c) {
- case '@':
- if (currentSelector.equals("")) {
- state = ParserState.MEDIA_SELECTORS;
- } else {
- currentSelector += c;
- }
- break;
- case '{':
- state = ParserState.ATTRIBUTE;
- break;
- case ' ': case '\n':
- break;
- // todo: do better than blindly create a string; pattern match on css selectors
- default:
- currentSelector += c;
- break;
- }
+ case SELECTORS: caseSelectors(c);
break;
- case MEDIA_SELECTORS:
- switch (c) {
- // todo: don't entirely disregard media queries, also split between @media/@...
- case '{':
- state = ParserState.SELECTORS;
- // discard currentSelector
- currentSelector = "";
- break;
- default:
- currentSelector += c;
- break;
- }
+ case MEDIA_SELECTORS: caseMediaSelectors(c);
break;
- case ATTRIBUTE:
- switch (c) {
- case ':':
- state = ParserState.VALUE;
- break;
- case '}':
- state = ParserState.SELECTORS;
- if (!currentValue.equals("") || !currentProperty.equals("")) {
- System.out.println("something's wrong");
- currentProperty = "";
- currentValue = "";
- }
- result.add(new Pair<>(currentSelector, currentRule));
- System.out.println(currentRule);
- currentSelector = "";
- currentRule = new ArrayList<>();
- break;
- case ' ': case '\n':
- break;
- default:
- currentProperty += c;
- break;
- }
+ case ATTRIBUTE: caseAttribute(c);
break;
- case VALUE:
- switch (c) {
- case ';':
- state = ParserState.ATTRIBUTE;
- currentRule.add(new Pair<>(currentProperty, currentValue));
- currentProperty = "";
- currentValue = "";
- break;
- case '}':
- state = ParserState.SELECTORS;
- if (!currentValue.equals("") || !currentProperty.equals("")) {
- currentRule.add(new Pair<>(currentProperty, currentValue));
- currentProperty = "";
- currentValue = "";
- }
- result.add(new Pair<>(currentSelector, currentRule));
- currentSelector = "";
- currentRule = new ArrayList<>();
- break;
- case '\'':
- state = ParserState.SINGLE_QUOTES;
- currentValue += c;
- break;
- case '\"':
- state = ParserState.DOUBLE_QUOTES;
- currentValue += c;
- break;
- case ' ': case '\n':
- break;
- default:
- currentValue += c;
- break;
- }
+ case VALUE: caseValue(c);
break;
- // quotes in css are exclusively? for paths: so we want to include the quotes themselves
- case SINGLE_QUOTES:
- switch (c) {
- case '\'':
- if (previousChar != '\\') {
- state = ParserState.VALUE;
- currentValue += c;
- previousChar = '\0';
- } else {
- currentValue = currentValue.substring(0, currentValue.length() - 2);
- currentValue += c;
- previousChar = c;
- }
- break;
- default:
- currentValue += c;
- break;
- }
+ case SINGLE_QUOTES: caseSingleQuotes(c);
break;
- case DOUBLE_QUOTES:
- switch (c) {
- case '\"':
- if (previousChar != '\\') {
- state = ParserState.VALUE;
- currentValue += c;
- previousChar = '\0';
- } else {
- currentValue = currentValue.substring(0, currentValue.length() - 2);
- currentValue += c;
- previousChar = c;
- }
- break;
- default:
- currentValue += c;
- break;
- }
+ case DOUBLE_QUOTES: caseDoubleQuotes(c);
break;
}
}
@@ -189,12 +86,179 @@ public class CssParser {
}
/**
- * Takes an input string with units and returns out the value in pixels.
- * This is a fault-tolerant system.
+ * EFFECTS: Handles and updates parser state/buffers for a single character while in the SELECTORS state.
+ * See also: the (slightly wrong) context-free grammar commented at the start of this file.
+ * MODIFIES: this
+ */
+ private void caseSelectors(char c) {
+ switch (c) {
+ case '@':
+ if (currentSelector.equals("")) {
+ state = ParserState.MEDIA_SELECTORS;
+ } else {
+ currentSelector += c;
+ }
+ break;
+ case '{':
+ state = ParserState.ATTRIBUTE;
+ break;
+ case ' ': case '\n':
+ break;
+ // todo: do better than blindly create a string; pattern match on css selectors
+ default:
+ currentSelector += c;
+ break;
+ }
+ }
+
+ /**
+ * EFFECTS: Handles and updates parser state/buffers for a single character while in the MEDIA_SELECTORS state.
+ * MODIFIES: this
+ */
+ private void caseMediaSelectors(char c) {
+ switch (c) {
+ // todo: don't entirely disregard media queries, also split between @media/@...
+ case '{':
+ state = ParserState.SELECTORS;
+ // discard currentSelector
+ currentSelector = "";
+ break;
+ default:
+ currentSelector += c;
+ break;
+ }
+ }
+
+ /**
+ * EFFECTS: Handles and updates parser state/buffers for a single character while in the ATTRIBUTE state.
+ * MODIFIES: this
+ */
+ private void caseAttribute(char c) {
+ switch (c) {
+ case ':':
+ state = ParserState.VALUE;
+ break;
+ case '}':
+ state = ParserState.SELECTORS;
+ if (!currentValue.equals("") || !currentProperty.equals("")) {
+ // System.out.println("something's wrong");
+ currentProperty = "";
+ currentValue = "";
+ }
+ result.add(new Pair<>(currentSelector, currentRule));
+ currentSelector = "";
+ currentRule = new ArrayList<>();
+ break;
+ case ' ': case '\n':
+ break;
+ default:
+ currentProperty += c;
+ break;
+ }
+ }
+
+ /**
+ * EFFECTS: Handles and updates parser state/buffers for a single character while in the VALUE state.
+ * MODIFIES: this
+ */
+ private void caseValue(char c) {
+ switch (c) {
+ case ';':
+ state = ParserState.ATTRIBUTE;
+ updateCurrentRule();
+ break;
+ case '}':
+ state = ParserState.SELECTORS;
+ if (!currentValue.equals("") || !currentProperty.equals("")) {
+ updateCurrentRule();
+ }
+ result.add(new Pair<>(currentSelector, currentRule));
+ currentSelector = "";
+ currentRule = new ArrayList<>();
+ break;
+ // todo: handle spaces better: they're actually important inside values
+ case ' ': case '\n': break; // believe me, i think this is ugly too but it passes checkstyle
+ case '\'':
+ state = ParserState.SINGLE_QUOTES;
+ currentValue += c;
+ break;
+ // intentional use of TERRIBLE SMOKING FOOTGUN behavior to check boxes
+ case '\"': state = ParserState.DOUBLE_QUOTES;
+ default: currentValue += c;
+ break;
+ }
+ }
+
+ /**
+ * Helper function to check method length boxes.
+ * EFFECTS: Adds a new property to the current rule.
+ * MODIFIES: this
+ */
+ private void updateCurrentRule() {
+ currentRule.add(new Pair<>(currentProperty, currentValue));
+ currentProperty = "";
+ currentValue = "";
+ }
+
+ // todo: handle additional escaped characters, though what we have right now isn't bad
+
+ /**
+ * EFFECTS: Handles and updates parser state/buffers for a single character while in the SINGLE_QUOTES state.
+ * MODIFIES: this
+ */
+ private void caseSingleQuotes(char c) {
+ switch (c) {
+ case '\'':
+ if (previousChar != '\\') {
+ state = ParserState.VALUE;
+ // quotes in css are exclusively? for paths: so we want to include the quotes themselves
+ currentValue += c;
+ previousChar = '\0';
+ } else {
+ // possibly not the best way to handle this, may be better to keep the backslash
+ currentValue = currentValue.substring(0, currentValue.length() - 2);
+ currentValue += c;
+ previousChar = c;
+ }
+ break;
+ default:
+ currentValue += c;
+ break;
+ }
+ }
+
+ /**
+ * EFFECTS: Handles and updates parser state/buffers for a single character while in the DOUBLE_QUOTES state.
+ * MODIFIES: this
+ */
+ private void caseDoubleQuotes(char c) {
+ switch (c) {
+ case '\"':
+ if (previousChar != '\\') {
+ state = ParserState.VALUE;
+ currentValue += c;
+ previousChar = '\0';
+ } else {
+ currentValue = currentValue.substring(0, currentValue.length() - 2);
+ currentValue += c;
+ previousChar = c;
+ }
+ break;
+ default:
+ currentValue += c;
+ break;
+ }
+ }
+
+ /**
+ * Takes an input string with units and returns out the value in pixels. This is a fault-tolerant system.
* When given an invalid string (i.e. "12p53x"), it will produce an invalid result instead of throwing.
* However, it should parse every valid string correctly.
+ * <br>
+ * REQUIRES: A string of the form [NUMBER][VALIDUNIT]
+ * EFFECTS: Returns a number, in pixels, that has been converted appropriately
*/
- private double parseUnits(String input) {
+ private static double parseUnits(String input) {
String numbers = "";
String units = "";
// imagine making a language without iterable strings, fml
@@ -210,9 +274,17 @@ public class CssParser {
try {
value = Float.parseFloat(numbers);
} catch (NumberFormatException e) {
- System.out.printf("Did not parse a float from %s, proceeding with value 0.0...%n", numbers);
+ // System.out.printf("Did not parse a float from %s, proceeding with value 0.0...%n", numbers);
value = 0.0;
}
+ return convertUnits(units, value);
+ }
+
+ /**
+ * REQUIRES: a String that is a unit, otherwise defaults to pixels
+ * EFFECTS: converts a value in some units to a value in pixels
+ */
+ private static double convertUnits(String units, double value) {
// god case/break is such a fault-provoking design i hate it
// good thing we avoid breaks entirely here lmao
switch (units) {
@@ -226,7 +298,7 @@ public class CssParser {
case "in": return value * 96;
// not handled: % em ex ch rem lh rlh vw vh vmin vmax vb vi svw svh lvw lvh dvw dvh
default:
- System.out.printf("Unit %s not implemented, defaulting to %s in pixels...%n", units, value);
+ // System.out.printf("Unit %s not implemented, defaulting to %s in pixels...%n", units, value);
return value;
}
}