|
|
(41 intermediate revisions not shown) |
Line 1: |
Line 1: |
| <html> | | <html> |
| | | |
- | <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" type="text/javascript"></script> | + | <!-- MathJax (LaTeX for the web) --> |
| + | <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script> |
| + | |
| | | |
| <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" type="text/javascript"></script> | | <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" type="text/javascript"></script> |
- |
| |
- |
| |
- | <script>
| |
- | (function() {
| |
- | var supports = (function() {
| |
- | var supports = {};
| |
- |
| |
- | var html;
| |
- | var work = this.document.createElement('div');
| |
- |
| |
- | html = "<P><I></P></I>";
| |
- | work.innerHTML = html;
| |
- | supports.tagSoup = work.innerHTML !== html;
| |
- |
| |
- | work.innerHTML = "<P><i><P></P></i></P>";
| |
- | supports.selfClose = work.childNodes.length === 2;
| |
- |
| |
- | return supports;
| |
- | })();
| |
- |
| |
- |
| |
- |
| |
- | // Regular Expressions for parsing tags and attributes
| |
- | var startTag = /^<([\-A-Za-z0-9_]+)((?:\s+[\w\-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/;
| |
- | var endTag = /^<\/([\-A-Za-z0-9_]+)[^>]*>/;
| |
- | var attr = /([\-A-Za-z0-9_]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;
| |
- | var fillAttr = /^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noresize|noshade|nowrap|readonly|selected)$/i;
| |
- |
| |
- | var DEBUG = false;
| |
- |
| |
- | function htmlParser(stream, options) {
| |
- | stream = stream || '';
| |
- |
| |
- | // Options
| |
- | options = options || {};
| |
- |
| |
- | for(var key in supports) {
| |
- | if(supports.hasOwnProperty(key)) {
| |
- | if(options.autoFix) {
| |
- | options['fix_'+key] = true;//!supports[key];
| |
- | }
| |
- | options.fix = options.fix || options['fix_'+key];
| |
- | }
| |
- | }
| |
- |
| |
- | var stack = [];
| |
- |
| |
- | var append = function(str) {
| |
- | stream += str;
| |
- | };
| |
- |
| |
- | var prepend = function(str) {
| |
- | stream = str + stream;
| |
- | };
| |
- |
| |
- | // Order of detection matters: detection of one can only
| |
- | // succeed if detection of previous didn't
| |
- | var detect = {
| |
- | comment: /^<!--/,
| |
- | endTag: /^<\//,
| |
- | atomicTag: /^<\s*(script|style|noscript|iframe|textarea)[\s>]/i,
| |
- | startTag: /^</,
| |
- | chars: /^[^<]/
| |
- | };
| |
- |
| |
- | // Detection has already happened when a reader is called.
| |
- | var reader = {
| |
- |
| |
- | comment: function() {
| |
- | var index = stream.indexOf("-->");
| |
- | if ( index >= 0 ) {
| |
- | return {
| |
- | content: stream.substr(4, index),
| |
- | length: index + 3
| |
- | };
| |
- | }
| |
- | },
| |
- |
| |
- | endTag: function() {
| |
- | var match = stream.match( endTag );
| |
- |
| |
- | if ( match ) {
| |
- | return {
| |
- | tagName: match[1],
| |
- | length: match[0].length
| |
- | };
| |
- | }
| |
- | },
| |
- |
| |
- | atomicTag: function() {
| |
- | var start = reader.startTag();
| |
- | if(start) {
| |
- | var rest = stream.slice(start.length);
| |
- | // for optimization, we check first just for the end tag
| |
- | if(rest.match(new RegExp("<\/\\s*" + start.tagName + "\\s*>", "i"))) {
| |
- | // capturing the content is inefficient, so we do it inside the if
| |
- | var match = rest.match(new RegExp("([\\s\\S]*?)<\/\\s*" + start.tagName + "\\s*>", "i"));
| |
- | if(match) {
| |
- | // good to go
| |
- | return {
| |
- | tagName: start.tagName,
| |
- | attrs: start.attrs,
| |
- | content: match[1],
| |
- | length: match[0].length + start.length
| |
- | };
| |
- | }
| |
- | }
| |
- | }
| |
- | },
| |
- |
| |
- | startTag: function() {
| |
- | var match = stream.match( startTag );
| |
- |
| |
- | if ( match ) {
| |
- | var attrs = {};
| |
- |
| |
- | match[2].replace(attr, function(match, name) {
| |
- | var value = arguments[2] || arguments[3] || arguments[4] ||
| |
- | fillAttr.test(name) && name || null;
| |
- |
| |
- | attrs[name] = value;
| |
- | });
| |
- |
| |
- | return {
| |
- | tagName: match[1],
| |
- | attrs: attrs,
| |
- | unary: !!match[3],
| |
- | length: match[0].length
| |
- | };
| |
- | }
| |
- | },
| |
- |
| |
- | chars: function() {
| |
- | var index = stream.indexOf("<");
| |
- | return {
| |
- | length: index >= 0 ? index : stream.length
| |
- | };
| |
- | }
| |
- | };
| |
- |
| |
- | var readToken = function() {
| |
- |
| |
- | // Enumerate detects in order
| |
- | for (var type in detect) {
| |
- |
| |
- | if(detect[type].test(stream)) {
| |
- | if(DEBUG) { console.log('suspected ' + type); }
| |
- |
| |
- | var token = reader[type]();
| |
- | if(token) {
| |
- | if(DEBUG) { console.log('parsed ' + type, token); }
| |
- | // Type
| |
- | token.type = token.type || type;
| |
- | // Entire text
| |
- | token.text = stream.substr(0, token.length);
| |
- | // Update the stream
| |
- | stream = stream.slice(token.length);
| |
- |
| |
- | return token;
| |
- | }
| |
- | return null;
| |
- | }
| |
- | }
| |
- | };
| |
- |
| |
- | var readTokens = function(handlers) {
| |
- | var tok;
| |
- | while(tok = readToken()) {
| |
- | // continue until we get an explicit "false" return
| |
- | if(handlers[tok.type] && handlers[tok.type](tok) === false) {
| |
- | return;
| |
- | }
| |
- | }
| |
- | };
| |
- |
| |
- | var clear = function() {
| |
- | var rest = stream;
| |
- | stream = '';
| |
- | return rest;
| |
- | };
| |
- |
| |
- | var rest = function() {
| |
- | return stream;
| |
- | };
| |
- |
| |
- | if(options.fix) {
| |
- | (function() {
| |
- | // Empty Elements - HTML 4.01
| |
- | var EMPTY = /^(AREA|BASE|BASEFONT|BR|COL|FRAME|HR|IMG|INPUT|ISINDEX|LINK|META|PARAM|EMBED)$/i;
| |
- |
| |
- | // Elements that you can| intentionally| leave open
| |
- | // (and which close themselves)
| |
- | var CLOSESELF = /^(COLGROUP|DD|DT|LI|OPTIONS|P|TD|TFOOT|TH|THEAD|TR)$/i;
| |
- |
| |
- |
| |
- | var stack = [];
| |
- | stack.last = function() {
| |
- | return this[this.length - 1];
| |
- | };
| |
- | stack.lastTagNameEq = function(tagName) {
| |
- | var last = this.last();
| |
- | return last && last.tagName &&
| |
- | last.tagName.toUpperCase() === tagName.toUpperCase();
| |
- | };
| |
- |
| |
- | stack.containsTagName = function(tagName) {
| |
- | for(var i = 0, tok; tok = this[i]; i++) {
| |
- | if(tok.tagName === tagName) {
| |
- | return true;
| |
- | }
| |
- | }
| |
- | return false;
| |
- | };
| |
- |
| |
- | var correct = function(tok) {
| |
- | if(tok && tok.type === 'startTag') {
| |
- | // unary
| |
- | tok.unary = EMPTY.test(tok.tagName) || tok.unary;
| |
- | }
| |
- | return tok;
| |
- | };
| |
- |
| |
- | var readTokenImpl = readToken;
| |
- |
| |
- | var peekToken = function() {
| |
- | var tmp = stream;
| |
- | var tok = correct(readTokenImpl());
| |
- | stream = tmp;
| |
- | return tok;
| |
- | };
| |
- |
| |
- | var closeLast = function() {
| |
- | var tok = stack.pop();
| |
- |
| |
- | // prepend close tag to stream.
| |
- | prepend('</'+tok.tagName+'>');
| |
- | };
| |
- |
| |
- | var handlers = {
| |
- | startTag: function(tok) {
| |
- | var tagName = tok.tagName;
| |
- | // Fix tbody
| |
- | if(tagName.toUpperCase() === 'TR' && stack.lastTagNameEq('TABLE')) {
| |
- | prepend('<TBODY>');
| |
- | prepareNextToken();
| |
- | } else if(options.fix_selfClose &&
| |
- | CLOSESELF.test(tagName) &&
| |
- | stack.containsTagName(tagName)) {
| |
- | if(stack.lastTagNameEq(tagName)) {
| |
- | closeLast();
| |
- | } else {
| |
- | prepend('</'+tok.tagName+'>');
| |
- | prepareNextToken();
| |
- | }
| |
- | } else if (!tok.unary) {
| |
- | stack.push(tok);
| |
- | }
| |
- | },
| |
- |
| |
- | endTag: function(tok) {
| |
- | var last = stack.last();
| |
- | if(last) {
| |
- | if(options.fix_tagSoup && !stack.lastTagNameEq(tok.tagName)) {
| |
- | // cleanup tag soup
| |
- | closeLast();
| |
- | } else {
| |
- | stack.pop();
| |
- | }
| |
- | } else if (options.fix_tagSoup) {
| |
- | // cleanup tag soup part 2: skip this token
| |
- | skipToken();
| |
- | }
| |
- | }
| |
- | };
| |
- |
| |
- | var skipToken = function() {
| |
- | // shift the next token
| |
- | readTokenImpl();
| |
- |
| |
- | prepareNextToken();
| |
- | };
| |
- |
| |
- | var prepareNextToken = function() {
| |
- | var tok = peekToken();
| |
- | if(tok && handlers[tok.type]) {
| |
- | handlers[tok.type](tok);
| |
- | }
| |
- | };
| |
- |
| |
- | // redefine readToken
| |
- | readToken = function() {
| |
- | prepareNextToken();
| |
- | return correct(readTokenImpl());
| |
- | };
| |
- | })();
| |
- | }
| |
- |
| |
- | return {
| |
- | append: append,
| |
- | readToken: readToken,
| |
- | readTokens: readTokens,
| |
- | clear: clear,
| |
- | rest: rest,
| |
- | stack: stack
| |
- | };
| |
- |
| |
- | }
| |
- |
| |
- | htmlParser.supports = supports;
| |
- |
| |
- | htmlParser.tokenToString = function(tok) {
| |
- | var handler = {
| |
- | comment: function(tok) {
| |
- | return '<--' + tok.content + '-->';
| |
- | },
| |
- | endTag: function(tok) {
| |
- | return '</'+tok.tagName+'>';
| |
- | },
| |
- | atomicTag: function(tok) {
| |
- | console.log(tok);
| |
- | return handler.startTag(tok) +
| |
- | tok.content +
| |
- | handler.endTag(tok);
| |
- | },
| |
- | startTag: function(tok) {
| |
- | var str = '<'+tok.tagName;
| |
- | for (var key in tok.attrs) {
| |
- | var val = tok.attrs[key];
| |
- | // escape quotes
| |
- | str += ' '+key+'="'+(val ? val.replace(/(^|[^\\])"/g, '$1\\\"') : '')+'"';
| |
- | }
| |
- | return str + (tok.unary ? '/>' : '>');
| |
- | },
| |
- | chars: function(tok) {
| |
- | return tok.text;
| |
- | }
| |
- | };
| |
- | return handler[tok.type](tok);
| |
- | };
| |
- |
| |
- | htmlParser.escapeAttributes = function(attrs) {
| |
- | var escapedAttrs = {};
| |
- | // escape double-quotes for writing html as a string
| |
- |
| |
- | for(var name in attrs) {
| |
- | var value = attrs[name];
| |
- | escapedAttrs[name] = value && value.replace(/(^|[^\\])"/g, '$1\\\"');
| |
- | }
| |
- | return escapedAttrs;
| |
- | };
| |
- |
| |
- | for(var key in supports) {
| |
- | htmlParser.browserHasFlaw = htmlParser.browserHasFlaw || (!supports[key]) && key;
| |
- | }
| |
- |
| |
- | this.htmlParser = htmlParser;
| |
- | })();
| |
| </script> | | </script> |
| | | |
- | | + | <script src="http://lokeshdhakar.com/projects/lightbox2/js/jquery-1.11.0.min.js"></script> |
- | | + | <script src="http://lokeshdhakar.com/projects/lightbox2/js/lightbox.js"></script> |
- | <script> | + | |
- | | + | |
- | // postscribe.js 1.1.2
| + | |
- | // (c) Copyright 2012 to the present, Krux
| + | |
- | // postscribe is freely distributable under the MIT license.
| + | |
- | // For all details and documentation:
| + | |
- | // http://krux.github.com/postscribe
| + | |
- | | + | |
- | | + | |
- | (function() {
| + | |
- | | + | |
- | var global = this;
| + | |
- | | + | |
- | if(global.postscribe) {
| + | |
- | return;
| + | |
- | }
| + | |
- | | + | |
- | // Debug write tasks.
| + | |
- | var DEBUG = true;
| + | |
- | | + | |
- | // Turn on to debug how each chunk affected the DOM.
| + | |
- | var DEBUG_CHUNK = false;
| + | |
- | | + | |
- | // # Helper Functions
| + | |
- | | + | |
- | var slice = Array.prototype.slice;
| + | |
- | | + | |
- | // A function that intentionally does nothing.
| + | |
- | function doNothing() {}
| + | |
- | | + | |
- | | + | |
- | // Is this a function?
| + | |
- | function isFunction(x) {
| + | |
- | return "function" === typeof x;
| + | |
- | }
| + | |
- | | + | |
- | // Loop over each item in an array-like value.
| + | |
- | function each(arr, fn, _this) {
| + | |
- | var i, len = (arr && arr.length) || 0;
| + | |
- | for(i = 0; i < len; i++) {
| + | |
- | fn.call(_this, arr[i], i);
| + | |
- | }
| + | |
- | }
| + | |
- | | + | |
- | // Loop over each key/value pair in a hash.
| + | |
- | function eachKey(obj, fn, _this) {
| + | |
- | var key;
| + | |
- | for(key in obj) {
| + | |
- | if(obj.hasOwnProperty(key)) {
| + | |
- | fn.call(_this, key, obj[key]);
| + | |
- | }
| + | |
- | }
| + | |
- | }
| + | |
- | | + | |
- | // Set properties on an object.
| + | |
- | function set(obj, props) {
| + | |
- | eachKey(props, function(key, value) {
| + | |
- | obj[key] = value;
| + | |
- | });
| + | |
- | return obj;
| + | |
- | }
| + | |
- | | + | |
- | // Set default options where some option was not specified.
| + | |
- | function defaults(options, _defaults) {
| + | |
- | options = options || {};
| + | |
- | eachKey(_defaults, function(key, val) {
| + | |
- | if(options[key] == null) {
| + | |
- | options[key] = val;
| + | |
- | }
| + | |
- | });
| + | |
- | return options;
| + | |
- | }
| + | |
- | | + | |
- | // Convert value (e.g., a NodeList) to an array.
| + | |
- | function toArray(obj) {
| + | |
- | try {
| + | |
- | return slice.call(obj);
| + | |
- | } catch(e) {
| + | |
- | var ret = [];
| + | |
- | each(obj, function(val) {
| + | |
- | ret.push(val);
| + | |
- | });
| + | |
- | return ret;
| + | |
- | }
| + | |
- | }
| + | |
- | | + | |
- | // Test if token is a script tag.
| + | |
- | function isScript(tok) {
| + | |
- | return (/^script$/i).test(tok.tagName);
| + | |
- | }
| + | |
- | | + | |
- | // # Class WriteStream
| + | |
- | | + | |
- | // Stream static html to an element, where "static html" denotes "html without scripts".
| + | |
- | | + | |
- | // This class maintains a *history of writes devoid of any attributes* or "proxy history".
| + | |
- | // Injecting the proxy history into a temporary div has no side-effects,
| + | |
- | // other than to create proxy elements for previously written elements.
| + | |
- | | + | |
- | // Given the `staticHtml` of a new write, a `tempDiv`'s innerHTML is set to `proxy_history + staticHtml`.
| + | |
- | // The *structure* of `tempDiv`'s contents, (i.e., the placement of new nodes beside or inside of proxy elements),
| + | |
- | // reflects the DOM structure that would have resulted if all writes had been squashed into a single write.
| + | |
- | | + | |
- | // For each descendent `node` of `tempDiv` whose parentNode is a *proxy*, `node` is appended to the corresponding *real* element within the DOM.
| + | |
- | | + | |
- | // Proxy elements are mapped to *actual* elements in the DOM by injecting a data-id attribute into each start tag in `staticHtml`.
| + | |
- | var WriteStream = (function(){
| + | |
- | | + | |
- | // Prefix for data attributes on DOM elements.
| + | |
- | var BASEATTR = 'data-ps-';
| + | |
- | | + | |
- | // get / set data attributes
| + | |
- | function data(el, name, value) {
| + | |
- | var attr = BASEATTR + name;
| + | |
- | | + | |
- | if(arguments.length === 2) {
| + | |
- | // Get
| + | |
- | var val = el.getAttribute(attr);
| + | |
- | | + | |
- | // IE 8 returns a number if it's a number
| + | |
- | return val == null ? val : String(val);
| + | |
- | | + | |
- | } else if( value != null && value !== '') {
| + | |
- | // Set
| + | |
- | el.setAttribute(attr, value);
| + | |
- | | + | |
- | } else {
| + | |
- | // Remove
| + | |
- | el.removeAttribute(attr);
| + | |
- | }
| + | |
- | }
| + | |
- | | + | |
- | function WriteStream(root, options) {
| + | |
- | var doc = root.ownerDocument;
| + | |
- | | + | |
- | set(this, {
| + | |
- | root: root,
| + | |
- | | + | |
- | options: options,
| + | |
- | | + | |
- | win: doc.defaultView || doc.parentWindow,
| + | |
- | | + | |
- | doc: doc,
| + | |
- | | + | |
- | parser: global.htmlParser('', { autoFix: true }),
| + | |
- | | + | |
- | // Actual elements by id.
| + | |
- | actuals: [root],
| + | |
- | | + | |
- | // Embodies the "structure" of what's been written so far, devoid of attributes.
| + | |
- | proxyHistory: '',
| + | |
- | | + | |
- | // Create a proxy of the root element.
| + | |
- | proxyRoot: doc.createElement(root.nodeName),
| + | |
- | | + | |
- | scriptStack: [],
| + | |
- | | + | |
- | writeQueue: []
| + | |
- | });
| + | |
- | | + | |
- | data(this.proxyRoot, 'proxyof', 0);
| + | |
- | | + | |
- | }
| + | |
- | | + | |
- | | + | |
- | WriteStream.prototype.write = function() {
| + | |
- | [].push.apply(this.writeQueue, arguments);
| + | |
- | // Process writes
| + | |
- | // When new script gets pushed or pending this will stop
| + | |
- | // because new writeQueue gets pushed
| + | |
- | var arg;
| + | |
- | while(!this.deferredRemote &&
| + | |
- | this.writeQueue.length) {
| + | |
- | arg = this.writeQueue.shift();
| + | |
- | | + | |
- | if(isFunction(arg)) {
| + | |
- | this.callFunction(arg);
| + | |
- | } else {
| + | |
- | this.writeImpl(arg);
| + | |
- | }
| + | |
- | }
| + | |
- | };
| + | |
- | | + | |
- | WriteStream.prototype.callFunction = function(fn) {
| + | |
- | var tok = { type: "function", value: fn.name || fn.toString() };
| + | |
- | this.onScriptStart(tok);
| + | |
- | fn.call(this.win, this.doc);
| + | |
- | this.onScriptDone(tok);
| + | |
- | };
| + | |
- | | + | |
- | WriteStream.prototype.writeImpl = function(html) {
| + | |
- | this.parser.append(html);
| + | |
- | | + | |
- | var tok, tokens = [];
| + | |
- | | + | |
- | // stop if we see a script token
| + | |
- | while((tok = this.parser.readToken()) && !isScript(tok)) {
| + | |
- | tokens.push(tok);
| + | |
- | }
| + | |
- | | + | |
- | this.writeStaticTokens(tokens);
| + | |
- | | + | |
- | if(tok) {
| + | |
- | this.handleScriptToken(tok);
| + | |
- | }
| + | |
- | };
| + | |
- | | + | |
- | | + | |
- | // ## Contiguous non-script tokens (a chunk)
| + | |
- | WriteStream.prototype.writeStaticTokens = function(tokens) {
| + | |
- | | + | |
- | var chunk = this.buildChunk(tokens);
| + | |
- | | + | |
- | if(!chunk.actual) {
| + | |
- | // e.g., no tokens, or a noscript that got ignored
| + | |
- | return;
| + | |
- | }
| + | |
- | chunk.html = this.proxyHistory + chunk.actual;
| + | |
- | this.proxyHistory += chunk.proxy;
| + | |
- | | + | |
- | this.proxyRoot.innerHTML = chunk.html;
| + | |
- | | + | |
- | if(DEBUG_CHUNK) {
| + | |
- | chunk.proxyInnerHTML = this.proxyRoot.innerHTML;
| + | |
- | }
| + | |
- | | + | |
- | this.walkChunk();
| + | |
- | | + | |
- | if(DEBUG_CHUNK) {
| + | |
- | chunk.actualInnerHTML = this.root.innerHTML; //root
| + | |
- | }
| + | |
- | | + | |
- | return chunk;
| + | |
- | };
| + | |
- | | + | |
- | | + | |
- | WriteStream.prototype.buildChunk = function (tokens) {
| + | |
- | var nextId = this.actuals.length,
| + | |
- | | + | |
- | // The raw html of this chunk.
| + | |
- | raw = [],
| + | |
- | | + | |
- | // The html to create the nodes in the tokens (with id's injected).
| + | |
- | actual = [],
| + | |
- | | + | |
- | // Html that can later be used to proxy the nodes in the tokens.
| + | |
- | proxy = [];
| + | |
- | | + | |
- | each(tokens, function(tok) {
| + | |
- | | + | |
- | raw.push(tok.text);
| + | |
- | | + | |
- | if(tok.attrs) { // tok.attrs <==> startTag or atomicTag or cursor
| + | |
- | // Ignore noscript tags. They are atomic, so we don't have to worry about children.
| + | |
- | if(!(/^noscript$/i).test(tok.tagName)) {
| + | |
- | var id = nextId++;
| + | |
- | | + | |
- | // Actual: inject id attribute: replace '>' at end of start tag with id attribute + '>'
| + | |
- | actual.push(
| + | |
- | tok.text.replace(/(\/?>)/, ' '+BASEATTR+'id='+id+' $1')
| + | |
- | );
| + | |
- | | + | |
- | // Don't proxy scripts: they have no bearing on DOM structure.
| + | |
- | if(tok.attrs.id !== "ps-script") {
| + | |
- | // Proxy: strip all attributes and inject proxyof attribute
| + | |
- | proxy.push(
| + | |
- | // ignore atomic tags (e.g., style): they have no "structural" effect
| + | |
- | tok.type === 'atomicTag' ? '' :
| + | |
- | '<'+tok.tagName+' '+BASEATTR+'proxyof='+id+(tok.unary ? '/>' : '>')
| + | |
- | );
| + | |
- | }
| + | |
- | }
| + | |
- | | + | |
- | } else {
| + | |
- | // Visit any other type of token
| + | |
- | // Actual: append.
| + | |
- | actual.push(tok.text);
| + | |
- | // Proxy: append endTags. Ignore everything else.
| + | |
- | proxy.push(tok.type === 'endTag' ? tok.text : '');
| + | |
- | }
| + | |
- | });
| + | |
- | | + | |
- | return {
| + | |
- | tokens: tokens,
| + | |
- | raw: raw.join(''),
| + | |
- | actual: actual.join(''),
| + | |
- | proxy: proxy.join('')
| + | |
- | };
| + | |
- | };
| + | |
- | | + | |
- | WriteStream.prototype.walkChunk = function() {
| + | |
- | var node, stack = [this.proxyRoot];
| + | |
- | | + | |
- | // use shift/unshift so that children are walked in document order
| + | |
- | | + | |
- | while((node = stack.shift()) != null) {
| + | |
- | | + | |
- | var isElement = node.nodeType === 1;
| + | |
- | var isProxy = isElement && data(node, 'proxyof');
| + | |
- | | + | |
- | // Ignore proxies
| + | |
- | if(!isProxy) {
| + | |
- | | + | |
- | if(isElement) {
| + | |
- | // New actual element: register it and remove the the id attr.
| + | |
- | this.actuals[data(node, 'id')] = node;
| + | |
- | data(node, 'id', null);
| + | |
- | }
| + | |
- | | + | |
- | // Is node's parent a proxy?
| + | |
- | var parentIsProxyOf = node.parentNode && data(node.parentNode, 'proxyof');
| + | |
- | if(parentIsProxyOf) {
| + | |
- | // Move node under actual parent.
| + | |
- | this.actuals[parentIsProxyOf].appendChild(node);
| + | |
- | }
| + | |
- | }
| + | |
- | // prepend childNodes to stack
| + | |
- | stack.unshift.apply(stack, toArray(node.childNodes));
| + | |
- | }
| + | |
- | };
| + | |
- | | + | |
- | // ### Script tokens
| + | |
- | | + | |
- | WriteStream.prototype.handleScriptToken = function(tok) {
| + | |
- | var remainder = this.parser.clear();
| + | |
- | | + | |
- | if(remainder) {
| + | |
- | // Write remainder immediately behind this script.
| + | |
- | this.writeQueue.unshift(remainder);
| + | |
- | }
| + | |
- | | + | |
- | tok.src = tok.attrs.src || tok.attrs.SRC;
| + | |
- | | + | |
- | if(tok.src && this.scriptStack.length) {
| + | |
- | // Defer this script until scriptStack is empty.
| + | |
- | // Assumption 1: This script will not start executing until
| + | |
- | // scriptStack is empty.
| + | |
- | this.deferredRemote = tok;
| + | |
- | } else {
| + | |
- | this.onScriptStart(tok);
| + | |
- | }
| + | |
- | | + | |
- | // Put the script node in the DOM.
| + | |
- | var _this = this;
| + | |
- | this.writeScriptToken(tok, function() {
| + | |
- | _this.onScriptDone(tok);
| + | |
- | });
| + | |
- | | + | |
- | };
| + | |
- | | + | |
- | WriteStream.prototype.onScriptStart = function(tok) {
| + | |
- | tok.outerWrites = this.writeQueue;
| + | |
- | this.writeQueue = [];
| + | |
- | this.scriptStack.unshift(tok);
| + | |
- | };
| + | |
- | | + | |
- | WriteStream.prototype.onScriptDone = function(tok) {
| + | |
- | // Pop script and check nesting.
| + | |
- | if(tok !== this.scriptStack[0]) {
| + | |
- | this.options.error({ message: "Bad script nesting or script finished twice" });
| + | |
- | return;
| + | |
- | }
| + | |
- | this.scriptStack.shift();
| + | |
- | | + | |
- | // Append outer writes to queue and process them.
| + | |
- | this.write.apply(this, tok.outerWrites);
| + | |
- | | + | |
- | // Check for pending remote
| + | |
- | | + | |
- | // Assumption 2: if remote_script1 writes remote_script2 then
| + | |
- | // the we notice remote_script1 finishes before remote_script2 starts.
| + | |
- | // I think this is equivalent to assumption 1
| + | |
- | if(!this.scriptStack.length && this.deferredRemote) {
| + | |
- | this.onScriptStart(this.deferredRemote);
| + | |
- | this.deferredRemote = null;
| + | |
- | }
| + | |
- | };
| + | |
- | | + | |
- | // Build a script and insert it into the DOM.
| + | |
- | // Done is called once script has executed.
| + | |
- | WriteStream.prototype.writeScriptToken = function(tok, done) {
| + | |
- | var el = this.buildScript(tok);
| + | |
- | | + | |
- | if(tok.src) {
| + | |
- | // Fix for attribute "SRC" (capitalized). IE does not recognize it.
| + | |
- | el.src = tok.src;
| + | |
- | this.scriptLoadHandler(el, done);
| + | |
- | }
| + | |
- | | + | |
- | try {
| + | |
- | this.insertScript(el);
| + | |
- | if(!tok.src) {
| + | |
- | done();
| + | |
- | }
| + | |
- | } catch(e) {
| + | |
- | this.options.error(e);
| + | |
- | done();
| + | |
- | }
| + | |
- | };
| + | |
- | | + | |
- | // Build a script element from an atomic script token.
| + | |
- | WriteStream.prototype.buildScript = function(tok) {
| + | |
- | var el = this.doc.createElement(tok.tagName);
| + | |
- | | + | |
- | // Set attributes
| + | |
- | eachKey(tok.attrs, function(name, value) {
| + | |
- | el.setAttribute(name, value);
| + | |
- | });
| + | |
- | | + | |
- | // Set content
| + | |
- | if(tok.content) {
| + | |
- | el.text = tok.content;
| + | |
- | }
| + | |
- | | + | |
- | return el;
| + | |
- | };
| + | |
- | | + | |
- | | + | |
- | // Insert script into DOM where it would naturally be written.
| + | |
- | WriteStream.prototype.insertScript = function(el) {
| + | |
- | // Append a span to the stream. That span will act as a cursor
| + | |
- | // (i.e. insertion point) for the script.
| + | |
- | this.writeImpl('<span id="ps-script"/>');
| + | |
- | | + | |
- | // Grab that span from the DOM.
| + | |
- | var cursor = this.doc.getElementById("ps-script");
| + | |
- | | + | |
- | // Replace cursor with script.
| + | |
- | cursor.parentNode.replaceChild(el, cursor);
| + | |
- | };
| + | |
- | | + | |
- | | + | |
- | WriteStream.prototype.scriptLoadHandler = function(el, done) {
| + | |
- | function cleanup() {
| + | |
- | el = el.onload = el.onreadystatechange = el.onerror = null;
| + | |
- | done();
| + | |
- | }
| + | |
- | | + | |
- | // Error handler
| + | |
- | var error = this.options.error;
| + | |
- | | + | |
- | // Set handlers
| + | |
- | set(el, {
| + | |
- | onload: function() { cleanup(); },
| + | |
- | | + | |
- | onreadystatechange: function() {
| + | |
- | if(/^(loaded|complete)$/.test( el.readyState )) {
| + | |
- | cleanup();
| + | |
- | }
| + | |
- | },
| + | |
- | | + | |
- | onerror: function() {
| + | |
- | error({ message: 'remote script failed ' + el.src });
| + | |
- | cleanup();
| + | |
- | }
| + | |
- | });
| + | |
- | };
| + | |
- | | + | |
- | return WriteStream;
| + | |
- | | + | |
- | }());
| + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | | + | |
- | // Public-facing interface and queuing
| + | |
- | var postscribe = (function() {
| + | |
- | var nextId = 0;
| + | |
- | | + | |
- | var queue = [];
| + | |
- | | + | |
- | var active = null;
| + | |
- | | + | |
- | function nextStream() {
| + | |
- | var args = queue.shift();
| + | |
- | if(args) {
| + | |
- | args.stream = runStream.apply(null, args);
| + | |
- | }
| + | |
- | }
| + | |
- | | + | |
- | | + | |
- | function runStream(el, html, options) {
| + | |
- | active = new WriteStream(el, options);
| + | |
- | | + | |
- | // Identify this stream.
| + | |
- | active.id = nextId++;
| + | |
- | active.name = options.name || active.id;
| + | |
- | postscribe.streams[active.name] = active;
| + | |
- | | + | |
- | // Override document.write.
| + | |
- | var doc = el.ownerDocument;
| + | |
- | | + | |
- | var stash = { write: doc.write, writeln: doc.writeln };
| + | |
- | | + | |
- | function write(str) {
| + | |
- | str = options.beforeWrite(str);
| + | |
- | active.write(str);
| + | |
- | options.afterWrite(str);
| + | |
- | }
| + | |
- | | + | |
- | set(doc, { write: write, writeln: function(str) { write(str + '\n'); } });
| + | |
- | | + | |
- | // Override window.onerror
| + | |
- | var oldOnError = active.win.onerror || doNothing;
| + | |
- | | + | |
- | // This works together with the try/catch around WriteStream::insertScript
| + | |
- | // In modern browsers, exceptions in tag scripts go directly to top level
| + | |
- | active.win.onerror = function(msg, url, line) {
| + | |
- | options.error({ msg: msg + ' - ' + url + ':' + line });
| + | |
- | oldOnError.apply(active.win, arguments);
| + | |
- | };
| + | |
- | | + | |
- | // Write to the stream
| + | |
- | active.write(html, function streamDone() {
| + | |
- | // restore document.write
| + | |
- | set(doc, stash);
| + | |
- | | + | |
- | // restore window.onerror
| + | |
- | active.win.onerror = oldOnError;
| + | |
- | | + | |
- | options.done();
| + | |
- | active = null;
| + | |
- | nextStream();
| + | |
- | });
| + | |
- | | + | |
- | return active;
| + | |
- | }
| + | |
- | | + | |
- | | + | |
- | function postscribe(el, html, options) {
| + | |
- | if(isFunction(options)) {
| + | |
- | options = { done: options };
| + | |
- | }
| + | |
- | options = defaults(options, {
| + | |
- | done: doNothing,
| + | |
- | error: function(e) { throw e; },
| + | |
- | beforeWrite: function(str) { return str; },
| + | |
- | afterWrite: doNothing
| + | |
- | });
| + | |
- | | + | |
- | el =
| + | |
- | // id selector
| + | |
- | (/^#/).test(el) ? global.document.getElementById(el.substr(1)) :
| + | |
- | // jquery object. TODO: loop over all elements.
| + | |
- | el.jquery ? el[0] : el;
| + | |
- | | + | |
- | | + | |
- | var args = [el, html, options];
| + | |
- | | + | |
- | el.postscribe = {
| + | |
- | cancel: function() {
| + | |
- | if(args.stream) {
| + | |
- | // TODO: implement this
| + | |
- | args.stream.abort();
| + | |
- | } else {
| + | |
- | args[1] = doNothing;
| + | |
- | }
| + | |
- | }
| + | |
- | };
| + | |
- | | + | |
- | queue.push(args);
| + | |
- | if(!active) {
| + | |
- | nextStream();
| + | |
- | }
| + | |
- | | + | |
- | return el.postscribe;
| + | |
- | }
| + | |
- | | + | |
- | return set(postscribe, {
| + | |
- | // Streams by name.
| + | |
- | streams: {},
| + | |
- | // Queue of streams.
| + | |
- | queue: queue,
| + | |
- | // Expose internal classes.
| + | |
- | WriteStream: WriteStream
| + | |
- | });
| + | |
- | | + | |
- | }());
| + | |
- | | + | |
- | // export postscribe
| + | |
- | global.postscribe = postscribe;
| + | |
- | | + | |
- | }());
| + | |
- | | + | |
- | </script> | + | |
| | | |
| </html> | | </html> |