/*
    Getme Ltd reserve all intellectual property rights of this source code.
    
    EXCEPT AS OTHERWISE EXPRESSLY PROVIDED IN A WRITTEN AGREEMENT BETWEEN YOU 
    AND GETME LTD, THIS SOURCE CODE IS PROVIDED "AS IS" AND WITHOUT ANY 
    WARRANTIES OF ANY KIND, INCLUDING WARRANTIES OF MERCHANTABILITY OR FITNESS 
    FOR A PARTICULAR PURPOSE.

    IN NO EVENT SHALL GETME LTD BE LIABLE FOR ANY DIRECT, INDIRECT, 
    INCIDENTIAL, SPECIAL OR CONSEQUENTIAL DAMAGES OR DAMAGES FROM BUSINESS 
    INTERRUPTION OR LOSS OF PROFITS, REVENUE, DATA OR USE, INCURRED BY YOU OR 
    ANY THIRD PARTY, WHETHER IN CONTRACT OR TORT, ARISING FROM YOUR ACCESS TO, 
    OR USE OF, THIS SOURCE CODE EVEN IF GETME LTD HAS BEEN ADVISED OF THE 
    POSSIBILITY OF SUCH DAMAGES.
    
    Copyright (c)2009 Getme Ltd.
 */

/**
 * @fileoverview    Inline XHTML(1.0/Strict) manipulation and optimization.
 * @version         0.0.2
 * @author          Anthony Blackshaw ant@getme.co.uk
 * @requires        FSM
 * @alpha           ALBUS
 */
 
 
// Dependency check(s)
if (window.FSM===undefined) {
    throw Error('Finite State Machine library (fsm.js) is required.');
}

if(!Array.indexOf) {
    /**
     * Ensure indexOf method is supported (cheers for this IE)
     * @ignore
     */    
    Array.prototype.indexOf = function(obj) {
        for(var i=0; i<this.length; i++) {
            if(this[i]==obj) {
                return i;
            }
        }
        return -1;
    }
}


/**
 * The IXMO namespace.
 * The Inline XHTML manipulation and optimization library uses IXMO to 
 * namespace it's classes and functions.
 * @final
 * @type Namespace
 */  
var IXMO = {};


/**
 * Create a new inline XHTML parser.
 * @class An inline XHTML parser class.
 * @constructor
 */  
IXMO.Parser = function() {

    /** 
     * The position of the Character the Parser is currently parsing.
     * @private
     * @type Number
     */
    this._parsing_pos = 0;
    
    /** 
     * The current line being parsed (used for debugging).
     * @private
     * @type Number
     */
    this._parsing_line = 1;
    
    /** 
     * The current column being parsed (used for debugging).
     * @private
     * @type Number
     */
    this._parsing_column = 0; 

    /** 
     * A list of Characters being parsed by the Parser.
     * @private
     * @type Array
     */
    this._character_list = new Array();
    
    /** 
     * A stack used to manage tag balance as the XHTML is parsed.
     * @private
     * @type Array
     */
    this._tag_stack = new Array();
    
    /** 
     * Entity being parsed.
     * @private
     * @type String
     */
    this._current_entity = '';
    
    /** 
     * Name of the Tag being parsed.
     * @private
     * @type String
     */
    this._current_tag_name = '';
    
    /** 
     * Whether or not the Tag being parsed is short tagged.
     * @private
     * @type Boolean
     */
    this._current_tag_short = false;
    
    /** 
     * List of attributes for the Tag being parsed.
     * @private
     * @type Array
     */    
    this._current_attribute_list = new Array();
    
    /** 
     * Name of the Attribute being parsed.
     * @private
     * @type String
     */    
    this._current_attribute_name = '';
    
    /** 
     * Value of the Attribute being parsed.
     * @private
     * @type String
     */ 
    this._current_attribute_value = '';
    
    /** 
     * A Finite State Machine (FSM) used to handled parsing the XHTML.
     * @private
     * @type FSM.Machine
     */ 
    this._machine = new FSM.Machine(this);
    this._machine.set_initial_state('character_or_entity_or_tag');
    
    // Character or Tag
    this._machine.add_transition_any('character_or_entity_or_tag',null,
        function(action){
            this.data._store_character(action);
        });
    this._machine.add_transition('<','character_or_entity_or_tag','openning_or_closing_tag');
    this._machine.add_transition('&','character_or_entity_or_tag','entity');
    // Entity
    this._machine.add_transitions(IXMO.Parser.ENTITY,'entity',null,
        function(action){
            this.data._current_entity+=action;
        });
    this._machine.add_transition(';','entity','character_or_entity_or_tag',
        function(){
            this.data._store_character('&'+this.data._current_entity+';');
            this.data._current_entity = '';
        });
    // Opening or Closing Tag
    this._machine.add_transitions([' ','\n'],'openning_or_closing_tag');
    this._machine.add_transitions(IXMO.Parser.ALPHA,'openning_or_closing_tag','opening_tag',
        function(){
            this.data._parsing_pos--;
        });
    this._machine.add_transition('/','openning_or_closing_tag','closing_tag');
    // Opening Tag
    this._machine.add_transitions([' ','\n'],'opening_tag');
    this._machine.add_transitions(IXMO.Parser.ALPHA,'opening_tag','tag_name_opening',
        function(){
            this.data._parsing_pos--;
        });
    // Closing Tag
    this._machine.add_transitions([' ','\n'],'closing_tag');
    this._machine.add_transitions(IXMO.Parser.ALPHA,'closing_tag','tag_name_closing',
        function(){
            this.data._parsing_pos--;
        });
    // Tag name opening
    this._machine.add_transitions(IXMO.Parser.ALPHA_NUMERIC,'tag_name_opening',null,
        function(action){
            this.data._current_tag_name += action;
        });   
    this._machine.add_transitions([' ','\n'],'tag_name_opening','attribute_or_tag_end');
    this._machine.add_transition('/','tag_name_opening','tag_opening_short_tag',
        function() {
            this.data._current_tag_short = true;
        });
    this._machine.add_transition('>','tag_name_opening','character_or_entity_or_tag',
        function(){
            this.data._push_tag();
        });
    this._machine.add_transitions([' ','\n'],'tag_opening_short_tag');
    this._machine.add_transition('>','tag_opening_short_tag','character_or_entity_or_tag',
        function(){
            this.data._push_tag();
        });
    this._machine.add_transitions([' ','\n'],'attribute_or_tag_end');
    this._machine.add_transition('/','attribute_or_tag_end','tag_opening_short_tag',
        function() {
            this.data._current_tag_short = true;
        });
    this._machine.add_transition('>','attribute_or_tag_end','character_or_entity_or_tag',
        function(){
            this.data._push_tag();
        });
    this._machine.add_transitions(IXMO.Parser.ALPHA,'attribute_or_tag_end','attribute_name',
        function(){
            this.data._parsing_pos--;
        });
    // Tag name closing
    this._machine.add_transitions(IXMO.Parser.ALPHA_NUMERIC,'tag_name_closing',null,
        function(action){
            this.data._current_tag_name += action;
        }); 
    this._machine.add_transitions([' ','\n'],'tag_name_closing','tag_name_must_close');
    this._machine.add_transition('>','tag_name_closing','character_or_entity_or_tag',
        function(){
            this.data._pop_tag();
        });
    this._machine.add_transitions([' ','\n'],'tag_name_must_close');
    this._machine.add_transition('>','tag_name_must_close','character_or_entity_or_tag',
        function(){
            this.data._pop_tag();
        });
    // Attribute name
    this._machine.add_transitions(IXMO.Parser.ALPHA_NUMERIC,'attribute_name',null,
        function(action){
            this.data._current_attribute_name += action;
        });
    this._machine.add_transitions([' ','\n'],'attribute_name','attribute_name_must_get_value');
    this._machine.add_transition('=','attribute_name','attribute_delimiter');
    this._machine.add_transitions([' ','\n'],'attribute_name_must_get_value');
    this._machine.add_transition('=','attribute_name_must_get_value','attribute_delimiter');
    // Attribute delimiter
    this._machine.add_transitions([' ','\n'],'attribute_delimiter');
    this._machine.add_transition('\'','attribute_delimiter','attribute_value_single_delimiter');
    this._machine.add_transition('"','attribute_delimiter','attribute_value_double_delimiter');
    // Fix for IE's inability to output quoted attributes
    this._machine.add_transitions(IXMO.Parser.ALPHA_NUMERIC,'attribute_delimiter','attribute_value_no_delimiter',function(){
            this.data._parsing_pos--;
        });
    this._machine.add_transition(' ','attribute_value_no_delimiter','attribute_or_tag_end',function(){
            this.data._store_attribute('\"');
        });
    this._machine.add_transitions(['/','>'],'attribute_value_no_delimiter','attribute_or_tag_end',function(){
            this.data._parsing_pos--;
            this.data._store_attribute('\"');
        });
    this._machine.add_transition_any('attribute_value_no_delimiter',null,
        function(action){
            this.data._current_attribute_value += action;
        });
    // Attibute value single delimiter
    this._machine.add_transition('\'','attribute_value_single_delimiter','attribute_or_tag_end',
        function(){
            this.data._store_attribute('\'');
        });
    this._machine.add_transition_any('attribute_value_single_delimiter',null,
        function(action){
            this.data._current_attribute_value += action;
        });
    // Attribute value double
    this._machine.add_transition('"','attribute_value_double_delimiter','attribute_or_tag_end',
        function(){
            this.data._store_attribute('"');
        });
    this._machine.add_transition_any('attribute_value_double_delimiter',null,
        function(action){
            this.data._current_attribute_value+=action;
        });
}

