aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorj-james2022-10-17 15:29:53 +0000
committerj-james2022-10-17 15:29:53 +0000
commit21b8e5f6cdcd9fab275efce5e88f02addfd19e7e (patch)
tree8a629525a63f3ff8a0123d4eefc30bc0b578af16 /src
parent9dad27fef462d20adec671efe6d1e795966f5300 (diff)
Implement fairly comprehensive ParserTests
100% class and method coverage, 95% line coverage
Diffstat (limited to 'src')
-rw-r--r--src/main/model/css/CssParser.java64
-rw-r--r--src/main/model/html/ElementNode.java17
-rw-r--r--src/main/model/html/HtmlParser.java25
-rw-r--r--src/test/model/CssParserTest.java56
-rw-r--r--src/test/model/HtmlParserTest.java82
5 files changed, 188 insertions, 56 deletions
diff --git a/src/main/model/css/CssParser.java b/src/main/model/css/CssParser.java
index 25b6752..a382b14 100644
--- a/src/main/model/css/CssParser.java
+++ b/src/main/model/css/CssParser.java
@@ -4,7 +4,10 @@ import org.javatuples.*;
import java.util.*;
-/*
+/**
+ * This class represents the state of and implements an LL(1) CSS parser.
+ * For convenience, the following (slightly wrong) context-free grammar for CSS is below.
+ * <br>
* RULES ::= (RULE)+
* RULE ::= SELECTORS '{' (PROPERTY | (PROPERTY ';')*) '}'
* SELECTORS ::= SELECTOR (COMBINATOR SELECTOR)*
@@ -216,13 +219,14 @@ public class CssParser {
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 = currentValue.substring(0, currentValue.length() - 1);
currentValue += c;
previousChar = c;
}
break;
default:
currentValue += c;
+ previousChar = c;
break;
}
}
@@ -239,13 +243,14 @@ public class CssParser {
currentValue += c;
previousChar = '\0';
} else {
- currentValue = currentValue.substring(0, currentValue.length() - 2);
+ currentValue = currentValue.substring(0, currentValue.length() - 1);
currentValue += c;
previousChar = c;
}
break;
default:
currentValue += c;
+ previousChar = c;
break;
}
}
@@ -258,7 +263,7 @@ public class CssParser {
* REQUIRES: A string of the form [NUMBER][VALIDUNIT]
* EFFECTS: Returns a number, in pixels, that has been converted appropriately
*/
- private static double parseUnits(String input) {
+ public static double parseUnits(String input) {
String numbers = "";
String units = "";
// imagine making a language without iterable strings, fml
@@ -305,30 +310,29 @@ public class CssParser {
}
/*
- * body {
- * background-color: #f0f0f2;
- * margin: 0;
- * padding: 0;
- * font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI",
- * "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
- *
- * }
- * div {
- * width: 600px;
- * margin: 5em auto;
- * padding: 2em;
- * background-color: #fdfdff;
- * border-radius: 0.5em;
- * box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
- * }
- * a:link, a:visited {
- * color: #38488f;
- * text-decoration: none;
- * }
- * @media (max - width : 700px) {
- * div {
- * margin: 0 auto;
- * width: auto;
- * }
- * }
+body {
+ background-color: #f0f0f2;
+ margin: 0;
+ padding: 0;
+ font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI",
+ "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+}
+div {
+ width: 600px;
+ margin: 5em auto;
+ padding: 2em;
+ background-color: #fdfdff;
+ border-radius: 0.5em;
+ box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);
+}
+a:link, a:visited {
+ color: #38488f;
+ text-decoration: none;
+}
+@media (max - width : 700px) {
+ div {
+ margin: 0 auto;
+ width: auto;
+ }
+}
*/
diff --git a/src/main/model/html/ElementNode.java b/src/main/model/html/ElementNode.java
index a1ad90c..98995d0 100644
--- a/src/main/model/html/ElementNode.java
+++ b/src/main/model/html/ElementNode.java
@@ -28,7 +28,16 @@ public class ElementNode implements Node {
* MODIFIES: this
*/
public ElementNode(String tag, ArrayList<Pair<String, String>> attributes) {
- this(tag, attributes, new ArrayList<Node>());
+ this(tag, attributes, new ArrayList<>());
+ }
+
+ /**
+ * Overloads the constructor for ease of use. Should probably only be used for tests.
+ * EFFECTS: Constructs a new ElementNode from the arguments provided.
+ * MODIFIES: this
+ */
+ public ElementNode(String tag) {
+ this(tag, new ArrayList<>(), new ArrayList<>());
}
/**
@@ -43,12 +52,16 @@ public class ElementNode implements Node {
return this.tag;
}
+ public ArrayList<Pair<String, String>> getAttributes() {
+ return this.attributes;
+ }
+
public ArrayList<Node> getChildren() {
return this.children;
}
// We implement this method for easy debugging.
public String getData() {
- return getTag();
+ return getTag() + " " + getAttributes().toString();
}
}
diff --git a/src/main/model/html/HtmlParser.java b/src/main/model/html/HtmlParser.java
index e9dc0c4..bfdd57c 100644
--- a/src/main/model/html/HtmlParser.java
+++ b/src/main/model/html/HtmlParser.java
@@ -5,15 +5,18 @@ import java.util.*;
import model.util.Node;
import org.javatuples.*;
-/*
+/**
+ * This class represents the state of and implements an LL(1) HTML parser.
+ * For convenience, the following (defo wrong) context-free grammar for HTML is below.
+ * <br>
* HTML ::= '<!DOCTYPE html>' (NODE)*
- * NODE ::= '<'TAG (' ' WORD '=' ('"'TEXT'"' | TEXT))* '>' (NODE)* '</' TAG '>'
- * | '<'SINGLE_TAG (' ' WORD '=' ('"'TEXT'"' | TEXT))* ('>'|'/>')
+ * NODE ::= '<'TAG (' ' WORD '=' ('"'TEXT'"' | TEXT))* '>' (NODE)* '<\/' TAG '>'
+ * | '<'SELF_CLOSING_TAG (' ' WORD '=' ('"'TEXT'"' | TEXT))* ('>'|'/>')
* | (TEXT | NODE)*
* TEXT ::= UNICODE - {'"'} + {'\"'}
* TAG ::= 'body' | 'div' | ...
- * SINGLE_TAG ::= 'img' | ...
- * (note that \forall T \in SINGLE_TAG, T \notin TAG)
+ * SELF_CLOSING_TAG ::= 'img' | ...
+ * (note that \forall T \in SELF_CLOSING_TAG, T \notin TAG)
*/
public class HtmlParser {
@@ -216,16 +219,16 @@ public class HtmlParser {
*/
private void caseValue(char c) {
switch (c) {
- case '\'':
- state = ParserState.SINGLE_QUOTES;
+ case '\'': state = ParserState.SINGLE_QUOTES;
break;
- case '\"':
- state = ParserState.DOUBLE_QUOTES;
+ case '\"': state = ParserState.DOUBLE_QUOTES;
break;
case ' ': case '\n':
+ state = ParserState.KEY;
currentAttributes.add(new Pair<>(currentKey, currentValue));
currentKey = "";
currentValue = "";
+ break; // THE FOOTGUN DESIGN STRIKES AGAIN
case '>':
if (!currentKey.equals("") || !currentValue.equals("")) {
currentAttributes.add(new Pair<>(currentKey, currentValue));
@@ -251,7 +254,7 @@ public class HtmlParser {
state = ParserState.VALUE;
previousChar = '\0';
} else {
- currentValue = currentValue.substring(0, currentValue.length() - 2);
+ currentValue = currentValue.substring(0, currentValue.length() - 1);
currentValue += c;
previousChar = c;
}
@@ -274,7 +277,7 @@ public class HtmlParser {
state = ParserState.VALUE;
previousChar = '\0';
} else {
- currentValue = currentValue.substring(0, currentValue.length() - 2);
+ currentValue = currentValue.substring(0, currentValue.length() - 1);
currentValue += c;
previousChar = c;
}
diff --git a/src/test/model/CssParserTest.java b/src/test/model/CssParserTest.java
index fa395f6..35f5310 100644
--- a/src/test/model/CssParserTest.java
+++ b/src/test/model/CssParserTest.java
@@ -1,16 +1,68 @@
package model;
import model.css.CssParser;
+import org.javatuples.Pair;
import org.junit.jupiter.api.Test;
+import java.util.ArrayList;
+
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CssParserTest {
@Test
void testIdiomaticCss() {
- var idiomaticCss = "body { background-color: #f0f0f2; margin: 0; padding: 0; font-family: -apple-system, system-ui, BlinkMacSystemFont, \"Segoe UI\", \"Open Sans\", \"Helvetica Neue\", Helvetica, Arial, sans-serif;}div { width: 600px; margin: 5em auto; padding: 2em; background-color: #fdfdff; border-radius: 0.5em; box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);}a:link, a:visited { color: #38488f; text-decoration: none;}@media (max - width : 700px) { div { margin: 0 auto; width: auto; }}";
+ var idiomaticCss = "body { background-color: '#\\'f0f0f2'; margin: 0; padding: 0; font-family: -apple-system, system-ui, BlinkMacSystemFont, \"Segoe\\\" UI\", 'Open\\' Sans', \"Helvetica Neue\", Helvetica, Arial, sans-serif;}div { width: 600px; margin: 5em auto; padding: 2em; background-color: #fdfdff; border-radius: 0.5em; box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);}a:link, a:visited { color: #38488f; text-decoration: none;}@media (max - width : 700px) { @media () {div { margin: 0 auto; width: auto }}}";
+
+ ArrayList<Pair<String, ArrayList<Pair<String, String>>>> expected = new ArrayList<>();
+ ArrayList<Pair<String, String>> body = new ArrayList<>();
+ ArrayList<Pair<String, String>> divOne = new ArrayList<>();
+ ArrayList<Pair<String, String>> selectors = new ArrayList<>();
+ ArrayList<Pair<String, String>> divTwo = new ArrayList<>();
+ expected.add(new Pair<>("body", body));
+ expected.add(new Pair<>("div", divOne));
+ expected.add(new Pair<>("a:link,a:visited", selectors));
+ expected.add(new Pair<>("div", divTwo));
+ body.add(new Pair<>("background-color", "'#'f0f0f2'"));
+ body.add(new Pair<>("margin", "0"));
+ body.add(new Pair<>("padding", "0"));
+ body.add(new Pair<>("font-family", "-apple-system,system-ui,BlinkMacSystemFont,\"Segoe\" UI\",'Open' Sans',\"Helvetica Neue\",Helvetica,Arial,sans-serif"));
+ divOne.add(new Pair<>("width", "600px"));
+ divOne.add(new Pair<>("margin", "5emauto"));
+ divOne.add(new Pair<>("padding", "2em"));
+ divOne.add(new Pair<>("background-color", "#fdfdff"));
+ divOne.add(new Pair<>("border-radius", "0.5em"));
+ divOne.add(new Pair<>("box-shadow", "2px3px7px2pxrgba(0,0,0,0.02)"));
+ selectors.add(new Pair<>("color", "#38488f"));
+ selectors.add(new Pair<>("text-decoration", "none"));
+ divTwo.add(new Pair<>("margin", "0auto"));
+ divTwo.add(new Pair<>("width", "auto"));
+
CssParser parser = new CssParser();
- System.out.println(parser.parseCSS(idiomaticCss));
+ assertEqualsCss(parser.parseCSS(idiomaticCss), expected);
+ // System.out.println(parser.parseCSS(idiomaticCss));
+ }
+
+ @Test
+ void testConversions() {
+ assertEquals(CssParser.parseUnits("0gosf"), 0);
+ assertEquals(CssParser.parseUnits("0px"), 0);
+ assertEquals(CssParser.parseUnits("0.0pc"), 0);
+ assertEquals(CssParser.parseUnits("0.00pt"), 0);
+ assertEquals(CssParser.parseUnits("0cm"), 0);
+ assertEquals(CssParser.parseUnits("0mm"), 0);
+ assertEquals(CssParser.parseUnits("0Q"), 0);
+ assertEquals(CssParser.parseUnits("0in"), 0);
+ }
+
+ public static void assertEqualsCss(ArrayList<Pair<String, ArrayList<Pair<String, String>>>> css,
+ ArrayList<Pair<String, ArrayList<Pair<String, String>>>> expected) {
+ for (int i = 0; i < css.size(); i++) {
+ assertEquals(css.get(i).getValue0(), expected.get(i).getValue0());
+ for (int j = 0; j < css.get(i).getValue1().size(); j++) {
+ assertEquals(css.get(i).getValue1().get(j).getValue0(), expected.get(i).getValue1().get(j).getValue0());
+ assertEquals(css.get(i).getValue1().get(j).getValue1(), expected.get(i).getValue1().get(j).getValue1());
+ }
+ }
}
}
diff --git a/src/test/model/HtmlParserTest.java b/src/test/model/HtmlParserTest.java
index 58b4555..36c5935 100644
--- a/src/test/model/HtmlParserTest.java
+++ b/src/test/model/HtmlParserTest.java
@@ -2,39 +2,99 @@ package model;
import model.html.ElementNode;
import model.html.HtmlParser;
+import model.html.TextNode;
import model.util.Node;
+import org.javatuples.Pair;
import org.junit.jupiter.api.Test;
import java.util.*;
+import static org.junit.jupiter.api.Assertions.*;
+
public class HtmlParserTest {
String idiomaticHtml = "<!DOCTYPE html><html><head></head><body><p>Hello, world!</p></body></html>";
- String brokenHtml = "<html><foo><bar></bar><ba";
- String trailingTextHtml = "<html><foo><bar></bar>ba";
+ String brokenHtml = "<html><foo><bar></bar><><ba";
+ String trailingTextHtml = "bot<html><foo><bar></bar>ba";
+ String attributesHtml = "<html><attr hello=\"world\" foo='bar' strange=\"cha\\\"rm\" up='do\\'wn'></attr></html>";
@Test
void testIdiomaticHtml() {
- String[] idiomaticHtmlArray = {"<!DOCTYPE html>","<html>","<head>","</head>","<body>","<p>","Hello,world!","</p>","</body>","</html>"};
+ ArrayList<Node> expected = new ArrayList<>();
+ ArrayList<Node> expectedChildren = new ArrayList<>();
+ ArrayList<Node> expectedGrandChildren = new ArrayList<>();
+ ArrayList<Node> expectedGreatGrandChildren = new ArrayList<>();
+ expected.add(new ElementNode("html", new ArrayList<>(), expectedChildren));
+ expectedChildren.add(new ElementNode("head"));
+ expectedChildren.add(new ElementNode("body", new ArrayList<>(), expectedGrandChildren));
+ expectedGrandChildren.add(new ElementNode("p", new ArrayList<>(), expectedGreatGrandChildren));
+ expectedGreatGrandChildren.add(new TextNode("Hello, world!"));
+
HtmlParser parser = new HtmlParser();
- displayHtmlTree(parser.parseHtml(idiomaticHtml));
-// assertEquals(HtmlParser.parseHtmlLL(idiomaticHtml), Arrays.asList(idiomaticHtmlArray));
+ assertEqualsHtml(parser.parseHtml(idiomaticHtml), expected);
+ // displayHtmlTree(parser.parseHtml(idiomaticHtml));
}
@Test
void testBrokenHtml() {
- String[] brokenHtmlArray = {"<html>","<foo>","<bar>","</bar>","<ba>"};
+ ArrayList<Node> expected = new ArrayList<>();
+ ArrayList<Node> expectedChildren = new ArrayList<>();
+ ArrayList<Node> expectedGrandChildren = new ArrayList<>();
+ expected.add(new ElementNode("html", new ArrayList<>(), expectedChildren));
+ expectedChildren.add(new ElementNode("foo", new ArrayList<>(), expectedGrandChildren));
+ expectedGrandChildren.add(new ElementNode("bar", new ArrayList<>()));
+ expectedGrandChildren.add(new TextNode("<>"));
+
HtmlParser parser = new HtmlParser();
- displayHtmlTree(parser.parseHtml(brokenHtml));
-// assertEquals(HtmlParser.parseHtmlLL(brokenHtml), Arrays.asList(brokenHtmlArray));
+ assertEqualsHtml(parser.parseHtml(brokenHtml), expected);
+ // displayHtmlTree(parser.parseHtml(brokenHtml));
}
@Test
void testTrailingTextHtml() {
- String[] trailingTextHtmlArray = {"<html>","<foo>","<bar>","</bar>","ba"};
+ ArrayList<Node> expected = new ArrayList<>();
+ ArrayList<Node> expectedChildren = new ArrayList<>();
+ ArrayList<Node> expectedGrandChildren = new ArrayList<>();
+ expected.add(new TextNode("bot"));
+ expected.add(new ElementNode("html", new ArrayList<>(), expectedChildren));
+ expected.add(new TextNode("ba"));
+ expectedChildren.add(new ElementNode("foo", new ArrayList<>(), expectedGrandChildren));
+ expectedGrandChildren.add(new ElementNode("bar", new ArrayList<>()));
+
HtmlParser parser = new HtmlParser();
- displayHtmlTree(parser.parseHtml(trailingTextHtml));
-// assertEquals(HtmlParser.parseHtmlLL(trailingTextHtml), Arrays.asList(trailingTextHtmlArray));
+ assertEqualsHtml(parser.parseHtml(trailingTextHtml), expected);
+ // displayHtmlTree(parser.parseHtml(trailingTextHtml));
+ }
+
+ @Test
+ void testAttributesHtml() {
+ ArrayList<Node> expected = new ArrayList<>();
+ ArrayList<Node> expectedChildren = new ArrayList<>();
+ ArrayList<Pair<String, String>> expectedAttributes = new ArrayList<>();
+ expected.add(new ElementNode("html", new ArrayList<>(), expectedChildren));
+ expectedChildren.add(new ElementNode("attr", expectedAttributes));
+ expectedAttributes.add(new Pair<>("hello", "world"));
+ expectedAttributes.add(new Pair<>("foo", "bar"));
+ expectedAttributes.add(new Pair<>("strange", "cha\"rm"));
+ expectedAttributes.add(new Pair<>("up", "do'wn"));
+
+ HtmlParser parser = new HtmlParser();
+ var parsed = parser.parseHtml(attributesHtml);
+ displayHtmlTree(parsed);
+ assertEqualsHtml(parsed, expected);
+ }
+
+ /**
+ * Complicated helper function for tests.
+ */
+ private static void assertEqualsHtml(ArrayList<Node> html, ArrayList<Node> expected) {
+ for (int i = 0; i < html.size(); i++) {
+ assertEquals(html.get(i).getData(), expected.get(i).getData());
+ // System.out.println(html.get(i).getData() + " " + expected.get(i).getData());
+ if (html.get(i) instanceof ElementNode) {
+ assertEqualsHtml(((ElementNode) html.get(i)).getChildren(), ((ElementNode) expected.get(i)).getChildren());
+ }
+ }
}
/**