michael@0: /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /** michael@0: * A test suite that runs WHATWG HTML parser tests. michael@0: * The tests are from html5lib. michael@0: * michael@0: * http://html5lib.googlecode.com/ michael@0: */ michael@0: michael@0: /** michael@0: * A few utility functions. michael@0: */ michael@0: function log(entry) { michael@0: michael@0: } michael@0: michael@0: function startsWith(s, s2) { michael@0: return s.indexOf(s2)==0; michael@0: } michael@0: michael@0: function trimString(s) { michael@0: return(s.replace(/^\s+/,'').replace(/\s+$/,'')); michael@0: } michael@0: michael@0: /** michael@0: * Parses an individual testcase into an array containing the input michael@0: * string, a string representing the expected tree (DOM), and a list michael@0: * of error messages. michael@0: * michael@0: * @param A string containing a single testcase michael@0: */ michael@0: function parseTestcase(testcase) { michael@0: var lines = testcase.split("\n"); michael@0: michael@0: /* check that the first non-empty, non-comment line is #data */ michael@0: for each (var line in lines) { michael@0: if (!line || startsWith(line, "##")) { michael@0: continue; michael@0: } michael@0: if (line == "#data") michael@0: break; michael@0: log(lines); michael@0: throw "Unknown test format." michael@0: } michael@0: michael@0: var input = []; michael@0: var output = []; michael@0: var errors = []; michael@0: var fragment = []; michael@0: var currentList = input; michael@0: for each (var line in lines) { michael@0: if (startsWith(line, "##todo")) { michael@0: todo(false, line.substring(6)); michael@0: continue; michael@0: } michael@0: if (!(startsWith(line, "#error") || michael@0: startsWith(line, "#document") || michael@0: startsWith(line, "#document-fragment") || michael@0: startsWith(line, "#data"))) { michael@0: currentList.push(line); michael@0: } else if (line == "#errors") { michael@0: currentList = errors; michael@0: } else if (line == "#document") { michael@0: currentList = output; michael@0: } else if (line == "#document-fragment") { michael@0: currentList = fragment; michael@0: } michael@0: } michael@0: while (!output[output.length - 1]) { michael@0: output.pop(); // zap trailing blank lines michael@0: } michael@0: //logger.log(input.length, output.length, errors.length); michael@0: return [input.join("\n"), output.join("\n"), errors, fragment[0]]; michael@0: } michael@0: michael@0: /** michael@0: * A generator function that accepts a list of strings. Each list michael@0: * member corresponds to the contents of a ".dat" file from the michael@0: * html5lib test suite. michael@0: * michael@0: * @param The list of strings michael@0: */ michael@0: function test_parser(testlist) { michael@0: for each (var testgroup in testlist) { michael@0: var tests = testgroup.split("#data\n"); michael@0: tests = ["#data\n" + test for each(test in tests) if (test)]; michael@0: for each (var test in tests) { michael@0: yield parseTestcase(test); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Transforms a DOM document to a string matching the format in michael@0: * the test cases. michael@0: * michael@0: * @param the DOM document michael@0: */ michael@0: function docToTestOutput(doc) { michael@0: var walker = doc.createTreeWalker(doc, NodeFilter.SHOW_ALL, null); michael@0: return addLevels(walker, "", "| ").slice(0,-1); // remove the last newline michael@0: } michael@0: michael@0: /** michael@0: * Creates a walker for a fragment that skips over the root node. michael@0: * michael@0: * @param an element michael@0: */ michael@0: function createFragmentWalker(elt) { michael@0: return elt.ownerDocument.createTreeWalker(elt, NodeFilter.SHOW_ALL, michael@0: function (node) { michael@0: return elt == node ? NodeFilter.FILTER_SKIP : NodeFilter.FILTER_ACCEPT; michael@0: }); michael@0: } michael@0: michael@0: /** michael@0: * Transforms the descendants of an element to a string matching the format michael@0: * in the test cases. michael@0: * michael@0: * @param an element michael@0: */ michael@0: function fragmentToTestOutput(elt) { michael@0: var walker = createFragmentWalker(elt); michael@0: return addLevels(walker, "", "| ").slice(0,-1); // remove the last newline michael@0: } michael@0: michael@0: function addLevels(walker, buf, indent) { michael@0: if(walker.firstChild()) { michael@0: do { michael@0: buf += indent; michael@0: switch (walker.currentNode.nodeType) { michael@0: case Node.ELEMENT_NODE: michael@0: buf += "<" michael@0: var ns = walker.currentNode.namespaceURI; michael@0: if ("http://www.w3.org/1998/Math/MathML" == ns) { michael@0: buf += "math "; michael@0: } else if ("http://www.w3.org/2000/svg" == ns) { michael@0: buf += "svg "; michael@0: } else if ("http://www.w3.org/1999/xhtml" != ns) { michael@0: buf += "otherns "; michael@0: } michael@0: buf += walker.currentNode.localName + ">"; michael@0: if (walker.currentNode.hasAttributes()) { michael@0: var valuesByName = {}; michael@0: var attrs = walker.currentNode.attributes; michael@0: for (var i = 0; i < attrs.length; ++i) { michael@0: var localName = attrs[i].localName; michael@0: var name; michael@0: var attrNs = attrs[i].namespaceURI; michael@0: if (null == attrNs) { michael@0: name = localName; michael@0: } else if ("http://www.w3.org/XML/1998/namespace" == attrNs) { michael@0: name = "xml " + localName; michael@0: } else if ("http://www.w3.org/1999/xlink" == attrNs) { michael@0: name = "xlink " + localName; michael@0: } else if ("http://www.w3.org/2000/xmlns/" == attrNs) { michael@0: name = "xmlns " + localName; michael@0: } else { michael@0: name = "otherns " + localName; michael@0: } michael@0: valuesByName[name] = attrs[i].value; michael@0: } michael@0: var keys = Object.keys(valuesByName).sort(); michael@0: for (var i = 0; i < keys.length; ++i) { michael@0: buf += "\n" + indent + " " + keys[i] + michael@0: "=\"" + valuesByName[keys[i]] +"\""; michael@0: } michael@0: } michael@0: break; michael@0: case Node.DOCUMENT_TYPE_NODE: michael@0: buf += ""; michael@0: break; michael@0: case Node.COMMENT_NODE: michael@0: buf += ""; michael@0: break; michael@0: case Node.TEXT_NODE: michael@0: buf += "\"" + walker.currentNode.nodeValue + "\""; michael@0: break; michael@0: } michael@0: buf += "\n"; michael@0: // In the case of template elements, children do not get inserted as michael@0: // children of the template element, instead they are inserted michael@0: // as children of the template content (which is a document fragment). michael@0: if (walker.currentNode instanceof HTMLTemplateElement) { michael@0: buf += indent + " content\n"; michael@0: // Walk through the template content. michael@0: var templateWalker = createFragmentWalker(walker.currentNode.content); michael@0: buf = addLevels(templateWalker, buf, indent + " "); michael@0: } michael@0: buf = addLevels(walker, buf, indent + " "); michael@0: } while(walker.nextSibling()); michael@0: walker.parentNode(); michael@0: } michael@0: return buf; michael@0: } michael@0: