/*global setImmediate: false, setTimeout: false, console: false */
(function () {

    var async = {};

    // global on the server, window in the browser
    var root, previous_async;

    root = this;
    if (root != null) {
      previous_async = root.async;
    }

    async.noConflict = function () {
        root.async = previous_async;
        return async;
    };

    function only_once(fn) {
        var called = false;
        return function() {
            if (called) throw new Error("Callback was already called.");
            called = true;
            fn.apply(root, arguments);
        }
    }

    //// cross-browser compatiblity functions ////

    var _each = function (arr, iterator) {
        if (arr.forEach) {
            return arr.forEach(iterator);
        }
        for (var i = 0; i < arr.length; i += 1) {
            iterator(arr[i], i, arr);
        }
    };

    var _map = function (arr, iterator) {
        if (arr.map) {
            return arr.map(iterator);
        }
        var results = [];
        _each(arr, function (x, i, a) {
            results.push(iterator(x, i, a));
        });
        return results;
    };

    var _reduce = function (arr, iterator, memo) {
        if (arr.reduce) {
            return arr.reduce(iterator, memo);
        }
        _each(arr, function (x, i, a) {
            memo = iterator(memo, x, i, a);
        });
        return memo;
    };

    var _keys = function (obj) {
        if (Object.keys) {
            return Object.keys(obj);
        }
        var keys = [];
        for (var k in obj) {
            if (obj.hasOwnProperty(k)) {
                keys.push(k);
            }
        }
        return keys;
    };

    //// exported async module functions ////

    //// nextTick implementation with browser-compatible fallback ////
    if (typeof process === 'undefined' || !(process.nextTick)) {
        if (typeof setImmediate === 'function') {
            async.nextTick = function (fn) {
                setImmediate(fn);
            };
        }
        else {
            async.nextTick = function (fn) {
                setTimeout(fn, 0);
            };
        }
    }
    else {
        async.nextTick = process.nextTick;
    }

    async.each = function (arr, iterator, callback) {
        callback = callback || function () {};
        if (!arr.length) {
            return callback();
        }
        var completed = 0;
        _each(arr, function (x) {
            iterator(x, only_once(function (err) {
                if (err) {
                    callback(err);
                    callback = function () {};
                }
                else {
                    completed += 1;
                    if (completed >= arr.length) {
                        callback(null);
                    }
                }
            }));
        });
    };
    async.forEach = async.each;

    async.eachSeries = function (arr, iterator, callback) {
        callback = callback || function () {};
        if (!arr.length) {
            return callback();
        }
        var completed = 0;
        var iterate = function () {
            var sync = true;
            iterator(arr[completed], function (err) {
                if (err) {
                    callback(err);
                    callback = function () {};
                }
                else {
                    completed += 1;
                    if (completed >= arr.length) {
                        callback(null);
                    }
                    else {
                        if (sync) {
                            async.nextTick(iterate);
                        }
                        else {
                            iterate();
                        }
                    }
                }
            });
            sync = false;
        };
        iterate();
    };
    async.forEachSeries = async.eachSeries;

    async.eachLimit = function (arr, limit, iterator, callback) {
        var fn = _eachLimit(limit);
        fn.apply(null, [arr, iterator, callback]);
    };
    async.forEachLimit = async.eachLimit;

    var _eachLimit = function (limit) {

        return function (arr, iterator, callback) {
            callback = callback || function () {};
            if (!arr.length || limit <= 0) {
                return callback();
            }
            var completed = 0;
            var started = 0;
            var running = 0;

            (function replenish () {
                if (completed >= arr.length) {
                    return callback();
                }

                while (running < limit && started < arr.length) {
                    started += 1;
                    running += 1;
                    iterator(arr[started - 1], function (err) {
                        if (err) {
                            callback(err);
                            callback = function () {};
                        }
                        else {
                            completed += 1;
                            running -= 1;
                            if (completed >= arr.length) {
                                callback();
                            }
                            else {
                                replenish();
                            }
                        }
                    });
                }
            })();
        };
    };

    var doParallel = function (fn) {
        return function () {
            var args = Array.prototype.slice.call(arguments);
            return fn.apply(null, [async.each].concat(args));
        };
    };
    var doParallelLimit = function(limit, fn) {
        return function () {
            var args = Array.prototype.slice.call(arguments);
            return fn.apply(null, [_eachLimit(limit)].concat(args));
        };
    };
    var doSeries = function (fn) {
        return function () {
            var args = Array.prototype.slice.call(arguments);
            return fn.apply(null, [async.eachSeries].concat(args));
        };
    };

    var _asyncMap = function (eachfn, arr, iterator, callback) {
        var results = [];
        arr = _map(arr, function (x, i) {
            return {index: i, value: x};
        });
        eachfn(arr, function (x, callback) {
            iterator(x.value, function (err, v) {
                results[x.index] = v;
                callback(err);
            });
        }, function (err) {
            callback(err, results);
        });
    };
    async.map = doParallel(_asyncMap);
    async.mapSeries = doSeries(_asyncMap);
    async.mapLimit = function (arr, limit, iterator, callback) {
        return _mapLimit(limit)(arr, iterator, callback);
    };

    var _mapLimit = function(limit) {
        return doParallelLimit(limit, _asyncMap);
    };

    // reduce only has a series version, as doing reduce in parallel won't
    // work in many situations.
    async.reduce = function (arr, memo, iterator, callback) {
        async.eachSeries(arr, function (x, callback) {
            iterator(memo, x, function (err, v) {
                memo = v;
                callback(err);
            });
        }, function (err) {
            callback(err, memo);
        });
    };
    // inject alias
    async.inject = async.reduce;
    // foldl alias
    async.foldl = async.reduce;

    async.reduceRight = function (arr, memo, iterator, callback) {
        var reversed = _map(arr, function (x) {
            return x;
        }).reverse();
        async.reduce(reversed, memo, iterator, callback);
    };
    // foldr alias
    async.foldr = async.reduceRight;

    var _filter = function (eachfn, arr, iterator, callback) {
        var results = [];
        arr = _map(arr, function (x, i) {
            return {index: i, value: x};
        });
        eachfn(arr, function (x, callback) {
            iterator(x.value, function (v) {
                if (v) {
                    results.push(x);
                }
                callback();
            });
        }, function (err) {
            callback(_map(results.sort(function (a, b) {
                return a.index - b.index;
            }), function (x) {
                return x.value;
            }));
        });
    };
    async.filter = doParallel(_filter);
    async.filterSeries = doSeries(_filter);
    // select alias
    async.select = async.filter;
    async.selectSeries = async.filterSeries;

    var _reject = function (eachfn, arr, iterator, callback) {
        var results = [];
        arr = _map(arr, function (x, i) {
            return {index: i, value: x};
        });
        eachfn(arr, function (x, callback) {
            iterator(x.value, function (v) {
                if (!v) {
                    results.push(x);
                }
                callback();
            });
        }, function (err) {
            callback(_map(results.sort(function (a, b) {
                return a.index - b.index;
            }), function (x) {
                return x.value;
            }));
        });
    };
    async.reject = doParallel(_reject);
    async.rejectSeries = doSeries(_reject);

    var _detect = function (eachfn, arr, iterator, main_callback) {
        eachfn(arr, function (x, callback) {
            iterator(x, function (result) {
                if (result) {
                    main_callback(x);
                    main_callback = function () {};
                }
                else {
                    callback();
                }
            });
        }, function (err) {
            main_callback();
        });
    };
    async.detect = doParallel(_detect);
    async.detectSeries = doSeries(_detect);

    async.some = function (arr, iterator, main_callback) {
        async.each(arr, function (x, callback) {
            iterator(x, function (v) {
                if (v) {
                    main_callback(true);
                    main_callback = function () {};
                }
                callback();
            });
        }, function (err) {
            main_callback(false);
        });
    };
    // any alias
    async.any = async.some;

    async.every = function (arr, iterator, main_callback) {
        async.each(arr, function (x, callback) {
            iterator(x, function (v) {
                if (!v) {
                    main_callback(false);
                    main_callback = function () {};
                }
                callback();
            });
        }, function (err) {
            main_callback(true);
        });
    };
    // all alias
    async.all = async.every;

    async.sortBy = function (arr, iterator, callback) {
        async.map(arr, function (x, callback) {
            iterator(x, function (err, criteria) {
                if (err) {
                    callback(err);
                }
                else {
                    callback(null, {value: x, criteria: criteria});
                }
            });
        }, function (err, results) {
            if (err) {
                return callback(err);
            }
            else {
                var fn = function (left, right) {
                    var a = left.criteria, b = right.criteria;
                    return a < b ? -1 : a > b ? 1 : 0;
                };
                callback(null, _map(results.sort(fn), function (x) {
                    return x.value;
                }));
            }
        });
    };

    async.auto = function (tasks, callback) {
        callback = callback || function () {};
        var keys = _keys(tasks);
        if (!keys.length) {
            return callback(null);
        }

        var results = {};

        var listeners = [];
        var addListener = function (fn) {
            listeners.unshift(fn);
        };
        var removeListener = function (fn) {
            for (var i = 0; i < listeners.length; i += 1) {
                if (listeners[i] === fn) {
                    listeners.splice(i, 1);
                    return;
                }
            }
        };
        var taskComplete = function () {
            _each(listeners.slice(0), function (fn) {
                fn();
            });
        };

        addListener(function () {
            if (_keys(results).length === keys.length) {
                callback(null, results);
                callback = function () {};
            }
        });

        _each(keys, function (k) {
            var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k];
            var taskCallback = function (err) {
                var args = Array.prototype.slice.call(arguments, 1);
                if (args.length <= 1) {
                    args = args[0];
                }
                if (err) {
                    var safeResults = {};
                    _each(_keys(results), function(rkey) {
                        safeResults[rkey] = results[rkey];
                    });
                    safeResults[k] = args;
                    callback(err, safeResults);
                    // stop subsequent errors hitting callback multiple times
                    callback = function () {};
                }
                else {
                    results[k] = args;
                    async.nextTick(taskComplete);
                }
            };
            var requires = task.slice(0, Math.abs(task.length - 1)) || [];
            var ready = function () {
                return _reduce(requires, function (a, x) {
                    return (a && results.hasOwnProperty(x));
                }, true) && !results.hasOwnProperty(k);
            };
            if (ready()) {
                task[task.length - 1](taskCallback, results);
            }
            else {
                var listener = function () {
                    if (ready()) {
                        removeListener(listener);
                        task[task.length - 1](taskCallback, results);
                    }
                };
                addListener(listener);
            }
        });
    };

    async.waterfall = function (tasks, callback) {
        callback = callback || function () {};
        if (!tasks.length) {
            return callback();
        }
        var wrapIterator = function (iterator) {
            return function (err) {
                if (err) {
                    callback.apply(null, arguments);
                    callback = function () {};
                }
                else {
                    var args = Array.prototype.slice.call(arguments, 1);
                    var next = iterator.next();
                    if (next) {
                        args.push(wrapIterator(next));
                    }
                    else {
                        args.push(callback);
                    }
                    async.nextTick(function () {
                        iterator.apply(null, args);
                    });
                }
            };
        };
        wrapIterator(async.iterator(tasks))();
    };

    var _parallel = function(eachfn, tasks, callback) {
        callback = callback || function () {};
        if (tasks.constructor === Array) {
            eachfn.map(tasks, function (fn, callback) {
                if (fn) {
                    fn(function (err) {
                        var args = Array.prototype.slice.call(arguments, 1);
                        if (args.length <= 1) {
                            args = args[0];
                        }
                        callback.call(null, err, args);
                    });
                }
            }, callback);
        }
        else {
            var results = {};
            eachfn.each(_keys(tasks), function (k, callback) {
                tasks[k](function (err) {
                    var args = Array.prototype.slice.call(arguments, 1);
                    if (args.length <= 1) {
                        args = args[0];
                    }
                    results[k] = args;
                    callback(err);
                });
            }, function (err) {
                callback(err, results);
            });
        }
    };

    async.parallel = function (tasks, callback) {
        _parallel({ map: async.map, each: async.each }, tasks, callback);
    };

    async.parallelLimit = function(tasks, limit, callback) {
        _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback);
    };

    async.series = function (tasks, callback) {
        callback = callback || function () {};
        if (tasks.constructor === Array) {
            async.mapSeries(tasks, function (fn, callback) {
                if (fn) {
                    fn(function (err) {
                        var args = Array.prototype.slice.call(arguments, 1);
                        if (args.length <= 1) {
                            args = args[0];
                        }
                        callback.call(null, err, args);
                    });
                }
            }, callback);
        }
        else {
            var results = {};
            async.eachSeries(_keys(tasks), function (k, callback) {
                tasks[k](function (err) {
                    var args = Array.prototype.slice.call(arguments, 1);
                    if (args.length <= 1) {
                        args = args[0];
                    }
                    results[k] = args;
                    callback(err);
                });
            }, function (err) {
                callback(err, results);
            });
        }
    };

    async.iterator = function (tasks) {
        var makeCallback = function (index) {
            var fn = function () {
                if (tasks.length) {
                    tasks[index].apply(null, arguments);
                }
                return fn.next();
            };
            fn.next = function () {
                return (index < tasks.length - 1) ? makeCallback(index + 1): null;
            };
            return fn;
        };
        return makeCallback(0);
    };

    async.apply = function (fn) {
        var args = Array.prototype.slice.call(arguments, 1);
        return function () {
            return fn.apply(
                null, args.concat(Array.prototype.slice.call(arguments))
            );
        };
    };

    var _concat = function (eachfn, arr, fn, callback) {
        var r = [];
        eachfn(arr, function (x, cb) {
            fn(x, function (err, y) {
                r = r.concat(y || []);
                cb(err);
            });
        }, function (err) {
            callback(err, r);
        });
    };
    async.concat = doParallel(_concat);
    async.concatSeries = doSeries(_concat);

    async.whilst = function (test, iterator, callback) {
        if (test()) {
            var sync = true;
            iterator(function (err) {
                if (err) {
                    return callback(err);
                }
                if (sync) {
                    async.nextTick(function () {
                        async.whilst(test, iterator, callback);
                    });
                }
                else {
                    async.whilst(test, iterator, callback);
                }
            });
            sync = false;
        }
        else {
            callback();
        }
    };

    async.doWhilst = function (iterator, test, callback) {
        var sync = true;
        iterator(function (err) {
            if (err) {
                return callback(err);
            }
            if (test()) {
                if (sync) {
                    async.nextTick(function () {
                        async.doWhilst(iterator, test, callback);
                    });
                }
                else {
                    async.doWhilst(iterator, test, callback);
                }
            }
            else {
                callback();
            }
        });
        sync = false;
    };

    async.until = function (test, iterator, callback) {
        if (!test()) {
            var sync = true;
            iterator(function (err) {
                if (err) {
                    return callback(err);
                }
                if (sync) {
                    async.nextTick(function () {
                        async.until(test, iterator, callback);
                    });
                }
                else {
                    async.until(test, iterator, callback);
                }
            });
            sync = false;
        }
        else {
            callback();
        }
    };

    async.doUntil = function (iterator, test, callback) {
        var sync = true;
        iterator(function (err) {
            if (err) {
                return callback(err);
            }
            if (!test()) {
                if (sync) {
                    async.nextTick(function () {
                        async.doUntil(iterator, test, callback);
                    });
                }
                else {
                    async.doUntil(iterator, test, callback);
                }
            }
            else {
                callback();
            }
        });
        sync = false;
    };

    async.queue = function (worker, concurrency) {
        if (concurrency === undefined) {
            concurrency = 1;
        }
        function _insert(q, data, pos, callback) {
          if(data.constructor !== Array) {
              data = [data];
          }
          _each(data, function(task) {
              var item = {
                  data: task,
                  callback: typeof callback === 'function' ? callback : null
              };

              if (pos) {
                q.tasks.unshift(item);
              } else {
                q.tasks.push(item);
              }

              if (q.saturated && q.tasks.length === concurrency) {
                  q.saturated();
              }
              async.nextTick(q.process);
          });
        }

        var workers = 0;
        var q = {
            tasks: [],
            concurrency: concurrency,
            saturated: null,
            empty: null,
            drain: null,
            push: function (data, callback) {
              _insert(q, data, false, callback);
            },
            unshift: function (data, callback) {
              _insert(q, data, true, callback);
            },
            process: function () {
                if (workers < q.concurrency && q.tasks.length) {
                    var task = q.tasks.shift();
                    if (q.empty && q.tasks.length === 0) {
                        q.empty();
                    }
                    workers += 1;
                    var sync = true;
                    var next = function () {
                        workers -= 1;
                        if (task.callback) {
                            task.callback.apply(task, arguments);
                        }
                        if (q.drain && q.tasks.length + workers === 0) {
                            q.drain();
                        }
                        q.process();
                    };
                    var cb = only_once(function () {
                        var cbArgs = arguments;

                        if (sync) {
                            async.nextTick(function () {
                                next.apply(null, cbArgs);
                            });
                        } else {
                            next.apply(null, arguments);
                        }
                    });
                    worker(task.data, cb);
                    sync = false;
                }
            },
            length: function () {
                return q.tasks.length;
            },
            running: function () {
                return workers;
            }
        };
        return q;
    };

    async.cargo = function (worker, payload) {
        var working     = false,
            tasks       = [];

        var cargo = {
            tasks: tasks,
            payload: payload,
            saturated: null,
            empty: null,
            drain: null,
            push: function (data, callback) {
                if(data.constructor !== Array) {
                    data = [data];
                }
                _each(data, function(task) {
                    tasks.push({
                        data: task,
                        callback: typeof callback === 'function' ? callback : null
                    });
                    if (cargo.saturated && tasks.length === payload) {
                        cargo.saturated();
                    }
                });
                async.nextTick(cargo.process);
            },
            process: function process() {
                if (working) return;
                if (tasks.length === 0) {
                    if(cargo.drain) cargo.drain();
                    return;
                }

                var ts = typeof payload === 'number'
                            ? tasks.splice(0, payload)
                            : tasks.splice(0);

                var ds = _map(ts, function (task) {
                    return task.data;
                });

                if(cargo.empty) cargo.empty();
                working = true;
                worker(ds, function () {
                    working = false;

                    var args = arguments;
                    _each(ts, function (data) {
                        if (data.callback) {
                            data.callback.apply(null, args);
                        }
                    });

                    process();
                });
            },
            length: function () {
                return tasks.length;
            },
            running: function () {
                return working;
            }
        };
        return cargo;
    };

    var _console_fn = function (name) {
        return function (fn) {
            var args = Array.prototype.slice.call(arguments, 1);
            fn.apply(null, args.concat([function (err) {
                var args = Array.prototype.slice.call(arguments, 1);
                if (typeof console !== 'undefined') {
                    if (err) {
                        if (console.error) {
                            console.error(err);
                        }
                    }
                    else if (console[name]) {
                        _each(args, function (x) {
                            console[name](x);
                        });
                    }
                }
            }]));
        };
    };
    async.log = _console_fn('log');
    async.dir = _console_fn('dir');
    /*async.info = _console_fn('info');
    async.warn = _console_fn('warn');
    async.error = _console_fn('error');*/

    async.memoize = function (fn, hasher) {
        var memo = {};
        var queues = {};
        hasher = hasher || function (x) {
            return x;
        };
        var memoized = function () {
            var args = Array.prototype.slice.call(arguments);
            var callback = args.pop();
            var key = hasher.apply(null, args);
            if (key in memo) {
                callback.apply(null, memo[key]);
            }
            else if (key in queues) {
                queues[key].push(callback);
            }
            else {
                queues[key] = [callback];
                fn.apply(null, args.concat([function () {
                    memo[key] = arguments;
                    var q = queues[key];
                    delete queues[key];
                    for (var i = 0, l = q.length; i < l; i++) {
                      q[i].apply(null, arguments);
                    }
                }]));
            }
        };
        memoized.memo = memo;
        memoized.unmemoized = fn;
        return memoized;
    };

    async.unmemoize = function (fn) {
      return function () {
        return (fn.unmemoized || fn).apply(null, arguments);
      };
    };

    async.times = function (count, iterator, callback) {
        var counter = [];
        for (var i = 0; i < count; i++) {
            counter.push(i);
        }
        return async.map(counter, iterator, callback);
    };

    async.timesSeries = function (count, iterator, callback) {
        var counter = [];
        for (var i = 0; i < count; i++) {
            counter.push(i);
        }
        return async.mapSeries(counter, iterator, callback);
    };

    async.compose = function (/* functions... */) {
        var fns = Array.prototype.reverse.call(arguments);
        return function () {
            var that = this;
            var args = Array.prototype.slice.call(arguments);
            var callback = args.pop();
            async.reduce(fns, args, function (newargs, fn, cb) {
                fn.apply(that, newargs.concat([function () {
                    var err = arguments[0];
                    var nextargs = Array.prototype.slice.call(arguments, 1);
                    cb(err, nextargs);
                }]))
            },
            function (err, results) {
                callback.apply(that, [err].concat(results));
            });
        };
    };

    async.applyEach = function (fns /*args...*/) {
        var go = function () {
            var that = this;
            var args = Array.prototype.slice.call(arguments);
            var callback = args.pop();
            return async.each(fns, function (fn, cb) {
                fn.apply(that, args.concat([cb]));
            },
            callback);
        };
        if (arguments.length > 1) {
            var args = Array.prototype.slice.call(arguments, 1);
            return go.apply(this, args);
        }
        else {
            return go;
        }
    };

    // AMD / RequireJS
    if (typeof define !== 'undefined' && define.amd) {
        define([], function () {
            return async;
        });
    }
    // Node.js
    else if (typeof module !== 'undefined' && module.exports) {
        module.exports = async;
    }
    // included directly via <script> tag
    else {
        root.async = async;
    }

}());