/** 
 * A list of supported alpha characters supported when parsing.
 * @final
 * @type Array
 */
IXMO.Parser.ALPHA = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz'.split(''); 

/** 
 * A list of supported alpha numeric characters supported when parsing.
 * @final
 * @type Array
 */
IXMO.Parser.ALPHA_NUMERIC = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890_-:'.split(''); 

/** 
 * A list of supported characters supported when parsing an XHTML entity.
 * @final
 * @type Array
 */
IXMO.Parser.ENTITY = '#AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890_-:'.split('');

/**
 * Store a parsed Character.
 * @param {String} c A String representing the Character.
 * @private
 */
IXMO.Parser.prototype._store_character = function(c) {
    // Build a list of Tags that apply to the Character
    var tag_list = new Array();
    for(var i=0; i<this._tag_stack.length; i++) {
        tag_list.push(this._tag_stack[i]);
    }
    // Store the Character
    this._character_list.push(new IXMO.Character(c,tag_list));
}

/**
 * Push a parsed Tag on to the stack.
 * @private
 */
IXMO.Parser.prototype._push_tag = function() {
    // Build a list of Attributes that apply to the Tag
    var attribute_list = new Array();
    var attribute_name_list = new Array();
    for(var i=0; i<this._current_attribute_list.length; i++) {
        attribute_list.push(this._current_attribute_list[i]);
        attribute_name_list.push(this._current_attribute_list[i].name);
    }
    // Push the Tag on to the stack
    var tag = new IXMO.Tag(this._current_tag_name,attribute_list,this._current_tag_short);
    this._tag_stack.push(tag);
    // Handle short tagged Tags by adding an empty Character
    if (this._current_tag_short) {
        this._store_character('');
        this._tag_stack.pop();
    }
    // Reset the tag buffers
    this._current_tag_name = '';
    this._current_tag_short = false;
    this._current_attribute_list = new Array();
}

/**
 * Pop a parsed Tag off the stack.
 * @private
 */
IXMO.Parser.prototype._pop_tag = function() {
    var last_tag = this._tag_stack.pop();
    // Check the tags are balanced
    if (last_tag.original_name!=this._current_tag_name.toLowerCase()) {
        throw Error('Unmatched tag \'</'+this._current_tag_name+'>\'.');
    }
    // Reset the tag buffers
    this._current_tag_name = '';  
}

/**
 * Store a parsed attribute.
 * @private
 */
IXMO.Parser.prototype._store_attribute = function(delimiter) {
    var tag_name = this._current_tag_name;
    var attribute_name = this._current_attribute_name;
    // Check the attribute is supported
    if (!IXMO.Tag.supports_attribute(tag_name,attribute_name)) {
        throw Error('Attribute \''+attribute_name+'\' is not supported by \''+tag_name+'\' tag.');
    }
    // Store the attribute
    var attribute = new IXMO.Attribute(attribute_name,this._current_attribute_value,delimiter);
    this._current_attribute_list.push(attribute);
    // Reset the attribute buffers
    this._current_attribute_name = '';
    this._current_attribute_value = '';
}

/**
 * Return a parsing error.
 * @param {String} message The error message.
 * @return A parsing error.
 * @type String
 * @private
 */
IXMO.Parser.prototype._get_parsing_error = function(message) {
    return Error('Parsing Error at line: '+this._parsing_line+', column: '+this._parsing_column+' - '+message);
}

/**
 * Parse a String of XHTML. *Important* The parser only provides basic 
 * XML validation, not XHTML validation. Junk in may result in junk out. 
 * @param {String} xhtml The XHTML to parse.
 * @return A Soup of the parsed XHTML/
 * @type IXMO.Soup
 */
IXMO.Parser.prototype.parse = function(xhtml) {
    // Reset position tracking attributes 
    this._parsing_pos = 0;
    this._parsing_line = 1;
    this._parsing_column = 0;     
    // Reset the data buffers
    this._character_list = new Array();
    this._tag_stack = new Array();
    this._current_entity = '';
    this._current_tag_name = '';
    this._current_tag_short = false;
    this._current_attribute_list = new Array();
    this._current_attribute_name = '';
    this._current_attribute_value = '';    
    
    // Pre-processing...
    // Format the line breaks to '\n'
    xhtml = xhtml.replace(/\r\n/g,'\n');
    xhtml = xhtml.replace(/\r/g,'\n');
    // Remove all comments
    xhtml = xhtml.replace(/<!--.*-->/g,'');
    // Remove double spacing and trim post/prefixed spaces
    xhtml = xhtml.replace(/ +/g,' ');
    xhtml = xhtml.replace(/^\s+|\s+$/g,'');
    
    // Parse the XHTML 
    for (this._parsing_pos=0; this._parsing_pos<xhtml.length; this._parsing_pos++) {
        // Get the next character
        var c = xhtml.charAt(this._parsing_pos);
        // Debug tracking
        if (c=='\n') {
            this._parsing_column=0;
            this._parsing_line++;
        }
        this._parsing_column++;
        // Attempt to process the next character
        try {
            this._machine.process(c);
        } catch(error) {
            throw this._get_parsing_error(error.message);
        }
    }
    // Check the parser had finished when we run out of data to parse
    if (this._machine.get_current_state()!='character_or_entity_or_tag') {
        throw Error("Invalid XHTML parser expected more content.");
    }
    return new IXMO.Soup(this._character_list);
}


/**
 * Create a new inline XHMTL Soup.
 * @class A storage class for an array of Characters (with entity support) 
 * where each character can be assigned inline tags.
 * @constructor
 * @param {IXMO.Character} character_list A list of {@link IXMO.Character}.
 */ 
IXMO.Soup = function(character_list) {
    
    /** 
     * A list of {#link IXMO.Characters} that make up the Soup.
     * @private
     * @type Array
     */
    this._character_list = character_list || new Array();
}

/**
 * Apply a Tag to a selection of Characters in the Soup.
 * @private
 * @param {Array} selection The selection of Characters (specified as two 
 * positions). If no selection is specified the whole character range is 
 * selected. 
 * @return A valid selection
 */
IXMO.Soup.prototype._make_selection = function(selection) {
    if (!selection) {
        selection=[0,this._character_list.length];
    }
    if (!this.in_range(selection[0])||!this.in_range(selection[1])) {
        throw Error('Invalid selection, outside of character range.');
    }    
    return selection;
}   

/**
 * Split the soup at the designated position(s).
 * @param {Number|Array} position As a Number specifies the position at which
 * to make the split; as an Array specifies two positions, the first is where
 * the left split ends and the second where the right split begins.
 * @return An array of two soups resulting from the split.
 * @type Array
 */
IXMO.Soup.prototype.split = function(position) {
    if (position.constructor!=Array.prototype.constructor) {
        position = [position,position];
    }
    if (!this.in_range(position[0])||!this.in_range(position[1])) {
        throw Error('Invalid position, outside of character range.');
    }
    // Perform the split
    var split_left = new Array();
    var split_right = new Array();
    for (var i=0; i<position[0]; i++) {
        split_left.push(this._character_list[i].copy());
    }
    for (var j=position[1]; j<this._character_list.length; j++) {
        split_right.push(this._character_list[j].copy());
    }
    // Trim spaces
    var soup_left = new IXMO.Soup(split_left);
    var soup_right = new IXMO.Soup(split_right);
    soup_left.trim();
    soup_right.trim();
    return [soup_left,soup_right];
}

/**
 * Apply a Tag to a selection of Characters in the Soup.
 * @param {IXMO.Tag} tag The Tag to apply.
 * @param {Array} select The selection of Characters (specified as two 
 * positions) to apply the Tag to. If selection is not specified the Tag is 
 * applied to all Characters.
 * @see #apply_tags
 */
IXMO.Soup.prototype.apply_tag = function(tag, selection) {
    selection = this._make_selection(selection);
    for (var i=selection[0]; i<selection[1]; i++) {
        this._character_list[i].apply_tag(tag);
    }
}

/**
 * Apply multiple Tags to a selection of Characters in the Soup.
 * @param {Array} tag_list The list of {@link #IXMO.Tag}s to apply.
 * @param {Array} select The selection of Characters (specified as two 
 * positions) to apply the Tags to. If selection is not specified the Tags are 
 * applied to all Characters.
 * @see #apply_tag
 */
