diff options
author | j-james | 2022-10-17 15:29:53 +0000 |
---|---|---|
committer | j-james | 2022-10-17 15:29:53 +0000 |
commit | 21b8e5f6cdcd9fab275efce5e88f02addfd19e7e (patch) | |
tree | 8a629525a63f3ff8a0123d4eefc30bc0b578af16 /src | |
parent | 9dad27fef462d20adec671efe6d1e795966f5300 (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.java | 64 | ||||
-rw-r--r-- | src/main/model/html/ElementNode.java | 17 | ||||
-rw-r--r-- | src/main/model/html/HtmlParser.java | 25 | ||||
-rw-r--r-- | src/test/model/CssParserTest.java | 56 | ||||
-rw-r--r-- | src/test/model/HtmlParserTest.java | 82 |
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()); + } + } } /** |