// Walk through the dom nodes and insert the 'data-brackets-id' attribute at the

keep keep keep add keep keep keep keep keep

<!doctype html>

	<meta charset="utf-8">
	<title>jQuery UI Spinner - Time</title>
	<link rel="stylesheet" href="../../themes/base/jquery.ui.all.css">

	<link rel="stylesheet" href="../demos.css">

	Time spinner:
	<input id="spinner" name="spinner" value="08:30 PM">

	Select a culture to use for formatting:
	<select id="culture">
		<option value="en-EN" selected="selected">English</option>
		<option value="de-DE">German</option>
	</select>

	A custom widget extending spinner. Use the Globalization plugin to parse and output
	a timestamp, with custom step and page options. Cursor up/down spins minutes, page up/down
	spins hours.


This other content was loaded via ajax.

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean nec turpis justo, et facilisis ligula. In congue interdum odio, a scelerisque eros posuere ac. Aenean massa tellus, dictum sit amet laoreet ut, aliquam in orci. Duis eu aliquam ligula. Nullam vel placerat ligula. Fusce venenatis viverra dictum. Phasellus dui dolor, imperdiet in sodales at, mattis sed libero. Morbi ac ipsum ligula. Quisque suscipit dui vel diam pretium nec cursus lacus malesuada. Donec sollicitudin, eros eget dignissim mollis, risus leo feugiat tellus, vel posuere nisl ipsum eu erat. Quisque posuere lacinia imperdiet. Quisque nunc leo, elementum quis ultricies et, vehicula sit amet turpis. Nullam sed nunc nec nibh condimentum mattis. Quisque sed ligula sit amet nisi ultricies bibendum eget id nisi.