IXMO.Soup.prototype.apply_tags = function(tag_list, selection) {
    selection = this._make_selection(selection);
    for (var i=0; i<tag_list.length; i++) {
        this.apply_tag(tag_list[i],selection);
    }    
}

/**
 * Remove a Tag from a selection of Characters in the Soup.
 * @param {IXMO.Tag} tag The Tag to remove.
 * @param {Array} selection The selection of Characters (specified as two 
 * positions) to remove the Tag from. If selection is not specified the Tag is 
 * removed from all Characters.
 * @see #remove_tags
 * @see #remove_all_tags
 */
IXMO.Soup.prototype.remove_tag = function(tag, selection) {
    selection = this._make_selection(selection);
    for (var i=selection[0]; i<selection[1]; i++) {
        this._character_list[i].remove_tag(tag);
    }
} 

/**
 * Remove multiple Tags from a selection of Characters in the Soup.
 * @param {Array} tag_list The list of {@link #IXMO.Tag}s to remove.
 * @param {Array} select The selection of Characters (specified as two 
 * positions) to remove the Tags from. If selection is not specified the Tags 
 * are removed from all Characters.
 * @see #remove_tag
 * @see #remove_all_tags
 */
IXMO.Soup.prototype.remove_tags = function(tag_list, selection) {
    selection = this._make_selection(selection);
    for (var i=0; i<tag_list.length; i++) {
        this.remove_tag(tag_list[i],selection);
    }    
}

/**
 * Remove all Tags from a selection of Characters in the Soup.
 * @param {Array} selection The selection of Characters (specified as two 
 * positions) to remove all Tags from. If selection is not specified all the 
 * Tags are removed from all Characters.
 * @param {String} tag_name Optional. The name of the Tag type to remove.
 * @see #remove_tag
 * @see #remove_tags
 */
IXMO.Soup.prototype.remove_all_tags = function(selection, tag_name) {
    selection = this._make_selection(selection);
    for (var i=selection[0]; i<selection[1]; i++) {
        this._character_list[i].remove_all_tags(tag_name);
    }
}

/**
 * Cut a selection of Characters from the Soup.
 * @param {Array} selection The selection of Characters (specified as two 
 * positions) to cut from the Soup. If selection is not specified all the 
 * Characters are cut.
 * @return A Soup containing the cut selection.
 * @type IXMO.Soup
 * @see #copy
 * @see #paste
 */
IXMO.Soup.prototype.cut = function(selection) {
    selection = this._make_selection(selection);
    return new IXMO.Soup(this._character_list.splice(selection[0],selection[1]-selection[0]));
}

/**
 * Copy a selection of Characters from the Soup.
 * @param {Array} selection The selection of Characters (specified as two 
 * positions) to copy from the Soup. If selection is not specified all the
 * Characters are copied.
 * @return A Soup containing the copied selection.
 * @type IXMO.Soup
 * @see #cut
 * @see #paste
 */
IXMO.Soup.prototype.copy = function(selection) {
    selection = this._make_selection(selection);
    var c_list = new Array();
    for (var i=selection[0]; i<selection[1]; i++) {
        c_list.push(this._character_list[i].copy());
    }
    return new IXMO.Soup(c_list);
}

/**
 * Paste a Soup into the Soup.
 * @param {IXMO.Soup} soup The soup to paste.
 * @param {Number} position The position to paste the Soup at.
 * @see #copy
 * @see #cut
 */
IXMO.Soup.prototype.paste = function(soup, position) {
    if (!this.in_range(position)) {
        throw Error('Invalid position, outside of character range.');
    }
    var c_list = new Array();
    for (var i=0; i<this._character_list.length+1; i++ ) {
        if (i==position) {
            for (var j=0; j<soup._character_list.length; j++) {
               c_list.push(soup._character_list[j].copy()); 
            }
        }
        if (i<this._character_list.length) {
            c_list.push(this._character_list[i].copy());
        }
    }
    this._character_list = c_list;
}

/**
 * Erase a selection of Characters from the Soup.
 * @param {Array} selection The selection of Characters (specified as two 
 * positions) to erase from the Soup. If selection is not specified all the
 * Characters are erased.
 */
IXMO.Soup.prototype.erase = function(selection) {
    selection = this._make_selection(selection);
    this._character_list.splice(selection[0],selection[1]-selection[0]);
}

/**
 * Insert text into the Soup.
 * @param {String} text The text to be inserted.
 * @param {Number} position The position at which the text is inserted. 
 * @see #append
 * @see #prepend
 */
IXMO.Soup.prototype.insert = function(text, position) {
    if (!this.in_range(position)) {
        throw Error('Invalid position, outside of character range.');
    }
    var c_list = new Array();
    for (var i=0; i<this._character_list.length+1; i++) {
        if (i==position) {
            // Get the list of tags the inserted text inherits
            var inherited_tag_list = new Array();
            if (i>0) {
                inherited_tag_list = this._character_list[i-1].get_tag_list().slice(0)
            }
            // Insert the text
            for (var j=0; j<text.length; j++) {
               c_list.push(new IXMO.Character(text.charAt(j),inherited_tag_list)); 
            }
        }
        if (i<this._character_list.length) {
            c_list.push(this._character_list[i].copy());
        }
    }
    this._character_list = c_list;
}

/**
 * Append text to the Soup.
 * @param {String} text The text to append.
 * @see #insert
 * @see #prepend
 */
IXMO.Soup.prototype.append = function(text) {
    this.insert(text,this._character_list.length);
}

/**
 * Prepend text to the Soup.
 * @param {String} text The text to prepend.
 * @see #append
 * @see #insert
 */
IXMO.Soup.prototype.prepend = function(text) {
    this.insert(text,0);
}

/**
 * Select a selection of Characters from the soup and return them as a string.
 * @param {Array} selection The selection of Characters (specified as two 
 * positions) to select from the Soup. If selection is not specified all
 * the Characters are copied.
 * @return The selected text.
 * @type String
 */
IXMO.Soup.prototype.raw_select = function(selection) {
    selection = this._make_selection(selection);
    var c_string_list = new Array();
    for (var i=selection[0]; i<selection[1]; i++) {
        c_string_list.push(this._character_list[i].c);
    }
    return c_string_list.join('');
}

/**
 * Return a match value of;
 * <ul>
 * <li>-1 - if no Characters (loosely) matched all of the specified tag</li>
 * <li>0 - if some Characters (loosely) matched all of the specified tag</li>
 * <li>1 - if all Characters (loosely) matched all of the specified tag</li> 
 * </ul>
 * @param {IXMO.Tag} tag The Tag to match.
 * @param {Boolean} loose If true then only the Tag name needs to match, the 
 * Tags Attributes are not considered in the comparison.
 * @param {Array} selection The selection of Characters (specified as two 
 * positions) to consider from the Soup. If selection is not specified all
 * the Characters are considered.
 * @return The result of the match.
 * @type Number
 * @see #has_tags
 */
IXMO.Soup.prototype.has_tag = function(tag, loose, selection) {
    selection = this._make_selection(selection);
    // Perform the match
    var match_count = 0;
    for (var i=selection[0]; i<selection[1]; i++) {
        var c = this._character_list[i];
        if (c.has_tag(tag,loose)) {
            match_count++;
        }
    }
    switch (match_count) {
        case 0:
            return -1;
            break;
        case selection[1]-selection[0]:
            return 1;
            break;
        default:
            return 0;
    }
}

/**
 * Return a match value of;
 * <ul>
 * <li>-1 - if no Characters (loosely) matched all of the specified tags</li>
 * <li>0 - if some Characters (loosely) matched all of the specified tags</li>
 * <li>1 - if all Characters (loosely) matched all of the specified tags</li> 
 * </ul>
 * @param {Array} tag_list The list of Tags to match.
 * @param {Boolean} loose If true then only the Tag name needs to match, the 
 * Tags Attributes are not considered in the comparison.
 * @param {Array} selection The selection of Characters (specified as two 
 * positions) to consider from the Soup. If selection is not specified all
 * the Characters are considered.
 * @return The result of the match.
 * @type Number
 * @see #has_tag
 */
IXMO.Soup.prototype.has_tags = function(tag_list, loose, selection) {
    selection = this._make_selection(selection);
    // Perform the match
    var match_count = 0;
    for (var i=0; i<tag_list.length; i++) {
        var tag = tag_list[i];
        for (var j=selection[0]; j<selection[1]; j++) {
            var c = this._character_list[j];
            if (c.has_tag(tag,loose)) {
                match_count++;
            }
        }
    }
    switch (match_count) {
        case 0:
            return -1;
            break;
        case selection[1]-selection[0]:
            return 1;
            break;
        default:
            return 0;
    }
}

/**
 * Return a list of tags applied to the current selection.
 * @param {Array} selection The selection of Characters (specified as two 
 * positions) to consider from the Soup. If selection is not specified all
 * the Characters are considered.
 * @param {String} tag_name Optional. The tag type to return.
 * @return A list of tags applied to this selection.
 * @type Array
 */
