Seditio Source
Root |
./othercms/phpBB3/vendor/s9e/text-formatter/src/Parser/Tag.js
/**
* @constructor
*
* @param {number} type     Tag's type
* @param {string} name     Name of the tag
* @param {number} pos      Position of the tag in the text
* @param {number} len      Length of text consumed by the tag
* @param {number=} priority This tag's sorting tiebreaker
*/
function Tag(type, name, pos, len, priority)
{
this.type = +type;
this.name = name;
this.pos  = +pos;
this.len  = +len;
this.sortPriority = +priority || 0;

this.attributes = {};
this.cascade    = [];

// Invalidate this tag now if any value is not a number, they could wreck
// havoc in other parts of the program
if (isNaN(type + pos + len))
{
this.invalidate();
}
}

/** @const */
Tag.START_TAG = 1;

/** @const */
Tag.END_TAG = 2;

/** @const */
Tag.SELF_CLOSING_TAG = 3;

/**
* @type {!Object} Dictionary of attributes
*/
Tag.prototype.attributes;

/**
* @type {!Array.<!Tag>} List of tags that are invalidated when this tag is invalidated
*/
Tag.prototype.cascade;

/**
* @type {?Tag} End tag that unconditionally ends this start tag
*/
Tag.prototype.endTag;

/**
* @type {boolean} Whether this tag is be invalid
*/
Tag.prototype.invalid = false;

/**
* @type {number} Length of text consumed by this tag
*/
Tag.prototype.len;

/**
* @type {string} Name of this tag
*/
Tag.prototype.name;

/**
* @type {number} Position of this tag in the text
*/
Tag.prototype.pos;

/**
* @type {number} Tiebreaker used when sorting identical tags
*/
Tag.prototype.sortPriority;

/**
* @type {?Tag} Start tag that is unconditionally closed this end tag
*/
Tag.prototype.startTag;

/**
* @type {number} Tag type
*/
Tag.prototype.type;

/**
* Add a set of flags to this tag's
*
* @param {number} flags
*/
Tag.prototype.addFlags = function(flags)
{
this.flags |= flags;
};

/**
* Set given tag to be invalidated if this tag is invalidated
*
* @param {!Tag} tag
*/
Tag.prototype.cascadeInvalidationTo = function(tag)
{
this.cascade.push(tag);

// If this tag is already invalid, cascade it now
if (this.invalid)
{
tag.invalidate();
}
};

/**
* Invalidate this tag, as well as tags bound to this tag
*/
Tag.prototype.invalidate = function()
{
// Only invalidate if this tag is valid to prevent infinite loops
if (!this.invalid)
{
this.invalid = true;
this.cascade.forEach(
/**
* @param {!Tag} tag
*/
function(tag)
{
tag.invalidate();
}
);
}
};

/**
* Pair this tag with given tag
*
* @param {!Tag} tag
*/
Tag.prototype.pairWith = function(tag)
{
if (this.canBePaired(this, tag))
{
this.endTag  = tag;
tag.startTag = this;

this.cascadeInvalidationTo(tag);
}
else if (this.canBePaired(tag, this))
{
this.startTag = tag;
tag.endTag    = this;
}
};

/**
* Test whether two tags can be paired
*
* @param  {!Tag} startTag
* @param  {!Tag} endTag
* @return {boolean}
*/
Tag.prototype.canBePaired = function(startTag, endTag)
{
return startTag.name === endTag.name && startTag.type === Tag.START_TAG && endTag.type === Tag.END_TAG && startTag.pos <= startTag.pos;
};

/**
* Remove a set of flags from this tag's
*
* @param {number} flags
*/
Tag.prototype.removeFlags = function(flags)
{
this.flags &= ~flags;
};

/**
* Set the bitfield of boolean rules that apply to this tag
*
* @param {number} flags Bitfield of boolean rules that apply to this tag
*/
Tag.prototype.setFlags = function(flags)
{
this.flags = flags;
};

//==========================================================================
// Getters
//==========================================================================

/**
* Return this tag's attributes
*
* @return {!Object}
*/
Tag.prototype.getAttributes = function()
{
var attributes = {};
for (var attrName in this.attributes)
{
attributes[attrName] = this.attributes[attrName];
}

return attributes;
};