Proin ut erat vel nunc tincidunt commodo. Curabitur feugiat, nisi et vehicula viverra, nisl orci eleifend arcu, sed blandit lectus nisl quis nisi. In hac habitasse platea dictumst. In hac habitasse platea dictumst. Aenean rutrum gravida velit ac imperdiet. Integer vitae arcu risus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin tincidunt orci at leo egestas porta. Vivamus ac augue et enim bibendum hendrerit ut id urna. Donec sollicitudin pulvinar turpis vitae scelerisque. Etiam tempor porttitor est sed blandit. Phasellus varius consequat leo eget tincidunt. Aliquam ac dui lectus. In et consectetur orci. Duis posuere nulla ac turpis faucibus vestibulum. Sed ut velit et dolor rhoncus dapibus. Sed sit amet pellentesque est.

Nam in volutpat orci. Morbi sit amet orci in erat egestas dignissim. Etiam mi sapien, tempus sed iaculis a, adipiscing quis tellus. Suspendisse potenti. Nam malesuada tristique vestibulum. In tempor tellus dignissim neque consectetur eu vestibulum nisl pellentesque. Phasellus ultrices cursus velit, id aliquam nisl fringilla quis. Cras varius elit sed urna ultrices congue. Sed ornare odio sed velit pellentesque id varius nisl sodales. Sed auctor ligula egestas mi pharetra ut consectetur erat pharetra.


/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, $, CodeMirror */
/*unittests: HTML Instrumentation*/

/**
 * HTMLInstrumentation
 *
 * This module contains functions for "instrumenting" html code. Instrumented code
 * adds a "data-brackets-id" attribute to every tag in the code. The value of this
 * tag is guaranteed to be unique.
 *
 * The primary function is generateInstrumentedHTML(). This does just what it says -
 * it will read the HTML content in the doc and generate instrumented code by injecting
 * "data-brackets-id" attributes.
 *
 * There are also helper functions for returning the tagID associated with a specified
 * position in the document.
 */