IXMO.Soup.prototype.get_tag_list = function(selection, tag_name) {
    selection = this._make_selection(selection);
    var tag_map = new Object();
    var tag_list = new Array();
    for (var i=selection[0]; i<selection[1]; i++) {
        var c = this._character_list[i];
        var c_tag_list = c.get_tag_list(tag_name);
        for (var j=0; j<c_tag_list.length; j++) {
            var tag = c_tag_list[j];
            if (!tag_map[tag.render_open()]) {
                tag_map[tag.render_open()] = true;
                tag_list.push(tag);
            }
        }
    }
    return tag_list;
}

/**
 * Optimise the Soup (currently this uses a simple tag character length
 * optimisation).
 * @private
 */
IXMO.Soup.prototype._optimise = function() {
    // Reset all the character tags
    for (var i=0; i<this._character_list.length; i++) {
        this._character_list[i].reset_tag_run_lengths();
    }    
    var tag_stack = new Array();
    var tag_rendered_stack = new Array();
    var prev_c = null;
    for (var j=this._character_list.length; j>0; j--) {
        var c = this._character_list[j-1];
        // Do we need to close any tags
        var closing_tag_list = new Array();
        for (var k=tag_stack.length-1; k>=0; k--) {
            var tag = tag_stack[k];
            closing_tag_list.push(tag);
            if (!c.has_tag(tag)) {
                for (var n=0; n<closing_tag_list.length; n++) {
                    tag_stack.pop();
                    tag_rendered_stack.pop();                
                }
                closing_tag_list = new Array();
            }
        }    
        // Do we need to open any tags
        var opening_tag_list = c.get_tag_list();
        for (var m=0; m<opening_tag_list.length; m++) {
            var tag_rendered = opening_tag_list[m].render_open();
            if (tag_rendered_stack.indexOf(tag_rendered)==-1) {
                tag_stack.push(opening_tag_list[m]);
                tag_rendered_stack.push(tag_rendered);                
            }
        }
        for (var p=0; p<tag_stack.length; p++) {
            var tag = tag_stack[p];
            if (prev_c) {
                c.set_tag_run_length(tag,prev_c.get_tag_run_length(tag)+1);
            } else {
                c.set_tag_run_length(tag,1);
            }
        }
        prev_c = c;           
    }
    // Re-order all the tags so that they are optimized
    for (var m=0; m<this._character_list.length; m++) {
        var c = this._character_list[m];
        c.order_tag_list();
    }
}

/**
 * Render the Soup.
 * @param {Boolean} optimise If true the output is optimised before being 
 * rendered.
 * @param {Boolean} debug If true the output of the render will be returned in
 * a visible format.
 * @return The rendered Soup.
 * @type String
 */
IXMO.Soup.prototype.render = function(optimise, debug) {
    // Pre-process and re-order the applied tag's based on run length
    if (optimise) {
        this._optimise();
    }
    var tag_stack = new Array();
    var tag_rendered_stack = new Array();
    var soup_str = '';
    var original_parenthesis = IXMO.Tag.PARENTHESIS;
    // Render the Soup
    for (var i=0; i<this._character_list.length; i++) {
        var c = this._character_list[i];
        // Do we need to close any tags
        var closing_tag_list = new Array();
        for (var j=tag_stack.length-1; j>=0; j--) {
            var tag = tag_stack[j];
            closing_tag_list.push(tag);
            if (!c.has_tag(tag)) {
                for (var m=0; m<closing_tag_list.length; m++) {
                    if (debug) {
                        IXMO.Tag.PARENTHESIS = ['[',']'];
                    }                
                    soup_str += closing_tag_list[m].render_close();
                    IXMO.Tag.PARENTHESIS = original_parenthesis;
                    tag_stack.pop();
                    tag_rendered_stack.pop();                
                }
                closing_tag_list = new Array();
            }
        }    
        // Do we need to open any tags
        var opening_tag_list = c.get_tag_list();
        for (var k=0; k<opening_tag_list.length; k++) {
            var tag_rendered = opening_tag_list[k].render_open();
            if (tag_rendered_stack.indexOf(tag_rendered)==-1) {
                if (debug) {
                    IXMO.Tag.PARENTHESIS = ['[',']'];
                    soup_str+=opening_tag_list[k].render_open();
                } else {
                    soup_str+=tag_rendered;
                }   
                IXMO.Tag.PARENTHESIS = original_parenthesis;
                tag_stack.push(opening_tag_list[k]);
                tag_rendered_stack.push(tag_rendered);                
            }
        }
        soup_str += c.c;
    }
    // Close all remaining tags
    for (var j=tag_stack.length-1; j>=0; j--) {
        if (debug) {
            IXMO.Tag.PARENTHESIS = ['[',']'];
        }          
        soup_str += tag_stack[j].render_close();
        IXMO.Tag.PARENTHESIS = original_parenthesis;
    }
    // Reset the parenthesis
    return soup_str;
}

/**
 * Return the lenth of the Soup (in Characters).
 * @return The length of the Soup.
 * @type Number
 */
IXMO.Soup.prototype.get_length = function() {
    return this._character_list.length;
}
    
/**
 * Return true if the specified position is in the range 0 to the length of the
 * Soup.
 * @param {Number} position The position to check is in range.
 * @return Whether the position is in the range.
 * @type Boolean
 */
IXMO.Soup.prototype.in_range = function(position) {
    return !(position<0||position>this._character_list.length);
}

/**
 * Trim the whitespace characters from the Soup.
 */
IXMO.Soup.prototype.trim = function() {
    var c_list = this._character_list;
    // Left trim
    while ([' ','\n'].indexOf(c_list[0])!=-1) {
        c_list.shift();
    }
    // Right trim
    while ([' ','\n'].indexOf(c_list[c_list.length-1])!=-1) {
        c_list.pop();
    }
}


/**
 * Create a new inline XHTML Tag.
 * @class A class that provides semantic information for 
 * {@link IXMO.Character}s within a {@link IXMO.Soup}.
 * @constructor
 * @param {String} name The Tags name (must be a supported inline XHTML tag 
 * name).
 * @param {Array|Object} attribute_list Optional. A list of 
 * {@link IXMO.Attribute}s for the Tag, or an Object that can be converted to a
 * list of Attributes (e.g <code>{'href':'http://example.com', title:'A link to
 * example.com'}</code>). Only valid Attributes supported by the tag are 
 * allowed.
 * @param {Boolean} short_tagged If true then the tag is output short tagged,
 * though in XHTML it is legal to short tag many tags, only a sensible subset
 * of these possibilities is supported,
 * <ul>
 * <li>images <code>img</code></li>
 * <li>line breaks <code>br</code></li>
 * </ul> 
 */ 
IXMO.Tag = function(name, attribute_list, short_tagged) {
    
    name=name.toLowerCase();

    /** 
     * A flag which determines if the Tag has been changed since the last 
     * render open.
     * @private
     * @type Boolean
     */    
    this._modified = true;

    /** 
     * A cached version of the Tags render_open output.
     * @private
     * @type String
     */    
    this._render_open_cache = '';

    if (short_tagged) {
        // Determine if this Tag supports being short tagged
        if (IXMO.Tag.SUPPORTED_SHORT_TAG_LIST.indexOf(name)==-1) {
            throw 'The \'<'+name+'>\' tag cannot be short tagged.';
        }
    }        
    
    /** 
     * A flag which determines if the Tag is short tagged.
     * @private
     * @type Boolean
     */    
    this._short_tagged = short_tagged;
    
    // Check the Tag name is supported
    if (IXMO.Tag.SUPPORTED_TAG_LIST.indexOf(name)==-1) {
        throw 'Unsupported tag \'<'+name+'>\'';
    }
    
    /** 
     * A list of Attributes for the Tag.
     * @private
     * @type Array
     */    
    this._attribute_list = new Array();
    
    /** 
     * A list of Attribute names for the Tag. Used to optimize Attribute 
     * lookups.
     * @private
     * @type Array
     */    
    this._attribute_name_list = new Array();
    
    // Add the Attributes to the tag
    attribute_list = attribute_list || new Array();
    if (attribute_list.constructor==Array.prototype.constructor) {
        for (var i=0; i<attribute_list.length; i++ ) {
            this.set_attribute(attribute_list[i].name,attribute_list[i].value,attribute_list[i].get_delimiter());
        }
    } else if (attribute_list.constructor==Object.prototype.constructor) {
        for (var attribute_name in attribute_list) {
            this.set_attribute(attribute_name,attribute_list[attribute_name]);
        }
    }
        
    if (IXMO.Tag.REQUIRED_ATTRIBUTE_MAP[name]) {
        // Ensure required attributes have been set
        for (var j=0; j<IXMO.Tag.REQUIRED_ATTRIBUTE_MAP[name].length; s++) {
            var required_name = IXMO.Tag.REQUIRED_ATTRIBUTE_MAP[name][j];
            if (!this.get_attribute(required_name)) {
                throw 'Attribute \''+required_name+'\' is required by \''+name+'\' tag.';
            }
        }
    }
    
    /**
     * The original Tag name (will only differ the Tag has been renamed to 
     * (improve semantic quality).
     * @type String
     */       
    this.original_name = name;    
    
    if (IXMO.Tag.TAG_RENAME_MAP[name]) {
        // Automatically rename the tag for better semantic quality
        name = IXMO.Tag.TAG_RENAME_MAP[name];
    }
    
    /**
     * The name of the Tag.
     * @type String
     */       
    this.name = name;
}

