Add a starter JavaScript version of Scour

This commit is contained in:
Jeff Schiller 2011-12-29 16:11:55 -08:00
parent 60b48353b3
commit fbcbedef37
6 changed files with 1568 additions and 0 deletions

120
lite/index.html Normal file
View file

@ -0,0 +1,120 @@
<!DOCTYPE html>
<html>
<head>
<title>Scour Lite</title>
<style type='text/css'>
.hidden { display: none; }
#status {
border: solid 1px lightgrey;
color: grey;
margin: 10px;
padding: 5px;
}
#status p {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<h1>Scour Lite</h1>
<input id='fileInput' type='file'/>
<p id='progressPara' class='hidden'>
<span id='stage'>Progress</span>: <progress id='progress' max='100'></progress>
</p>
<div>
<div id='originalSvg' class='hidden'>
Original:
<a id='originalLinkText' href='#'>[text]</a>
<a id='originalLinkSvg' href='#'>[svg]</a>
</div>
<div id='scouredSvg' class='hidden'>
Scoured:
<a id='scouredLinkText' href='#'>[text]</a>
<a id='scouredLinkSvg' href='#'>[svg]</a>
</div>
</div>
<div id='status' class='hidden'></div>
</body>
<script type='text/javascript' src='pdom.js'></script>
<script type='text/javascript'>
var input = document.getElementById('fileInput');
var stageSpan = document.getElementById('stage');
var progress = document.getElementById('progress');
var originalSvg;
var scouredSvg;
document.getElementById('originalLinkText').onclick = function(evt) {
window.open('data:text/plain;base64,' + window.btoa(originalSvg));
};
document.getElementById('originalLinkSvg').onclick = function(evt) {
window.open('data:image/svg+xml;base64,' + window.btoa(originalSvg));
};
document.getElementById('scouredLinkText').onclick = function(evt) {
window.open('data:text/plain;base64,' + window.btoa(scouredSvg));
};
document.getElementById('scouredLinkSvg').onclick = function(evt) {
window.open('data:image/svg+xml;base64,' + window.btoa(scouredSvg));
};
var handleMessage = function(evt) {
if (typeof evt.data == 'string') {
var p = document.createElement('p');
p.innerHTML = evt.data;
var status = document.getElementById('status');
status.insertBefore(p, status.firstChild);
} else {
if (evt.data.stage) {
stageSpan.innerHTML = evt.data.stage;
}
if (evt.data.message) {
handleMessage({data: evt.data.message});
}
if (evt.data.progress) {
handleProgress(evt.data.progress);
}
if (evt.data.scouredSvg) {
document.getElementById('scouredSvg').className = '';
scouredSvg = evt.data.scouredSvg;
handleMessage({
data: 'Scoured SVG came out to be ' +
evt.data.scouredSvg.length + ' bytes'
});
}
}
};
var handleProgress = function(evt) {
progress.max = evt.total;
progress.value = evt.loaded;
};
var getFile = function(evt) {
stageSpan.innerHTML = 'Loading';
var showElems = ['progressPara', 'status', 'originalSvg'];
for (var i in showElems) {
document.getElementById(showElems[i]).className = '';
}
if (input.files.length == 1) {
var theFile = input.files[0];
// TODO: One day, all browsers will support passing of File blobs
// to the worker and the creation of FileReaders inside workers.
// When that day comes, shove this code into scour.js.
var fr = new FileReader();
// TODO: Use addEventListener when WebKit supports it
// https://bugs.webkit.org/show_bug.cgi?id=42723
fr.onload = function(evt) {
handleMessage({data:'Loaded \'' + theFile.name + '\'' + ' (' + theFile.size + ' bytes)'});
var worker = new Worker('scour.js');
worker.addEventListener('message', handleMessage);
originalSvg = evt.target.result;
var parser = new pdom.DOMParser();
var pdoc = parser.parseFromString(originalSvg);
// TODO: Restore this once pdom is working satisfactorily.
worker.postMessage(originalSvg);
};
fr.onprogress = handleProgress;
fr.readAsText(theFile);
}
};
input.addEventListener('change', getFile, false);
</script>
</html>

49
lite/muther.js Normal file
View file

@ -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, '&amp;').
replace(/</g, '&lt;').
replace(/>/g, '&gt;');
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;
}
};

856
lite/pdom.js Normal file
View file