define(function (require, exports, module) {
    "use strict";

    var DocumentManager = require("document/DocumentManager"),
        Tokenizer       = require("language/HTMLTokenizer").Tokenizer,
        PriorityQueue   = require("thirdparty/priority_queue").PriorityQueue,
        MurmurHash3     = require("thirdparty/murmurhash3_gc");

    var seed = Math.floor(Math.random() * 65535);

    // Hash of scanned documents. Key is the full path of the doc. Value is an object
    // with two properties: timestamp and dom. Timestamp is the document timestamp,
    // dom is the root node of a simple DOM tree.
    var _cachedValues = {};

    /**
     * @private
     * Returns true if the specified tag is empty. This could be an empty HTML tag like
     * <meta> or <link>, or a closed tag like

     */
    function _isEmptyTag(payload) {
        if (payload.closed || !payload.nodeName) {
            return true;
        }

        if (/(!doctype|area|base|basefont|br|wbr|col|frame|hr|img|input|isindex|link|meta|param|embed)/i
                .test(payload.nodeName)) {
            return true;
        }

        return false;
    }

    /**
     * Remove a document from the cache
     */
    function _removeDocFromCache(evt, document) {
        if (_cachedValues.hasOwnProperty(document.file.fullPath)) {
            delete _cachedValues[document.file.fullPath];
            $(document).off(".htmlInstrumentation");
        }
    }

    var tagID = 1;

    /**
     * Scan a document to prepare for HTMLInstrumentation
     * @param {Document} doc The doc to scan.
     * @return {Object} Root DOM node of the scanned document.
     */
    function scanDocument(doc) {
        if (!_cachedValues.hasOwnProperty(doc.file.fullPath)) {
            $(doc).on("change.htmlInstrumentation", function () {
                if (_cachedValues[doc.file.fullPath]) {
                    _cachedValues[doc.file.fullPath].dirty = true;
                }
            });

            // Assign to cache, but don't set a value yet
            _cachedValues[doc.file.fullPath] = null;
        }

        var cachedValue = _cachedValues[doc.file.fullPath];
        if (cachedValue && !cachedValue.dirty && cachedValue.timestamp === doc.diskTimestamp) {
            return cachedValue.dom;
        }

        var text = doc.getText(),
            dom = _buildSimpleDOM(text);

        // Cache results
        _cachedValues[doc.file.fullPath] = {
            timestamp: doc.diskTimestamp,
            dom: dom,
            dirty: false
        };

        return dom;
    }

    /**
     * Generate instrumented HTML for the specified document. Each tag has a "data-brackets-id"
     * attribute with a unique ID for its value. For example, "
" becomes something like
     * "
". The attribute value is just a number that is guaranteed
     * to be unique.
     * @param {Document} doc The doc to scan.
     * @return {string} instrumented html content
     */
    function generateInstrumentedHTML(doc) {
        var dom = scanDocument(doc),
            gen = doc.getText();

        // Walk through the dom nodes and insert the 'data-brackets-id' attribute at the
        // end of the open tag
        var insertCount = 0;

        function walk(node) {
            if (node.tag) {
                var attrText = " data-brackets-id='" + node.tagID + "'";

                // Insert the attribute as the first attribute in the tag.
                var insertIndex = node.start + node.tag.length + 1 + insertCount;
                gen = gen.substr(0, insertIndex) + attrText + gen.substr(insertIndex);
                insertCount += attrText.length;
            }

            if (node.children) {
                node.children.forEach(walk);
            }
        }

        walk(dom);
        return gen;
    }

    /**
     * Mark the text for the specified editor. Either the scanDocument() or
     * the generateInstrumentedHTML() function must be called before this function
     * is called.
     *
     * NOTE: This function is "private" for now (has a leading underscore), since
     * the API is likely to change in the future.
     *
     * @param {Editor} editor The editor whose text should be marked.
     * @return none
     */
    function _markText(editor) {
        var cache = _cachedValues[editor.document.file.fullPath],
            dom = cache && cache.dom;

        if (!dom) {
            console.error("Couldn't find the dom for " + editor.document.file.fullPath);
            return;
        }

        _markTextFromDOM(editor, dom);
    }

    function _getMarkerAtDocumentPos(editor, pos) {
        var i,
            cm = editor._codeMirror,
            marks = cm.findMarksAt(pos),
            match;

        var _distance = function (mark) {
            var markerLoc = mark.find();
            if (markerLoc) {
                var markPos = markerLoc.from;
                return (cm.indexFromPos(pos) - cm.indexFromPos(markPos));
            } else {
                return Number.MAX_VALUE;
            }
        };

        for (i = 0; i < marks.length; i++) {
            if (!match) {
                match = marks[i];
            } else {
                if (_distance(marks[i]) < _distance(match)) {
                    match = marks[i];
                }
            }
        }

        return match;
    }

    /**
     * Get the instrumented tagID at the specified position. Returns -1 if
     * there are no instrumented tags at the location.
     * The _markText() function must be called before calling this function.
     *
     * NOTE: This function is "private" for now (has a leading underscore), since
     * the API is likely to change in the future.
     *
     * @param {Editor} editor The editor to scan.
     * @return {number} tagID at the specified position, or -1 if there is no tag
     */
    function _getTagIDAtDocumentPos(editor, pos) {
        var match = _getMarkerAtDocumentPos(editor, pos);

        return (match) ? match.tagID : -1;
    }

    var voidElements = {
        area: true,
        base: true,
        basefont: true,
        br: true,
        col: true,
        command: true,
        embed: true,
        frame: true,
        hr: true,
        img: true,
        input: true,
        isindex: true,
        keygen: true,
        link: true,
        meta: true,
        param: true,
        source: true,
        track: true,
        wbr: true
    };

    function SimpleDOMBuilder(text, startOffset) {
        this.stack = [];
        this.t = new Tokenizer(text, {}, {
            onopentagend: this._handleOpenTagEnd.bind(this),
            onselfclosingtag: function () { },
            oncomment: function () { },
            ontext: function () { }
        });
        this.lastTag = null;
        this.currentTag = null;
        this.startOffset = startOffset || 0;
    }

    function _updateHash(node) {
        if (node.children) {
            var i,
                weight = 0,
                hashes = "";
            for (i = 0; i < node.children.length; i++) {
                weight += node.children[i].weight;
                hashes += node.children[i].signature;
            }
            node.weight = weight;
            node.signature = MurmurHash3.hashString(hashes, hashes.length, seed);
        } else {
            var tagLabel = node.tag + node.tagID;
            node.signature = MurmurHash3.hashString(tagLabel, tagLabel.length, seed);
        }
    }

    SimpleDOMBuilder.prototype._handleOpenTagEnd = function () {
        if (this.currentTag) {
            // We're closing an open tag. Record the end of the open tag as the end of the
            // range. (If we later find a close tag for this tag, the end will get overwritten
            // with the end of the close tag.)
            // TODO: current index is private in the tokenizer right now
            this.currentTag.end = this.startOffset + this.t._index + 1;
            this.lastTag = this.currentTag;
            this.currentTag = null;
        }
    };

    function getTextNodeID(textNode) {
        var childIndex = textNode.parent.children.indexOf(textNode);
        if (childIndex === 0) {
            return textNode.parent.tagID + ".0";
        }
        return textNode.parent.children[childIndex - 1].tagID + "t";
    }

    SimpleDOMBuilder.prototype.build = function () {
        var token, tagLabel;
        var stack = this.stack;
        var attributeName = null;
        var nodeMap = {};
        var signatureMap = {};

        while ((token = this.t.nextToken()) !== null) {
            if (!token.contents) {
                console.error("Token has no contents: ", token);
            } else if (token.type === "opentagname") {
                var newTag = {
                    tag: token.contents,
                    children: [],
                    attributes: {},
                    parent: (stack.length ? stack[stack.length - 1] : null),
                    start: this.startOffset + token.start - 1,
                    weight: 0
                };
                newTag.tagID = this.getID(newTag);
                nodeMap[newTag.tagID] = newTag;
                if (newTag.parent) {
                    newTag.parent.children.push(newTag);
                }
                this.currentTag = newTag;
                if (voidElements.hasOwnProperty(newTag.tag)) {
                    tagLabel = newTag.tag + newTag.tagID;
                    newTag.weight = 0;
                    newTag.signature = MurmurHash3.hashString(tagLabel, tagLabel.length, seed);
                    signatureMap[newTag.signature] = newTag;
                } else {
                    stack.push(newTag);
                }
            } else if (token.type === "closetag") {
                this.lastTag = stack.pop();
                _updateHash(this.lastTag);
                this.lastTag.end = this.startOffset + token.end + 1;
                signatureMap[this.lastTag.signature] = this.lastTag;
                if (this.lastTag.tag !== token.contents) {
                    console.error("Mismatched tag: ", this.lastTag.tag, token.contents);
                }
            } else if (token.type === "attribname") {
                attributeName = token.contents;
            } else if (token.type === "attribvalue" && attributeName !== null) {
                this.currentTag.attributes[attributeName] = token.contents;
                attributeName = null;
            } else if (token.type === "text") {
                // TODO: Not clear why we need this since it should already have been nulled out in
                // _handleOpenTagEnd, but that doesn't seem to be sufficient for some reason.
                this.currentTag = null;
                if (stack.length) {
                    var parent = stack[stack.length - 1];
                    var newNode = {
                        parent: stack[stack.length - 1],
                        content: token.contents,
                        weight: token.contents.length,
                        signature: MurmurHash3.hashString(token.contents, token.contents.length, seed)
                    };
                    parent.children.push(newNode);
                    newNode.tagID = getTextNodeID(newNode);
                    nodeMap[newNode.tagID] = newNode;
                    signatureMap[newNode.signature] = newNode;
                }
            }
        }

        var dom = (stack.length ? stack[0] : this.lastTag);
        dom.nodeMap = nodeMap;
        dom.signatureMap = signatureMap;
        return dom;
    };

    SimpleDOMBuilder.prototype.getID = function () {
        return tagID++;
    };

    function _buildSimpleDOM(text) {
        var builder = new SimpleDOMBuilder(text);
        return builder.build();
    }

    function _dumpDOM(root) {
        var result = "",
            indent = "";

        function walk(node) {
            if (node.tag) {
                result += indent + "TAG " + node.tagID + " " + node.tag + " " + JSON.stringify(node.attributes) + "\n";
            } else {
                result += indent + "TEXT " + node.tagID + " " + node.content + "\n";
            }
            if (node.children) {
                indent += "  ";
                node.children.forEach(walk);
                indent = indent.slice(2);
            }
        }
        walk(root);

        return result;
    }

    // TODO: I'd be worried about stack overflows here
    function _markTags(cm, node) {
        node.children.forEach(function (childNode) {
            if (childNode.tag) {
                _markTags(cm, childNode);
            }
        });
        var mark = cm.markText(cm.posFromIndex(node.start), cm.posFromIndex(node.end));
        mark.tagID = node.tagID;
    }

    function _markTextFromDOM(editor, dom) {
        var cm = editor._codeMirror;

        // Remove existing marks
        var marks = cm.getAllMarks();
        cm.operation(function () {
            marks.forEach(function (mark) {
                if (mark.hasOwnProperty("tagID")) {
                    mark.clear();
                }
            });
        });

        // Mark
        _markTags(cm, dom);
    }

    function DOMUpdater(previousDOM, editor, changeList) {
        var text, startOffset = 0;

        // TODO: if there's more than one change in the changelist, we need to figure out how
        // to find all the affected ranges since we can't directly map the ranges of intermediate
        // changes to marked ranges after the fact.
        //
        // Probably the best we can do is try to determine what the first and last affected positions
        // are in the whole changelist, then figure out what marks those positions are in, and walk
        // up the tree to the common parent. However, that's not trivial since each change's position
        // is specified in terms of the document's state after the previous change. It's probably easy
        // to find the first affected position, but harder to find the last. Perhaps we could just
        // dirty the whole doc after the first affected position.
        //
        // For now, just punt and redo the whole doc if there's more than one change.
        if (changeList && !changeList.next) {
            // If both the beginning and end of the change are still within the same marked
            // range after the change, then assume we can just dirty the tag containing them,
            // otherwise punt. (TODO: We could chase upward in the tree to find the common parent if
            // the tags for the beginning and end of the change are different.)
            var startMark = _getMarkerAtDocumentPos(editor, changeList.from),
                endMark = _getMarkerAtDocumentPos(editor, changeList.to);
            if (startMark && startMark === endMark) {
                var newMarkRange = startMark.find();
                if (newMarkRange) {
                    text = editor._codeMirror.getRange(newMarkRange.from, newMarkRange.to);
                    //console.log("incremental update: " + text);
                    this.changedTagID = startMark.tagID;
                    startOffset = editor._codeMirror.indexFromPos(newMarkRange.from);
                }
            }
        }

        if (!this.changedTagID) {
            // We weren't able to incrementally update, so just rebuild and diff everything.
            text = editor.document.getText();
        }

        this.constructor(text, startOffset);
        this.editor = editor;
        this.cm = editor._codeMirror;
        this.previousDOM = previousDOM;
    }

    DOMUpdater.prototype = new SimpleDOMBuilder();

    DOMUpdater.prototype.getID = function (newTag) {
        // TODO: _getTagIDAtDocumentPos is likely a performance bottleneck
        // Get the mark at the start of the tagname (not before the beginning of the tag, because that's
        // actually inside the parent).
        var currentTagID = _getTagIDAtDocumentPos(this.editor, this.cm.posFromIndex(newTag.start + 1));
        if (currentTagID === -1 || (newTag.parent && currentTagID === newTag.parent.tagID)) {
            currentTagID = tagID++;
        }
        return currentTagID;
    };

    DOMUpdater.prototype._updateMarkedRanges = function (nodeMap) {
        // TODO: this is really inefficient - should we cache the mark for each node?
        var updateIDs = Object.keys(nodeMap),
            cm = this.cm,
            marks = cm.getAllMarks();
        marks.forEach(function (mark) {
            if (mark.hasOwnProperty("tagID") && nodeMap[mark.tagID]) {
                var node = nodeMap[mark.tagID];
                mark.clear();
                mark = cm.markText(cm.posFromIndex(node.start), cm.posFromIndex(node.end));
                mark.tagID = node.tagID;
                updateIDs.splice(updateIDs.indexOf(node.tagID), 1);
            }
        });

        // Any remaining updateIDs are new.
        updateIDs.forEach(function (id) {
            var node = nodeMap[id],
                mark = cm.markText(cm.posFromIndex(node.start), cm.posFromIndex(node.end));
            mark.tagID = id;
        });
    }

    DOMUpdater.prototype._buildNodeMap = function (root) {
        var nodeMap = {};

        function walk(node) {
            if (node.tagID) {
                nodeMap[node.tagID] = node;
            }
            if (node.children) {
                node.children.forEach(walk);
            }
        }

        walk(root);
        root.nodeMap = nodeMap;
    }

    DOMUpdater.prototype._handleDeletions = function (nodeMap, oldSubtreeMap, newSubtreeMap) {
        var deletedIDs = [];
        Object.keys(oldSubtreeMap).forEach(function (key) {
            if (!newSubtreeMap.hasOwnProperty(key)) {
                deletedIDs.push(key);
                delete nodeMap[key];
            }
        });

        // TODO: again, really inefficient - should we cache the mark for each node?
        var marks = this.cm.getAllMarks();
        marks.forEach(function (mark) {
            if (mark.hasOwnProperty("tagID") && deletedIDs.indexOf(mark.tagID) !== -1) {
                mark.clear();
            }
        });
    }

    DOMUpdater.prototype.update = function () {
        var newSubtree = this.build(),
            result = {
                // default result if we didn't identify a changed portion
                newDOM: newSubtree,
                oldSubtree: this.previousDOM,
                newSubtree: newSubtree
            };

        if (this.changedTagID) {
            // Find the old subtree that's going to get swapped out.
            var oldSubtree = this.previousDOM.nodeMap[this.changedTagID],
                parent = oldSubtree.parent;

            // If we didn't have a parent, then the whole tree changed anyway, so
            // we'll just return the default result.
            if (parent) {
                var childIndex = parent.children.indexOf(oldSubtree);
                if (childIndex === -1) {
                    // This should never happen...
                    console.error("DOMUpdater.update(): couldn't locate old subtree in tree");
                } else {
                    // Swap the new subtree in place of the old subtree.
                    oldSubtree.parent = null;
                    newSubtree.parent = parent;
                    parent.children[childIndex] = newSubtree;

                    // Overwrite any node mappings in the parent DOM with the
                    // mappings for the new subtree. We keep the nodeMap around
                    // on the new subtree so that the differ can use it later.
                    // TODO: should we ever null out the nodeMap on the subtree?
                    $.extend(this.previousDOM.nodeMap, newSubtree.nodeMap);

                    // Update marked ranges for all items in the new subtree.
                    this._updateMarkedRanges(newSubtree.nodeMap);

                    // Build a local nodeMap for the old subtree so the differ can
                    // use it.
                    this._buildNodeMap(oldSubtree);

                    // Clean up the info for any deleted nodes that are no longer in
                    // the new tree.
                    this._handleDeletions(this.previousDOM.nodeMap, oldSubtree.nodeMap, newSubtree.nodeMap);

                    // Update the signatures for all parents of the new subtree.
                    var curParent = parent;
                    while (curParent) {
                        _updateHash(curParent);
                        curParent = curParent.parent;
                    }

                    // TODO: add/update marked ranges for any nodes inside subtree
                    // (will need to offset the start/end of each node by the offset
                    // of the beginning of the changed range)

                    result.newDOM = this.previousDOM;
                    result.oldSubtree = oldSubtree;
                }
            }
        }

        return result;
    };

    function attributeCompare(edits, oldNode, newNode) {
        // shallow copy the old attributes object so that we can modify it
        var oldAttributes = $.extend({}, oldNode.attributes),
            newAttributes = newNode.attributes;
        Object.keys(newAttributes).forEach(function (attributeName) {
            if (oldAttributes[attributeName] !== newAttributes[attributeName]) {
                var type = oldAttributes.hasOwnProperty(attributeName) ? "attrChange" : "attrAdd";
                edits.push({
                    type: type,
                    tagID: oldNode.tagID,
                    attribute: attributeName,
                    value: newAttributes[attributeName]
                });
            }
            delete oldAttributes[attributeName];
        });
        Object.keys(oldAttributes).forEach(function (attributeName) {
            edits.push({
                type: "attrDel",
                tagID: oldNode.tagID,
                attribute: attributeName
            });
        });
    }

    function NodeState(node) {
        this.node = node;
        this.childPointer = 0;
    }

    NodeState.prototype.toJSON = function () {
        return {
            tagID: this.node.tagID,
            childPointer: this.childPointer
        };
    };

    function DOMNavigator(root) {
        this.stack = [new NodeState(root)];
    }

    DOMNavigator.prototype.next = function () {
        var parentState = this.stack[this.stack.length - 1];
        var children = parentState.node.children;
        if (parentState.childPointer > children.length - 1) {
            this.stack.pop();
            if (this.stack.length === 0) {
                return null;
            }
            this.stack[this.stack.length - 1].childPointer++;
            return this.next();
        }
        var child = children[parentState.childPointer];
        if (child.children) {
            this.stack.push(new NodeState(child));
            return child;
        } else {
            parentState.childPointer++;
            return child;
        }
    };

    DOMNavigator.prototype.getPosition = function () {
        var parentState = this.stack[this.stack.length - 1];
        return {
            tagID: parentState.node.tagID,
            child: parentState.childPointer - 1
        };
    };

    function domdiffOld(edits, oldNode, newNode) {
        var oldNav = new DOMNavigator(oldNode),
            newNav = new DOMNavigator(newNode);

        attributeCompare(edits, oldNode, newNode);

        var oldChild = oldNav.next(),
            newChild = newNav.next();

        while (oldChild && newChild) {
            if (oldChild.tag && newChild.tag) {
                attributeCompare(edits, oldChild, newChild);

            // Check to see if they're both text nodes
            } else if (!oldChild.tag && !newChild.tag) {
                if (oldChild.content !== newChild.content) {
                    var position = oldNav.getPosition();
                    edits.push({
                        type: "textReplace",
                        tagID: position.tagID,
                        child: position.child,
                        content: newChild.content
                    });
                }
            }
            oldChild = oldNav.next();
            newChild = newNav.next();
        }
    }

    function compareByWeight(a, b) {
        return b.weight - a.weight;
    }

    function domdiff(oldNode, newNode) {
        var queue = new PriorityQueue(compareByWeight),
            edits = [],
            matches = {},
            elementInserts = {},
            textInserts = {},
            textChanges = {},
            currentElement,
            oldElement,
            elementDeletes = {};

        queue.push(newNode);

        while (currentElement = queue.shift()) {
            oldElement = oldNode.nodeMap[currentElement.tagID];
            if (oldElement) {
                matches[currentElement.tagID] = true;
                if (oldElement.children) {
                    if (oldElement.signature !== currentElement.signature) {
                        if (currentElement.children) {
                            currentElement.children.forEach(function (child) {
                                queue.push(child);
                            });
                        }
                    }
                } else {
                    if (oldElement.signature !== currentElement.signature) {
                        textChanges[currentElement.tagID] = true;
                    }
                }
            } else {
                if (currentElement.children) {
                    elementInserts[currentElement.tagID] = true;
                } else {
                    textInserts[currentElement.tagID] = true;
                }
                if (currentElement.children) {
                    currentElement.children.forEach(function (child) {
                        queue.push(child);
                    });
                }
            }
        }

        Object.keys(matches).forEach(function (tagID) {
            var currentElement;
            var subtreeRoot = newNode.nodeMap[tagID];
            if (subtreeRoot.children) {
                attributeCompare(edits, oldNode.nodeMap[tagID], subtreeRoot);
                var nav = new DOMNavigator(subtreeRoot);
                while (currentElement = nav.next()) {
                    var currentTagID = currentElement.tagID;
                    if (!oldNode.nodeMap[currentTagID]) {
                        // this condition can happen for new elements
                        continue;
                    }
                    matches[currentTagID] = true;
                    if (currentElement.children) {
                        attributeCompare(edits, oldNode.nodeMap[currentTagID], currentElement);
                    }
                }
            }
        });

        var initializeTextEdit = function (element) {
            var edit = {
                parentID: element.parent.tagID
            };

            var match = /(\d+)t/.exec(element.tagID);
            if (match) {
                edit.afterID = match[1];
            } else {
                edit.firstChild = true;
            }
            return edit;
        }

        var findDeletions = function (element) {
            if (!matches[element.tagID]) {
                if (element.children) {
                    edits.push({
                        type: "elementDelete",
                        tagID: element.tagID
                    });
                } else {
                    var edit = initializeTextEdit(element);
                    edit.type = "textDelete";
                    edits.push(edit);
                }
            } else if (element.children) {
                var i;
                for (i = 0; i < element.children.length; i++) {
                    findDeletions(element.children[i]);
                }
            }
        };

        findDeletions(oldNode);

        Object.keys(elementInserts).forEach(function (nonMatchingID) {
            var newElement = newNode.nodeMap[nonMatchingID];
            var childIndex = newElement.parent.children.indexOf(newElement);
            edits.push({
                type: "elementInsert",
                tagID: newElement.tagID,
                tag: newElement.tag,
                attributes: newElement.attributes,
                parentID: newElement.parent.tagID,
                child: childIndex
            });
        });

        Object.keys(textInserts).forEach(function (nonMatchingID) {
            var newElement = newNode.nodeMap[nonMatchingID];
            edits.push({
                type: "textInsert",
                tagID: newElement.parent.tagID,
                child: newElement.parent.children.indexOf(newElement),
                content: newElement.content
            });
        });

        Object.keys(textChanges).forEach(function (changedID) {
            var changedElement = newNode.nodeMap[changedID];
            var edit = initializeTextEdit(changedElement);
            edit.type = "textReplace";
            edit.content = changedElement.content;
            edits.push(edit);
        });
        return edits;
    }

    function _updateDOM(previousDOM, editor, changeList) {
        var updater = new DOMUpdater(previousDOM, editor, changeList);
        var result = updater.update();
        var edits = domdiff(result.oldSubtree, result.newSubtree);
        return {
            dom: result.newDOM,
            edits: edits
        };
    }

    function getUnappliedEditList(editor, changeList) {
        var cachedValue = _cachedValues[editor.document.file.fullPath];
        if (!cachedValue || !cachedValue.dom) {
            console.warn("No previous DOM to compare change against");
            return [];
        } else {
            var result = _updateDOM(cachedValue.dom, editor, changeList);
            _cachedValues[editor.document.file.fullPath] = {
                timestamp: editor.document.diskTimestamp, // TODO: update?
                dom: result.dom
            };
            return result.edits;
        }
    }

    $(DocumentManager).on("beforeDocumentDelete", _removeDocFromCache);

    exports.scanDocument = scanDocument;
    exports.generateInstrumentedHTML = generateInstrumentedHTML;
    exports.getUnappliedEditList = getUnappliedEditList;
    exports._markText = _markText;
    exports._getMarkerAtDocumentPos = _getMarkerAtDocumentPos;
    exports._getTagIDAtDocumentPos = _getTagIDAtDocumentPos;
    exports._buildSimpleDOM = _buildSimpleDOM;
    exports._markTextFromDOM = _markTextFromDOM;
    exports._updateDOM = _updateDOM;
    exports._DOMNavigator = DOMNavigator;
    exports._seed = seed;
});