/** 
 * A two element Array containing the parenthesis for rendering the Tag, 
 * modifying the value from ['<','>'] to ['[',']'] can be useful for debugging.
 * @type Array
 */
IXMO.Tag.PARENTHESIS = ['<','>'];

/** 
 * A list of supported inline XHTML Tags.
 * @final
 * @type Array
 */
IXMO.Tag.SUPPORTED_TAG_LIST = 'a,abbr,acronym,address,b,bdo,big,br,cite,code,del,dfn,em,i,img,ins,kbd,q,samp,small,span,strong,sub,sup,tt,var'.split(',');

/** 
 * A list of inline XHTML Tags that we allow to be short tagged.
 * @final
 * @type Array
 */
IXMO.Tag.SUPPORTED_SHORT_TAG_LIST = 'br,img'.split(',');

/** 
 * A list of attributes that (almost) all Tags support.
 * @final
 * @type Array
 */
IXMO.Tag.SUPPORTED_ATTRIBUTE_LIST = 'id,class,style,title,lang,xml:lang,dir,onclick,ondblclick,onmousedown,onmouseup,onmousemove,onmouseout,onkeypress,onkeydown,onkeyup'.split(',');

/** 
 * A map of additional attributes that only some Tags support.
 * @final
 * @type Object
 */
IXMO.Tag.SUPPORTED_ADDITIONAL_ATTRIBUTE_MAP = {'del':['cite','datetime'],'ins':['cite','datetime'],'a':['charset','type','name','href','hreflang','rel','rev','shape','coords'],'q':['cite'],'img':['src','alt','longdesc','height','width','usemap','ismap']};

/** 
 * A map of special case Tags that support a specific set of Attributes.
 * @final
 * @type Object
 */
IXMO.Tag.SUPPORTED_SPECIAL_ATTRIBUTE_MAP = {'br':['id','class','style','title']};

/** 
 * A map of Attributes that are required by specific Tags.
 * @final
 * @type Object
 */
IXMO.Tag.REQUIRED_ATTRIBUTE_MAP =  {'bdo':['dir',]};

/** 
 * A map of Tags that we automatically rename for improved semantic quality.
 * @final
 * @type Object
 */
IXMO.Tag.TAG_RENAME_MAP = {'b':'strong','i':'em'};

/**
 * Return true if the specified Attribute name is supported by a Tag of the 
 * specified name.
 * @param {String} tag_name The name of the Tag to check is supported.
 * @param {String} attribute_name The name of the Attribute to check is supported.
 * @return Whether the Attribute is supported by the Tag type.
 * @type Boolean
 */
IXMO.Tag.supports_attribute = function(tag_name,attribute_name) {
    tag_name = tag_name.toLowerCase();
    attribute_name = attribute_name.toLowerCase();
    // Check for special case Tag Attributes
    if (IXMO.Tag.SUPPORTED_SPECIAL_ATTRIBUTE_MAP[tag_name]) {
        if(IXMO.Tag.SUPPORTED_SPECIAL_ATTRIBUTE_MAP[tag_name].indexOf(attribute_name)==-1) {
            return false;
        }
    } else {
        // Check for general Tag Attributes
        if (IXMO.Tag.SUPPORTED_ATTRIBUTE_LIST.indexOf(attribute_name)==-1) {
            // Check for Tag additional Attributes
            if (IXMO.Tag.SUPPORTED_ADDITIONAL_ATTRIBUTE_MAP[tag_name]) {
                if (IXMO.Tag.SUPPORTED_ADDITIONAL_ATTRIBUTE_MAP[tag_name].indexOf(attribute_name)==-1) {
                    return false;
                }
            } else {
                return false;
            }
        }
    }
    return true;
}

/**
 * Set an Attribute for the Tag.
 * @param {String} name The name of the Attribute to set.
 * @param {String} value Optional. The value of the Attribute. If no value is 
 * specified the Attribute will be dropped from the Tag.
 * @param {String} delimiter Optional. The delimiter character used to surround 
 * the value, either a single(') or double(") quote.
 * @see #get_attribute
 */
IXMO.Tag.prototype.set_attribute = function(name, value, delimiter) {
    name=name.toLowerCase();
    var attribute = this.get_attribute(name);
    if (value!=null && value!='') {
        // Set attribute
        if (!attribute) {
            // Add attribute
            attribute = new IXMO.Attribute(name,value,delimiter);
            this._attribute_list.push(attribute);
            this._attribute_name_list.push(attribute.name);
        } else {
            // Update attribute
            attribute.value = value;
            if (delimiter) {
                attribute.set_delimiter(delimiter);
            }
        }
    } else {
        // Remove attribute
        if (attribute) {
            var attribute_index = this._attribute_name_list.indexOf(name);
            this._attribute_list.splice(attribute_index,1);
            this._attribute_name_list.splice(attribute_index,1);
        }
    }
    this._modified = true;
}

/**
 * Get an Attribute for the Tag.
 * @param {String} name The name of the Attribute to get.
 * @return The value of the Attribute.
 * @type String 
 * @see #set_attribute
 */
IXMO.Tag.prototype.get_attribute = function(name) {
    name=name.toLowerCase();
    var attribute_index = this._attribute_name_list.indexOf(name);
    if (attribute_index!=-1) {
        return this._attribute_list[attribute_index].value;
    }
}

/**
 * Render the openning Tag in the form; <name attributes>".
 * @return The rendered opening Tag.
 * @type String
 * @see #render_close
 */
IXMO.Tag.prototype.render_open = function() {
    if (this._modified||this._render_open_cache.charAt(1)!=IXMO.Tag.PARENTHESIS[0]) {
        // Refresh the cache
        var tag_str = IXMO.Tag.PARENTHESIS[0]+this.name
        var attribute_list = this._attribute_list.slice();
        attribute_list.sort(IXMO.Attribute.sort_by_name);
        for (var i=0; i<attribute_list.length; i++) {
            tag_str += ' '+attribute_list[i].render();
        }
        if (this._short_tagged) {
            tag_str += '/';
        }
        this._render_open_cache = tag_str+IXMO.Tag.PARENTHESIS[1];
        this._modified = false;
    }
    return this._render_open_cache;
}

/**
 * Render the closing Tag in the form; </name>".
 * @return The rendered closing Tag.
 * @type String
 * @see #render_open
 */
IXMO.Tag.prototype.render_close = function() {
    if (this._short_tagged) {
        return '';
    } else {
        return IXMO.Tag.PARENTHESIS[0]+'/'+this.name+IXMO.Tag.PARENTHESIS[1];
    }
}

/**
 * Return true if the tag is short tagged.
 * @return Whether the tag is short tagged.
 * @type Boolean
 */
IXMO.Tag.prototype.is_short_tagged = function() {
    return this._short_tagged;
}

/**
 * Return a copy of the Tag.
 * @return A copy of the Tag.
 * @type IXMO.Tag
 */
IXMO.Tag.prototype.copy = function() {
    attribute_list = new Array();
    for (var i=0; i<this._attribute_list.length; i++) {
        attribute_list.push(this._attribute_list[i].copy());
    }
    return new IXMO.Tag(this.name,attribute_list,this.is_short_tagged());
}


/**
 * Create a new Attribute. 
 * @class A storage class for Attributes against an {@link IXMO.Tag}.
 * @constructor
 * @param {String} name The Attributes name.
 * @param {String} value The Attributes value.
 * @param {String} delimiter The delimiter character used to surround the 
 * value, either a single(') or double(") quote.
 */ 
IXMO.Attribute = function(name, value, delimiter) {
    
    name=name.toLowerCase();
    
    /** 
     * The delimiter character used to surround the Attributes value when 
     * rendered.
     * @private
     * @type String
     */
    this._delimiter = null;
    
    /** 
     * The Attributes name.
     * @type String
     */    
    this.name = name;
    
    /** 
     * The Attributes value.
     * @type String
     */        
    this.value = value;
    
    // Now that the value has been defined we can safely set the delimiter
    this.set_delimiter(delimiter||'"');
}

/**
 * Sort function for sorting Attributes by their name.
 * @return The comparision value (1,0,-1).
 * @type Number
 */
IXMO.Attribute.sort_by_name = function(a, b) {
    if (a.name>b.name) {
		return 1;
	} else if (a.name<b.name) {
		return -1;
	}
	return 0;
}