@ -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.<Node>} 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.<string, pdom.Node>} 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.<pdom.Node>}
* @private
*/
pdom.NamedNodeMap.prototype.attrs_ = [];
/**
* The node map.
* @type {Object.<string, pdom.Node>}
* @private
*/
pdom.NamedNodeMap.prototype.nodeMap_ = {};
/**
* Sets the internal node map (and updates the array).
* @param {Object.<string, pdom.Node>} 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.<pdom.Node>}
* @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.<string,string>} 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.<string, string>}
* @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.<string, string>}
*/
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, '<?xml ', '?>');
};
/**
* 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 = '<!DOCTYPE ';
if (text.indexOf(head, start) == 0) {
// Deal with [] in the DOCTYPE.
var startBracket = text.indexOf('[', start + head.length);
if (startBracket != -1) {
var endBracket = text.indexOf(']', startBracket + 1);
if (endBracket == -1) {
throw 'Could not find end ] in DOCTYPE';
}
start = endBracket + 1;
}
var endDocType = text.indexOf('>', 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 = '<!--';
var COMMENT_END = '-->';
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 (<!DOCTYPE ...[]>)
var DOCTYPE_START = '<!DOCTYPE ';
var DOCTYPE_END = '>';
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) == i) {
// Look for end of end tag.
var endEndTagIndex = 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 </' + tagName + '> instead of </' +
parsingContext.currentNode.tagName + '>';
}
// 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 += '</' + node.tagName + '>';
} else {
str += '/>'
}
return str;
case pdom.Node.TEXT_NODE:
return node.data;
}
};

203
lite/pdom_support.html Normal file
View file

@ -0,0 +1,203 @@
<!DOCTYPE html>
<html>
<head>
<title>DOM Core Support in pdom</title>
<style type="text/css">
table * { font-size: small; }
table a { color: white; font-weight: bold; }
.no { background-color: red; color: white; }
.yes { background-color: green; color: white; }
.partial { background-color: orange; color: white; }
.dom2 { display: none; }
.dom3 { display: none; }
</style>
</head>
<body>
<h1>DOM Core Support in pdom</h1>
<p>This table shows the current level of <a href="http://www.w3.org/TR/DOM-Level-3-Core/">DOM Core</a> support in pdom. At present, this table only shows <a href="http://www.w3.org/TR/REC-DOM-Level-1/">DOM Core Level 1</a> and a couple properties/methods from DOM Level 2.</p>
<table border="1">
<tr><th>Interface</th><th>Property/Method</th><th>Support?</th></tr>
<!--
// DOM Core Level 2 additions
interface DOMImplementation {
DocumentType createDocumentType(in DOMString qualifiedName, in DOMString publicId, in DOMString systemId)
Document createDocument(in DOMString namespaceURI, in DOMString qualifiedName, in DocumentType doctype)
};
interface Node {
boolean isSupported(in DOMString feature, in DOMString version);
DOMString prefix;
DOMString localName;
boolean hasAttributes();
};
interface NamedNodeMap {
Node getNamedItemNS(in DOMString namespaceURI, in DOMString localName);
Node setNamedItemNS(in Node arg)
Node removeNamedItemNS(in DOMString namespaceURI, in DOMString localName)
};
interface Attr : Node {
Element ownerElement;
};
interface Element : Node {
DOMString getAttributeNS(in DOMString namespaceURI, in DOMString localName);
void setAttributeNS(in DOMString namespaceURI, in DOMString qualifiedName, in DOMString value)
void removeAttributeNS(in DOMString namespaceURI, in DOMString localName)
Attr getAttributeNodeNS(in DOMString namespaceURI, in DOMString localName);
Attr setAttributeNodeNS(in Attr newAttr)
NodeList getElementsByTagNameNS(in DOMString namespaceURI, in DOMString localName);
boolean hasAttributeNS(in DOMString namespaceURI, in DOMString localName);
};
interface DocumentType : Node {
DOMString publicId;
DOMString systemId;
DOMString internalSubset;
};
interface Document : Node {
Node importNode(in Node importedNode, in boolean deep)
Element createElementNS(in DOMString namespaceURI, in DOMString qualifiedName)
Attr createAttributeNS(in DOMString namespaceURI, in DOMString qualifiedName)
NodeList getElementsByTagNameNS(in DOMString namespaceURI, in DOMString localName);
};
};
-->
<tr id="Node"><td rowspan="18" class="partial"><a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1950641247">Node</a></td>
<td class="no">DOMString <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-F68D095">nodeName</a></td><td class="no"></td></tr><tr>
<td class="no">DOMString <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-F68D080">nodeValue</a></td><td class="no"></td></tr><tr>
<td class="yes">unsigned short <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-111237558">nodeType</a></td><td class="yes"></td></tr><tr>
<td class="yes">Node <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1060184317">parentNode</a></td><td class="yes"></td></tr><tr>
<td class="yes">NodeList <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1451460987">childNodes</a></td><td class="yes"></td></tr><tr>
<td class="yes">Node <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-169727388">firstChild</a></td><td class="yes"></td></tr><tr>
<td class="yes">Node <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-61AD09FB">lastChild</a></td><td class="yes"></td></tr><tr>
<td class="yes">Node <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-640FB3C8">previousSibling</a></td><td class="yes"></td></tr><tr>
<td class="yes">Node <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-6AC54C2F">nextSibling</a></td><td class="yes"></td></tr><tr>
<td class="yes">NamedNodeMap <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-84CF096">attributes</a></td><td class="yes"></td></tr><tr>
<td class="no">Document <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#node-ownerDoc">ownerDocument</a></td><td class="no"></td></tr><tr>
<td class="no">Node <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-952280727">insertBefore</a>(in Node newChild, in Node refChild)</td><td class="no"></td></tr><tr>
<td class="no">Node <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-785887307">replaceChild</a>(in Node newChild, in Node oldChild)</td><td class="no"></td></tr><tr>
<td class="yes">Node <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1734834066">removeChild</a>(in Node oldChild)</td><td class="yes"></td></tr><tr>
<td class="yes">Node <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-184E7107">appendChild</a>(in Node newChild)</td><td class="yes"></td></tr><tr>
<td class="yes">boolean <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-810594187">hasChildNodes</a>()</td><td class="yes"></td></tr><tr>
<td class="no">Node cloneNode(in boolean deep)</td><td class="no"></td></tr><tr>
<td class="yes">DOMString <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-NodeNSname">namespaceURI</a></td><td class="yes"></td></tr><tr>
</tr>
<tr id="Element"><td rowspan="10" class="partial"><a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-745549614">Element</a> : <a href="#Node">Node</a></td>
<td class="yes">DOMString <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-104682815">tagName</a></td><td class="yes"></td></tr><tr>
<td class="yes">DOMString <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-666EE0F9">getAttribute</a>(in DOMString name)</td><td class="yes"></td></tr><tr>
<td class="yes">void <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-F68F082">setAttribute</a>(in DOMString name, in DOMString value)</td><td class="yes"></td></tr><tr>
<td class="yes">void <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-6D6AC0F9">removeAttribute</a>(in DOMString name)</td><td class="yes"></td></tr><tr>
<td class="no">Attr <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-217A91B8">getAttributeNode</a>(in DOMString name)</td><td class="no"></td></tr><tr>
<td class="no">Attr <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-887236154">setAttributeNode</a>(in Attr newAttr)</td><td class="no"></td></tr><tr>
<td class="no">Attr <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-D589198">removeAttributeNode</a>(in Attr oldAttr)</td><td class="no"></td></tr><tr>
<td class="no">NodeList <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1938918D">getElementsByTagName</a>(in DOMString name)</td><td class="no"></td></tr><tr>
<td class="yes">boolean <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-ElHasAttr">hasAttribute</a>(in DOMString name)</td><td class="yes"></td></tr><tr>
</tr>
<tr id="Document"><td rowspan="13" class="partial"><a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#i-Document">Document</a> : <a href="#Node">Node</a></td>
<td class="no">DocumentType doctype</td><td class="no"></td></tr><tr>
<td class="no">DOMImplementation implementation</td><td class="no"></td></tr><tr>
<td class="yes">Element <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-87CD092">documentElement</a></td><td class="yes"></td></tr><tr>
<td class="no">Element <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-2141741547">createElement</a>(in DOMString tagName)</td><td class="no"></td></tr><tr>
<td class="no">DocumentFragment createDocumentFragment()</td><td class="no"></td></tr><tr>
<td class="no"><a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1975348127">createTextNode</a>(in DOMString data)</td><td class="no"></td></tr><tr>
<td class="no">createComment(in DOMString data)</td><td class="no"></td></tr><tr>
<td class="no">createCDATASection(in DOMString data)</td><td class="no"></td></tr><tr>
<td class="no">createProcessingInstruction(in DOMString target, in DOMString data)</td><td class="no"></td></tr><tr>
<td class="no">Attr <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1084891198">createAttribute</a>(in DOMString name)</td><td class="no"></td></tr><tr>
<td class="no">EntityReference createEntityByReference(in DOMString name)</td><td class="no"></td></tr><tr>
<td class="no">NodeList <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-A6C9094">getElementsByTagName</a>(in DOMString tagName)</td><td class="no"></td></tr><tr>
<td class="no">Element <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-getElBId">getElementById</a>(in DOMString elementId)</td><td class="no"></td></tr><tr>
</tr>
<tr id="NodeList"><td rowspan="2" class="yes"><a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-536297177">NodeList</a></td>
<td class="yes">Node <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-844377136">item</a>(in unsigned long index)</td><td class="yes"></td></tr><tr>
<td class="yes">unsigned long <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-203510337">length</a></td><td class="yes"></td></tr><tr>
</tr>
<tr id="NamedNodeMap"><td rowspan="5" class="partial"><a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1780488922">NamedNodeMap</a></td>
<td class="yes">Node <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1074577549">getNamedItem</a>(in DOMString name)</td><td class="yes"></td></tr><tr>
<td class="no">Node <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1025163788">setNamedItem</a>(in Node arg)</td><td class="no"></td></tr><tr>
<td class="no">Node <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-D58B193">removeNamedItem</a>(in DOMString name)</td><td class="no"></td></tr><tr>
<td class="yes">Node <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-349467F9">item</a>(in unsigned long index)</td><td class="yes"></td></tr><tr>
<td class="yes">unsigned long <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-6D0FB19E">length</a></td><td class="yes"></td></tr><tr>
</tr>
<tr id="CharacterData"><td rowspan="7" class="partial"><a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-FF21A306">CharacterData</a> : <a href="#Node">Node</a></td>
<td class="yes">DOMString <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-72AB8359">data</a></td><td class="yes"></td></tr><tr>
<td class="no">unsigned long <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-7D61178C">length</a></td><td class="no"></td></tr><tr>
<td class="no">DOMString substringData(in unsigned long offset, in unsigned long count)</td><td class="no"></td></tr><tr>
<td class="no">void appendData(in DOMString arg)</td><td class="no"></td></tr><tr>
<td class="no">void insertData(in unsigned long offset, in DOMString arg)</td><td class="no"></td></tr><tr>
<td class="no">void deleteData(in unsigned long offset, in unsigned long count)</td><td class="no"></td></tr><tr>
<td class="no">void replaceData(in unsigned long offset, in unsigned long count, in DOMString arg)</td><td class="no"></td></tr><tr>
</tr>
<tr id="Text"><td rowspan="1" class="partial"><a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1312295772">Text</a> : <a href="#CharacterData">CharacterData</a></td>
<td class="no">Text splitText(in unsigned long offset)</td><td class="no"></td></tr><tr>
</tr>
<tr id="Attr"><td rowspan="4" class="partial"><a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-637646024">Attr</a> : <a href="#Node">Node</a></td>
<td class="yes">DOMString <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-1112119403">name</a></td><td class="no"></td></tr><tr>
<td class="no">boolean specified</td><td class="no"></td></tr><tr>
<td class="yes">DOMString <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-221662474">value</a></td><td class="no"></td></tr><tr>
<td class="no">Element <a href="http://www.w3.org/TR/DOM-Level-3-Core/core.html#Attr-ownerElement">ownerElement</a></td><td class="no"></td></tr><tr>
</tr>
<tr><td rowspan="1" class="partial">Comment : CharacterData</td>
<td class="no">(empty)</td><td class="no"></td></tr><tr>
</tr>
<tr><td rowspan="1" class="no">CDATASection : Text</td>
<td class="no">(empty)</td><td class="no"></td></tr><tr>
</tr>
<tr><td rowspan="1" class="yes">DOMException</td>
<td class="yes">unsigned short code</td><td class="yes"></td></tr><tr>
</tr>
<tr><td rowspan="1" class="no">DOMImplementation</td>
<td class="no">boolean hasFeature(in DOMString feature, in DOMString version)</td><td class="no"></td></tr><tr>
</tr>
<tr><td rowspan="1" class="no">DocumentFragment</td>
<td class="no">(empty)</td><td class="no"></td></tr><tr>
</tr>
<tr><td rowspan="3" class="partial">DocumentType : <a href="#Node">Node<a></td>
<td class="no">DOMString name</td><td class="no"></td></tr><tr>
<td class="no">NamedNodeMap entities</td><td class="no"></td></tr><tr>
<td class="no">NamedNodeMap notations</td><td class="no"></td></tr><tr>
</tr>
<tr><td rowspan="2" class="no">Notation : <a href="#Node">Node</a></td>
<td class="no">DOMString publicId</td><td class="no"></td></tr><tr>
<td class="no">DOMString systemId</td><td class="no"></td></tr><tr>
</tr>
<tr><td rowspan="3" class="no">Entity : <a href="#Node">Node</a></td>
<td class="no">DOMString publicId</td><td class="no"></td></tr><tr>
<td class="no">DOMString systemId</td><td class="no"></td></tr><tr>
<td class="no">DOMString notationName</td><td class="no"></td></tr><tr>
</tr>
<tr><td rowspan="1" class="no">EntityReference : <a href="#Node">Node<a/></td>
<td class="no">(empty)</td><td class="no"></td></tr><tr>
</tr>
<tr><td rowspan="2" class="no">ProcessingInstruction : <a href="#Node">Node</a></td>
<td class="no">DOMString target</td><td class="no"></td></tr><tr>
<td class="no">DOMString data</td><td class="no"></td></tr><tr>
</tr>
</table>
</body>
</html>

256
lite/pdom_test.html Normal file
View file

@ -0,0 +1,256 @@
<!DOCTYPE html>
<html>
<head>
<title>pdom unit tests</title>
<script src='pdom.js'></script>
<style type='text/css'>
progress {
width: 600px;
}
</style>
<script src='muther.js'></script>
</head>
<body></body>
<script type='text/javascript'>
muther.test([
function testConstruction() {
var parser = new pdom.DOMParser();
muther.assert(parser, 'Could not construct parser');
},
function testParseEmptyDoc() {
var doc = new pdom.DOMParser().parseFromString('<?xml version="1.0"?><empty/>');
muther.assert(doc, 'Could not parse doc');
muther.assert(doc instanceof pdom.XMLDocument, 'parseFromString() did not return a pdom.XMLDocument');
muther.assert(doc.childNodes.length == 1, 'Doc node had incorrect # of nodes: ' + doc.childNodes.length);
muther.assert(doc.documentElement, 'Document had no documentElement');
muther.assert(doc.documentElement instanceof pdom.Element, 'documentElement not a pdom.Element');
muther.assert(doc.documentElement.tagName == 'empty', 'documentElement not an <empty/>');
muther.assert(doc.documentElement.childNodes.length == 0, 'documentElement had child nodes');
},
function testParseSimpleGetAttribute() {
var doc = new pdom.DOMParser().parseFromString(
'<?xml version="1.0"?><simple bar="foo" foo=\'bar\'' +
' baz = " blah blah "' +
'/>');
muther.assert(doc, 'Could not parse doc');
muther.assert(doc.documentElement, 'Document had no documentElement');
var elem = doc.documentElement;
muther.assert(elem.getAttribute('bar') == 'foo', 'bar attribute not correct');
muther.assert(elem.getAttribute('foo') == 'bar', 'foo attribute not correct');
muther.assert(elem.getAttribute('baz') == ' blah blah ', 'baz attribute not correct');
},
function testNodeTypes() {
var doc = new pdom.DOMParser().parseFromString('<simple foo="bar"><!-- Comment -->Text</simple>');
muther.assert(doc.nodeType == 9, 'Document nodetype not 9');
muther.assert(doc.documentElement.nodeType == 1, 'Document element not 1');
muther.assert(doc.documentElement.attributes.item(0).nodeType == 2, 'Attribute nodeType not 2');
muther.assert(doc.documentElement.childNodes.item(0).nodeType == 8, 'Comment nodetype not 8');
muther.assert(doc.documentElement.childNodes.item(1).nodeType == 3, 'Text nodetype not 3');
},
function testParentNode() {
var doc = new pdom.DOMParser().parseFromString('<simple foo="bar">Text</simple>');
muther.assert(doc.documentElement.parentNode == doc,
'parentNode of documentElement not the document');
muther.assert(doc.documentElement == doc.documentElement.childNodes.item(0).parentNode,
'parentNode of text not the documentElement');
muther.assert(doc.documentElement.attributes.item(0).parentNode == null,
'parentNode of Attr is not null');
},
function testConvenienceNodeProperties() {
var doc = new pdom.DOMParser().parseFromString(
'<parent><child1/><child2/><child3/></parent>');
var parent = doc.documentElement;
var child1 = parent.childNodes.item(0);
var child2 = parent.childNodes.item(1);
var child3 = parent.childNodes.item(2);
muther.assert(parent.firstChild === child1, 'firstChild did not work');
muther.assert(parent.lastChild === child3, 'lastChild did not work');
muther.assert(child2.previousSibling === child1, 'previousSibling did not work');
muther.assert(child2.nextSibling === child3, 'nextSibling did not work');
},
function testNamespaceless() {
var doc = new pdom.DOMParser().parseFromString('<parent><child/></parent>');
var parent = doc.documentElement;
var child = parent.firstChild;
muther.assert(parent.namespaceURI === null, 'parent namespaceURI did not return null');
muther.assert(child.namespaceURI === null, 'child namespaceURI did not return null');
},
function testDefaultNamespaceURI() {
var doc = new pdom.DOMParser().parseFromString(
'<parent xmlns="http://foo/"><child xmlns="http://bar/"><grandchild/></child></parent>');
var parent = doc.documentElement;
var child = parent.firstChild;
var grandchild = child.firstChild;
muther.assert(parent.namespaceURI === "http://foo/", 'parent namespaceURI was not correct');
muther.assert(child.namespaceURI === "http://bar/", 'child namespaceURI was not correct');
muther.assert(grandchild.namespaceURI === "http://bar/",
'grandchild namespaceURI was not correct');
},
function testPrefixedNamespaceURI() {
var doc = new pdom.DOMParser().parseFromString(
'<parent xmlns="http://foo/" xmlns:bar="http://bar/">' +
'<child/><bar:child><grandchild/></bar:child></parent>');
var parent = doc.documentElement;
var firstChild = parent.firstChild;
var secondChild = firstChild.nextSibling;
var grandChild = secondChild.firstChild;
muther.assert(secondChild.namespaceURI === "http://bar/",
'prefixed namespaceURI did not work');
muther.assert(grandChild.namespaceURI === "http://bar/",
'prefixed namespaceURI did not work');
},
function testTagName() {
var doc = new pdom.DOMParser().parseFromString('<simple/>');
muther.assert(doc.documentElement.tagName == 'simple',
'tagName was not "simple"');
},
function testHasChildNodes() {
var doc = new pdom.DOMParser().parseFromString('<parent><child/></parent>');
muther.assert(doc.documentElement.hasChildNodes(), 'documentElement had no child nodes');
muther.assert(!doc.documentElement.childNodes.item(0).hasChildNodes(),
'child-less element had child nodes');
},
function testRemoveChild() {
var doc = new pdom.DOMParser().parseFromString('<parent><child/></parent>');
var root = doc.documentElement;
var child = root.childNodes.item(0);
var retValue = root.removeChild(child);
muther.assert(!root.hasChildNodes(), 'Parent still has children');
muther.assert(child == retValue, 'removeChild did not return the removed child');
muther.assert(!child.parentNode, 'Removed child still has a parent');
try {
root.removeChild(child);
muther.assert(false, 'removeChild() did not throw an exception');
} catch(e) {
muther.assert(e.code == 8, 'DOMException not thrown with code 8');
}
},
function testAppendChild() {
var doc = new pdom.DOMParser().parseFromString('<parent><child1/><child2/><child3/></parent>');
var root = doc.documentElement;
var child1 = root.childNodes.item(0);
var child2 = root.childNodes.item(1);
var retValue = root.appendChild(child1);
muther.assert(child1 === retValue, 'appendChild did not return the appended child');
muther.assert(root.firstChild === child2, 'appendChild did not remove the appended child');
muther.assert(root.lastChild === child1, 'appendChild did not append the child');
retValue = child1.appendChild(child2);
muther.assert(root.childNodes.length == 2, 'root did not have 2 children');
muther.assert(child1.hasChildNodes(), 'child1 did not have any child nodes');
muther.assert(child1.firstChild == child2, 'child1\'s child is not child2');;
},
function testGetAttribute() {
var doc = new pdom.DOMParser().parseFromString('<parent foo="bar"></parent>');
var root = doc.documentElement;
muther.assert(root.getAttribute('foo') == 'bar', 'Element did not have a foo attribute');
muther.assert(root.getAttribute('blah') == '', 'getAttribute("blah") did not return an empty string');
},
function testRemoveAttribute() {
var doc = new pdom.DOMParser().parseFromString('<parent foo="bar" baz="blah"></parent>');
var root = doc.documentElement;
var attrMap = root.attributes;
root.removeAttribute('baz');
muther.assert(root.getAttribute('baz') == '', 'Element still has a baz attribute');
muther.assert(root.getAttribute('foo') == 'bar', 'Element does not have a foo attribute');
// Also tests liveness of the NamedNodeMap.
muther.assert(attrMap.length == 1, 'attributes NamedNodeMap was not updated after a removeAttribute');
},
function testSetAttribute() {
var doc = new pdom.DOMParser().parseFromString('<parent foo="bar"></parent>');
var root = doc.documentElement;
var attrMap = root.attributes;
root.setAttribute('foo', 'baz');
root.setAttribute('blah', 'boo');
muther.assert(attrMap.length == 2, 'attributes NamedNodeMap was not updated after a setAttribute');
muther.assert(root.getAttribute('foo') == 'baz', 'Foo attribute value not updated after a setAttribute');
muther.assert(root.getAttribute('blah') == 'boo', 'Blah attribute value not updated after a setAttribute');
},
function testHasAttribute() {
var doc = new pdom.DOMParser().parseFromString('<parent foo="bar"></parent>');
var root = doc.documentElement;
var attrMap = root.attributes;
muther.assert(root.hasAttribute('foo'), 'hasAttribute() did not work for parsed attribute');
muther.assert(!root.hasAttribute('blah'), 'hasAttribute() returned true for unknown attribute');
root.setAttribute('foo', 'baz');
root.setAttribute('blah', 'boo');
muther.assert(root.hasAttribute('foo'), 'hasAttribute() did not work for a set attribute');
muther.assert(root.hasAttribute('blah'), 'hasAttribute() returned false for a set attribute');
root.removeAttribute('foo');
muther.assert(!root.hasAttribute('foo'), 'hasAttribute() return true for a removed attribute');
},
function testParseAttributes() {
var parser = new pdom.DOMParser();
var doc = parser.parseFromString('<simple bar="baz" foo="blah"/>');
muther.assert(doc, 'Could not parse doc');
muther.assert(doc.documentElement, 'Document had no documentElement');
var elem = doc.documentElement;
var attrMap = elem.attributes;
muther.assert(attrMap, 'Could not get NamedNodeMap for attributes');
muther.assert(attrMap.length == 2, 'Attribute map did not have two items');
muther.assert(attrMap.item(0) instanceof pdom.Node, 'item(0) did not return a Node');
muther.assert(attrMap.item(1) instanceof pdom.Node, 'item(1) did not return a Node');
muther.assert(attrMap.item(0).name == 'foo' || attrMap.item(0).name == 'bar',
'Unknown node returned from map');
var fooAttr = attrMap.getNamedItem('foo');
muther.assert(fooAttr instanceof pdom.Attr, 'foo attribute was not an Attr node');
muther.assert(fooAttr.name == 'foo', 'foo attribute did not have name of "foo"');
muther.assert(fooAttr.value == 'blah', 'foo attribute did not have value of "blah"');
var barAttr = attrMap.getNamedItem('bar');
muther.assert(barAttr instanceof pdom.Attr, 'bar attribute was not an Attr node');
muther.assert(barAttr.name == 'bar', 'bar attribute did not have name of "bar"');
muther.assert(barAttr.value == 'baz', 'bar attribute did not have value of "baz"');
barAttr.value = 'fungus';
muther.assert(attrMap.getNamedItem('bar').value == 'fungus',
'Mutating Attr node value did not update the node in NamedNodeMap');
},
function testSerializerRoundtripSimple() {
var parser = new pdom.DOMParser();
var xmlText = '<simple foo="bar">blah</simple>';
var doc = parser.parseFromString(xmlText, 'text/xml');
var serializer = new pdom.XMLSerializer();
var serializedText = serializer.serializeToString(doc);
muther.assert(serializedText == xmlText,
('Serializing empty doc failed. Expected: ' + xmlText +
' Actual: ' + serializedText));
},
]);
</script>
</html>

84
lite/scour.js Normal file
View file

@ -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.<string>} 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);
};