/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define */

define({
    /**
     * Errors
     */

    // General file io error strings
    "GENERIC_ERROR"                     : "(erro {0})",
    "NOT_FOUND_ERR"                     : "O ficheiro não foi encontrado.",
    "NOT_READABLE_ERR"                  : "O ficheiro não pode ser lido.",
    "NO_MODIFICATION_ALLOWED_ERR"       : "A pasta de destino não pode ser modificado.",
    "NO_MODIFICATION_ALLOWED_ERR_FILE"  : "As permissões não permitem modificações.",
    "FILE_EXISTS_ERR"                   : "O ficheiro já existe.",

    // Project error strings
    "ERROR_LOADING_PROJECT"             : "Erro ao carregar o projeto",
    "OPEN_DIALOG_ERROR"                 : "Ocorreu um erro na abertura do ficheiro. (error {0})",
    "REQUEST_NATIVE_FILE_SYSTEM_ERROR"  : "Ocorreu um erro ao tentar carregar a pasta {0}. (error {1})",
    "READ_DIRECTORY_ENTRIES_ERROR"      : "Ocorreu um erro ao ler o conteúdo da pasta {0}. (error {1})",

    // File open/save error string
    "ERROR_OPENING_FILE_TITLE"          : "Erro ao abrir ficheiro",
    "ERROR_OPENING_FILE"                : "Ocorreu um erro ao tentar abrir o ficheiro {0}. {1}",
    "ERROR_RELOADING_FILE_TITLE"        : "Ocorreu um erro ao recarregar as mudanças a partir do disco",
    "ERROR_RELOADING_FILE"              : "Ocorreu um erro ao tentar recarregar o ficheiro {0}. {1}",
    "ERROR_SAVING_FILE_TITLE"           : "Erro ao guardar ficheiro",
    "ERROR_SAVING_FILE"                 : "Ocorreu um erro ao tentar guardar o ficheiro {0}. {1}",
    "ERROR_RENAMING_FILE_TITLE"         : "Erro ao renomear ficheiro",
    "ERROR_RENAMING_FILE"               : "Ocorreu um erro ao tentar renomear o ficheiro {0}. {1}",
    "INVALID_FILENAME_TITLE"            : "Nome do ficheiro inválido",
    "INVALID_FILENAME_MESSAGE"          : "O nome do ficheiro não pode conter os seguintes caracteres: /?*:;{}<>\\|",
    "FILE_ALREADY_EXISTS"               : "O ficheiro {0} já existe.",
    "ERROR_CREATING_FILE_TITLE"         : "Erro ao criar ficheiro.",
    "ERROR_CREATING_FILE"               : "Ocorreu um erro ao tentar criar o ficheiro {0}. {1}",

    // Application error strings
    "ERROR_IN_BROWSER_TITLE"            : "Oops! {APP_NAME} não funciona em navegadores ainda.",
    "ERROR_IN_BROWSER"                  : "{APP_NAME} é feito em HTML, e é executado como uma aplicação de desktop para que possa usá-lo para editar ficheiros locais. Por favor, use o shell da aplicação no github.com/adobe/brackets-shell repo para executar {APP_NAME}.",

    // FileIndexManager error string
    "ERROR_MAX_FILES_TITLE"             : "Erro a indexar Ficheiros",
    "ERROR_MAX_FILES"                   : "O número máximo de ficheiros foi indexado. Acções que procurem ficheiros no index podem não funcionar corretamente.",

    // Live Development error strings
    "ERROR_LAUNCHING_BROWSER_TITLE"     : "Erro ao abrir o navegador",
    "ERROR_CANT_FIND_CHROME"            : "O navegador Google Chrome não foi encontrado. Por favor, verifique se está instalado.",
    "ERROR_LAUNCHING_BROWSER"           : "Ocorreu um erro ao iniciar o navegador. (error {0})",

    "LIVE_DEVELOPMENT_ERROR_TITLE"      : "Erro no Live Preview",
    "LIVE_DEVELOPMENT_RELAUNCH_TITLE"   : "A conectar ao navegador",
    "LIVE_DEVELOPMENT_ERROR_MESSAGE"    : "Para que a Live Preview funcione, o Google Chrome deve ser aberto com a opção de depuração(debug) remota ativada.

Gostaria de reabrir Goggle Chrome e permitir a depuração remota?",
    "LIVE_DEV_NEED_HTML_MESSAGE"        : "Abra um ficheiro HTML de modo a executar o Live Preview.",
    "LIVE_DEVELOPMENT_INFO_TITLE"       : "Bem-vindo ao Live Preview!",
    "LIVE_DEVELOPMENT_INFO_MESSAGE"     : "Live Preview liga o {APP_NAME} ao seu navegador. Ele faz uma pré-vizualização do seu ficheiro HTML no navegador, e cada vez que editar o código a pré-vizualização é automáticamente actualizada.

Nesta versão inicial do {APP_NAME}, o Live Preview só funciona com a edição de ficheiros CSS e apenas com Google Chrome. Iremos implementá-lo para HTML e JavaScript em breve!

(Só vai ver esta mensagem uma vez.)",

    "LIVE_DEV_STATUS_TIP_NOT_CONNECTED" : "Live Preview",
    "LIVE_DEV_STATUS_TIP_PROGRESS1"     : "Live Preview: A ligar\u2026",
    "LIVE_DEV_STATUS_TIP_PROGRESS2"     : "Live Preview: A iniciar\u2026",
    "LIVE_DEV_STATUS_TIP_CONNECTED"     : "Live Preview: Desligar",
    "LIVE_DEV_STATUS_TIP_OUT_OF_SYNC"   : "Live Preview: Clique para desligar (Guarde o ficheiro para atualizar)",

    "SAVE_CLOSE_TITLE"                  : "Guardar alterações",
    "SAVE_CLOSE_MESSAGE"                : "Você quer guardar as alterações feitas no ficheiro {0}?",
    "SAVE_CLOSE_MULTI_MESSAGE"          : "Você quer guardar as alterações para os seguintes ficheiros?",
    "EXT_MODIFIED_TITLE"                : "Mudanças externas",
    "EXT_MODIFIED_MESSAGE"              : "{0} foi modificado no disco, mas também tem alterações não guardadas em {APP_NAME}.

Que versão quer manter?",
    "EXT_DELETED_MESSAGE"               : "{0} foi apagado do disco, mas tem alterações não guardadas em {APP_NAME}.

Quer manter as suas alterações?",

    // Find, Replace, Find in Files
    "SEARCH_REGEXP_INFO"                : "Usar /re/ sintaxe para usar regexp na pesquisa",
    "WITH"                              : "Com",
    "BUTTON_YES"                        : "Sim",
    "BUTTON_NO"                         : "Não",
    "BUTTON_STOP"                       : "Parar",

    "OPEN_FILE"                         : "Abrir ficheiro",
    "CHOOSE_FOLDER"                     : "Escolha uma pasta",

    "RELEASE_NOTES"                     : "Notas de lançamento",
    "NO_UPDATE_TITLE"                   : "Está atualizado!",
    "NO_UPDATE_MESSAGE"                 : "Está a executar a versão mais recente de {APP_NAME}.",

    "FIND_IN_FILES_TITLE"               : "Para \"{4}\" {5} - {0} {1} em {2} {3}",
    "FIND_IN_FILES_SCOPED"              : "em {0}",
    "FIND_IN_FILES_NO_SCOPE"            : "no projeto",
    "FIND_IN_FILES_FILE"                : "ficheiro",
    "FIND_IN_FILES_FILES"               : "ficheiros",
    "FIND_IN_FILES_MATCH"               : "resultado",
    "FIND_IN_FILES_MATCHES"             : "resultado",
    "FIND_IN_FILES_MORE_THAN"           : "Mais que ",
    "FIND_IN_FILES_MAX"                 : " (a mostar os primeiros {0} resultados)",
    "FIND_IN_FILES_FILE_PATH"           : "Ficheiro: {0}",
    "FIND_IN_FILES_LINE"                : "linha: {0}",

    "ERROR_FETCHING_UPDATE_INFO_TITLE"  : "Erro ao receber as atualizações",
    "ERROR_FETCHING_UPDATE_INFO_MSG"    : "Houve um problema a receber a atualização mais recente a partir do servidor. Por favor, verifique se você está conectado à Internet e tente novamente.",

    /**
     * ProjectManager
     */

    "UNTITLED" : "Sem título",

    /**
     * Keyboard modifier names
     */

    "KEYBOARD_CTRL"   : "Ctrl",
    "KEYBOARD_SHIFT"  : "Shift",
    "KEYBOARD_SPACE"  : "Space",

    /**
     * StatusBar strings
     */
    "STATUSBAR_CURSOR_POSITION"             : "Linha {0}, Coluna {1}",
    "STATUSBAR_INDENT_TOOLTIP_SPACES"       : "Mudar a indentação para espaços",
    "STATUSBAR_INDENT_TOOLTIP_TABS"         : "Mudar a indentação para tabulação",
    "STATUSBAR_INDENT_SIZE_TOOLTIP_SPACES"  : "Mudar o número de espaços usados ao indentar",
    "STATUSBAR_INDENT_SIZE_TOOLTIP_TABS"    : "Mudar a largura do caractere de tabulação",
    "STATUSBAR_SPACES"                      : "Espaços",
    "STATUSBAR_TAB_SIZE"                    : "Tamanho da tabulação",
    "STATUSBAR_LINE_COUNT"                  : "{0} Linhas",

    /**
     * Command Name Constants
     */

    // File menu commands
    "FILE_MENU"                           : "Ficheiro",
    "CMD_FILE_NEW"                        : "Novo",
    "CMD_FILE_NEW_FOLDER"                 : "Nova pasta",
    "CMD_FILE_OPEN"                       : "Abrir\u2026",
    "CMD_ADD_TO_WORKING_SET"              : "Adicionar ao conjunto de trabalho",
    "CMD_OPEN_FOLDER"                     : "Abrir pasta\u2026",
    "CMD_FILE_CLOSE"                      : "Fechar",
    "CMD_FILE_CLOSE_ALL"                  : "Fechar tudo",
    "CMD_FILE_SAVE"                       : "Guardar",
    "CMD_FILE_SAVE_ALL"                   : "Guardar tudo",
    "CMD_LIVE_FILE_PREVIEW"               : "Live Preview",
    "CMD_PROJECT_SETTINGS"                : "Definições do projeto\u2026",
    "CMD_FILE_RENAME"                     : "Renomear",
    "CMD_QUIT"                            : "Sair",

    // Edit menu commands
    "EDIT_MENU"                           : "Editar",
    "CMD_SELECT_ALL"                      : "Selecionar tudo",
    "CMD_SELECT_LINE"                     : "Selecionar linha",
    "CMD_FIND"                            : "Encontrar",
    "CMD_FIND_IN_FILES"                   : "Encontrar nos ficheiros",
    "CMD_FIND_IN_SUBTREE"                 : "Encontrar em\u2026",
    "CMD_FIND_NEXT"                       : "Encontrar próximo",
    "CMD_FIND_PREVIOUS"                   : "Encontrar anterior",
    "CMD_REPLACE"                         : "Substituir",
    "CMD_INDENT"                          : "Indentar",
    "CMD_UNINDENT"                        : "Recuar indentação",
    "CMD_DUPLICATE"                       : "Duplicar",
    "CMD_DELETE_LINES"                    : "Apagar linha(s) selecionadas",
    "CMD_COMMENT"                         : "Alternar comentário linhas",
    "CMD_BLOCK_COMMENT"                   : "Alternar comentário bloco",
    "CMD_LINE_UP"                         : "Mover linha(s) para cima",
    "CMD_LINE_DOWN"                       : "Mover linha(s) para baixo",

    // View menu commands
    "VIEW_MENU"                           : "Ver",
    "CMD_HIDE_SIDEBAR"                    : "Esconder barra lateral",
    "CMD_SHOW_SIDEBAR"                    : "Mostrar barra lateral",
    "CMD_INCREASE_FONT_SIZE"              : "Aumentar tamanho da fonte",
    "CMD_DECREASE_FONT_SIZE"              : "Diminuir tamanho da fonte",
    "CMD_RESTORE_FONT_SIZE"               : "Restaurar tamanho da fonte",
    "CMD_SORT_WORKINGSET_BY_ADDED"        : "Ordenar por data adicionado",
    "CMD_SORT_WORKINGSET_BY_NAME"         : "Ordenar por nome",
    "CMD_SORT_WORKINGSET_BY_TYPE"         : "Ordenar por tipo",
    "CMD_SORT_WORKINGSET_AUTO"            : "Ordenação automática",

    // Navigate menu Commands
    "NAVIGATE_MENU"                       : "Navegar",
    "CMD_QUICK_OPEN"                      : "Abertura rápida",
    "CMD_GOTO_LINE"                       : "Ir para a linha",
    "CMD_GOTO_DEFINITION"                 : "Ir para definição",
    "CMD_TOGGLE_QUICK_EDIT"               : "Edição rápida",
    "CMD_QUICK_EDIT_PREV_MATCH"           : "Resultado anterior",
    "CMD_QUICK_EDIT_NEXT_MATCH"           : "Resultado seguinte",
    "CMD_NEXT_DOC"                        : "Ficheiro seguinte",
    "CMD_PREV_DOC"                        : "Ficheiro anterior",
    "CMD_SHOW_IN_TREE"                    : "Mostrar na lista de pastas",

    // Help menu commands
    "HELP_MENU"                           : "Ajuda",
    "CMD_SHOW_EXTENSIONS_FOLDER"          : "Mostrar pasta de extensões",
    "CMD_CHECK_FOR_UPDATE"                : "Verificar atualizações",
    "CMD_ABOUT"                           : "Sobre",
    "CMD_FORUM"                           : "Forum do {APP_NAME}",

    // Special commands invoked by the native shell
    "CMD_CLOSE_WINDOW"                    : "Fechar a janela",
    "CMD_ABORT_QUIT"                      : "Cancelar a saída",

    // Strings for main-view.html
    "EXPERIMENTAL_BUILD"                   : "Versão Experimental",
    "SEARCH_RESULTS"                       : "Resultados da pesquisa",
    "OK"                                   : "OK",
    "DONT_SAVE"                            : "Não guardar",
    "SAVE"                                 : "Guardar",
    "CANCEL"                               : "Cancelar",
    "RELOAD_FROM_DISK"                     : "Atualizar a partir do disco",
    "KEEP_CHANGES_IN_EDITOR"               : "Manter mudanças no editor",
    "CLOSE_DONT_SAVE"                      : "Fechar (Não guardar)",
    "RELAUNCH_CHROME"                      : "Re-abrir Chrome",
    "ABOUT"                                : "Sobre",
    "CLOSE"                                : "Fechar",
    "ABOUT_TEXT_LINE1"                     : "sprint {VERSION_MINOR} versão experimental {VERSION}",
    "ABOUT_TEXT_LINE3"                     : "Avisos, termos e condições de softwares de terceiros estão localizados em http://www.adobe.com/go/thirdparty/ e aqui incorporados por referência.",
    "ABOUT_TEXT_LINE4"                     : "Documentação e fontes https://github.com/adobe/brackets/",
    "UPDATE_NOTIFICATION_TOOLTIP"          : "Nova versão de {APP_NAME} disponível! Clique aqui para mais detalhes.",
    "UPDATE_AVAILABLE_TITLE"               : "Atualização disponível",
    "UPDATE_MESSAGE"                       : "Hey, {APP_NAME} {VERSION} disponível. Aqui estão alguns dos novos recursos:",
    "GET_IT_NOW"                           : "Obter agora!",
    "PROJECT_SETTINGS_TOOLTIP"             : "Definições do projeto",
    "PROJECT_SETTINGS_TITLE"               : "Definições do projeto para: {0}",
    "PROJECT_SETTING_BASE_URL"             : "Live Preview URL base",
    "PROJECT_SETTING_BASE_URL_HINT"        : "(deixe em branco para url do ficheiro)",
    "BASEURL_ERROR_INVALID_PROTOCOL"       : "O {0} protocolo não é suportado pelo Live Preview—por favor usar http: or https: .",
    "BASEURL_ERROR_SEARCH_DISALLOWED"      : "O URL base não pode conter parametros de pesquisa como \"{0}\".",
    "BASEURL_ERROR_HASH_DISALLOWED"        : "O URL base não pode conter cardinal como \"{0}\".",
    "BASEURL_ERROR_INVALID_CHAR"           : "Caracteres especiais como '{0}' têm que ser %-codificados.",
    "BASEURL_ERROR_UNKOWN_ERROR"           : "Erro desconhecido na análise do URL base",

    // extensions/default/DebugCommands
    "DEBUG_MENU"                           : "Debug",
    "CMD_SHOW_DEV_TOOLS"                   : "Mostrar ferramentas de desenvolvimento",
    "CMD_REFRESH_WINDOW"                   : "Recarregar o {APP_NAME}",
    "CMD_NEW_BRACKETS_WINDOW"              : "Nova janela do {APP_NAME}",
    "CMD_SWITCH_LANGUAGE"                  : "Mudar linguagem",
    "CMD_RUN_UNIT_TESTS"                   : "Executar testes",
    "CMD_SHOW_PERF_DATA"                   : "Mostrar dados de desempenho",

    "LANGUAGE_TITLE"                       : "Mudar idioma",
    "LANGUAGE_MESSAGE"                     : "Por favor, selecione o idioma desejado na lista abaixo:",
    "LANGUAGE_SUBMIT"                      : "Recarregar {APP_NAME}",
    "LANGUAGE_CANCEL"                      : "Cancelar",

    // extensions/default/JSLint
    "CMD_JSLINT"                           : "Activar JSLint",
    "JSLINT_ERRORS"                        : "Erros JSLint",
    "JSLINT_ERROR_INFORMATION"             : "1 Erro JSLint",
    "JSLINT_ERRORS_INFORMATION"            : "{0} Erros JSLint",
    "JSLINT_NO_ERRORS"                     : "Sem erros JSLint - bom trabalho!",
    "JSLINT_DISABLED"                      : "JSLint desabilitado ou não funcionando para o arquivo atual"
});