/**
 * Render the Attribute in the form; name="value'".
 * @return The rendered Attribute.
 * @type String
 */
IXMO.Attribute.prototype.render = function() {    
    return this.name.toLowerCase()+'='+this._delimiter+this.value+this._delimiter;
}

/**
 * Set the delimiter used by the Attribute.
 * @param {String} delimiter The delimiter character used to surround the
 * attribute.
 */
IXMO.Attribute.prototype.set_delimiter = function(delimiter) {
    // Check a supported delimiter has been specified
    if (delimiter!='"' && delimiter!='\'') {
        throw 'The delimiter must be either a single (\') or double (") quote.';
    }
    // Check that it's safe to convert the delimiter
    if (this.value.search(delimiter)!=-1) {
        throw 'The delimiter cannot be changed to ('+delimiter+') because the Attributes value contains one or more instance of the specifed delimiter.';
    }
    this._delimiter = delimiter;
}

/**
 * Return the Attributes value delimiter.
 * @return The Attributes delimiter ['|"].
 * @type String
 */
IXMO.Attribute.prototype.get_delimiter = function() {    
    return this._delimiter;
}

/**
 * Return a copy of the Attribute.
 * @return A copy of the Attribute.
 * @type IXMO.Attribute
 */
IXMO.Attribute.prototype.copy = function() {
    return new IXMO.Attribute(this.name,this.value,this.get_delimiter());
}


/**
 * Create a new Character. 
 * @class  A rich storage class for a Character that supports for entities,
 * short-tagged tags and tag formatting. Typically Characters are handled via
 * their storage class {@link IXMO.Soup}.
 * @constructor
 * @param {String} character_str A string representation of the Character. If
 * the Character represents an entity the string representation will be greater 
 * than one in length, if it represents a short-tagged tag (e.g. an image or 
 * line break) then it will be zero in length ('').
 * @param {Array} tag_list Optional. An array of {@link IXMO.Tag}s that apply
 * to the Character.
 */ 
IXMO.Character = function(character_str, tag_list) {
    
    /** 
     * A list of {@link #IXMO.Tag}s that apply to this character.
     * @private
     * @type Array
     */    
    this._tag_list = new Array();
    
    /** 
     * A list of Tags names used to optimize the {@link #has_tag} loose lookup.
     * {@link #has_tag} lookup.
     * @private
     * @type Array
     */        
    this._tag_name_list = new Array();
    
    /** 
     * A list of pre-rendered (opening) Tags used to optimize the 
     * {@link #has_tag} exact lookup.
     * @private
     * @type Array
     */        
    this._tag_rendered_list = new Array();    

    /** 
     * A list of Tags and the their run_lengths used to optimize rendering.
     * @private
     * @type Array
     */        
    this._tag_run_length_list = new Array();    
    
    /** 
     * A string representation of the character.
     * @type String
     */
    if (character_str.length==1 && IXMO.Character.ENTITY_MAP[character_str.charCodeAt(0)]) {
        // ENTITY replacement
        this.c = IXMO.Character.ENTITY_MAP[character_str.charCodeAt(0)];
    } else {
        this.c = character_str;
    }

    // Apply any Tags to the Character
    for (var i=0; i<tag_list.length; i++) {
        this.apply_tag(tag_list[i]);
    }  
}

/** 
 * A map of character codes that require conversion to ENTITIES in XHTML. 
 * Information sourced from:
 * http://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references
 * @type Object
 */
