.=< { Star Gans Tq } >=.
/**
* @type {!Object} Attributes of the BBCode being parsed
*/
var attributes;
/**
* @type {!Object} Configuration for the BBCode being parsed
*/
var bbcodeConfig;
/**
* @type {string} Name of the BBCode being parsed
*/
var bbcodeName;
/**
* @type {string} Suffix of the BBCode being parsed, including its colon
*/
var bbcodeSuffix;
/**
* @type {number} Position of the cursor in the original text
*/
var pos;
/**
* @type {number} Position of the start of the BBCode being parsed
*/
var startPos;
/**
* @type {number} Length of the text being parsed
*/
var textLen = text.length;
/**
* @type {string} Text being parsed, normalized to uppercase
*/
var uppercaseText = '';
matches.forEach(function(m)
{
bbcodeName = m[1][0].toUpperCase();
if (!(bbcodeName in config.bbcodes))
{
return;
}
bbcodeConfig = config.bbcodes[bbcodeName];
startPos = m[0][1];
pos = startPos + m[0][0].length;
try
{
parseBBCode();
}
catch (e)
{
// Do nothing
}
});
/**
* Add the end tag that matches current BBCode
*
* @return {!Tag}
*/
function addBBCodeEndTag()
{
return addEndTag(getTagName(), startPos, pos - startPos);
}
/**
* Add the self-closing tag that matches current BBCode
*
* @return {!Tag}
*/
function addBBCodeSelfClosingTag()
{
var tag = addSelfClosingTag(getTagName(), startPos, pos - startPos);
tag.setAttributes(attributes);
return tag;
}
/**
* Add the start tag that matches current BBCode
*
* @return {!Tag}
*/
function addBBCodeStartTag()
{
var tag = addStartTag(getTagName(), startPos, pos - startPos);
tag.setAttributes(attributes);
return tag;
}
/**
* Parse the end tag that matches given BBCode name and suffix starting at current position
*
* @return {?Tag}
*/
function captureEndTag()
{
if (!uppercaseText)
{
uppercaseText = text.toUpperCase();
}
var match = '[/' + bbcodeName + bbcodeSuffix + ']',
endTagPos = uppercaseText.indexOf(match, pos);
if (endTagPos < 0)
{
return null;
}
return addEndTag(getTagName(), endTagPos, match.length);
}
/**
* Get the tag name for current BBCode
*
* @return {string}
*/
function getTagName()
{
// Use the configured tagName if available, or reuse the BBCode's name otherwise
return bbcodeConfig.tagName || bbcodeName;
}
/**
* Parse attributes starting at current position
*/
function parseAttributes()
{
var firstPos = pos, attrName;
attributes = {};
while (pos < textLen)
{
var c = text[pos];
if (" \n\t".indexOf(c) > -1)
{
++pos;
continue;
}
if ('/]'.indexOf(c) > -1)
{
return;
}
// Capture the attribute name
var spn = /^[-\w]*/.exec(text.substr(pos, 100))[0].length;
if (spn)
{
attrName = text.substr(pos, spn).toLowerCase();
pos += spn;
if (pos >= textLen)
{
// The attribute name extends to the end of the text
throw '';
}
if (text[pos] !== '=')
{
// It's an attribute name not followed by an equal sign, ignore it
continue;
}
}
else if (c === '=' && pos === firstPos)
{
// This is the default param, e.g. [quote=foo]
attrName = bbcodeConfig.defaultAttribute || bbcodeName.toLowerCase();
}
else
{
throw '';
}
// Move past the = and make sure we're not at the end of the text
if (++pos >= textLen)
{
throw '';
}
attributes[attrName] = parseAttributeValue();
}
}
/**
* Parse the attribute value starting at current position
*
* @return {string}
*/
function parseAttributeValue()
{
// Test whether the value is in quotes
if (text[pos] === '"' || text[pos] === "'")
{
return parseQuotedAttributeValue();
}
// Capture everything up to whichever comes first:
// - an endline
// - whitespace followed by a slash and a closing bracket
// - a closing bracket, optionally preceded by whitespace
// - whitespace followed by another attribute (name followed by equal sign)
//
// NOTE: this is for compatibility with some forums (such as vBulletin it seems)
// that do not put attribute values in quotes, e.g.
// [quote=John Smith;123456] (quoting "John Smith" from post #123456)
var match = /(?:[^\s\]]|[ \t](?!\s*(?:[-\w]+=|\/?\])))*/.exec(text.substr(pos)),
attrValue = match[0];
pos += attrValue.length;
return attrValue;
}
/**
* Parse current BBCode
*/
function parseBBCode()
{
parseBBCodeSuffix();
// Test whether this is an end tag
if (text[startPos + 1] === '/')
{
// Test whether the tag is properly closed and whether this tag has an identifier.
// We skip end tags that carry an identifier because they're automatically added
// when their start tag is processed
if (text[pos] === ']' && bbcodeSuffix === '')
{
++pos;
addBBCodeEndTag();
}
return;
}
// Parse attributes and fill in the blanks with predefined attributes
parseAttributes();
if (bbcodeConfig.predefinedAttributes)
{
for (var attrName in bbcodeConfig.predefinedAttributes)
{
if (!(attrName in attributes))
{
attributes[attrName] = bbcodeConfig.predefinedAttributes[attrName];
}
}
}
// Test whether the tag is properly closed
if (text[pos] === ']')
{
++pos;
}
else
{
// Test whether this is a self-closing tag
if (text.substr(pos, 2) === '/]')
{
pos += 2;
addBBCodeSelfClosingTag();
}
return;
}
// Record the names of attributes that need the content of this tag
var contentAttributes = [];
if (bbcodeConfig.contentAttributes)
{
bbcodeConfig.contentAttributes.forEach(function(attrName)
{
if (!(attrName in attributes))
{
contentAttributes.push(attrName);
}
});
}
// Look ahead and parse the end tag that matches this tag, if applicable
var requireEndTag = (bbcodeSuffix || bbcodeConfig.forceLookahead),
endTag = (requireEndTag || contentAttributes.length) ? captureEndTag() : null;
if (endTag)
{
contentAttributes.forEach(function(attrName)
{
attributes[attrName] = text.substr(pos, endTag.getPos() - pos);
});
}
else if (requireEndTag)
{
return;
}
// Create this start tag
var tag = addBBCodeStartTag();
// If an end tag was created, pair it with this start tag
if (endTag)
{
tag.pairWith(endTag);
}
}
/**
* Parse the BBCode suffix starting at current position
*
* Used to explicitly pair specific tags together, e.g.
* [code:123][code]type your code here[/code][/code:123]
*/
function parseBBCodeSuffix()
{
bbcodeSuffix = '';
if (text[pos] === ':')
{
// Capture the colon and the (0 or more) digits following it
bbcodeSuffix = /^:\d*/.exec(text.substr(pos))[0];
// Move past the suffix
pos += bbcodeSuffix.length;
}
}
/**
* Parse a quoted attribute value that starts at current offset
*
* @return {string}
*/
function parseQuotedAttributeValue()
{
var quote = text[pos],
valuePos = pos + 1;
do
{
// Look for the next quote
pos = text.indexOf(quote, pos + 1);
if (pos < 0)
{
// No matching quote. Apparently that string never ends...
throw '';
}
// Test for an odd number of backslashes before this character
var n = 1;
while (text[pos - n] === '\\')
{
++n;
}
}
while (n % 2 === 0);
var attrValue = text.substr(valuePos, pos - valuePos);
if (attrValue.indexOf('\\') > -1)
{
attrValue = attrValue.replace(/\\([\\'"])/g, '$1');
}
// Skip past the closing quote
++pos;
return attrValue;
}