<!doctype html>

	<meta charset="utf-8">
	<title>Menu Visual Test: Default</title>
	<link rel="stylesheet" href="../../../themes/base/jquery.ui.all.css">

Default inline menu

Aberdeen

Ada

Adamsville

Addyston

Delphi

Saarland

Salzburg

Inline with disabled items and submenus

Aberdeen

Ada

Adamsville

Addyston

		Delphi

Ada

Saarland

Salzburg

Saarland

		Salzburg

				Delphi

Ada

Saarland

Salzburg

				Delphi

Ada

Saarland

Salzburg

Perch

Amesville

Menu with icons

Aberdeen

Ada

Adamsville

Addyston

		Delphi

Ada

Saarland

Salzburg

Saarland

		Salzburg

				Delphi

Ada

Saarland

Salzburg

				Delphi

Ada

Saarland

Salzburg

Perch

Long menu with scroll overflow, to test menu's scroll-on-keypress behaviour

Aberdeen

Ada

Adamsville

Addyston

Adelphi

Adena

Adrian

Akron

Albany

Alexandria

Alger

Alledonia

Alliance

Alpha

Alvada

Alvordton

Amanda

Amelia

Amesville

Aberdeen

Ada

Adamsville

Addyston

Adelphi

Adena

Adrian

Akron

