feat: Add wildcard tag parsing and extraction functions for enhanced tag handling

This commit is contained in:
newtextdoc1111
2025-05-16 03:32:32 +09:00
parent b6e37d2e09
commit 2f8c805233
+120 -8
View File
@@ -1,4 +1,6 @@
// --- String Helper Functions ---
const REG_WILDCARD_WEIGHTED_TAG = /(\d+)[_\s]*::(.*?)(?=\||$)/g;
const REG_WILDCARD_SIMPLE_WORD = /[^{}_|]+/g;
/**
* Converts Hiragana to Katakana.
@@ -179,16 +181,113 @@ export function isValidTag(tag) {
return true;
}
/**
* Recursively finds all words in a string, even those inside nested braces.
* This helps extract all possible tags from complex nested wildcards.
* @param {string} text The text to extract words from
* @param {number} baseStart The starting position of the text in the original string
* @returns {Array<{start: number, end: number, tag: string}>} Array of parsed tags
*/
function extractAllWords(text, baseStart) {
const result = [];
// First, extract weighted tag patterns like "20::from above"
let weightMatch = REG_WILDCARD_WEIGHTED_TAG.exec(text);
while (weightMatch !== null) {
const tagText = weightMatch[2].trim();
if (tagText) {
// Calculate position with original offsets
const fullMatchStart = baseStart + weightMatch.index;
const tagTextStart = fullMatchStart + weightMatch[0].indexOf(tagText);
const tagTextEnd = tagTextStart + tagText.length;
result.push({
start: tagTextStart,
end: tagTextEnd,
tag: tagText
});
}
weightMatch = REG_WILDCARD_WEIGHTED_TAG.exec(text);
}
// If no weighted tags were found, extract simple words
if (result.length === 0) {
// Regular expression to match words (sequences of non-whitespace characters)
// We consider a word to be any continuous sequence of characters that's not a space, pipe, or brace
const wordRegex = REG_WILDCARD_SIMPLE_WORD;
let match;
// Find all standalone words in the text
while ((match = wordRegex.exec(text)) !== null) {
const wordStart = baseStart + match.index;
const wordEnd = wordStart + match[0].length;
// Remove leading and trailing spaces from the matched word
const trimmedTag = match[0].trim();
const leadingSpaces = match[0].length - match[0].trimStart().length;
const adjustedStart = wordStart + leadingSpaces;
const adjustedEnd = wordStart + leadingSpaces + trimmedTag.length;
result.push({
start: adjustedStart,
end: adjustedEnd,
tag: trimmedTag
});
}
}
return result;
}
/**
* Parses a wildcard selection and returns individual tags.
* Supports syntax like {tag1|tag2|tag3} and {weight::tag1|weight::tag2}.
* Also handles nested wildcards like {tag1 {tag2|tag3}|tag4}.
* @param {string} tag The complete tag text that might contain a wildcard
* @param {number} startPos The starting position of the tag in the original text
* @param {number} endPos The ending position of the tag in the original text
* @returns {Array<{start: number, end: number, tag: string}>} Array of parsed tags or null
*/
function parseWildcardSelection(tag, startPos, endPos) {
// Trim the tag for matching but keep original position
const trimmedTag = tag.trim();
// Check if this is a wildcard selection
if (!trimmedTag.startsWith('{') || !trimmedTag.endsWith('}')) {
return null; // Not a wildcard
}
// Calculate position offsets for the trim operation
const leadingSpaces = tag.length - tag.trimStart().length;
const tagStart = startPos + leadingSpaces;
// For nested wildcards, we'll extract all words from the content
// This treats each word as a separate tag, regardless of nesting
// Extract the content between the outermost braces
const wildcardContent = trimmedTag.substring(1, trimmedTag.length - 1);
// Extract all words from the wildcard content, including those in nested structures
const allTags = extractAllWords(wildcardContent, tagStart + 1);
return allTags.length > 0 ? allTags : null;
}
/**
* Finds all tag positions in the given text.
* Searches for tags separated by commas or newlines.
* Also handles wildcard selections in the format {tag1|tag2|tag3}.
* @param {string} text The text to search in
* @returns {Array<{start: number, end: number, tag: string}>} Array of tag positions and content
*/
export function findAllTagPositions(text) {
if (!text) return [];
const positions = [];
let startPos = 0;
// Process text segment by segment (comma or newline separated)
while (startPos < text.length) {
// Skip any leading whitespace, commas, or newlines
while (startPos < text.length &&
@@ -206,14 +305,26 @@ export function findAllTagPositions(text) {
if (endPosNewline === -1) endPosNewline = text.length;
const endPos = Math.min(endPosComma, endPosNewline);
const tag = text.substring(startPos, endPos);
const tagText = text.substring(startPos, endPos);
if (tag.trim().length > 0) {
positions.push({
start: startPos,
end: endPos,
tag: tag
});
if (tagText.trim().length > 0) {
const trimmedTag = tagText.trim();
// Check if this is a wildcard selection
if (trimmedTag.startsWith('{') && trimmedTag.endsWith('}')) {
// Process wildcard using our existing wildcard parser
const wildcardTags = parseWildcardSelection(tagText, startPos, endPos);
if (wildcardTags) {
positions.push(...wildcardTags);
}
} else {
// Normal tag, add it directly
positions.push({
start: startPos,
end: endPos,
tag: tagText
});
}
}
// Move to the next tag
@@ -222,6 +333,7 @@ export function findAllTagPositions(text) {
return positions;
}
/**
* Extracts existing tags from the textarea with search normalization (possibly duplicated).
* @param {HTMLTextAreaElement} textarea The textarea element to extract tags from