IXMO.Character.ENTITY_MAP = {
34:'&quot;',    // '"' quotation mark (= APL quote)
38:'&amp;',     // '&' ampersand
39:'&#39;',     // ''' apostrophe (= apostrophe-quote); Can't replace this with 
                // it's verbose version because IE doesn't support it.
60:'&lt;',      // '<' less-than sign
62:'&gt;',      // '>' greater-than sign
160:'&#160;',   // ' ' no-break space (= non-breaking space)
161:'&#161;',   // '¡' inverted exclamation mark
162:'&#162;',   // '¢' cent sign
163:'&#163;',   // '£' pound sign
164:'&#164;',   // '¤' currency sign
165:'&#165;',   // '¥' yen sign (= yuan sign)
166:'&#166;',   // '¦' broken bar (= broken vertical bar)
167:'&#167;',   // '§' section sign
168:'&#168;',   // '¨' diaeresis (= spacing diaeresis); see German umlaut
169:'&#169;',   // '©' copyright sign
170:'&#170;',   // 'ª' feminine ordinal indicator
171:'&#171;',   // '«' left-pointing double angle quotation mark (= left 
                // pointing guillemet)
172:'&#172;',   // '¬' not sign
173:'&#173;',   // ' ' soft hyphen (= discretionary hyphen)
174:'&#174;',   // '®' registered sign ( = registered trade mark sign)
175:'&#175;',   // '¯' macron (= spacing macron = overline = APL overbar)
176:'&#176;',   // '°' degree sign
177:'&#177;',   // '±' plus-minus sign (= plus-or-minus sign)
178:'&#178;',   // '²' superscript two (= superscript digit two = squared)
179:'&#179;',   // '³' superscript three (= superscript digit three = cubed)
180:'&#180;',   // '´' acute accent (= spacing acute)
181:'&#181;',   // 'µ' micro sign
182:'&#182;',   // '¶' pilcrow sign ( = paragraph sign)
183:'&#183;',   // '·' middle dot (= Georgian comma = Greek middle dot)
184:'&#184;',   // '¸' cedilla (= spacing cedilla)
185:'&#185;',   // '¹' superscript one (= superscript digit one)
186:'&#186;',   // 'º' masculine ordinal indicator
187:'&#187;',   // '»' right-pointing double angle quotation mark (= right 
                // pointing guillemet)
188:'&#188;',   // '¼' vulgar fraction one quarter (= fraction one quarter)
189:'&#189;',   // '½' vulgar fraction one half (= fraction one half)
190:'&#190;',   // '¾' vulgar fraction three quarters (= fraction three 
                // quarters)
191:'&#191;',   // '¿' inverted question mark (= turned question mark)
192:'&#192;',   // 'À' Latin capital letter A with grave (= Latin capital 
                // letter A grave)
193:'&#193;',   // 'Á' Latin capital letter A with acute
194:'&#194;',   // 'Â' Latin capital letter A with circumflex
195:'&#195;',   // 'Ã' Latin capital letter A with tilde
196:'&#196;',   // 'Ä' Latin capital letter A with diaeresis
197:'&#197;',   // 'Å' Latin capital letter A with ring above (= Latin capital 
                // letter A ring)
198:'&#198;',   // 'Æ' Latin capital letter AE (= Latin capital ligature AE)
199:'&#199;',   // 'Ç' Latin capital letter C with cedilla
200:'&#200;',   // 'È' Latin capital letter E with grave
201:'&#201;',   // 'É' Latin capital letter E with acute
202:'&#202;',   // 'Ê' Latin capital letter E with circumflex
203:'&#203;',   // 'Ë' Latin capital letter E with diaeresis
204:'&#204;',   // 'Ì' Latin capital letter I with grave
205:'&#205;',   // 'Í' Latin capital letter I with acute
206:'&#206;',   // 'Î' Latin capital letter I with circumflex
207:'&#207;',   // 'Ï' Latin capital letter I with diaeresis
208:'&#208;',   // 'Ð' Latin capital letter ETH
209:'&#209;',   // 'Ñ' Latin capital letter N with tilde
210:'&#210;',   // 'Ò' Latin capital letter O with grave
211:'&#211;',   // 'Ó' Latin capital letter O with acute
212:'&#212;',   // 'Ô' Latin capital letter O with circumflex
213:'&#213;',   // 'Õ' Latin capital letter O with tilde
214:'&#214;',   // 'Ö' Latin capital letter O with diaeresis
215:'&#215;',   // '×' multiplication sign
216:'&#216;',   // 'Ø' Latin capital letter O with stroke (= Latin capital 
                // letter O slash)
217:'&#217;',   // 'Ù' Latin capital letter U with grave
218:'&#218;',   // 'Ú' Latin capital letter U with acute
219:'&#219;',   // 'Û' Latin capital letter U with circumflex
220:'&#220;',   // 'Ü' Latin capital letter U with diaeresis
221:'&#221;',   // 'Ý' Latin capital letter Y with acute
222:'&#222;',   // 'Þ' Latin capital letter THORN
223:'&#223;',   // 'ß' Latin small letter sharp s (= ess-zed); see German 
                // Eszett
224:'&#224;',   // 'à' Latin small letter a with grave
225:'&#225;',   // 'á' Latin small letter a with acute
226:'&#226;',   // 'â' Latin small letter a with circumflex
227:'&#227;',   // 'ã' Latin small letter a with tilde
228:'&#228;',   // 'ä' Latin small letter a with diaeresis
229:'&#229;',   // 'å' Latin small letter a with ring above
230:'&#230;',   // 'æ' Latin small letter ae (= Latin small ligature ae)
231:'&#231;',   // 'ç' Latin small letter c with cedilla
232:'&#232;',   // 'è' Latin small letter e with grave
233:'&#233;',   // 'é' Latin small letter e with acute
234:'&#234;',   // 'ê' Latin small letter e with circumflex
235:'&#235;',   // 'ë' Latin small letter e with diaeresis
236:'&#236;',   // 'ì' Latin small letter i with grave
237:'&#237;',   // 'í' Latin small letter i with acute
238:'&#238;',   // 'î' Latin small letter i with circumflex
239:'&#239;',   // 'ï' Latin small letter i with diaeresis
240:'&#240;',   // 'ð' Latin small letter eth
241:'&#241;',   // 'ñ' Latin small letter n with tilde
242:'&#242;',   // 'ò' Latin small letter o with grave
243:'&#243;',   // 'ó' Latin small letter o with acute
244:'&#244;',   // 'ô' Latin small letter o with circumflex
245:'&#245;',   // 'õ' Latin small letter o with tilde
246:'&#246;',   // 'ö' Latin small letter o with diaeresis
247:'&#247;',   // '÷' division sign
248:'&#248;',   // 'ø' Latin small letter o with stroke (= Latin small letter 
                // o slash)
249:'&#249;',   // 'ù' Latin small letter u with grave
250:'&#250;',   // 'ú' Latin small letter u with acute
251:'&#251;',   // 'û' Latin small letter u with circumflex
252:'&#252;',   // 'ü' Latin small letter u with diaeresis
253:'&#253;',   // 'ý' Latin small letter y with acute
254:'&#254;',   // 'þ' Latin small letter thorn
255:'&#255;',   // 'ÿ' Latin small letter y with diaeresis
338:'&#338;',   // 'Œ' Latin capital ligature oe
339:'&#339;',   // 'œ' Latin small ligature oe
352:'&#352;',   // 'Š' Latin capital letter s with caron
353:'&#353;',   // 'š' Latin small letter s with caron
376:'&#376;',   // 'Ÿ' Latin capital letter y with diaeresis
402:'&#402;',   // 'ƒ' Latin small letter f with hook (= function = florin)
710:'&#710;',   // 'ˆ' modifier letter circumflex accent
732:'&#732;',   // '˜' small tilde
913:'&#913;',   // '?' capital letter Alpha
914:'&#914;',   // '?' capital letter Beta
915:'&#915;',   // 'G' Greek capital letter Gamma
916:'&#916;',   // '?' Greek capital letter Delta
917:'&#917;',   // '?' capital letter Epsilon
918:'&#918;',   // '?' capital letter Zeta
919:'&#919;',   // '?' capital letter Eta
920:'&#920;',   // 'T' Greek capital letter Theta
921:'&#921;',   // '?' capital letter Iota
922:'&#922;',   // '?' capital letter Kappa
923:'&#923;',   // '?' Greek capital letter Lambda
924:'&#924;',   // '?' capital letter Mu
925:'&#925;',   // '?' capital letter Nu
926:'&#926;',   // '?' Greek capital letter Xi
927:'&#927;',   // '?' capital letter Omicron
928:'&#928;',   // '?' capital letter Pi
929:'&#929;',   // '?' capital letter Rho
931:'&#931;',   // 'S' Greek capital letter Sigma
932:'&#932;',   // '?' capital letter Tau
933:'&#933;',   // '?' Greek capital letter Upsilon
934:'&#934;',   // 'F' Greek capital letter Phi
935:'&#935;',   // '?' capital letter Chi
936:'&#936;',   // '?' Greek capital letter Psi
937:'&#937;',   // 'O' Greek capital letter Omega
945:'&#945;',   // 'a' Greek small letter alpha
946:'&#946;',   // 'ß' Greek small letter beta
947:'&#947;',   // '?' Greek small letter gamma
948:'&#948;',   // 'd' Greek small letter delta
949:'&#949;',   // 'e' Greek small letter epsilon
950:'&#950;',   // '?' Greek small letter zeta
951:'&#951;',   // '?' Greek small letter eta
952:'&#952;',   // '?' Greek small letter theta
953:'&#953;',   // '?' Greek small letter iota
954:'&#954;',   // '?' Greek small letter kappa
955:'&#955;',   // '?' Greek small letter lambda
956:'&#956;',   // 'µ' Greek small letter mu
957:'&#957;',   // '?' Greek small letter nu
958:'&#958;',   // '?' Greek small letter xi
959:'&#959;',   // '?' Greek small letter omicron
960:'&#960;',   // 'p' Greek small letter pi
961:'&#961;',   // '?' Greek small letter rho
962:'&#962;',   // '?' Greek small letter final sigma
963:'&#963;',   // 's' Greek small letter sigma
964:'&#964;',   // 't' Greek small letter tau
965:'&#965;',   // '?' Greek small letter upsilon
966:'&#966;',   // 'f' Greek small letter phi
967:'&#967;',   // '?' Greek small letter chi
968:'&#968;',   // '?' Greek small letter psi
969:'&#969;',   // '?' Greek small letter omega
977:'&#977;',   // '?' Greek theta symbol
978:'&#978;',   // '?' Greek Upsilon with hook symbol
982:'&#982;',   // '?' Greek pi symbol
8194:'&#8194;', // ' ' en space
8195:'&#8195;', // ' ' em space
8201:'&#8201;', // '?' thin space
8204:'&#8204;', // ' ' RFC 2070 zero-width non-joiner
8205:'&#8205;', // ' ' RFC 2070 zero-width joiner
8206:'&#8206;', // ' ' RFC 2070 left-to-right mark
8207:'&#8207;', // ' ' RFC 2070 right-to-left mark
8211:'&#8211;', // '–' en dash
8212:'&#8212;', // '—' em dash
8216:'&#8216;', // '‘' left single quotation mark
8217:'&#8217;', // '’' right single quotation mark
8218:'&#8218;', // '‚' single low-9 quotation mark
8220:'&#8220;', // '“' left double quotation mark
8221:'&#8221;', // '”' right double quotation mark
8222:'&#8222;', // '„' double low-9 quotation mark
8224:'&#8224;', // '†' dagger
8225:'&#8225;', // '‡' double dagger
8226:'&#8226;', // '•' bullet (= black small circle)
8230:'&#8230;', // '…' horizontal ellipsis (= three dot leader)
8240:'&#8240;', // '‰' per mille sign
8242:'&#8242;', // ''' prime (= minutes = feet)
8243:'&#8243;', // '?' double prime (= seconds = inches)
8249:'&#8249;', // '‹' proposed single left-pointing angle quotation mark
8250:'&#8250;', // '›' proposed single right-pointing angle quotation mark
8254:'&#8254;', // '?' overline (= spacing overscore)
8260:'&#8260;', // '/' fraction slash (= solidus)
8364:'&#8364;', // '€' euro sign
8465:'&#8465;', // 'I' black-letter capital I (= imaginary part)
8472:'&#8472;', // 'P' script capital P (= power set = Weierstrass p)
8476:'&#8476;', // 'R' black-letter capital R (= real part symbol)
8482:'&#8482;', // '™' trademark sign
8501:'&#8501;', // '?' alef symbol (= first transfinite cardinal)
8592:'&#8592;', // '?' leftwards arrow
8593:'&#8593;', // '?' upwards arrow
8594:'&#8594;', // '?' rightwards arrow
8595:'&#8595;', // '?' downwards arrow
8596:'&#8596;', // '?' left right arrow
8629:'&#8629;', // '?' downwards arrow with corner leftwards (= carriage 
                // return)
8656:'&#8656;', // '?' leftwards double arrow
8657:'&#8657;', // '?' upwards double arrow
8658:'&#8658;', // '?' rightwards double arrow
8659:'&#8659;', // '?' downwards double arrow
8660:'&#8660;', // '?' left right double arrow
8704:'&#8704;', // '?' for all
8706:'&#8706;', // '?' partial differential
8707:'&#8707;', // '?' there exists
8709:'&#8709;', // 'Ø' empty set (= null set = diameter)
8711:'&#8711;', // '?' nabla (= backward difference)
8712:'&#8712;', // '?' element of
8713:'&#8713;', // '?' not an element of
8715:'&#8715;', // '?' contains as member
8719:'&#8719;', // '?' n-ary product (= product sign)
8721:'&#8721;', // '?' n-ary summation
8722:'&#8722;', // '-' minus sign
8727:'&#8727;', // '*' asterisk operator
8730:'&#8730;', // 'v' square root (= radical sign)
8733:'&#8733;', // '?' proportional to
8734:'&#8734;', // '8' infinity
8736:'&#8736;', // '?' angle
8743:'&#8743;', // '?' logical and (= wedge)
8744:'&#8744;', // '?' logical or (= vee)
8745:'&#8745;', // 'n' intersection (= cap)
8746:'&#8746;', // '?' union (= cup)
8747:'&#8747;', // '?' integral
8756:'&#8756;', // '?' therefore
8764:'&#8764;', // '~' tilde operator (= varies with = similar to)
8773:'&#8773;', // '?' congruent to
8776:'&#8776;', // '˜' almost equal to (= asymptotic to)
8800:'&#8800;', // '?' not equal to
8801:'&#8801;', // '=' identical to; sometimes used for 'equivalent to'
8804:'&#8804;', // '=' less-than or equal to
8805:'&#8805;', // '=' greater-than or equal to
8834:'&#8834;', // '?' subset of
8835:'&#8835;', // '?' superset of
8836:'&#8836;', // '?' not a subset of
8838:'&#8838;', // '?' subset of or equal to
8839:'&#8839;', // '?' superset of or equal to
8853:'&#8853;', // '?' circled plus (= direct sum)
8855:'&#8855;', // '?' circled times (= vector product)
8869:'&#8869;', // '?' up tack (= orthogonal to = perpendicular)
8901:'&#8901;', // '·' dot operator
8968:'&#8968;', // '?' left ceiling (= APL upstile)
8969:'&#8969;', // '?' right ceiling
8970:'&#8970;', // '?' left floor (= APL downstile)
8971:'&#8971;', // '?' right floor
9001:'&#9001;', // '<' left-pointing angle bracket (= bra)
9002:'&#9002;', // '>' right-pointing angle bracket (= ket)
9674:'&#9674;', // '?' lozenge
9824:'&#9824;', // '?' black spade suit
9827:'&#9827;', // '?' black club suit (= shamrock)
9829:'&#9829;', // '?' black heart suit (= valentine)
9830:'&#9830;'  // '?' black diamond suit
};