/**
* Return this tag's end tag
*
* @return {?Tag} This tag's end tag
*/
Tag.prototype.getEndTag = function()
{
return this.endTag;
};

/**
* Return the bitfield of boolean rules that apply to this tag
*
* @return {number}
*/
Tag.prototype.getFlags = function()
{
return this.flags;
};

/**
* Return the length of text consumed by this tag
*
* @return {number}
*/
Tag.prototype.getLen = function()
{
return this.len;
};

/**
* Return this tag's name
*
* @return {string}
*/
Tag.prototype.getName = function()
{
return this.name;
};

/**
* Return this tag's position
*
* @return {number}
*/
Tag.prototype.getPos = function()
{
return this.pos;
};

/**
* Return this tag's tiebreaker
*
* @return {number}
*/
Tag.prototype.getSortPriority = function()
{
return this.sortPriority;
};

/**
* Return this tag's start tag
*
* @return {?Tag} This tag's start tag
*/
Tag.prototype.getStartTag = function()
{
return this.startTag;
};

/**
* Return this tag's type
*
* @return {number}
*/
Tag.prototype.getType = function()
{
return this.type;
};

//==========================================================================
// Tag's status
//==========================================================================

/**
* Test whether this tag can close given start tag
*
* @param  {!Tag} startTag
* @return {boolean}
*/
Tag.prototype.canClose = function(startTag)
{
if (this.invalid
|| !this.canBePaired(startTag, this)
|| (this.startTag && this.startTag !== startTag)
|| (startTag.endTag && startTag.endTag !== this))
{
return false;
}

return true;
};

/**
* Test whether this tag is a br tag
*
* @return {boolean}
*/
Tag.prototype.isBrTag = function()
{
return (this.name === 'br');
};

/**
* Test whether this tag is an end tag (self-closing tags inclusive)
*
* @return {boolean}
*/
Tag.prototype.isEndTag = function()
{
return !!(this.type & Tag.END_TAG);
};

/**
* Test whether this tag is an ignore tag
*
* @return {boolean}
*/
Tag.prototype.isIgnoreTag = function()
{
return (this.name === 'i');
};

/**
* Test whether this tag is invalid
*
* @return {boolean}
*/
Tag.prototype.isInvalid = function()
{
return this.invalid;
};

/**
* Test whether this tag represents a paragraph break
*
* @return {boolean}
*/
Tag.prototype.isParagraphBreak = function()
{
return (this.name === 'pb');
};

/**
* Test whether this tag is a self-closing tag
*
* @return {boolean}
*/
Tag.prototype.isSelfClosingTag = function()
{
return (this.type === Tag.SELF_CLOSING_TAG);
};

/**
* Test whether this tag is a special tag: "br", "i", "pb" or "v"
*
* @return {boolean}
*/
Tag.prototype.isSystemTag = function()
{
return ('br i pb v'.indexOf(this.name) > -1);
};

/**
* Test whether this tag is a start tag (self-closing tags inclusive)
*
* @return {boolean}
*/
Tag.prototype.isStartTag = function()
{
return !!(this.type & Tag.START_TAG);
};

/**
* Test whether this tag represents verbatim text
*
* @return {boolean}
*/
Tag.prototype.isVerbatim = function()
{
return (this.name === 'v');
};

//==========================================================================
// Attributes handling
//==========================================================================

/**
* Return the value of given attribute
*
* @param  {string} attrName
* @return {string}
*/
Tag.prototype.getAttribute = function(attrName)
{
return this.attributes[attrName];
};

/**
* Return whether given attribute is set
*
* @param  {string} attrName
* @return {boolean}
*/
Tag.prototype.hasAttribute = function(attrName)
{
return (attrName in this.attributes);
};

/**
* Remove given attribute
*
* @param {string} attrName
*/
Tag.prototype.removeAttribute = function(attrName)
{
delete this.attributes[attrName];
};

/**
* Set the value of an attribute
*
* @param {string} attrName  Attribute's name
* @param {*}       attrValue Attribute's value
*/
Tag.prototype.setAttribute = function(attrName, attrValue)
{
this.attributes[attrName] = attrValue;
};

/**
* Set all of this tag's attributes at once
*
* @param {!Object} attributes
*/
Tag.prototype.setAttributes = function(attributes)
{
this.attributes = {};
for (var attrName in attributes)
{
this.attributes[attrName] = attributes[attrName];
}
};