.
This commit is contained in:
@@ -0,0 +1,248 @@
|
||||
/*****************************************************************************
|
||||
** Common tagulous functionality
|
||||
*****************************************************************************/
|
||||
|
||||
var Tagulous = (function () {
|
||||
|
||||
/**************************************************************************
|
||||
** Port of utils.py
|
||||
*/
|
||||
|
||||
// Constants to improve legibility
|
||||
var COMMA = ',',
|
||||
SPACE = ' ',
|
||||
QUOTE = '"'
|
||||
;
|
||||
|
||||
function escapeQuotes(count) {
|
||||
return Array(Math.floor(count / 2) + 1).join(QUOTE);
|
||||
}
|
||||
|
||||
|
||||
function parseTags(str, spaceDelimiter, withRaw) {
|
||||
/** Parse a tag string
|
||||
|
||||
Returns a sorted list of unique tags
|
||||
|
||||
If spaceDelimiter is false, only commas will be used as the tag
|
||||
name delimiter. If it is unset, it will default to true; when true,
|
||||
spaces are used as well as commas.
|
||||
|
||||
If withRaw == true, returns a tuple of two lists:
|
||||
tags List of tags, unsorted and not unique
|
||||
raws List of raw strings after parsed tag
|
||||
|
||||
If the last tag is not explicitly ended with a
|
||||
delimiter, the corresponding item in ``raws `` will be
|
||||
``null`` to indicate that the parser unexpectedly ran
|
||||
out of characters.
|
||||
|
||||
This can help with parsing live input; if the last item
|
||||
in ``raw`` is an empty string the tag has been closed;
|
||||
if it is ``null`` then the tag is still being written.
|
||||
*/
|
||||
|
||||
// Empty string is easy
|
||||
if (!str) {
|
||||
if (withRaw) {
|
||||
return [[],[]];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
// Prep vars for parser
|
||||
var tags = [],
|
||||
raws = [],
|
||||
tag = '',
|
||||
delimiter = SPACE,
|
||||
strLen = str.length,
|
||||
strLast = strLen - 1,
|
||||
index, inQuote, chr, tagLen, leftCount, rightCount
|
||||
;
|
||||
|
||||
// Disable spaces
|
||||
if (spaceDelimiter === false) {
|
||||
delimiter = COMMA;
|
||||
}
|
||||
|
||||
// Loop through chars
|
||||
for (index=0; index<strLen; index++) {
|
||||
chr = str[index];
|
||||
|
||||
// See if it's a delimiter
|
||||
if (!inQuote) {
|
||||
// Comma delimiter takes priority
|
||||
if (delimiter !== COMMA && chr === COMMA) {
|
||||
delimiter = COMMA;
|
||||
|
||||
// All previous tags were actually just one tag
|
||||
tag = str.substring(0, index).replace(/^ +| +$/g, '');
|
||||
tags = [];
|
||||
raws = [];
|
||||
|
||||
// Strip start/end quotes
|
||||
tagLen = tag.length;
|
||||
tag = tag.replace(/^"+/, '');
|
||||
leftCount = tagLen - tag.length;
|
||||
tagLen = tag.length;
|
||||
tag = tag.replace(/"+$/, '');
|
||||
rightCount = tagLen - tag.length;
|
||||
|
||||
// Escape inner quotes
|
||||
tag = tag.replace(/""/g, QUOTE);
|
||||
|
||||
// Add back escaped start/end quotes
|
||||
tag = escapeQuotes(leftCount) +
|
||||
tag +
|
||||
escapeQuotes(rightCount)
|
||||
;
|
||||
|
||||
// Add back insignificant unquoted quotes
|
||||
if (leftCount % 2 == 1) {
|
||||
if (rightCount % 2 != 1) {
|
||||
tag = QUOTE + tag;
|
||||
}
|
||||
} else if (rightCount % 2 == 1) {
|
||||
tag += QUOTE;
|
||||
}
|
||||
}
|
||||
|
||||
// Found end of tag
|
||||
if (chr === delimiter) {
|
||||
tag = tag.replace(/ +$/, '');
|
||||
if (tag) {
|
||||
tags.push(tag);
|
||||
if (withRaw) {
|
||||
raws.push(str.substring(index + 1));
|
||||
}
|
||||
tag = '';
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If tag is empty, ignore whitespace
|
||||
if (!tag && chr === SPACE) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Now either in a quote, or not a delimiter
|
||||
// If it's not a quote, add to tag
|
||||
if (chr != QUOTE) {
|
||||
tag += chr;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Char is quote - count how many quotes appear here and skip them
|
||||
leftCount = 1;
|
||||
while (index < strLast && str[index + 1] == QUOTE) {
|
||||
leftCount++;
|
||||
index++;
|
||||
}
|
||||
|
||||
if (!tag) {
|
||||
// Quote at start
|
||||
// If an odd number, now in quote
|
||||
if (leftCount % 2 == 1) {
|
||||
inQuote = true;
|
||||
}
|
||||
|
||||
// Tag starts with escaped quotes
|
||||
tag = escapeQuotes(leftCount);
|
||||
} else {
|
||||
// Quote in middle or at end
|
||||
// Add any escaped
|
||||
tag += escapeQuotes(leftCount);
|
||||
|
||||
// An odd number followed by a delimiter will mean it has ended
|
||||
// Need to look ahead to figure it out
|
||||
if (leftCount % 2 == 1) {
|
||||
// If it's the last character, it has closed
|
||||
if (index == strLast) {
|
||||
inQuote = false;
|
||||
break;
|
||||
}
|
||||
|
||||
for (var i2=index + 1, c2; i2<strLen; i2++) {
|
||||
c2 = str[i2];
|
||||
if (c2 == SPACE) {
|
||||
if (delimiter == SPACE) {
|
||||
// Quotes closed; tag will end next loop
|
||||
inQuote = false;
|
||||
break;
|
||||
} else {
|
||||
// Spaces are insignificant during whitespace
|
||||
// Tag may continue, keep checking chars
|
||||
continue;
|
||||
}
|
||||
} else if (c2 == COMMA) {
|
||||
// Quotes closed; tag will end next loop
|
||||
// Delimiter doesn't matter, comma always wins
|
||||
inQuote = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Tag has not ended
|
||||
// Add odd quote to tag and keep building
|
||||
tag += QUOTE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Chars expanded
|
||||
if (tag) {
|
||||
// Partial tag remains; add to stack
|
||||
if (inQuote) {
|
||||
// Add the quote back to the start - it wasn't significant
|
||||
tag = QUOTE + tag;
|
||||
}
|
||||
tags.push(tag);
|
||||
if (withRaw) {
|
||||
// Indicate that the ending was unexpected
|
||||
raws.push(null);
|
||||
}
|
||||
}
|
||||
|
||||
// If withRaw is true, don't want to filter or sort tags
|
||||
if (withRaw) {
|
||||
return [tags, raws];
|
||||
}
|
||||
|
||||
// Enforce uniqueness and sort
|
||||
for (var unique={}, finalTags=[], i=0, l=tags.length; i<l; i++) {
|
||||
if (!unique[tags[i]]) {
|
||||
finalTags.push(tags[i]);
|
||||
unique[tags[i]] = true;
|
||||
}
|
||||
}
|
||||
finalTags.sort();
|
||||
|
||||
return finalTags;
|
||||
}
|
||||
|
||||
|
||||
function renderTags(tags) {
|
||||
var safe = [], i, str;
|
||||
for (i=0; i<tags.length; i++) {
|
||||
str = tags[i].replace(/"/g, QUOTE + QUOTE);
|
||||
if (!str) {
|
||||
continue;
|
||||
}
|
||||
if (str.indexOf(COMMA) > -1 || str.indexOf(SPACE) > -1) {
|
||||
safe.push(QUOTE + str + QUOTE);
|
||||
} else {
|
||||
safe.push(str);
|
||||
}
|
||||
}
|
||||
safe.sort();
|
||||
return safe.join(', ');
|
||||
}
|
||||
|
||||
return {
|
||||
parseTags: parseTags,
|
||||
renderTags: renderTags
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user