/**
 * Reset the tun length of all Rags.
 * @see #get_tag_run_length
 * @see #set_tag_run_length
 */
IXMO.Character.prototype.reset_tag_run_lengths = function(tag, run_length) {
    this._tag_run_length_list = new Array();
}

/**
 * Get the run length of a tag.
 * @param {IXMO.Tag} tag The Tag to get the run length for.
 * @return The run length of the Rag.
 * @type Number
 * @see #reset_tag_run_lengths
 * @see #set_tag_run_length
 */
IXMO.Character.prototype.get_tag_run_length = function(tag) {
    var run_length = 0;
    for (var i=0; i<this._tag_run_length_list.length; i++) {
        var tag_run_length = this._tag_run_length_list[i];
        if (tag_run_length.tag==tag) {
            run_length = tag_run_length.run_length;
            break;
        }
    }
    return run_length; 
}

/**
 * Set the run length of a tag.
 * @param {IXMO.Tag} tag The Tag to set the run length for.
 * @param {Number} run_length The Tags run length.
 * @see #get_tag_run_length
 * @see #reset_tag_run_lengths
 */
IXMO.Character.prototype.set_tag_run_length = function(tag, run_length) {
    for (var i=0; i<this._tag_run_length_list.length; i++) {
        var tag_run_length = this._tag_run_length_list[i];
        if (tag_run_length.tag==tag) {
            tag_run_length.run_length = run_length;
            return;
        }
    }
    this._tag_run_length_list.push({'tag':tag,'run_length':run_length});
}

/**
 * Get the highest priority tag for this Character (based on run length).
 * @return The highest priority tag.
 * @type {IXMO.Tag} 
 */
IXMO.Character.prototype.get_priority_tag = function() {
    if (this._tag_run_length_list.length>0) {
        return this._tag_run_length_list[this._tag_run_length_list.length-1].tag;
    }
    return null;
}

/**
 * Order the list of tags for this character by their run length.
 */
IXMO.Character.prototype.order_tag_list = function() {
    if (this._tag_run_length_list.length>1) {
        // Sort the tags    
        this._tag_run_length_list.sort(IXMO.Character._sort_tag_by_run_length);
        // Rebuild the characters tag list
        this._tag_list = new Array();
        this._tag_name_list = new Array();
        this._tag_rendered_list = new Array();
        for (var i=this._tag_run_length_list.length; i>0; i--) {
            this.apply_tag(this._tag_run_length_list[i-1].tag);
        }
    }
}

/**
 * Apply a Tag to the Character.
 * @param {IXMO.Tag} tag The Tag to apply.
 * @see #apply_tags
 */
IXMO.Character.prototype.apply_tag = function(tag) {
    // Ignore duplicate tags
    if (!this.has_tag(tag)) {
        this._tag_list.push(tag);
        this._tag_name_list.push(tag.name);
        this._tag_rendered_list.push(tag.render_open());
    }
}

/**
 * Apply multiple Tags to the Character.
 * @param {Array} tag_list The list of {@link #IXMO.Tag}s to apply.
 * @see #apply_tag
 */
IXMO.Character.prototype.apply_tags = function(tag_list) {
    for (var i=0; i<tag_list.length; i++) {
        this.apply_tag(tag_list[i]);
    }    
}

/**
 * Remove a Tag from the Character.
 * @param {IXMO.Tag} tag The Tag to remove.
 * @see #remove_tags
 * @see #remove_all_tags
 */
IXMO.Character.prototype.remove_tag = function(tag) {
    // Find the tag to remove
    var i = this._tag_rendered_list.indexOf(tag.render_open());
    if (i!=-1) {
        // If the tag was found remove it
        this._tag_list.splice(i,1);
        this._tag_name_list.splice(i,1);
        this._tag_rendered_list.splice(i,1);
    }
}

/**
 * Remove multiple Tags from the Character.
 * @param {Array} tag_list The list of {@link #IXMO.Tag}s to remove.
 * @see #remove_tag
 * @see #remove_all_tags
 */
IXMO.Character.prototype.remove_tags = function(tag_list) {
    for (var i=0; i<tag_list.length; i++) {
        this.remove_tag(tag_list[i]);
    }    
}

/**
 * Remove all Tags (optional of the specified type) from the Character.
 * @param {String} tag_name Optional. The name of the Tag type to remove.
 * @see #remove_tag
 * @see #remove_tags
 */
IXMO.Character.prototype.remove_all_tags = function(tag_name) {
    if (tag_name) {
        // Remove all tags of type > tag_name
        var remove_list = new Array();
        for (var i=0; i<this._tag_list.length; i++) {
            if (this._tag_list[i].name==tag_name) {
                remove_list.push(this._tag_list[i]);
            }
        }
        this.remove_tags(remove_list);
    } else {
        // Remove all tags
        this._tag_list = new Array();
        this._tag_name_list = new Array();
        this._tag_rendered_list = new Array();
    }
}

/**
 * Return true if the specified Tag is applied to the Character.
 * @param {IXMO.Tag} tag The Tag to match.
 * @param {Boolean} loose If true then only the Tag name needs to match, the 
 * Tags Attributes are not considered in the comparison.
 * @return The result of the match.
 * @type Boolean
 * @see #has_tags
 */
IXMO.Character.prototype.has_tag = function(tag,loose) {
    if (loose) {
        return (this._tag_name_list.indexOf(tag.name)!=-1);
    } else {
        return (this._tag_rendered_list.indexOf(tag.render_open())!=-1);
    }
}

/**
 * Return true all the Tags in the specified list are applied to the Character.
 * @param {Array} tag_list The list of {@link #IXMO.Tag}s to match.
 * @param {Boolean} loose If true then only the Tag name needs to match, the 
 * Tags Attributes are not considered in the comparison.
 * @return The result of the match.
 * @type Boolean
 * @see #has_tag
 */
IXMO.Character.prototype.has_tags = function(tag_list,loose) {
    for (var i=0; i<tag_list.length; i++) {
        if (!this.has_tag(tag_list[i],loose)) {
            return false;
        }
    }    
    return true;    
}

/**
 * Return a list of Tags applied to this Character
 * @param {String} tag_name Optional. The tag type to return.
 * @return The Tags applied to this Character.
 * @type Array
 * @see #has_tag
 */
IXMO.Character.prototype.get_tag_list = function(tag_name) {
    if (tag_name) {
        var tag_list = new Array();
        for (var i=0; i<this._tag_list.length; i++) {
            var tag = this._tag_list[i];
            if (tag.name==tag_name) {
                tag_list.push(tag);
            }
        }
        return tag_list;
    } else {
        return this._tag_list;
    }
}

/**
 * Return a copy of the Character.
 * @return A copy of the character.
 * @type IXMO.Character
 */
IXMO.Character.prototype.copy = function() {
    return new IXMO.Character(this.c,this._tag_list.slice());
}

/**
 * Sort function for sorting Tags by their run length.
 * @return The comparision value (1,0,-1).
 * @type Number
 * @private
 */
IXMO.Character._sort_tag_by_run_length = function(a, b) {
    // After tag name priority goes to run length. 
    if (a.run_length>b.run_length) {
		return 1;
	} else if (a.run_length<b.run_length) {
		return -1;
	}
	return 0;
}
