import std/algorithm import std/options import std/strutils import std/tables import std/unittest import test1 import chame/htmlparser import chame/minidom type TCError = object s: string FragmentType = enum FT_HTML, FT_SVG, FT_MATHML TCFragment = object fragmentType: FragmentType ctx: Element ScriptMode = enum SCRIPT_BOTH, SCRIPT_OFF, SCRIPT_ON TCTest = object data: string errors: seq[TCError] newerrors: seq[TCError] fragment: Option[TCFragment] script: ScriptMode document: Document TCTestParser = object s: string i: int pi: int factory: MAtomFactory linei: int func has(ctx: TCTestParser): bool = return ctx.i < ctx.s.len proc reconsumeLine(ctx: var TCTestParser) = ctx.i = ctx.pi dec ctx.linei proc consumeLine(ctx: var TCTestParser): string = result = "" ctx.pi = ctx.i inc ctx.linei while ctx.has: if ctx.s[ctx.i] == '\n': inc ctx.i break result &= ctx.s[ctx.i] inc ctx.i proc parseTestData(ctx: var TCTestParser): string = result = "" while ctx.has: let line = ctx.consumeLine() if line == "#errors": return if result.len > 0: result &= '\n' result &= line doAssert false, "errors expected" proc parseTestNewErrors(ctx: var TCTestParser): seq[TCError] = result = @[] while ctx.has: let line = ctx.consumeLine() case line of "#document-fragment", "#script-off", "#script-on", "#document": ctx.reconsumeLine() return result.add(TCError(s: line)) proc parseTestErrors(ctx: var TCTestParser): seq[TCError] = result = @[] while ctx.has: let line = ctx.consumeLine() case line of "#new-errors": ctx.reconsumeLine() result.add(ctx.parseTestNewErrors()) return of "#document-fragment", "#script-off", "#script-on", "#document": ctx.reconsumeLine() return result.add(TCError(s: line)) proc parseTestFragment(ctx: var TCTestParser): TCFragment = var line = ctx.consumeLine() var fragmentType = FT_HTML if line.startsWith("svg "): fragmentType = FT_SVG line = line.substr("svg ".len) elif line.startsWith("math "): fragmentType = FT_MATHML line = line.substr("math ".len) let namespace = case fragmentType of FT_SVG: Namespace.SVG of FT_MATHML: Namespace.MATHML of FT_HTML: Namespace.HTML let element = Element( namespace: namespace, localName: ctx.factory.strToAtom(line) ) return TCFragment( fragmentType: fragmentType, ctx: element ) proc parseDoctype(ctx: TCTestParser, s: string): DocumentType = let doctype = DocumentType() var i = "': doctype.name &= s[i] inc i while s[i] == ' ': inc i if s[i] == '>': return doctype assert s[i] == '"' inc i while i < s.len and s[i] != '"': doctype.publicId &= s[i] inc i assert s[i] == '"' inc i assert s[i] == ' ' inc i assert s[i] == '"' inc i while i < s.len and s[i] != '"': doctype.systemId &= s[i] inc i while i + 1 < s.len and s[i + 1] == '"': doctype.systemId &= s[i] inc i assert s[i] == '"' inc i assert s[i] == '>' return doctype func until(s: string, c: set[char]): string = result = "" var i = 0 while i < s.len: if s[i] in c: break result.add(s[i]) inc i func until(s: string, c: char): string = s.until({c}) func after(s: string, c: set[char]): string = var i = 0 while i < s.len: if s[i] in c: return s.substr(i + 1) inc i return "" func after(s: string, c: char): string = s.after({c}) proc parseTestDocument(ctx: var TCTestParser): Document = result = Document(factory: ctx.factory) var stack = @[Node(result)] template top: auto = stack[^1] var thistext: Text = nil var thiscomment: Comment = nil var indent = 1 template pop_node = let node = stack.pop() if node of Element: Element(node).attrs.sort(proc(a, b: Attribute): int = cmp(a.name, b.name)) indent -= 2 while ctx.has: let line = ctx.consumeLine() if thistext != nil: if line.endsWith("\""): thistext.data &= line.substr(0, line.high - 1) thistext = nil else: thistext.data &= line & "\n" continue if thiscomment != nil: if line.endsWith(" -->"): thiscomment.data &= line.substr(0, line.high - " -->".len) thiscomment = nil else: thiscomment.data &= line & "\n" continue if line == "": break assert line[0] == '|' and line[1] == ' ' while indent >= line.len or not line.startsWith('|' & ' '.repeat(indent)): let node = stack.pop() if node of Element: Element(node).attrs.sort(proc(a, b: Attribute): int = cmp(a.name, b.name)) indent -= 2 let str = line.substr(indent + 1) if str.startsWith(""): comment.data = str.substr("".len) elif str.startsWith(""): var nameStr = str.substr(1, str.high - 1) var namespace = Namespace.HTML if nameStr.startsWith("svg "): nameStr = nameStr.substr("svg ".len) namespace = Namespace.SVG elif nameStr.startsWith("math "): nameStr = nameStr.substr("math ".len) namespace = Namespace.MATHML let element = if nameStr == "template": HTMLTemplateElement() else: Element() element.localName = ctx.factory.strToAtom(nameStr) element.namespace = namespace element.document = result top.childList.add(element) stack.add(element) indent += 2 elif str == "content": let fragment = DocumentFragment() HTMLTemplateElement(top).content = fragment stack.add(fragment) indent += 2 elif str[0] == '"': let text = Text() top.childList.add(text) if str[^1] != '"' or str.len == 1: text.data = str.substr(1) & "\n" thistext = text else: text.data = str.substr(1, str.high - 1) else: assert '=' in str var name = str.until('=') var prefix = NO_PREFIX var ns = NO_NAMESPACE if name.startsWith("xml "): ns = Namespace.XML prefix = PREFIX_XML name = name.substr("xml ".len) elif name.startsWith("xmlns "): ns = Namespace.XMLNS prefix = PREFIX_XMLNS name = name.substr("xmlns ".len) elif name.startsWith("xlink "): ns = Namespace.XLINK prefix = PREFIX_XLINK name = name.substr("xlink ".len) let na = ctx.factory.strToAtom(name) let value = str.after('=')[1..^2] Element(top).attrs.add((prefix, ns, na, value)) while indent > 1: pop_node proc parseTest(ctx: var TCTestParser): TCTest = doAssert ctx.consumeLine() == "#data" var t = TCTest() t.data = ctx.parseTestData() t.errors = ctx.parseTestErrors() while ctx.has: let line = ctx.consumeLine() case line of "#document-fragment": t.fragment = some(ctx.parseTestFragment()) of "#script-off": t.script = SCRIPT_OFF of "#script-on": t.script = SCRIPT_ON of "#document": assert t.document == nil t.document = ctx.parseTestDocument() break of "": break return t proc parseTests*(s: string, factory: MAtomFactory): seq[TCTest] = result = @[] var parser = TCTestParser(s: s, factory: factory) while parser.i < s.len: let test = parser.parseTest() result.add(test) var s = "" for x in test.document.childList: s &= $x & '\n' proc checkTest(nodein, nodep: Node) = check nodein.childList.len == nodep.childList.len if nodein.childList.len != nodep.childList.len: echo nodein echo nodep if nodein of Element: check nodep of Element let nodein = Element(nodein) let nodep = Element(nodep) check nodein.localName == nodep.localName check nodein.namespace == nodep.namespace if nodein.attrs != nodep.attrs: echo "NODEIN", $nodein echo "NODEP", $nodep check nodein.attrs == nodep.attrs elif nodein of DocumentFragment: assert false elif nodein of CharacterData: check nodep of CharacterData check CharacterData(nodein).data == CharacterData(nodep).data elif nodein of DocumentType: check nodep of DocumentType let nodein = DocumentType(nodein) let nodep = DocumentType(nodep) check nodein.name == nodep.name check nodein.publicId == nodep.publicId check nodein.systemId == nodep.systemId for i in 0 ..< nodein.childList.len: checkTest(nodein.childList[i], nodep.childList[i])