Albany

Alexandria

Alger

Alledonia

Alliance

Alpha

Alvada

Alvordton

Amanda

Amelia

Amesville

Menu with custom markup

Aberdeen

Ada

Adamsville

Addyston

		Delphi

Ada

Saarland

Salzburg

Saarland

		Salzburg

				Delphi

Ada

Saarland

Salzburg

				Delphi

Ada

Saarland

Salzburg

Perch

Menu with custom markup, multi-line items

			John Doe
			78 West Main St Apt 3A
			Bloomsburg, PA 12345

			Jane Doe
			78 West Main St Apt 3A
			Bloomsburg, PA 12345

			James Doe
			78 West Main St Apt 3A
			Bloomsburg, PA 12345

			Jenny Doe
			78 West Main St Apt 3A
			Bloomsburg, PA 12345

			John Doe
			78 West Main St Apt 3A
			Bloomsburg, PA 12345

Ada

Saarland

Salzburg

Inline with dividers and elements without anchors

Group 1

Aberdeen

Ada

Adamsville

Addyston

Group 2

		Delphi

Ada

Saarland

Salzburg

Saarland

---

Group 3

		Salzburg

				Delphi

Ada

 -

Saarland

—

Salzburg

–

				Delphi

Ada

Saarland

Salzburg

Perch

Amesville

	Log:


var path = require('path');
var fs = require('fs');

function Mime() {
  // Map of extension -> mime type
  this.types = Object.create(null);

  // Map of mime type -> extension
  this.extensions = Object.create(null);
}

/**
 * Define mimetype -> extension mappings.  Each key is a mime-type that maps
 * to an array of extensions associated with the type.  The first extension is
 * used as the default extension for the type.
 *
 * e.g. mime.define({'audio/ogg', ['oga', 'ogg', 'spx']});
 *
 * @param map (Object) type definitions
 */
