diff --git a/lite/index.html b/lite/index.html new file mode 100644 index 0000000..834708b --- /dev/null +++ b/lite/index.html @@ -0,0 +1,120 @@ + + + + Scour Lite + + + +

Scour Lite

+ + +
+ + +
+ + + + + diff --git a/lite/muther.js b/lite/muther.js new file mode 100644 index 0000000..672615e --- /dev/null +++ b/lite/muther.js @@ -0,0 +1,49 @@ +/** + * Mini Unit Test Harness + * Copyright(c) 2011, Google Inc. + * + * A really tiny unit test harness. + */ + +var muther = muther || {}; + +muther.assert = function(cond, err) { + if (!cond) { + throw err; + } +}; + +muther.addTest_ = function(testDiv, innerHTML, pass) { + var theTest = document.createElement('div'); + // Convert all angle brackets into displayable text. + innerHTML = innerHTML.replace(/&/g, '&'). + replace(//g, '>'); + theTest.innerHTML = innerHTML; + theTest.setAttribute('style', pass ? 'color:#090' : 'color:#900'); + testDiv.appendChild(theTest); +}; + +// Run through all tests and record the results. +muther.test = function(testsToRun) { + var progress = document.createElement('progress'); + var testDiv = document.createElement('div'); + document.body.insertBefore(testDiv, document.body.firstChild); + document.body.insertBefore(progress, document.body.firstChild); + + var max = testsToRun.length; + progress.max = max; + progress.value = 0; + testDiv.innerHTML = max + ' Tests'; + for (var t = 0; t < max; ++t) { + var test = testsToRun[t]; + try { + test(); + muther.addTest_(testDiv, test.name + ': Pass', true); + } catch(e) { + muther.addTest_(testDiv, test.name + ': Fail. ' + e, false); + } + progress.value += 1; + } +}; + diff --git a/lite/pdom.js b/lite/pdom.js new file mode 100644 index 0000000..c4b9d76 --- /dev/null +++ b/lite/pdom.js @@ -0,0 +1,856 @@ +/** + * Pico DOM + * Copyright(c) 2011, Google Inc. + * + * A really tiny implementation of the DOM for use in Web Workers. + */ + +// TODO: Look into defineProperty instead of getters. + +var pdom = pdom || {}; + +// =========================================================================== +// Stolen from Closure because it's the best way to do Java-like inheritance. +pdom.base = function(me, opt_methodName, var_args) { + var caller = arguments.callee.caller; + if (caller.superClass_) { + // This is a constructor. Call the superclass constructor. + return caller.superClass_.constructor.apply( + me, Array.prototype.slice.call(arguments, 1)); + } + + var args = Array.prototype.slice.call(arguments, 2); + var foundCaller = false; + for (var ctor = me.constructor; + ctor; ctor = ctor.superClass_ && ctor.superClass_.constructor) { + if (ctor.prototype[opt_methodName] === caller) { + foundCaller = true; + } else if (foundCaller) { + return ctor.prototype[opt_methodName].apply(me, args); + } + } + + // If we did not find the caller in the prototype chain, + // then one of two things happened: + // 1) The caller is an instance method. + // 2) This method was not called by the right caller. + if (me[opt_methodName] === caller) { + return me.constructor.prototype[opt_methodName].apply(me, args); + } else { + throw Error( + 'goog.base called from a method of one name ' + + 'to a method of a different name'); + } +}; +pdom.inherits = function(childCtor, parentCtor) { + /** @constructor */ + function tempCtor() {}; + tempCtor.prototype = parentCtor.prototype; + childCtor.superClass_ = parentCtor.prototype; + childCtor.prototype = new tempCtor(); + childCtor.prototype.constructor = childCtor; +}; +// =========================================================================== + + +/** + * A DOMException + * + * @param {number} code The DOM exception code. + * @constructor + */ +pdom.DOMException = function(code) { + this.__defineGetter__('code', function() { return code }); +}; +pdom.DOMException.INDEX_SIZE_ERR = 1; +pdom.DOMException.DOMSTRING_SIZE_ERR = 2; +pdom.DOMException.HIERARCHY_REQUEST_ERR = 3; +pdom.DOMException.WRONG_DOCUMENT_ERR = 4; +pdom.DOMException.INVALID_CHARACTER_ERR = 5; +pdom.DOMException.NO_DATA_ALLOWED_ERR = 6; +pdom.DOMException.NO_MODIFICATION_ALLOWED_ERR = 7; +pdom.DOMException.NOT_FOUND_ERR = 8; +pdom.DOMException.NOT_SUPPORTED_ERR = 9; +pdom.DOMException.INUSE_ATTRIBUTE_ERR = 10; +pdom.DOMException.INVALID_STATE_ERR = 11; +pdom.DOMException.SYNTAX_ERR = 12; +pdom.DOMException.INVALID_MODIFICATION_ERR = 13; +pdom.DOMException.NAMESPACE_ERR = 14; +pdom.DOMException.INVALID_ACCESS_ERR = 15; +pdom.DOMException.VALIDATION_ERR = 16; +pdom.DOMException.TYPE_MISMATCH_ERR = 17; + + +/** + * A NodeList. + * + * @param {Array.} nodeArray The array of nodes. + * @constructor + */ +pdom.NodeList = function(nodeArray) { + this.nodes_ = nodeArray; + + this.__defineGetter__('length', function() { return this.nodes_.length; }); +}; + + +/** + * @param {number} index The index of the node to return. + * @return {pdom.Node} The node. + */ +pdom.NodeList.prototype.item = function(index) { + if (index >= 0 && index < this.length) { + return this.nodes_[index]; + } + return null; +}; + + +/** + * @param {Object.} nodeMap An object containing the + * attribute name-Node pairs. + * @constructor + */ +pdom.NamedNodeMap = function(nodeMap) { + this.setNodeMapInternal(nodeMap); + this.__defineGetter__('length', function() { return this.attrs_.length }); +}; + + +/** + * An array of the nodes. + * @type {Array.} + * @private + */ +pdom.NamedNodeMap.prototype.attrs_ = []; + + +/** + * The node map. + * @type {Object.} + * @private + */ +pdom.NamedNodeMap.prototype.nodeMap_ = {}; + + +/** + * Sets the internal node map (and updates the array). + * @param {Object.} The node map. + */ +pdom.NamedNodeMap.prototype.setNodeMapInternal = function(nodeMap) { + this.nodeMap_ = {}; + this.attrs_ = []; + for (var name in nodeMap) { + var attr = new pdom.Attr(name, nodeMap[name]); + this.attrs_.push(attr); + this.nodeMap_[name] = attr; + } +}; + + +/** + * @param {string} name The name of the node to return. + * @return {pdom.Node} The named node. + */ +pdom.NamedNodeMap.prototype.getNamedItem = function(name) { + return this.nodeMap_[name] || null; +}; + + +/** + * @param {number} index The index of the node to return. + */ +pdom.NamedNodeMap.prototype.item = function(index) { + if (index >= 0 && index < this.attrs_.length) { + return this.attrs_[index]; + } + return null; +}; + + +/** + * A Node. + * + * @param {pdom.Node} opt_parentNode The parent node, which can be null. + * @constructor + */ +pdom.Node = function(opt_parentNode) { + this.parentNode_ = opt_parentNode; + + this.__defineGetter__('nodeType', function() { throw 'Unknown type of Node' }); + this.__defineGetter__('parentNode', function() { return this.parentNode_ }); + + /** + * An array of child nodes. + * @type {Array.} + * @private + */ + this.childNodes_ = []; + + // Read-only properties. + this.__defineGetter__('childNodes', function() { + return new pdom.NodeList(this.childNodes_); + }); + this.__defineGetter__('firstChild', function() { + return this.childNodes_[0] || null; + }); + this.__defineGetter__('lastChild', function() { + return this.childNodes_.length <= 0 ? null : + this.childNodes_[this.childNodes_.length - 1]; + }); + this.__defineGetter__('previousSibling', function() { + var parent = this.parentNode; + if (parent) { + var familySize = parent.childNodes_.length; + for (var i = 0; i < familySize; ++i) { + var child = parent.childNodes_[i]; + if (child === this && i > 0) { + return parent.childNodes_[i - 1]; + } + } + } + return null; + }); + this.__defineGetter__('nextSibling', function() { + var parent = this.parentNode; + if (parent) { + var familySize = parent.childNodes_.length; + for (var i = 0; i < familySize; ++i) { + var child = parent.childNodes_[i]; + if (child === this && i < familySize - 1) { + return parent.childNodes_[i + 1]; + } + } + } + return null; + }); + this.__defineGetter__('attributes', function() { return null }); + + this.__defineGetter__('namespaceURI', function() { + if (this.parentNode_) { + return this.parentNode_.namespaceURI; + } + return null; + }); +}; + + +pdom.Node.ELEMENT_NODE = 1; +pdom.Node.ATTRIBUTE_NODE = 2; +pdom.Node.TEXT_NODE = 3; +pdom.Node.CDATA_SECTION_NODE = 4; +pdom.Node.ENTITY_REFERENCE_NODE = 5; +pdom.Node.ENTITY_NODE = 6; +pdom.Node.PROCESSING_INSTRUCTION_NODE = 7; +pdom.Node.COMMENT_NODE = 8; +pdom.Node.DOCUMENT_NODE = 9; +pdom.Node.DOCUMENT_TYPE_NODE = 10; +pdom.Node.DOCUMENT_FRAGMENT_NODE = 11; +pdom.Node.NOTATION_NODE = 12; + + +/** + * @return {boolean} Whether the node has any children. + */ +pdom.Node.prototype.hasChildNodes = function() { + return this.childNodes_.length > 0; +}; + + +/** + * @param {pdom.Node} child The node to remove. + * @return {pdom.Node} The removed node. + */ +pdom.Node.prototype.removeChild = function(child) { + var max = this.childNodes.length; + for (var i = 0; i < max; ++i) { + if (this.childNodes_[i] == child) { + this.childNodes_.splice(i, 1); + child.parentNode_ = null; + return child; + } + } + throw new pdom.DOMException(pdom.DOMException.NOT_FOUND_ERR); +}; + + +/** + * @param {pdom.Node} child The node to append. + * @return {pdom.Node} The appended node. + */ +pdom.Node.prototype.appendChild = function(child) { + if (child.parentNode) { + child.parentNode.removeChild(child); + } + this.childNodes_.push(child); + return child; +}; + + +/** + * A XML Document. + * + * @param {string} opt_text The optional text of the document. + * @param {pdom.Node} opt_parentNode The parent node, which can be null. + * @constructor + * @extends {pdom.Node} + */ +pdom.XMLDocument = function(opt_text) { + pdom.base(this, null); + + this.__defineGetter__('nodeType', function() { + return pdom.Node.DOCUMENT_NODE; + }); + this.__defineGetter__('documentElement', function() { + for (var i = 0; i < this.childNodes_.length; ++i) { + if (this.childNodes_[i].nodeType == 1) { + return this.childNodes_[i]; + } + } + return null; + }); +}; +pdom.inherits(pdom.XMLDocument, pdom.Node); + + +/** + * A DocumentType node. + * + * @constructor + * @extends {pdom.Node} + */ +pdom.DocumentType = function() { + pdom.base(this, null); + + this.__defineGetter__('nodeType', function() { + return pdom.Node.DOCUMENT_TYPE_NODE + }); +}; +pdom.inherits(pdom.DocumentType, pdom.Node); + + +/** + * An Attr node. + * + * @param {string} name The name of the attribute. + * @param {string} value The value of the attribute. + * @constructor + * @extends {pdom.Attr} + */ +pdom.Attr = function(name, value) { + pdom.base(this, null); + + this.__defineGetter__('nodeType', function() { + return pdom.Node.ATTRIBUTE_NODE; + }); + this.__defineGetter__('name', function() { return name }); + + /** + * @type {string} + */ + this.value = value; +}; +pdom.inherits(pdom.Attr, pdom.Node); + + +/** + * An Element node. + * + * @param {string} tagName The tag name of this element. + * @param {pdom.Node} opt_parentNode The parent node, which can be null. + * @param {Object.} opt_attrs The attribute map. + * @constructor + * @extends {pdom.Node} + */ +pdom.Element = function(tagName, opt_parentNode, opt_attrs) { + pdom.base(this, opt_parentNode); + + /** + * Internal map of attributes for this element. + * + * @type {Object.} + * @private + */ + this.attributes_ = opt_attrs || {}; + + this.__defineGetter__('attributes', function() { + if (!this.attributeMap_) { + this.attributeMap_ = new pdom.NamedNodeMap(this.attributes_); + } + return this.attributeMap_; + }); + + this.__defineGetter__('nodeType', function() { + return pdom.Node.ELEMENT_NODE; + }); + this.__defineGetter__('tagName', function() { return tagName }); + this.__defineGetter__('nodeName', function() { return tagName }); + + /** + * @type {string} + * @private + */ + this.namespaceURI_ = this.parentNode_ ? this.parentNode_.namespaceURI : null; + + /** + * Map of namespace prefix to URI. + * + * @type {Object.} + */ + this.nsPrefixMapInternal = {}; + + // Generate map of prefixes to namespace URIs. Also, discover if there is + // a default namespace on this element. + for (var attrName in this.attributes_) { + if (attrName.indexOf('xmlns:') == 0 && attrName.length > 6) { + var prefix = attrName.substring(6); + this.nsPrefixMapInternal[prefix] = this.attributes_[attrName]; + } else if (attrName === 'xmlns') { + this.namespaceURI_ = this.attributes_[attrName]; + } + } + + // If the tagname includes a colon, resolve the namespace prefix. + var colonIndex = tagName.indexOf(':'); + if (colonIndex != -1) { + var prefix = tagName.substring(0, colonIndex); + var node = this; + while (node) { + var uri = node.nsPrefixMapInternal[prefix]; + if (uri) { + this.namespaceURI_ = uri; + break; + } + node = node.parentNode; + } + } + + this.__defineGetter__('namespaceURI', function() { return this.namespaceURI_ }); +}; +pdom.inherits(pdom.Element, pdom.Node); + + +/** + * @type {pdom.NamedNodeMap} + * @private + */ +pdom.Element.prototype.attributeMap_ = null; + + +/** + * @param {string} attrName The attribute name to get. + */ +pdom.Element.prototype.getAttribute = function(attrName) { + var attrVal = this.attributes_[attrName] || ''; + return attrVal; +}; + + +/** + * @param {string} name The attribute name to set. + * @param {string} value The attribute value to set. + */ +pdom.Element.prototype.setAttribute = function(name, value) { + this.attributes_[name] = value; + if (this.attributeMap_) { + this.attributeMap_.setNodeMapInternal(this.attributes_); + } +}; + + +/** + * @param {string} name The attribute to remove. + */ +pdom.Element.prototype.removeAttribute = function(name) { + delete this.attributes_[name]; + if (this.attributeMap_) { + this.attributeMap_.setNodeMapInternal(this.attributes_); + } +}; + + +/** + * @return {boolean} Whether the element had an attribute. + */ +pdom.Element.prototype.hasAttribute = function(name) { + return !!this.attributes_[name]; +}; + + +/** + * CharacterData node. + * + * @param {string} opt_text The optional text of the document. + * @param {pdom.Node} opt_parentNode The parent node, which can be null. + * @constructor + * @extends {pdom.Node} + */ +pdom.CharacterData = function(opt_text, opt_parentNode) { + pdom.base(this, opt_parentNode); + + this.__defineGetter__('data', function() { return opt_text }); +}; +pdom.inherits(pdom.CharacterData, pdom.Node); + + +/** + * A Comment node. + * + * @param {string} opt_text The optional text of the comment. + * @param {pdom.Node} opt_parentNode The parent node, which can be null. + * @constructor + * @extends {pdom.CharacterData} + */ +pdom.Comment = function(opt_text, opt_parentNode) { + pdom.base(this, opt_text); + + this.__defineGetter__('nodeType', function() { + return pdom.Node.COMMENT_NODE; + }); +}; +pdom.inherits(pdom.Comment, pdom.CharacterData); + + +/** + * A Text node. + * + * @param {string} opt_text The optional text of the comment. + * @param {pdom.Node} opt_parentNode The parent node, which can be null. + * @constructor + * @extends {pdom.CharacterData} + */ +pdom.Text = function(opt_text, opt_parentNode) { + pdom.base(this, opt_text, opt_parentNode); + + this.__defineGetter__('nodeType', function() { + return pdom.Node.TEXT_NODE; + }); +}; +pdom.inherits(pdom.Text, pdom.CharacterData); + + + +pdom.parse = {}; + +/** + * Swallows all whitespace on the left. + * + * @private + * @return {boolean} True if some whitespace characters were swallowed. + */ +pdom.parse.swallowWS_ = function(parsingContext) { + var wsMatches = parsingContext.xmlText.match(/^\s+/); + if (wsMatches && wsMatches.length > 0) { + parsingContext.offset += wsMatches[0].length; + return true; + } + return false; +}; + + +/** + * @private + * @returns {boolean} True if some cruft was swallowed. + */ +pdom.parse.swallowXmlCruft_ = function(parsingContext, head, tail) { + pdom.parse.swallowWS_(parsingContext); + var text = parsingContext.xmlText; + var start = parsingContext.offset; + // If we find the start, strip it all off. + if (text.indexOf(head, start) == 0) { + var end = text.indexOf(tail, start + head.length); + if (end == -1) { + throw 'Could not find the end of the thing (' + tail + ')'; + } + parsingContext.offset = end + tail.length; + return true; + } + return false; +} + + +/** + * Parses the XML prolog, if present. + * + * @private + * @return {boolean} True if an XML prolog was found. + */ +pdom.parse.parseProlog_ = function(parsingContext) { + return pdom.parse.swallowXmlCruft_(parsingContext, ''); +}; + + +/** + * Parses the DOCTYPE, if present. + * + * @return {boolean} True if a DOCTYPE was found. + */ +pdom.parse.parseDocType_ = function(parsingContext) { + swallowWS(parsingContext); + var text = parsingContext.xmlText; + var start = parsingContext.offset; + var head = '', start + head.length); + if (endDocType == -1) { + throw 'Could not find the end of the DOCTYPE (>)'; + } + parsingContext.offset = endDocType + 2; + return true; + } + return false; +}; + + +/** + * Parses one node from the XML stream. + * + * @private + * @param {Object} parsingContext The parsing context. + * @return {pdom.Node} Returns the Node or null if none are found. + */ +pdom.parse.parseOneNode_ = function(parsingContext) { + var i = parsingContext.offset; + var xmlText = parsingContext.xmlText; + + // Detect if it's a comment () + var COMMENT_START = ''; + if (xmlText.indexOf(COMMENT_START, i) == i) { + var endComment = xmlText.indexOf(COMMENT_END, i + COMMENT_START.length + 1); + if (endComment == -1) { + throw "End tag for comment not found"; + } + var newComment = new pdom.Comment( + xmlText.substring(i + COMMENT_START.length, endComment), + parsingContext.currentNode); + parsingContext.currentNode.childNodes_.push(newComment); + parsingContext.offset = endComment + COMMENT_END.length; + return newComment; + } + + // Determine if it's a DOCTYPE () + var DOCTYPE_START = ''; + if (xmlText.indexOf(DOCTYPE_START, i) == i) { + // Deal with [] in the DOCTYPE. + var startBracket = xmlText.indexOf('[', i + DOCTYPE_START.length + 1); + if (startBracket != -1) { + var endBracket = xmlText.indexOf(']', startBracket + 1); + if (endBracket == -1) { + throw 'Could not find end ] in DOCTYPE'; + } + i = endBracket + 1; + } + + // TODO: Is this right? Shouldn't it be after the [] if they were present? + var endDocType = xmlText.indexOf('>', i + DOCTYPE_START.length + 1); + if (endDocType == -1) { + throw 'Could not find the end of the DOCTYPE (>)'; + } + var newDocType = new pdom.DocType(); + parsingContext.currentNode.childNodes_.push(newDocType); + parsingContext.offset = endDocType + 1; + return newDocType; + } + + // If we are inside an element, see if we have the end tag. + if (parsingContext.currentNode.nodeType == 1 && + xmlText.indexOf('', i + 2); + if (endEndTagIndex == -1) { + throw 'Could not find end of end tag'; + } + + // Check if the tagname matches the end tag. If not, that's an error. + var tagName = xmlText.substring(i + 2, endEndTagIndex); + if (tagName != parsingContext.currentNode.tagName) { + throw 'Found instead of '; + } + + // Otherwise, parsing of the current element is done. Return it and + // update the parsing context. + var elementToReturn = parsingContext.currentNode; + parsingContext.offset = endEndTagIndex + 1; + parsingContext.currentNode = elementToReturn.parentNode; + return elementToReturn; + } + + // TODO: Detect if the element has a proper name. + if (xmlText[i] == '<') { + var isSelfClosing = false; + var selfClosingElementIndex = xmlText.indexOf('/>', i + 1); + var endStartTagIndex = xmlText.indexOf('>', i + 1) + if (selfClosingElementIndex == -1 && endStartTagIndex == -1) { + throw 'Could not find end of start tag in Element'; + } + + // Self-closing element. + if (selfClosingElementIndex != -1 && + selfClosingElementIndex < endStartTagIndex) { + endStartTagIndex = selfClosingElementIndex; + isSelfClosing = true; + } + + var attrs = {}; + + // TODO: This should be whitespace, not space. + var tagNameIndex = xmlText.indexOf(' ', i + 1); + if (tagNameIndex == -1 || tagNameIndex > endStartTagIndex) { + tagNameIndex = endStartTagIndex; + } else { + // Find all attributes and record them. + var attrGlobs = xmlText.substring(tagNameIndex + 1, endStartTagIndex).trim(); + var j = 0; + while (j < attrGlobs.length) { + var equalsIndex = attrGlobs.indexOf('=', j); + if (equalsIndex == -1) { + break; + } + + // Found an attribute name-value pair. + var attrName = attrGlobs.substring(j, equalsIndex).trim(); + + j = equalsIndex + 1; + var theRest = attrGlobs.substring(j); + var singleQuoteIndex = theRest.indexOf('\'', 0); + var doubleQuoteIndex = theRest.indexOf('"', 0); + if (singleQuoteIndex == -1 && doubleQuoteIndex == -1) { + throw 'Attribute "' + attrName + '" found with no quoted value'; + } + + var quoteChar = '"'; + var quoteIndex = doubleQuoteIndex; + if (singleQuoteIndex != -1 && + ((doubleQuoteIndex != -1 && singleQuoteIndex < doubleQuoteIndex) || + doubleQuoteIndex == -1)) { + // Singly-quoted. + quoteChar = '\''; + quoteIndex = singleQuoteIndex; + } + + var endQuoteIndex = theRest.indexOf(quoteChar, quoteIndex + 1); + if (endQuoteIndex == -1) { + throw 'Did not find end quote for value of attribute "' + attrName + '"'; + } + + var attrVal = theRest.substring(quoteIndex + 1, endQuoteIndex); + attrs[attrName] = attrVal; + + j += endQuoteIndex + 1; + } + } + + var newElementNode = new pdom.Element( + xmlText.substring(i + 1, tagNameIndex), + parsingContext.currentNode, + attrs); + + parsingContext.offset = endStartTagIndex + 1; + parsingContext.currentNode.childNodes_.push(newElementNode); + + if (isSelfClosing) { + // Nudge it past the closing bracket. + parsingContext.offset += 1; + return newElementNode; + } + + // Else, recurse into this element. + parsingContext.currentNode = newElementNode; + return pdom.parse.parseOneNode_(parsingContext); + } + + // Everything else is a text node. + if (i != xmlText.length) { + var endTextIndex = xmlText.indexOf('<', i + 1); + if (endTextIndex == -1) { + endTextIndex = xmlText.length; + } + var theText = xmlText.substring(i, endTextIndex); + var newTextNode = new pdom.Text(theText, parsingContext.currentNode); + parsingContext.currentNode.childNodes_.push(newTextNode); + parsingContext.offset = endTextIndex; + return newTextNode; + } + + return null; +}; + + +/** + * A DOM Parser. + */ +pdom.DOMParser = function(xmlText) { +}; + + +/** + * + * @param {string} xmlText The XML Text. + * @return {pdom.XMLDocument} + */ +pdom.DOMParser.prototype.parseFromString = function(xmlText) { + var theDoc = new pdom.XMLDocument(xmlText); + var parsingContext = {xmlText: xmlText, offset: 0, currentNode: theDoc}; + pdom.parse.parseProlog_(parsingContext); + while (!!(node = pdom.parse.parseOneNode_(parsingContext))) { + // do nothing. + }; + return theDoc; +}; + + +/** + * A XML Serializer. + */ +pdom.XMLSerializer = function() { +}; + + +/** + * @param {pdom.Node} node A node. + * @return {string} The node serialized to text. + */ +pdom.XMLSerializer.prototype.serializeToString = function(node) { + if (!(node instanceof pdom.Node)) { + throw 'Argument XMLSerializer.serializeToString() was not a pdom.Node'; + } + var str = ''; + switch (node.nodeType) { + case pdom.Node.DOCUMENT_NODE: + return this.serializeToString(node.documentElement); + case pdom.Node.ELEMENT_NODE: + str = '<' + node.tagName; + if (node.attributes && node.attributes.length > 0) { + for (var i = 0; i < node.attributes.length; ++i) { + var attr = node.attributes.item(i); + str += ' ' + attr.name + '="' + attr.value + '"'; + } + } + if (node.childNodes.length > 0) { + str += '>'; + for (var i = 0; i < node.childNodes.length; ++i) { + var child = node.childNodes.item(i); + str += this.serializeToString(child); + } + str += ''; + } else { + str += '/>' + } + return str; + case pdom.Node.TEXT_NODE: + return node.data; + } +}; diff --git a/lite/pdom_support.html b/lite/pdom_support.html new file mode 100644 index 0000000..9a8e9b6 --- /dev/null +++ b/lite/pdom_support.html @@ -0,0 +1,203 @@ + + + + DOM Core Support in pdom + + + +

DOM Core Support in pdom

+

This table shows the current level of DOM Core support in pdom. At present, this table only shows DOM Core Level 1 and a couple properties/methods from DOM Level 2.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
InterfaceProperty/MethodSupport?
NodeDOMString nodeName
DOMString nodeValue
unsigned short nodeType
Node parentNode
NodeList childNodes
Node firstChild
Node lastChild
Node previousSibling
Node nextSibling
NamedNodeMap attributes
Document ownerDocument
Node insertBefore(in Node newChild, in Node refChild)
Node replaceChild(in Node newChild, in Node oldChild)
Node removeChild(in Node oldChild)
Node appendChild(in Node newChild)
boolean hasChildNodes()
Node cloneNode(in boolean deep)
DOMString namespaceURI
Element : NodeDOMString tagName
DOMString getAttribute(in DOMString name)
void setAttribute(in DOMString name, in DOMString value)
void removeAttribute(in DOMString name)
Attr getAttributeNode(in DOMString name)
Attr setAttributeNode(in Attr newAttr)
Attr removeAttributeNode(in Attr oldAttr)
NodeList getElementsByTagName(in DOMString name)
boolean hasAttribute(in DOMString name)
Document : NodeDocumentType doctype
DOMImplementation implementation
Element documentElement
Element createElement(in DOMString tagName)
DocumentFragment createDocumentFragment()
createTextNode(in DOMString data)
createComment(in DOMString data)
createCDATASection(in DOMString data)
createProcessingInstruction(in DOMString target, in DOMString data)
Attr createAttribute(in DOMString name)
EntityReference createEntityByReference(in DOMString name)
NodeList getElementsByTagName(in DOMString tagName)
Element getElementById(in DOMString elementId)
NodeListNode item(in unsigned long index)
unsigned long length
NamedNodeMapNode getNamedItem(in DOMString name)
Node setNamedItem(in Node arg)
Node removeNamedItem(in DOMString name)
Node item(in unsigned long index)
unsigned long length
CharacterData : NodeDOMString data
unsigned long length
DOMString substringData(in unsigned long offset, in unsigned long count)
void appendData(in DOMString arg)
void insertData(in unsigned long offset, in DOMString arg)
void deleteData(in unsigned long offset, in unsigned long count)
void replaceData(in unsigned long offset, in unsigned long count, in DOMString arg)
Text : CharacterDataText splitText(in unsigned long offset)
Attr : NodeDOMString name
boolean specified
DOMString value
Element ownerElement
Comment : CharacterData(empty)
CDATASection : Text(empty)
DOMExceptionunsigned short code
DOMImplementationboolean hasFeature(in DOMString feature, in DOMString version)
DocumentFragment(empty)
DocumentType : NodeDOMString name
NamedNodeMap entities
NamedNodeMap notations
Notation : NodeDOMString publicId
DOMString systemId
Entity : NodeDOMString publicId
DOMString systemId
DOMString notationName
EntityReference : Node(empty)
ProcessingInstruction : NodeDOMString target
DOMString data
+ + \ No newline at end of file diff --git a/lite/pdom_test.html b/lite/pdom_test.html new file mode 100644 index 0000000..45ca74b --- /dev/null +++ b/lite/pdom_test.html @@ -0,0 +1,256 @@ + + + + pdom unit tests + + + + + + + diff --git a/lite/scour.js b/lite/scour.js new file mode 100644 index 0000000..48c13d1 --- /dev/null +++ b/lite/scour.js @@ -0,0 +1,84 @@ +/** + * Scour Lite (the JS version) + * + * Copyright(c) 2011 Google Inc. + */ + +importScripts('pdom.js'); + +onmessage = function(evt) { + // Now evt.data contains the text of the SVG file. + postMessage({ + progress: {loaded: 100, total: 100}, + scouredSvg: scourString(evt.data) + }); +}; + +var NS = { + 'SVG': 'http://www.w3.org/2000/svg', + 'XLINK': 'http://www.w3.org/1999/xlink', + 'SODIPODI': 'http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd', + 'INKSCAPE': 'http://www.inkscape.org/namespaces/inkscape', + 'ADOBE_ILLUSTRATOR': 'http://ns.adobe.com/AdobeIllustrator/10.0/', + 'ADOBE_GRAPHS': 'http://ns.adobe.com/Graphs/1.0/', + 'ADOBE_SVG_VIEWER': 'http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/', + 'ADOBE_VARIABLES': 'http://ns.adobe.com/Variables/1.0/', + 'ADOBE_SFW': 'http://ns.adobe.com/SaveForWeb/1.0/', + 'ADOBE_EXTENSIBILITY': 'http://ns.adobe.com/Extensibility/1.0/', + 'ADOBE_FLOWS': 'http://ns.adobe.com/Flows/1.0/', + 'ADOBE_IMAGE_REPLACEMENT': 'http://ns.adobe.com/ImageReplacement/1.0/', + 'ADOBE_CUSTOM': 'http://ns.adobe.com/GenericCustomNamespace/1.0/', + 'ADOBE_XPATH': 'http://ns.adobe.com/XPath/1.0/' +}; + +var unwanted_ns = [ NS['SODIPODI'], NS['INKSCAPE'], NS['ADOBE_ILLUSTRATOR'], + NS['ADOBE_GRAPHS'], NS['ADOBE_SVG_VIEWER'], NS['ADOBE_VARIABLES'], + NS['ADOBE_SFW'], NS['ADOBE_EXTENSIBILITY'], NS['ADOBE_FLOWS'], + NS['ADOBE_IMAGE_REPLACEMENT'], NS['ADOBE_CUSTOM'], NS['ADOBE_XPATH'] ]; + +/** + * @param {pdom.Node|Node} node The parent node. + * @param {Array.} namespaces An array of namespace URIs. + */ +var removeNamespacedElements = function(node, namespaces) { + if (node.nodeType == 1) { + // Remove all namespace'd child nodes from this element. + var childrenToRemove = []; + for (var i = 0; i < node.childNodes.length; ++i) { + var child = node.childNodes.item(i); + if (namespaces.indexOf(child.namespaceURI) != -1) { + childrenToRemove.push(child); + } + } + + for (var i = 0; i < childrenToRemove.length; ++i) { + node.removeChild(childrenToRemove[i]); + } + + // Now recurse for children. + for (var i = 0; i < node.childNodes.length; ++i) { + removeNamespacedElements(node.childNodes.item(i), namespaces); + } + } +}; + + +/** + * @param {string} in_string The SVG document as a string. + * @param {object} opt_options An optional set of options. + */ +var scourString = function(in_string, opt_options) { + postMessage({progress: {loaded: 0, total: 100}}); + + var parser = new pdom.DOMParser(); + var options = opt_options || {}; + var doc = parser.parseFromString(in_string, 'text/xml'); + + // Remove editor stuff. + if (!options.keep_editor_data) { + removeNamespacedElements(doc.documentElement, unwanted_ns); + postMessage({message: 'Removed namespaced elements'}); + } + + return new pdom.XMLSerializer().serializeToString(doc); +};