<commit_before>/**
 * @module validator
 * @desc This module is responsible for validating the document being
 * edited in wed.
 * @author <NAME>
 */
define(/** @lends module:validator */ function (require, exports, module) {
"use strict";

var SimpleEventEmitter =
        require("./lib/simple_event_emitter").SimpleEventEmitter;
var validate = require("salve/validate");
var $ = require("jquery");
var util = require("./util");
var name_resolver = require("./name_resolver");
var oop = require("./oop");

// validation_stage values

var START_TAG = 1;
var CONTENTS = 2;
var END_TAG = 3;

// Working state values

var INCOMPLETE = 1;
var WORKING = 2;
var INVALID = 3;
var VALID = 4;

exports.INCOMPLETE = INCOMPLETE;
exports.WORKING = WORKING;
exports.INVALID = INVALID;
exports.VALID = VALID;

function Validator(schema, root) {
    // Call the constructor for our mixin
    SimpleEventEmitter.call(this);

    this.schema = schema;
    this.resolver = new name_resolver.NameResolver();

    this.root = root;
    this._timeout = 200;
    this._max_timespan = 100;
    this._timeout_id = undefined;
    this._initialized = false;
    this._errors = [];
    this._tree = undefined;
    this._bound_wrapper = this._workWrapper.bind(this);

    // Validation state
    this._events = [];
    this._validation_walker = undefined;
    this._working_state = undefined;
    this._part_done = 0;
    this._validation_stage = CONTENTS;
    this._previous_child = null;
    this._validation_stack = [new ProgressState(0, 1)];
    this._element_to_validate = null;

    this._setWorkingState(INCOMPLETE);

}

oop.implement(Validator, SimpleEventEmitter);

Validator.prototype.start = function () {
    if (this._timeout_id !== undefined)
        this.stop();

    if (!this._initialized)
        this.initialize(function () {
            this._timeout_id = window.setTimeout(this._bound_wrapper,
                                                 this._timeout);
        }.bind(this));
    else
        this._timeout_id = window.setTimeout(this._bound_wrapper,
                                             this._timeout);
    this._setWorkingState(WORKING, this._part_done);
};

Validator.prototype.initialize = function (done) {
    require(["requirejs/text!" + this.schema], function(x) {
        this._tree = validate.constructTree(x);
        this._validation_walker = this._tree.newWalker();
        this._element_to_validate = this.root;
        this._initialized = true;
        done();
    }.bind(this));
};

Validator.prototype.getNamespaces = function () {
    if (!this._initialized)
        throw new Error("calling getNamespaces on uninitialized validator");
    return this._tree.getNamespaces();
};

Validator.prototype._workWrapper = function () {
    this._restarting = false;
    if (this._work())
        this._timeout_id = window.setTimeout(this._bound_wrapper,
                                             this._timeout);
};

Validator.prototype._work = function () {
    if (!this._initialized)
        return true;

    var start_date = new Date();
    while (true) {
        // Give a chance to other operations to work.
        if ((this._max_timespan > 0) &&
            (new Date() - start_date) >= this._max_timespan)
            return true;

        var ret = this._cycle();
        if (!ret)
            return false;
    }
    return true;
};

Validator.prototype._cycle = function () {
    // Damn hoisting
    var event_result, ename, attr_ix, attr, name;

    var walker = this._validation_walker;

    var portion = this._validation_stack[0].portion;

    stage_change:
    while (true) {
        var $element_to_validate = $(this._element_to_validate);
        switch(this._validation_stage) {
        case START_TAG:
            this._validation_stack.unshift(
                new ProgressState(this._part_done, portion));

            // These are currently not needed:
            // $element_to_validate.data("wed_state_index_before",
            // this._events.length - 1);
            // $element_to_validate.data("wed_event_index_before",
            // this._events.length - 1);


            // Phantoms do not fire events, nor do "_real _text"
            // elements, nor do placeholders, or carets
            if (!$element_to_validate.hasClass("_phantom_wrap") &&
                !$element_to_validate.is(
                    "._real._text, ._placeholder, ._wed_caret")) {

                // Handle namespace declarations. Yes, this must
                // happen before we deal with the tag name.
                this.resolver.enterContext();
                var attr_ix_lim = this._element_to_validate.attributes.length;
                for(attr_ix = 0; attr_ix < attr_ix_lim; ++attr_ix) {
                    attr = this._element_to_validate.attributes[attr_ix];
                    if (attr.name === "data-wed-xmlns")
                        this.resolver.definePrefix("", attr.value);
                    else if (attr.name.lastIndexOf("data-wed-xmlns---",
                                                   0) === 0)
                        this.resolver.definePrefix(attr.name.slice(17),
                                                   attr.value);
                }
                // Enter the start tag. The 1st class name is
                // always the name of the tag in the original XML.
                name = this._element_to_validate.className.split(" ", 1)[0];
                ename = this.resolver.resolveName(name);
                this._fireAndProcessEvent(
                    walker,
                    new validate.Event("enterStartTag", ename.ns, ename.name),
                    this._element_to_validate);

                // Find all attributes, fire events for them. We
                // should not get any errors here, since the user
                // does not get to set these manually. An error
                // here would indicate an internal error.
                for(attr_ix = 0; attr_ix < attr_ix_lim; ++attr_ix) {
                    attr = this._element_to_validate.attributes[attr_ix];
                    // Skip those attributes which do not encode
                    // an original attribute or which are
                    // namespace attributes.
                    if ((attr.name.lastIndexOf("data-wed-", 0) !== 0) ||
                        (attr.name === "data-wed-xmlns") ||
                        (attr.name.lastIndexOf("data-wed-xmlns", 0) === 0))
                        continue;
                    var attr_name = util.decodeAttrName(attr.name);
                    ename = this.resolver.resolveName(attr_name, true);
                    this._fireAndProcessEvent(
                        walker,
                        new validate.Event("attributeName",
                                           ename.ns, ename.name),
                        this._element_to_validate);
                    this._fireAndProcessEvent(
                        walker,
                        new validate.Event("attributeValue", attr.value),
                        this._element_to_validate);
                }

                    // Leave the start tag.
                this._fireAndProcessEvent(
                    walker, new validate.Event("leaveStartTag"),
                    this._element_to_validate);
            }

            this._validation_stage = CONTENTS;
            $element_to_validate.data("wed_event_index_after_start",
                                      this._events.length);
            return true; // state change
            // break would be unreachable.
        case CONTENTS:
            var node = (this._previous_child === null)  ?
                // starting from scratch
                this._element_to_validate.firstChild :
                // already validation contents
                this._previous_child.nextSibling;

            while (node !== null) {
                switch(node.nodeType) {
                case Node.TEXT_NODE:
                    // We want to fire an event only if the text node
                    // is not just white space or if text is possible.
                    event_result = this._fireTextEventIfNeeded(node, walker);
                    if (event_result)
                        // XXX
                        console.log("XXX text error");
                    break;
                case Node.ELEMENT_NODE:
                    if (!$(node).hasClass("_phantom")) {
                        portion /= $element_to_validate.children().length;
                        this._element_to_validate = node;
                        this._validation_stage = START_TAG;
                        this._previous_child = null;
                        continue stage_change;
                    }
                    // else we do nothing because it is phantom.
                    break;
                default:
                    throw new Error("unexpected node type: " + node.nodeType);
                }
                node = node.nextSibling;
            }

            if (node === null)
                this._validation_stage = END_TAG;
            break;
        case END_TAG:
            // We've reached the end...
            if (this._element_to_validate === this.root) {
                event_result = walker.end();
                if (event_result)
                    this._processTagEventResult(event_result,
                                                this._element_to_validate);
                this._part_done = 1;
                this._setWorkingState(this._errors.length > 0 ? INVALID :
                                      VALID);
                $(this._element_to_validate).data("wed_event_index_after",
                                                  this._events.length);
                this.stop();
                return false;
            }

            // we need it later
            var original_element = this._element_to_validate;
            // Phantoms do not generate events, nor do "_real
            // _text" elements.
            if (!$element_to_validate.hasClass("_phantom_wrap") &&
                !$element_to_validate.is(
                    "._real._text, ._placeholder, ._wed_caret")) {
                // The 1st class name is always the name of the
                // tag in the original XML.
                name = this._element_to_validate.className.split(" ", 1)[0];
                ename = this.resolver.resolveName(name);
                this._fireAndProcessEvent(
                    walker,
                    new validate.Event("endTag", ename.ns, ename.name),
                    this._element_to_validate);
                this.resolver.leaveContext();
            }

            // Go back to the parent
            this._previous_child = this._element_to_validate;
            this._element_to_validate = this._element_to_validate.parentNode;

            if (this._element_to_validate !== this.root) {
                this._validation_stack.shift();
                this._part_done =
                    this._validation_stack[0].part_done += portion;
                portion = this._validation_stack[0].portion;
            }

            this._setWorkingState(WORKING, this._part_done);

            $(original_element).data("wed_event_index_after",
                                     this._events.length);
            this._validation_stage = CONTENTS;
            return true; // state_change

            // break; would be unreachable
        default:
            throw new Error("unexpected state");
        }
    }
};

Validator.prototype.stop = function () {
    if (this._timeout_id !== undefined)
        window.clearTimeout(this._timeout_id);
    this._timeout_id = undefined;

    // We are stopping prematurely, update the state
    if (this._working_state === WORKING)
        this._setWorkingState(INCOMPLETE);
};

Validator.prototype.restartAt = function (element) {
    if (this._working_state === WORKING)
        this.stop();
    if (this._initialized && !this._restarting) {
        this._restarting = true;
        $(this.root).findAndSelf("*").removeData(
            ["wed_event_index_after", "wed_event_index_after_start"]);
        this._validation_stage = CONTENTS;
        this._previous_child = null;
        this._validation_walker = this._tree.newWalker();
        this._events = [];
        this._element_to_validate = this.root;
        this._part_done = 0;
        this._errors = [];
        this._emit("reset-errors", { at: 0 });
    }
    this.start();
};

Validator.prototype._setWorkingState = function (new_state) {
    if (this._working_state !== new_state || new_state === WORKING) {
        this._working_state = new_state;
        this._emit("state-update");
    }
};

Validator.prototype.getWorkingState = function () {
    return {
        state: this._working_state,
        part_done: this._part_done
    };
};

Validator.prototype._processTagEventResult = function (result, element) {
    for(var ix = 0, err; (err = result[ix]) !== undefined; ++ix) {
        this._errors.push(err);
        this._emit("error", { error: err, element: element});
    }
};

Validator.prototype._fireAndProcessEvent = function (walker, event,
                                                     element_to_validate) {
    this._events.push(event);
    var event_result = walker.fireEvent(event);
    if (event_result)
        this._processTagEventResult(event_result, element_to_validate);
};

Validator.prototype._fireTextEventIfNeeded = function (text_node, walker) {
    if (($.trim(text_node.nodeValue).length > 0) ||
        (walker.possible().filter(function (x) {
            return x[0] === 'text';
        }).length > 0)) {
        var event = new validate.Event("text");
        this._events.push(event);
        return walker.fireEvent(event);
    }

    // Behave the same as when an event has been fired without error.
    return false;
};

Validator.prototype._validateUpTo = function (container, index) {
    var $container = $(container);

    // Set these to reasonable defaults. The rest of the code is
    // dedicated to changing these values to those necessary depending
    // on specifics of what is passed to the method.
    var $to_inspect = $container;
    var data_key = "wed_event_index_after";

    // This function could be called with container === root if the
    // document is empty or if the user has the caret before the start
    // tag of the first element of the actual structure we want to
    // validate or after the end tag of that element.
    if (container === this.root) {
        // We're before the top element, no events to fire.
        if (index === 0)
            return;
        // default values of $to_inspect and data_key are what we want
    }
    else {
        // Damn hoisting.
        var $prev;
        switch(container.nodeType) {
        case Node.TEXT_NODE:
            $prev = $(container).prev("._real, ._phantom_wrap");

            if ($prev.length > 0)
                $to_inspect = $prev;
            else {
                $to_inspect =  $(container.parentNode);
                data_key = "wed_event_index_after_start";
            }
            break;
        case Node.ELEMENT_NODE:
            var node = container.childNodes[index];

            $prev = (node === undefined) ?
                $(container).children("._real, ._phantom_wrap").last() :
                $(node).prev("._real, ._phantom_wrap");

            if ($prev.length > 0)
                $to_inspect = $prev;
            else
                // $to_inspect's default is fine
                data_key = "wed_event_index_after_start";
            break;
        default:
            throw new Error("unexpected node type: " + container.nodeType);
        }
    }

    while($to_inspect.data(data_key) === undefined)
        this._cycle();
};

function EventIndexException ()
{
    Error.call(this, "undefined event_index; "+
               "_validateUpTo should have taken care of that");
}

oop.inherit(EventIndexException, Error);

Validator.prototype._getWalkerAt = function(container, index) {
    // Make sure we have the data we need.
    this._validateUpTo(container, index);

    // Damn hoisting.
    var walker, ix;
    // This function could be called with container === root if the
    // document is empty or if the user has the caret before the start
    // tag of the first element of the actual structure we want to
    // validate or after the end tag of that element.
    if (container === this.root) {
        walker = this._tree.newWalker();

        // We're before the top element, no events to fire.
        if (index === 0)
            return walker;

        event_index = $(container).data("wed_event_index_after");
        if (event_index === undefined)
            throw new EventIndexException();

        // We're past the top element, fire all events.
        for(ix= 0; ix < this._events.length; ++ix)
            walker.fireEvent(this._events[ix]);
        return walker;
    }

    // Damn hoisting
    var $prev, event_index;
    switch(container.nodeType) {
    case Node.TEXT_NODE:
        $prev = $(container).prev("._real, ._phantom_wrap");

        event_index =
            ($prev.length > 0) ? $prev.data("wed_event_index_after") :
            $(container.parentNode).data("wed_event_index_after_start");

        if (event_index === undefined)
            throw new EventIndexException();

        walker = this._tree.newWalker();
        for(ix = 0; ix < event_index; ++ix)
            walker.fireEvent(this._events[ix]);

        // We will attempt to fire a text event either if our location
        // is inside the current text node or if another text node
        // appeared between this text node and the previous element
        // found.
        if (index > 0 ||
            $prev.nextUntil(container).filter(function (x) {
                return this.nodeType === Node.TEXT_NODE ||
                    $(this).hasClass("_text");
            }).length > 0)
            fireTextEventIfNeeded(container, walker);

        break;
    case Node.ELEMENT_NODE:
        var node = container.childNodes[index];

        $prev = (node === undefined) ?
            $(container).children("._real, ._phantom_wrap").last() :
            $(node).prev("._real, ._phantom_wrap");

        event_index =
            ($prev.length > 0) ? $prev.data("wed_event_index_after") :
            $(container).data("wed_event_index_after_start");

        if (event_index === undefined)
            throw new EventIndexException();

        walker = this._tree.newWalker();
        for(ix = 0; ix < event_index; ++ix)
            walker.fireEvent(this._events[ix]);

        // We will attempt to fire a text event if another
        // text node appeared between this text node and the
        // previous element found.
        if (((node === undefined) ? $prev.nextAll() :
             $prev.nextUntil(node)).filter(function (x) {
            return this.nodeType === Node.TEXT_NODE ||
                $(this).hasClass("_text");
        }).length > 0)
            fireTextEventIfNeeded(container, walker);

        break;
    default:
        throw new Error("unexpected node type: " + container.nodeType);
    }

    return walker;
};

// The meaning of container and index is the same as for selection
// ranges.
Validator.prototype.possibleAt = function (container, index) {
    var walker = this._getWalkerAt(container, index);
    return walker.possible();
};

//
// Private helper functions and classes
//

function ProgressState(part_done, portion) {
    this.part_done = part_done;
    this.portion = portion;
}


function fireTextEventIfNeeded(text_node, walker) {
    if (($.trim(text_node.nodeValue).length > 0) ||
        (walker.possible().filter(function (x) {
            return x[0] === 'text';
        }).length > 0)) {

        return walker.fireEvent(new validate.Event("text"));
    }

    // Behave the same as when an event has been fired without error.
    return false;
}<commit_msg>Added a bunch of documentation.<commit_after>/**
 * @module validator
 * @desc This module is responsible for validating the document being
 * edited in wed.
 * @author <NAME>
 */
define(/** @lends module:validator */ function (require, exports, module) {
"use strict";

var SimpleEventEmitter =
        require("./lib/simple_event_emitter").SimpleEventEmitter;
var validate = require("salve/validate");
var $ = require("jquery");
var util = require("./util");
var name_resolver = require("./name_resolver");
var oop = require("./oop");

// validation_stage values

var START_TAG = 1;
var CONTENTS = 2;
var END_TAG = 3;

// Working state values

var INCOMPLETE = 1;
var WORKING = 2;
var INVALID = 3;
var VALID = 4;

exports.INCOMPLETE = INCOMPLETE;
exports.WORKING = WORKING;
exports.INVALID = INVALID;
exports.VALID = VALID;

/**
 * @class
 * @param {String} schema A path to the schema to pass to salve for
 * validation. This is a path that will be interpreted by
 * RequireJS. The schema must have already been prepared for use by
 * salve. See salve's documentation.
 * @param {Node} root The root of the DOM tree to validate. This root
 * contains it the document to validate but is not
 * <strong>part</strong> of it.
 */
function Validator(schema, root) {
    // Call the constructor for our mixin
    SimpleEventEmitter.call(this);

    this.schema = schema;
    this.resolver = new name_resolver.NameResolver();

    this.root = root;
    this._timeout = 200;
    this._max_timespan = 100;
    this._timeout_id = undefined;
    this._initialized = false;
    this._errors = [];
    this._tree = undefined;
    this._bound_wrapper = this._workWrapper.bind(this);

    // Validation state
    this._events = [];
    this._validation_walker = undefined;
    this._working_state = undefined;
    this._part_done = 0;
    this._validation_stage = CONTENTS;
    this._previous_child = null;
    this._validation_stack = [new ProgressState(0, 1)];
    this._element_to_validate = null;

    this._setWorkingState(INCOMPLETE);

}

oop.implement(Validator, SimpleEventEmitter);

/**
 * Starts the background validation process. If the Validator object
 * is not initialized yet, this will initialize it.
 */
Validator.prototype.start = function () {
    if (this._timeout_id !== undefined)
        this.stop();

    if (!this._initialized)
        this.initialize(function () {
            this._timeout_id = window.setTimeout(this._bound_wrapper,
                                                 this._timeout);
        }.bind(this));
    else
        this._timeout_id = window.setTimeout(this._bound_wrapper,
                                             this._timeout);
    this._setWorkingState(WORKING, this._part_done);
};

/**
 * Create the structures needed for the Validator to run.
 *
 * @param {Function} done This function will be called once the
 * Validator is initialized. This is necessary because some of the
 * work to be done is asynchronous.
 */
Validator.prototype.initialize = function (done) {
    require(["requirejs/text!" + this.schema], function(x) {
        this._tree = validate.constructTree(x);
        this._validation_walker = this._tree.newWalker();
        this._element_to_validate = this.root;
        this._initialized = true;
        done();
    }.bind(this));
};

/**
 * Get the namespaces defined in the schema passed to the
 * Validator. It is a fatal error to call this on an uninitialized
 * Validator.
 *
 * @returns {Array.<String>} The namespaces known to the schema.
 */
Validator.prototype.getNamespaces = function () {
    if (!this._initialized)
        throw new Error("calling getNamespaces on uninitialized validator");
    return this._tree.getNamespaces();
};

/**
 * Convenience method. The bound version of this method
 * (<code>this._bound_wrapper</code> is what is called by the
 * timeouts to perform the background validation.
 *
 * @private
 */
Validator.prototype._workWrapper = function () {
    this._restarting = false;
    if (this._work())
        this._timeout_id = window.setTimeout(this._bound_wrapper,
                                             this._timeout);
};

/**
 * Controler method for the background validation. Keeps the validator
 * running only until done or until the maximum time span for one run
 * of the validator is reached.
 *
 * @private
 * @returns {Boolean} False if there is no more work to do. True
 * otherwise.
 */
Validator.prototype._work = function () {
    if (!this._initialized)
        return true;

    var start_date = new Date();
    while (true) {
        // Give a chance to other operations to work.
        if ((this._max_timespan > 0) &&
            (new Date() - start_date) >= this._max_timespan)
            return true;

        var ret = this._cycle();
        if (!ret)
            return false;
    }
    return true;
};

/**
 * Peforms one cycle of validation. "One cycle" is an arbitrarily
 * small unit of work.
 *
 * @private
 * @returns {Boolean} False if there is no more work to be done. True
 * otherwise.
 */
Validator.prototype._cycle = function () {
    // Damn hoisting
    var event_result, ename, attr_ix, attr, name;

    var walker = this._validation_walker;

    var portion = this._validation_stack[0].portion;

    stage_change:
    while (true) {
        var $element_to_validate = $(this._element_to_validate);
        switch(this._validation_stage) {
        case START_TAG:
            this._validation_stack.unshift(
                new ProgressState(this._part_done, portion));

            // These are currently not needed:
            // $element_to_validate.data("wed_state_index_before",
            // this._events.length - 1);
            // $element_to_validate.data("wed_event_index_before",
            // this._events.length - 1);


            // Phantoms do not fire events, nor do "_real _text"
            // elements, nor do placeholders, or carets
            if (!$element_to_validate.hasClass("_phantom_wrap") &&
                !$element_to_validate.is(
                    "._real._text, ._placeholder, ._wed_caret")) {

                // Handle namespace declarations. Yes, this must
                // happen before we deal with the tag name.
                this.resolver.enterContext();
                var attr_ix_lim = this._element_to_validate.attributes.length;
                for(attr_ix = 0; attr_ix < attr_ix_lim; ++attr_ix) {
                    attr = this._element_to_validate.attributes[attr_ix];
                    if (attr.name === "data-wed-xmlns")
                        this.resolver.definePrefix("", attr.value);
                    else if (attr.name.lastIndexOf("data-wed-xmlns---",
                                                   0) === 0)
                        this.resolver.definePrefix(attr.name.slice(17),
                                                   attr.value);
                }
                // Enter the start tag. The 1st class name is
                // always the name of the tag in the original XML.
                name = this._element_to_validate.className.split(" ", 1)[0];
                ename = this.resolver.resolveName(name);
                this._fireAndProcessEvent(
                    walker,
                    new validate.Event("enterStartTag", ename.ns, ename.name),
                    this._element_to_validate);

                // Find all attributes, fire events for them. We
                // should not get any errors here, since the user
                // does not get to set these manually. An error
                // here would indicate an internal error.
                for(attr_ix = 0; attr_ix < attr_ix_lim; ++attr_ix) {
                    attr = this._element_to_validate.attributes[attr_ix];
                    // Skip those attributes which do not encode
                    // an original attribute or which are
                    // namespace attributes.
                    if ((attr.name.lastIndexOf("data-wed-", 0) !== 0) ||
                        (attr.name === "data-wed-xmlns") ||
                        (attr.name.lastIndexOf("data-wed-xmlns", 0) === 0))
                        continue;
                    var attr_name = util.decodeAttrName(attr.name);
                    ename = this.resolver.resolveName(attr_name, true);
                    this._fireAndProcessEvent(
                        walker,
                        new validate.Event("attributeName",
                                           ename.ns, ename.name),
                        this._element_to_validate);
                    this._fireAndProcessEvent(
                        walker,
                        new validate.Event("attributeValue", attr.value),
                        this._element_to_validate);
                }

                    // Leave the start tag.
                this._fireAndProcessEvent(
                    walker, new validate.Event("leaveStartTag"),
                    this._element_to_validate);
            }

            this._validation_stage = CONTENTS;
            $element_to_validate.data("wed_event_index_after_start",
                                      this._events.length);
            return true; // state change
            // break would be unreachable.
        case CONTENTS:
            var node = (this._previous_child === null)  ?
                // starting from scratch
                this._element_to_validate.firstChild :
                // already validation contents
                this._previous_child.nextSibling;

            while (node !== null) {
                switch(node.nodeType) {
                case Node.TEXT_NODE:
                    // We want to fire an event only if the text node
                    // is not just white space or if text is possible.
                    event_result = this._fireTextEventIfNeeded(node, walker);
                    if (event_result)
                        // XXX
                        console.log("XXX text error");
                    break;
                case Node.ELEMENT_NODE:
                    if (!$(node).hasClass("_phantom")) {
                        portion /= $element_to_validate.children().length;
                        this._element_to_validate = node;
                        this._validation_stage = START_TAG;
                        this._previous_child = null;
                        continue stage_change;
                    }
                    // else we do nothing because it is phantom.
                    break;
                default:
                    throw new Error("unexpected node type: " + node.nodeType);
                }
                node = node.nextSibling;
            }

            if (node === null)
                this._validation_stage = END_TAG;
            break;
        case END_TAG:
            // We've reached the end...
            if (this._element_to_validate === this.root) {
                event_result = walker.end();
                if (event_result)
                    this._processTagEventResult(event_result,
                                                this._element_to_validate);
                this._part_done = 1;
                this._setWorkingState(this._errors.length > 0 ? INVALID :
                                      VALID);
                $(this._element_to_validate).data("wed_event_index_after",
                                                  this._events.length);
                this.stop();
                return false;
            }

            // we need it later
            var original_element = this._element_to_validate;
            // Phantoms do not generate events, nor do "_real
            // _text" elements.
            if (!$element_to_validate.hasClass("_phantom_wrap") &&
                !$element_to_validate.is(
                    "._real._text, ._placeholder, ._wed_caret")) {
                // The 1st class name is always the name of the
                // tag in the original XML.
                name = this._element_to_validate.className.split(" ", 1)[0];
                ename = this.resolver.resolveName(name);
                this._fireAndProcessEvent(
                    walker,
                    new validate.Event("endTag", ename.ns, ename.name),
                    this._element_to_validate);
                this.resolver.leaveContext();
            }

            // Go back to the parent
            this._previous_child = this._element_to_validate;
            this._element_to_validate = this._element_to_validate.parentNode;

            if (this._element_to_validate !== this.root) {
                this._validation_stack.shift();
                this._part_done =
                    this._validation_stack[0].part_done += portion;
                portion = this._validation_stack[0].portion;
            }

            this._setWorkingState(WORKING, this._part_done);

            $(original_element).data("wed_event_index_after",
                                     this._events.length);
            this._validation_stage = CONTENTS;
            return true; // state_change

            // break; would be unreachable
        default:
            throw new Error("unexpected state");
        }
    }
};

/**
 * Stops background validation.
 */
Validator.prototype.stop = function () {
    if (this._timeout_id !== undefined)
        window.clearTimeout(this._timeout_id);
    this._timeout_id = undefined;

    // We are stopping prematurely, update the state
    if (this._working_state === WORKING)
        this._setWorkingState(INCOMPLETE);
};

/**
 * Restarts validation from a specific point. After the call returns,
 * the background validation will be in effect. (So calling it on a
 * stopped validator has for side effect to start it.)
 *
 * @param {Node} node The element to start validation from.
 */
Validator.prototype.restartAt = function (node) {
    // An earlier implementation was trying to be clever and to avoid
    // restarting much earlier than strictly needed. That ended up
    // being more costly than doing this primitive restart from 0 no
    // matter what. Eventually, Validator should be updated so that on
    // large documents, restarting from a location towards the end
    // does not require revalidating the whole document. For now,
    // since wed is used for smallish documents, it would be a
    // premature optimization.

    if (this._working_state === WORKING)
        this.stop();
    if (this._initialized && !this._restarting) {
        this._restarting = true;
        $(this.root).findAndSelf("*").removeData(
            ["wed_event_index_after", "wed_event_index_after_start"]);
        this._validation_stage = CONTENTS;
        this._previous_child = null;
        this._validation_walker = this._tree.newWalker();
        this._events = [];
        this._element_to_validate = this.root;
        this._part_done = 0;
        this._errors = [];
        this._emit("reset-errors", { at: 0 });
    }
    this.start();
};

/**
 * Sets the working state of the validator. Emits a "state-update"
 * event if the state is really new.
 *
 * @private
 * @param new_state The new state of the validator.
 */
Validator.prototype._setWorkingState = function (new_state) {
    if (this._working_state !== new_state || new_state === WORKING) {
        this._working_state = new_state;
        this._emit("state-update");
    }
};

/**
 * Gets the validator working state.
 * @returns {Object} An object with two fields. The field
 * <code>state</code> is the actual working state. The field
 * <code>part_done</code> is the part of validation done so far.
 */
Validator.prototype.getWorkingState = function () {
    return {
        state: this._working_state,
        part_done: this._part_done
    };
};

/**
 * Processes the result of firing a tag event. It will emit an "error"
 * event for each error.
 *
 * @private
 * @param result The result of the walker's <code>fireEvent</code> call.
 * @param {Node} element The element to which the result belongs.
 */
Validator.prototype._processTagEventResult = function (result, element) {
    for(var ix = 0, err; (err = result[ix]) !== undefined; ++ix) {
        this._errors.push(err);
        this._emit("error", { error: err, element: element});
    }
};

/**
 * Convenience method to fire events.
 *
 * @private
 * @param {module:validate~Walker} walker The walker on which to fire events.
 * @param {module:validate~Event} event The event to fire.
 * @param {Node} element_to_validate The DOM node associated with this event.
 */
Validator.prototype._fireAndProcessEvent = function (walker, event,
                                                     element_to_validate) {
    this._events.push(event);
    var event_result = walker.fireEvent(event);
    if (event_result)
        this._processTagEventResult(event_result, element_to_validate);
};

Validator.prototype._fireTextEventIfNeeded = function (text_node, walker) {
/**
 * <p>Convenience method. This method will fire a text event only if
 * warranted, that is, if either the text node is non empty or if the
 * current location in validation accepts text nodes.</p>
 *
 * <p>Rationale: a non-empty text node must fire a text
 * event. However, if a text node is empty it is relevant for
 * validation only if it appears in a context where text is
 * expected.</p>
 *
 * @private
 * @param {Node} text_node The text node for which the method might
 * fire an event.
 * @param {module:validate~Walker} walker The walker on which to fire
 * events.
 speculative parsing operations.
 *
 * @returns The same possible values a walker returns.
 */

    if (($.trim(text_node.nodeValue).length > 0) ||
        (walker.possible().filter(function (x) {
            return x[0] === 'text';
        }).length > 0)) {
        var event = new validate.Event("text");
        this._events.push(event);
        return walker.fireEvent(event);
    }

    // Behave the same as when an event has been fired without error.
    return false;
};

/**
 * Force immediate validation up to the point specified by the
 * parameters. These parameters are interpreted in the same way a DOM
 * caret is.
 *
 * @private
 * @param {Node} container
 * @param {Integer} index
 */
Validator.prototype._validateUpTo = function (container, index) {
    var $container = $(container);

    // Set these to reasonable defaults. The rest of the code is
    // dedicated to changing these values to those necessary depending
    // on specifics of what is passed to the method.
    var $to_inspect = $container;
    var data_key = "wed_event_index_after";

    // This function could be called with container === root if the
    // document is empty or if the user has the caret before the start
    // tag of the first element of the actual structure we want to
    // validate or after the end tag of that element.
    if (container === this.root) {
        // We're before the top element, no events to fire.
        if (index === 0)
            return;
        // default values of $to_inspect and data_key are what we want
    }
    else {
        // Damn hoisting.
        var $prev;
        switch(container.nodeType) {
        case Node.TEXT_NODE:
            $prev = $(container).prev("._real, ._phantom_wrap");

            if ($prev.length > 0)
                $to_inspect = $prev;
            else {
                $to_inspect =  $(container.parentNode);
                data_key = "wed_event_index_after_start";
            }
            break;
        case Node.ELEMENT_NODE:
            var node = container.childNodes[index];

            $prev = (node === undefined) ?
                $(container).children("._real, ._phantom_wrap").last() :
                $(node).prev("._real, ._phantom_wrap");

            if ($prev.length > 0)
                $to_inspect = $prev;
            else
                // $to_inspect's default is fine
                data_key = "wed_event_index_after_start";
            break;
        default:
            throw new Error("unexpected node type: " + container.nodeType);
        }
    }

    while($to_inspect.data(data_key) === undefined)
        this._cycle();
};

/**
 * Exception to be raised if we can't find our place in the events
 * list. It is only to be raised by code in this module but the
 * documentation is left public for diagnosis purposes.
 *
 * @class
 */
function EventIndexException ()
{
    Error.call(this, "undefined event_index; "+
               "_validateUpTo should have taken care of that");
}

oop.inherit(EventIndexException, Error);

/**
 * Gets the walker which would represent the state of parsing at the
 * point expressed by the parameters. These parameters are interpreted
 * in the same way a DOM caret is. This method forces validation up to
 * the point specified.
 *
 * @private
 *
 * @param {Node} container
 * @param {Integer} index
 * @returns {module:validate~Walker} The walker.
 */
Validator.prototype._getWalkerAt = function(container, index) {
    // Make sure we have the data we need.
    this._validateUpTo(container, index);

    // Damn hoisting.
    var walker, ix;
    // This function could be called with container === root if the
    // document is empty or if the user has the caret before the start
    // tag of the first element of the actual structure we want to
    // validate or after the end tag of that element.
    if (container === this.root) {
        walker = this._tree.newWalker();

        // We're before the top element, no events to fire.
        if (index === 0)
            return walker;

        event_index = $(container).data("wed_event_index_after");
        if (event_index === undefined)
            throw new EventIndexException();

        // We're past the top element, fire all events.
        for(ix= 0; ix < this._events.length; ++ix)
            walker.fireEvent(this._events[ix]);
        return walker;
    }

    // Damn hoisting
    var $prev, event_index;
    switch(container.nodeType) {
    case Node.TEXT_NODE:
        $prev = $(container).prev("._real, ._phantom_wrap");

        event_index =
            ($prev.length > 0) ? $prev.data("wed_event_index_after") :
            $(container.parentNode).data("wed_event_index_after_start");

        if (event_index === undefined)
            throw new EventIndexException();

        walker = this._tree.newWalker();
        for(ix = 0; ix < event_index; ++ix)
            walker.fireEvent(this._events[ix]);

        // We will attempt to fire a text event either if our location
        // is inside the current text node or if another text node
        // appeared between this text node and the previous element
        // found.
        if (index > 0 ||
            $prev.nextUntil(container).filter(function (x) {
                return this.nodeType === Node.TEXT_NODE ||
                    $(this).hasClass("_text");
            }).length > 0)
            fireTextEventIfNeeded(container, walker);

        break;
    case Node.ELEMENT_NODE:
        var node = container.childNodes[index];

        $prev = (node === undefined) ?
            $(container).children("._real, ._phantom_wrap").last() :
            $(node).prev("._real, ._phantom_wrap");

        event_index =
            ($prev.length > 0) ? $prev.data("wed_event_index_after") :
            $(container).data("wed_event_index_after_start");

        if (event_index === undefined)
            throw new EventIndexException();

        walker = this._tree.newWalker();
        for(ix = 0; ix < event_index; ++ix)
            walker.fireEvent(this._events[ix]);

        // We will attempt to fire a text event if another
        // text node appeared between this text node and the
        // previous element found.
        if (((node === undefined) ? $prev.nextAll() :
             $prev.nextUntil(node)).filter(function (x) {
            return this.nodeType === Node.TEXT_NODE ||
                $(this).hasClass("_text");
        }).length > 0)
            fireTextEventIfNeeded(container, walker);

        break;
    default:
        throw new Error("unexpected node type: " + container.nodeType);
    }

    return walker;
};

/**
 * Returns the set of possible events for the location specified by
 * the parameters. A "possible event" is an event that if fired won't
 * result in a validation error. These parameters are interpreted in
 * the same way a DOM caret is. This method forces validation up to
 * the point specified.
 *
 * @param {Node} container
 * @param {Integer} index
 * @returns {module:validate~EventSet} A set of possible events.
 */
Validator.prototype.possibleAt = function (container, index) {
    var walker = this._getWalkerAt(container, index);
    return walker.possible();
};

//
// Private helper functions and classes
//

/**
 * Data structure for recording progress.
 *
 * @private
 * @class
 * @param {Float} part_done The part of the document done so far.
 * @param {Float} portion A ProgressState object is created in
 * relation to an element. The element covers portion X of the total
 * document. This parameters should be X.
 */
function ProgressState(part_done, portion) {
    this.part_done = part_done;
    this.portion = portion;
}


{'fraction_non_alphanumeric': 0.10406310068270697, 'fraction_numerical': 0.0026512891893683303, 'mean_word_length': 1.920317439834828, 'pattern_counts': {'":': 0, '<': 30, '<?xml version=': 0, '>': 46, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 8}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 42, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6677721', 'n_tokens_mistral': 12282, 'n_tokens_neox': 11688, 'n_words': 3366}