Permalink
Browse files

Initial import.

  • Loading branch information...
0 parents commit b5a02a01b23ae00c6caac751ca45bcc7964537d0 @bard committed Sep 17, 2008
Showing with 523 additions and 0 deletions.
  1. +204 −0 examples.js
  2. +319 −0 seethrough.js
204 examples.js
@@ -0,0 +1,204 @@
+/*
+ * Copyright 2008 by Massimiliano Mirra
+ *
+ * This file is part of seethrough.
+ *
+ * seethrough is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * SamePlace is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * The interactive user interfaces in modified source and object code
+ * versions of this program must display Appropriate Legal Notices, as
+ * required under Section 5 of the GNU General Public License version 3.
+ *
+ * In accordance with Section 7(b) of the GNU General Public License
+ * version 3, modified versions must display the "Powered by SamePlace"
+ * logo to users in a legible manner and the GPLv3 text must be made
+ * available to them.
+ *
+ * Author: Massimiliano Mirra, <bard [at] hyperstruct [dot] net>
+ *
+ */
+
+
+var ns_st = 'http://hyperstruct.net/seethrough#helma';
+
+var examples = [
+ {
+ name: 'empty children',
+
+ template: <div xmlns:st={ns_st}><head/></div>,
+
+ env: {},
+
+ result: <div xmlns:st={ns_st}><head/></div>
+ },
+
+ {
+ name: 'content',
+
+ template: <div xmlns:st={ns_st} st:content='meta.title'/>,
+
+ env: {
+ meta: { title: 'Hello, world!' }
+ },
+
+ result: <div xmlns:st={ns_st}>Hello, world!</div>
+ },
+
+ {
+ name: 'replace',
+
+ template:
+ <div xmlns:st={ns_st}>
+ <span st:replace='meta.title'/>
+ </div>,
+
+ env: {
+ meta: { title: 'Hello, world!' }
+ },
+
+ result:
+ <div xmlns:st={ns_st}>Hello, world!</div>
+ },
+
+ {
+ name: 'disable',
+
+ template:
+ <div xmlns:st={ns_st}>
+ <span st:disable='true'>Hello, world!</span>
+ </div>,
+
+ env: {},
+
+ result:
+ <div xmlns:st={ns_st}/>,
+ },
+
+ {
+ name: 'loop',
+
+ template:
+ <ul xmlns:st={ns_st}>
+ <li st:loop='person people'>
+ <span st:replace='person'/>
+ </li>
+ </ul>,
+
+ env: {
+ people: ['jim', 'spock', 'mccoy']
+ },
+
+ result:
+ <ul xmlns:st={ns_st}>
+ <li>jim</li>
+ <li>spock</li>
+ <li>mccoy</li>
+ </ul>
+ },
+
+ {
+ name: 'condition',
+
+ template:
+ <div xmlns:st={ns_st}>
+ <span st:condition="flag1">I will be here</span>
+ <span st:condition="flag2">I probably will not</span>
+ </div>,
+
+ env: {
+ flag1: true,
+ flag2: false
+ },
+
+ result:
+ <div xmlns:st={ns_st}>
+ <span>I will be here</span>
+ </div>
+ }
+];
+
+function verify = function() {
+ function compareXML(t1, t2) {
+ function compareXML1(tree1, tree2) {
+ if(tree1.nodeKind() != tree2.nodeKind())
+ throw new Error('Different node kinds. (' +
+ tree1.nodeKind() + ',' + tree2.nodeKind() + ')');
+
+ switch(tree1.nodeKind()) {
+ case 'element':
+ if(tree1.name() != tree2.name())
+ throw new Error('Different tag names. (' +
+ '<' + tree1.name() + '>, ' + '<' + tree2.name() + '>)');
+ break;
+ case 'text':
+ if(tree1.valueOf() != tree2.valueOf())
+ throw new Error('Different text values. (' +
+ '<' + tree1.valueOf() + '>, ' + '<' + tree2.valueOf() + '>)');
+
+ break;
+ default:
+ throw new Error('Unhandled node kind. (' + tree1.nodeKind() + ')');
+ }
+
+ var attrList1 = tree1.@*;
+ var attrList2 = tree2.@*;
+ if(attrList1.length() != attrList2.length())
+ throw new Error('Different attribute count for <' + tree1.name() + '>. (expected ' +
+ attrList1.length() + ', got ' + attrList2.length() + ')');
+
+ var childList1 = tree1.*;
+ var childList2 = tree2.*;
+ if(childList1.length() != childList2.length())
+ throw new Error('Different child count for <' + tree1.name() + '>. (expected ' +
+ childList1.length() + ', got ' + childList2.length() + ')');
+
+ for each(var attr in attrList1) {
+ if(tree1['@' + attr.name()] != tree2['@' + attr.name()])
+ throw new Error('Different values for attribute @' + attr.name() + '. (expected ' +
+ tree1['@' + attr.name()] + ', got ' + tree2['@' + attr.name()] + ')');
+ }
+
+ for(var i=0; i<childList1.length(); i++)
+ compareXML(childList1[i], childList2[i])
+
+ }
+
+ compareXML1(t1.normalize(), t2.normalize());
+ return true;
+ }
+
+ var results = seethrough.examples.map(function(example) {
+ try {
+ d.on = example.debug;
+ var rendered = seethrough
+ .compile(example.template)
+ .call(null, example.env)
+ compareXML(example.result, rendered);
+ } catch(e) {
+ return 'FAILURE: ' + example.name + '\n' +
+ e.message + '\n' +
+ e.stack + '\n' +
+ rendered.toXMLString();
+ }
+ }).filter(function(result) { return result; });
+
+ if(results.length > 0)
+ return results.join('\n');
+ else
+ return ('\n**************************************************\n' +
+ 'Verified successfully.\n\n' +
+ '**************************************************');
+};
+
+print(verify());
319 seethrough.js
@@ -0,0 +1,319 @@
+/*
+ * Copyright 2008 by Massimiliano Mirra
+ *
+ * This file is part of seethrough.
+ *
+ * seethrough is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * SamePlace is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * The interactive user interfaces in modified source and object code
+ * versions of this program must display Appropriate Legal Notices, as
+ * required under Section 5 of the GNU General Public License version 3.
+ *
+ * In accordance with Section 7(b) of the GNU General Public License
+ * version 3, modified versions must display the "Powered by SamePlace"
+ * logo to users in a legible manner and the GPLv3 text must be made
+ * available to them.
+ *
+ * Author: Massimiliano Mirra, <bard [at] hyperstruct [dot] net>
+ *
+ */
+
+
+var seethrough = {};
+
+
+// TAG/ATTRIBUTE PROCESSORS
+// ----------------------------------------------------------------------
+
+seethrough.processors = {
+ 'http://hyperstruct.net/seethrough#helma::attr': function() {
+ var dummy = <dummy/>;
+ return function stAttr(element, env, children) {
+ dummy.@[element.@name] = children(env).toString();
+ return dummy.@[element.@name]
+ }
+ },
+
+ 'http://hyperstruct.net/seethrough#helma::condition': function(attrValue) {
+ return function stCondition(element, env, children) {
+ if(seethrough.getEnv(env, attrValue))
+ return element.appendChild(children(env));
+ else
+ return new XML('');
+ }
+ },
+
+ 'http://hyperstruct.net/seethrough#helma::disable': function(attrValue) {
+ return function stDisable(element, env, children) {
+ return attrValue == 'true' ? new XML('') : element;
+ }
+ },
+
+ 'http://hyperstruct.net/seethrough#helma::replace': function(attrValue) {
+ return function stReplace(element, env, children) {
+ var envValue = seethrough.getEnv(env, attrValue);
+ switch(typeof(envValue)) { // this should belong in
+ case 'number':
+ case 'string':
+ return <dummy>{envValue}</dummy>.text();
+ case 'xml':
+ return envValue;
+ default:
+ throw new TypeError('Unhandled type for "' +
+ envValue +
+ '" (' + typeof(envValue) + ')');
+ }
+ }
+ },
+
+ 'http://hyperstruct.net/seethrough#helma::inspect': function(attrValue) {
+ return function stInspect(element, env, children) {
+ var envValue = seethrough.getEnv(env, attrValue);
+ var representation;
+ switch(typeof(envValue)) {
+ case 'number':
+ case 'string':
+ representation = envValue;
+ break;
+ case 'xml':
+ representation = envValue.toXMLString();
+ break;
+ case 'object':
+ representation = envValue.toSource();
+ break;
+ default:
+ throw new TypeError('Unhandled type for "' +
+ envValue +
+ '" (' + typeof(envValue) + ')');
+ }
+ // Force escaping
+ return <dummy>{representation}</dummy>.text();
+ }
+ },
+
+ 'http://hyperstruct.net/seethrough#helma::content': function(attrValue) {
+ return function(element, env, children) {
+ return element.appendChild(seethrough.getEnv(env, attrValue));
+ }
+ },
+
+// 'http://hyperstruct.net/seethrough#helma::extra': function(attrValue, children) {
+// return function(element, env) {
+// return element.appendChild(seethrough.getEnv(env, attrValue));
+// }
+// },
+
+ 'http://hyperstruct.net/seethrough#helma::loop': function(attrValue) {
+ var [iterName, collectionName] = attrValue.split(' ');
+ return function stLoop(element, env, children) {
+ var container = new XMLList();
+ if(iterName in env)
+ throw new Error('Overriding global name not yet supported.');
+
+ var collection = seethrough.getEnv(env, collectionName);
+ for each(var envValue in collection) {
+ env[iterName] = envValue;
+ container += element.copy().appendChild(children(env));
+ }
+ delete env[iterName];
+ return container;
+ }
+ },
+
+ 'http://hyperstruct.net/seethrough#helma::eval': function() {
+ return function stEval(element, env, children) {
+ return new XML(eval(children(env).toString()));
+ }
+ }
+};
+
+
+// ENVIRONMENT
+// ----------------------------------------------------------------------
+
+seethrough.getEnv = function(env, path) {
+ var value = env;
+
+ if(path.match(/\)$/))
+ return getenv2(env, path);
+
+ try {
+ for each(var step in path.split('.')) {
+ value = value[step];
+ }
+ } catch(e if e.name == 'TypeError') {
+ value = undefined;
+ }
+
+ if(typeof(value) == 'function') {
+ // might be better to leave the choice of what to do to
+ // the requester. that will leave the burden too, though
+ return value(env);
+ }
+ else
+ return value;
+};
+
+
+// COMPILATION
+// ----------------------------------------------------------------------
+
+seethrough.EMPTY = function() {
+ return function() {};
+};
+
+seethrough.compile = function(xml) {
+ var c = arguments.callee;
+
+ // Rhino brings up XML lists of length 0, Spidermonkey doesn't.
+ if(xml.length() > 1)
+ return c.list(xml);
+ else if(xml.length() == 0)
+ return seethrough.EMPTY();
+ else
+ switch(xml.nodeKind()) {
+ case 'element':
+ return c.element(xml);
+ break;
+ case 'text':
+ return c.text(xml);
+ break;
+ case 'comment':
+ return seethrough.EMPTY();
+ break;
+ default:
+ throw new Error('Compile error: unhandled node kind. (' + xml.nodeKind() + ')');
+ }
+}
+
+seethrough.compile.text = function(xmlText) {
+ return function() {
+ return xmlText;
+ }
+};
+
+seethrough.compile.list = function(xmlList) {
+ d(' - Compiling children');
+ var renderChildren = [];
+ for each(var xmlNode in xmlList) {
+ renderChildren.push(seethrough.compile(xmlNode));
+ }
+
+ return function(env) {
+ d(' * Rendering children')
+ var rendered = new XMLList();
+ for each(var renderChild in renderChildren) {
+ rendered += renderChild(env);
+ }
+ return rendered;
+ };
+};
+
+seethrough.compile.element = function(xmlElement) {
+ d('- Compiling element ' + xmlElement.name());
+ var xmlBase = xmlElement.copy();
+ delete xmlBase.*::*;
+
+ var elementProcessors = seethrough.makeProcessors(xmlElement);
+ if(elementProcessors.length > 0)
+ d(' - ' + elementProcessors.length + ' processor(s) to apply');
+
+ var children = seethrough.compile(xmlElement.children());
+
+ var render;
+ if(elementProcessors.length > 0)
+ render = function(env) {
+ var xmlOut = xmlBase.copy(); // ref to parent scope - leak
+ d('* Rendering ' + xmlOut.name());
+ for each(var fnStep in elementProcessors) {
+ xmlOut = fnStep(xmlOut, env, children);
+ }
+ return xmlOut;
+ };
+ else
+ render = function(env) {
+ try { // why don't exceptions get surfaced here?
+ var xmlOut = xmlBase.copy();
+ d('* Rendering ' + xmlOut.name());
+
+ xmlChildren = children(env);
+ if(typeof(xmlChildren) == 'undefined')
+ return xmlOut;
+
+ var xmlChild;
+ for(var i=0,l=xmlChildren.length(); i<l; i++) {
+ xmlChild = xmlChildren[i];
+ if(xmlChild.nodeKind() == 'attribute')
+ xmlOut.@[xmlChild.name()] = xmlChild.toString();
+ else
+ xmlOut.appendChild(xmlChild);
+ }
+
+ return xmlOut;
+ } catch(e) {
+ d(e + '\n' + e.stack);
+ }
+ };
+
+ render.src = xmlElement;
+ return render;
+};
+
+seethrough.makeProcessors = function(element) {
+ var steps = [], makeProcessor;
+
+ function makeAttributeProcessor(attr) {
+ var ns = attr.namespace();
+ var attrValue = attr.toString();
+ var processor = makeProcessor(attrValue);
+ return function(element, env, children) {
+ d(' * Applying processor for ' + attr.name());
+ delete element.@ns::[attr.localName()];
+ var result = processor(element, env, children);
+ return result;
+ }
+ }
+
+ // Handle attributes
+ for each(var attr in element.@*::*) {
+ makeProcessor = seethrough.processors[attr.name().toString()];
+ if(makeProcessor)
+ steps.push(makeAttributeProcessor(attr));
+ }
+
+ // Handle tag
+ makeProcessor = seethrough.processors[element.name().toString()];
+ if(makeProcessor)
+ steps.push(makeProcessor());
+
+ return steps;
+};
+
+
+// DEVELOPMENT UTILITIES
+// ----------------------------------------------------------------------
+
+function d(msg) {
+ if(!d.on)
+ return;
+
+ // debugging in command-line spidermonkey
+ if(typeof('print') == 'function')
+ print(msg);
+
+ // debugging in helma
+ if(typeof(app.log) == 'function')
+ app.log(msg);
+}
+d.on = false;

0 comments on commit b5a02a0

Please sign in to comment.