Mime.prototype.define = function (map) {
  for (var type in map) {
    var exts = map[type];

    for (var i = 0; i < exts.length; i++) {
      if (process.env.DEBUG_MIME && this.types[exts]) {
        console.warn(this._loading.replace(/.*\//, ''), 'changes "' + exts[i] + '" extension type from ' +
          this.types[exts] + ' to ' + type);
      }

      this.types[exts[i]] = type;
    }

    // Default extension is the first one we encounter
    if (!this.extensions[type]) {
      this.extensions[type] = exts[0];
    }
  }
};

/**
 * Load an Apache2-style ".types" file
 *
 * This may be called multiple times (it's expected).  Where files declare
 * overlapping types/extensions, the last file wins.
 *
 * @param file (String) path of file to load.
 */
Mime.prototype.load = function(file) {

  this._loading = file;
  // Read file and split into lines
  var map = {},
      content = fs.readFileSync(file, 'ascii'),
      lines = content.split(/[\r\n]+/);

  lines.forEach(function(line) {
    // Clean up whitespace/comments, and split into fields
    var fields = line.replace(/\s*#.*|^\s*|\s*$/g, '').split(/\s+/);
    map[fields.shift()] = fields;
  });

  this.define(map);

  this._loading = null;
};

/**
 * Lookup a mime type based on extension
 */
Mime.prototype.lookup = function(path, fallback) {
  var ext = path.replace(/.*[\.\/]/, '').toLowerCase();

  return this.types[ext] || fallback || this.default_type;
};

/**
 * Return file extension associated with a mime type
 */
Mime.prototype.extension = function(mimeType) {
  return this.extensions[mimeType];
};

// Default instance
var mime = new Mime();

// Load local copy of
// http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
mime.load(path.join(__dirname, 'types/mime.types'));

// Load additional types from node.js community
mime.load(path.join(__dirname, 'types/node.types'));

// Default type
mime.default_type = mime.lookup('bin');

//
// Additional API specific to the default instance
//

mime.Mime = Mime;

/**
 * Lookup a charset based on mime type.
 */
mime.charsets = {
  lookup: function(mimeType, fallback) {
    // Assume text types are utf8
    return (/^text\//).test(mimeType) ? 'UTF-8' : fallback;
  }
};

module.exports = mime;


this.lastTag.end = this.startOffset + token.end + 1;

if (this.lastTag.tag !== token.contents) {

console.error("Close tag with no matching open tag: " + token.contents);

/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */
/*global define, $ */

/**
 * HTMLDocument manages a single HTML source document
 *
 * # EDITING
 *
 * Editing the document will cause the corresponding node to be updated
 * by calling `applyChanges` on the DOMAgent. This will only work for
 * altering text nodes and will break when attempting to change DOM elements
 * or inserting or deleting nodes.
 *
 * # HIGHLIGHTING
 *
 * HTMLDocument supports highlighting nodes from the HighlightAgent and
 * highlighting the DOMNode corresponding to the cursor position in the
 * editor.
 */
define(function HTMLDocumentModule(require, exports, module) {
    "use strict";

    var DocumentManager     = require("document/DocumentManager"),
        DOMAgent            = require("LiveDevelopment/Agents/DOMAgent"),
        HighlightAgent      = require("LiveDevelopment/Agents/HighlightAgent"),
        HTMLInstrumentation = require("language/HTMLInstrumentation"),
        Inspector           = require("LiveDevelopment/Inspector/Inspector"),
        LiveDevelopment     = require("LiveDevelopment/LiveDevelopment"),
        RemoteAgent         = require("LiveDevelopment/Agents/RemoteAgent"),
        StringUtils         = require("utils/StringUtils");

    /** Constructor
     *
     * @param Document the source document from Brackets
     */
    var HTMLDocument = function HTMLDocument(doc, editor) {
        this.doc = doc;
        if (!editor) {
            return;
        }
        this.editor = editor;
        this._instrumentationEnabled = false;

        this.onCursorActivity = this.onCursorActivity.bind(this);
        this.onDocumentSaved = this.onDocumentSaved.bind(this);

        $(this.editor).on("cursorActivity", this.onCursorActivity);
        $(DocumentManager).on("documentSaved", this.onDocumentSaved);

        // Experimental code
        if (LiveDevelopment.config.experimental) {
            // Used by highlight agent to highlight editor text as selected in browser
            this.onHighlight = this.onHighlight.bind(this);
            $(HighlightAgent).on("highlight", this.onHighlight);
        }

        this.onChange = this.onChange.bind(this);
        $(this.editor).on("change", this.onChange);
    };

    /**
     * Enable instrumented HTML
     * @param enabled {boolean}
     */
    HTMLDocument.prototype.setInstrumentationEnabled = function setInstrumentationEnabled(enabled) {
        if (enabled && !this._instrumentationEnabled) {
            HTMLInstrumentation.scanDocument(this.doc);
            HTMLInstrumentation._markText(this.editor);
        }

        this._instrumentationEnabled = enabled;
    };

    /**
     * Returns a JSON object with HTTP response overrides
     * @returns {{body: string}}
     */
    HTMLDocument.prototype.getResponseData = function getResponseData(enabled) {
        var body = (this._instrumentationEnabled) ?
                HTMLInstrumentation.generateInstrumentedHTML(this.doc) : this.doc.getText();

        return {
            body: body
        };
    };

    /** Close the document */
    HTMLDocument.prototype.close = function close() {
        if (!this.editor) {
            return;
        }

        $(this.editor).off("cursorActivity", this.onCursorActivity);
        $(DocumentManager).off("documentSaved", this.onDocumentSaved);

        // Experimental code
        if (LiveDevelopment.config.experimental) {
            $(HighlightAgent).off("highlight", this.onHighlight);
            this.onHighlight();

            $(this.editor).off("change", this.onChange);
        }
    };

    /** Event Handlers *******************************************************/

    /** Triggered on cursor activity by the editor */
    HTMLDocument.prototype.onCursorActivity = function onCursorActivity(event, editor) {
        if (!this.editor) {
            return;
        }
        var codeMirror = this.editor._codeMirror;
        if (Inspector.config.highlight) {
            var tagID = HTMLInstrumentation._getTagIDAtDocumentPos(
                this.editor,
                codeMirror.getCursor()
            );

            if (tagID === -1) {
                HighlightAgent.hide();
            } else {
                HighlightAgent.domElement(tagID);
            }
        }
    };

    /** Triggered on change by the editor */
    HTMLDocument.prototype.onChange = function onChange(event, editor, change) {
        // Only handles attribute changes currently.
        // TODO: text changes should be easy to add
        // TODO: if new tags are added, need to instrument them
        var edits = HTMLInstrumentation.getUnappliedEditList(editor, change);
        edits.forEach(function (edit) {
            // Silly naming convention: $$ = remote $
            var html,
                $$target = RemoteAgent.remoteElement(edit.type === "textReplace" || edit.type === "textDelete" || edit.type === "elementInsert" ? edit.parentID : edit.tagID);
            switch (edit.type) {
            case "attrChange":
            case "attrAdd":
                $$target.attr(edit.attribute, edit.value);
                break;
            case "attrDel":
                $$target.removeAttr(edit.attribute);
                break;
            case "elementDelete":
                $$target.remove();
                break;
            case "elementInsert":
                html = "<" + edit.tag;
                Object.keys(edit.attributes).forEach(function (attr) {
                    html += " " + attr + "='" + StringUtils.htmlEscape(edit.attributes[attr]) + "'";
                });
                html += " data-brackets-id='" + edit.tagID + "'/>";
                $$target.insertChild(edit.child, html, false);
                break;
            case "textInsert":
                $$target.insertChild(edit.child, edit.content, true);
            case "textReplace":
                $$target.replaceChildText(edit.afterID, edit.content);
                break;
            case "textDelete":
                $$target.deleteChildText(edit.afterID);
                break;
            }
        });

//        var marker = HTMLInstrumentation._getMarkerAtDocumentPos(
//            this.editor,
//            editor.getCursorPos()
//        );
//
//        if (marker && marker.tagID) {
//            var range   = marker.find(),
//                text    = marker.doc.getRange(range.from, range.to);
//
//            // HACK maintain ID
//            text = text.replace(">", " data-brackets-id='" + marker.tagID + "'>");
//
//            // FIXME incorrectly replaces body elements with content only, missing body element
//            RemoteAgent.remoteElement(marker.tagID).replaceWith(text);
//        }

        // if (!this.editor) {
        //     return;
        // }
        // var codeMirror = this.editor._codeMirror;
        // while (change) {
        //     var from = codeMirror.indexFromPos(change.from);
        //     var to = codeMirror.indexFromPos(change.to);
        //     var text = change.text.join("\n");
        //     DOMAgent.applyChange(from, to, text);
        //     change = change.next;
        // }
    };

    /** Triggered by the HighlightAgent to highlight a node in the editor */
    HTMLDocument.prototype.onHighlight = function onHighlight(event, node) {
        if (!node || !node.location || !this.editor) {
            if (this._highlight) {
                this._highlight.clear();
                delete this._highlight;
            }
            return;
        }
        var codeMirror = this.editor._codeMirror;
        var to, from = codeMirror.posFromIndex(node.location);
        if (node.closeLocation) {
            to = node.closeLocation + node.closeLength;
        } else {
            to = node.location + node.length;
        }
        to = codeMirror.posFromIndex(to);
        if (this._highlight) {
            this._highlight.clear();
        }
        this._highlight = codeMirror.markText(from, to, { className: "highlight" });
    };

    /** Triggered when a document is saved */
    HTMLDocument.prototype.onDocumentSaved = function onDocumentSaved(event, doc) {
        if (doc === this.doc) {
            HTMLInstrumentation.scanDocument(this.doc);
            HTMLInstrumentation._markText(this.editor);
        }
    };

    // Export the class
    module.exports = HTMLDocument;
});


#!/usr/bin/env node
var far = require('far').create();

far.add(__dirname);
far.include(/test-.*\.js$/);

far.execute();


var testutil = require('testutil')
  , mkdirp = require('mkdirp')
  , jf = require('../lib/jsonfile')
  , fs = require('fs')
  , path = require('path');

TEST_DIR = ''

beforeEach(function(done) {
    TEST_DIR = testutil.generateTestPath('test-jsonfile');
    mkdirp(TEST_DIR, done);
})

suite('jsonfile');

test('- readFile()', function(done) {
    var file = path.join(TEST_DIR, 'somefile.json');
    var obj = {name: 'JP'}
    fs.writeFileSync(file, JSON.stringify(obj));

    jf.readFile(file, function(err, obj2) {
        F (err)
        T (obj2.name === obj.name)
        done();
    });
})

test('- writeFile()', function(done) {
    var file = path.join(TEST_DIR, 'somefile2.json');
    var obj = {name: 'JP'};

    jf.writeFile(file, obj, function(err) {
        F (err)
        fs.readFile(file, 'utf8', function(err,data) {
            var obj2 = JSON.parse(data);
            T (obj2.name === obj.name)
            done();
        });
    })
})

test('- readFileSync()', function(done) {
    var file = path.join(TEST_DIR, 'somefile3.json');
    var obj = {name: 'JP'}
    fs.writeFileSync(file, JSON.stringify(obj));

    try {
        var obj2 = jf.readFileSync(file);
        T (obj2.name === obj.name);
        done();
    } catch (err) {
        done(err);
    }
})

test('- writeFileSync()', function(done) {
    var file = path.join(TEST_DIR, 'somefile4.json');
    var obj = {name: 'JP'};

    jf.writeFileSync(file, obj);

    var obj2 = JSON.parse(fs.readFileSync(file, 'utf8'));
    T (obj2.name === obj.name);
    done();
})


<!DOCTYPE html>

	<meta charset="utf-8">
	<title>jQuery UI Effects Demos</title>
	<link rel="stylesheet" href="../demos.css">

Examples

Default functionality


console.error("Mismatched tag: ", this.lastTag.tag, token.contents);

} else if (token.type === "attribname") {

} else if (token.type === "attribvalue" && attributeName !== null) {