Skip to content

Commit

Permalink
Doctype serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
tmpvar committed Feb 15, 2011
2 parents 584904b + 2f0a8d2 commit 666442e
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 56 deletions.
74 changes: 52 additions & 22 deletions lib/jsdom/browser/domtohtml.js
Expand Up @@ -139,43 +139,73 @@ exports.formatHTML = function formatHTML(html) {

var rawTextElements = /SCRIPT|STYLE/i;

exports.generateHtmlRecursive = function generateHtmlRecursive(element, rawText) {
function stringifyDoctype (doctype) {
if (doctype.ownerDocument && doctype.ownerDocument._fullDT) {
return doctype.ownerDocument._fullDT;
}

var dt = '<!DOCTYPE ' + doctype.name;
if (doctype.publicId) {
// Public ID may never contain double quotes, so this is always safe.
dt += ' PUBLIC "' + doctype.publicId + '" ';
}
if (!doctype.publicId && doctype.systemId) {
dt += ' SYSTEM '
}
if (doctype.systemId) {
// System ID may contain double quotes OR single quotes, not never both.
if (doctype.systemId.indexOf('"') > -1) {
dt += "'" + doctype.systemId + "'";
} else {
dt += '"' + doctype.systemId + '"';
}
}
dt += '>';
return dt;
}

exports.generateHtmlRecursive = function generateHtmlRecursive(node, rawText) {
var ret = "", parent, current, i;
if (element) {
if (element.nodeType &&
element.nodeType === element.ENTITY_REFERENCE_NODE)
if (node) {
if (node.nodeType &&
node.nodeType === node.ENTITY_REFERENCE_NODE)
{
element = element._entity;
node = node._entity;
}

if (!rawText && element._parentNode) {
rawText = rawTextElements.test(element._parentNode.nodeName);
if (!rawText && node._parentNode) {
rawText = rawTextElements.test(node._parentNode.nodeName);
}

switch (element.nodeType) {
case element.ELEMENT_NODE:
current = exports.stringifyElement(element);
switch (node.nodeType) {
case node.ELEMENT_NODE:
current = exports.stringifyElement(node);
ret += current.start;

if (element._childNodes.length > 0) {
for (i=0; i<element._childNodes.length; i++) {
ret += exports.generateHtmlRecursive(element._childNodes[i], rawText);
if (node._childNodes.length > 0) {
for (i=0; i<node._childNodes.length; i++) {
ret += exports.generateHtmlRecursive(node._childNodes[i], rawText);
}
} else {
//ret += rawText ? element.nodeValue : HTMLEncode(element.nodeValue);
ret += element.nodeValue || '';
//ret += rawText ? node.nodeValue : HTMLEncode(node.nodeValue);
ret += node.nodeValue || '';
}
ret += current.end;
break;
case element.TEXT_NODE:
//ret += rawText ? element.nodeValue : HTMLEncode(element.nodeValue);
ret += element.nodeValue;
case node.TEXT_NODE:
//ret += rawText ? node.nodeValue : HTMLEncode(node.nodeValue);
ret += node.nodeValue;
break;
case element.COMMENT_NODE:
ret += '<!--' + element.nodeValue + '-->';
case node.COMMENT_NODE:
ret += '<!--' + node.nodeValue + '-->';
break;
case node.DOCUMENT_NODE:
for (i=0; i<node._childNodes.length; i++) {
ret += exports.generateHtmlRecursive(node._childNodes[i], rawText);
}
break;
case element.DOCUMENT_NODE:
ret += exports.generateHtmlRecursive(element.documentElement, rawText);
case node.DOCUMENT_TYPE_NODE:
ret += stringifyDoctype(node);
break;
}
}
Expand Down
6 changes: 1 addition & 5 deletions lib/jsdom/browser/index.js
Expand Up @@ -258,11 +258,7 @@ var browserAugmentation = exports.browserAugmentation = function(dom, options) {
});

dom.Document.prototype.__defineGetter__('outerHTML', function() {
var html = domToHtml(this.documentElement);
if (this.doctype) {
html = this.doctype.toString() + '\n' + html;
}
return html;
return domToHtml(this);
});

dom.Element.prototype.__defineGetter__('outerHTML', function() {
Expand Down
40 changes: 12 additions & 28 deletions lib/jsdom/level2/core.js
Expand Up @@ -63,18 +63,27 @@ core.DOMImplementation.prototype.createDocument = function(/* String */ na
/* String */ qualifiedName,
/* DocumentType */ doctype)
{
ns.validate(qualifiedName, namespaceURI);
if (qualifiedName || namespaceURI) {
ns.validate(qualifiedName, namespaceURI);
}

if (doctype && doctype._ownerDocument !== null) {
throw new core.DOMException(core.WRONG_DOCUMENT_ERR);
}

if (qualifiedName.indexOf(':') > -1 && !namespaceURI) {
if (qualifiedName && qualifiedName.indexOf(':') > -1 && !namespaceURI) {
throw new core.DOMException(NAMESPACE_ERR);
}

var document = new core.Document();
document.doctype = doctype || null;

if (doctype) {
document.doctype = doctype;
doctype._ownerDocument = document;
document.appendChild(doctype);
} else {
document.doctype = null;
}

if (doctype && !doctype.entities) {
doctype.entities = new dom.EntityNodeMap();
Expand Down Expand Up @@ -490,31 +499,6 @@ core.DocumentType.prototype.__defineGetter__("internalSubset", function() {
return this._internalSubset || null;
});

core.DocumentType.prototype.toString = function() {
if (this.ownerDocument._fullDT) {
return this.ownerDocument._fullDT;
}

var dt = '<!DOCTYPE ' + this.name;
if (this.publicId) {
// Public ID may never contain double quotes, so this is always safe.
dt += ' PUBLIC "' + this.publicId + '" ';
}
if (!this.publicId && this.systemId) {
dt += ' SYSTEM '
}
if (this.systemId) {
// System ID may contain double quotes OR single quotes, not never both.
if (this.systemId.indexOf('"') > -1) {
dt += "'" + this.systemId + "'";
} else {
dt += '"' + this.systemId + '"';
}
}
dt += '>';
return dt;
};

core.Document.prototype.importNode = function(/* Node */ importedNode,
/* bool */ deep)
{
Expand Down
33 changes: 32 additions & 1 deletion test/browser/index.js
Expand Up @@ -181,5 +181,36 @@ exports.tests = {
doc.write('<html><body><p></p><p></p></body></html>');
doc.body.innerHTML = '';
assertTrue('still has children', doc.body.childNodes.length == 0);
}
},
serialize_html5_doctype : function () {
var dom = new browser.DOMImplementation();
var doctype = dom.createDocumentType('html');
var document = dom.createDocument(null, null, doctype);
assertTrue('HTML 5 doctype did not serialize correctly',
/^\s*<!DOCTYPE html>/.test(document.outerHTML));
},
serialize_html4_strict_doctype : function () {
var dom = new browser.DOMImplementation();
var doctype = dom.createDocumentType('html',
'-//W3C//DTD HTML 4.01//EN',
'http://www.w3.org/TR/html4/strict.dtd');
var document = dom.createDocument(null, null, doctype);
assertTrue('HTML 4 strict doctype did not serialize correctly',
/^\s*<!DOCTYPE html PUBLIC "-\/\/W3C\/\/DTD HTML 4.01\/\/EN" "http:\/\/www.w3.org\/TR\/html4\/strict.dtd">/.
test(document.outerHTML));
},
serialize_system_doctype : function () {
var dom = new browser.DOMImplementation();
var doctype = dom.createDocumentType('foo', null, 'foo.dtd');
var document = dom.createDocument(null, null, doctype);
assertTrue('Doctype did not serialize correctly',
/^\s*<!DOCTYPE foo SYSTEM "foo.dtd">/.test(document.outerHTML));
},
serialize_doctype_containing_quotes : function () {
var dom = new browser.DOMImplementation();
var doctype = dom.createDocumentType('foo', null, 'foo "bar".dtd');
var document = dom.createDocument(null, null, doctype);
assertTrue('Doctype did not serialize correctly',
/^\s*<!DOCTYPE foo SYSTEM \'foo "bar".dtd\'>/.test(document.outerHTML));
},
};
32 changes: 32 additions & 0 deletions test/level2/core-extra.js
@@ -0,0 +1,32 @@
/* Contains extra test cases for parts of DOM Level 2 that aren't covered by
* the standard W3C test cases. */
exports.tests = {
create_empty_document: function () {
var dom = new DOMImplementation();
var document = dom.createDocument(null, null, null);
assertEquals("Document shouldn't contain any nodes",
0, document.childNodes.length);
},
create_document_with_namespaceURI_but_not_qualifiedName: function () {
var dom = new DOMImplementation();
assertThrows('createDocument accepted invalid arguments',
function () {
dom.createDocument("http://example.org/motorcycle", null, null);
},
DOMException, DOMException.NAMESPACE_ERR);
},
doctype_ownerDocument: function () {
var dom = new DOMImplementation();
var doctype = dom.createDocumentType("bananas");
var document = dom.createDocument(null, null, doctype);
assertEquals('Doctype does not belong to the document',
document, doctype.ownerDocument);
},
doctype_child_of_ownerDocument: function () {
var dom = new DOMImplementation();
var doctype = dom.createDocumentType("hatstand");
var document = dom.createDocument(null, null, doctype);
assertEquals('Doctype is not a child of the document',
doctype, document.firstChild);
}
}
7 changes: 7 additions & 0 deletions test/runner.js
Expand Up @@ -95,6 +95,13 @@ var suites = {
global.builder.testDirectory = "level2/core";
}
},
"level2/core-extra" : { cases: require("./level2/core-extra").tests, setUp : function () {
mixin(global, require("../lib/jsdom/level2/core").dom.level2.core);
global.builder.contentType = "text/xml";
global.builder.type = "xml";
global.builder.testDirectory = "level2/core-extra";
}
},
"level2/events" : { cases: require("./level2/events").tests, setUp : function() {
mixin(global, require("../lib/jsdom/level2/events").dom.level2.events);
global.events = require("../lib/jsdom/level2/events").dom.level2.events;
Expand Down

0 comments on commit 666442e

Please sign in to comment.