69 lines
2.3 KiB
TypeScript
69 lines
2.3 KiB
TypeScript
/**
|
|
* XML utility functions for KGSP
|
|
* Contains functions for extracting and processing XML from strings
|
|
*/
|
|
|
|
/**
|
|
* Extract all XML blocks from a given string
|
|
* This function finds complete XML elements (from opening to closing tag) within mixed content,
|
|
* such as LLM responses that contain XML tool invocations alongside natural language text.
|
|
*
|
|
* @param input - The input string that may contain XML blocks
|
|
* @returns Array of XML strings found in the input, or empty array if none found
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const response = `
|
|
* I'll create a chord for you:
|
|
* <add_notes>
|
|
* <note>
|
|
* <pitch>C4</pitch>
|
|
* <start_beat>0</start_beat>
|
|
* <length>4</length>
|
|
* </note>
|
|
* </add_notes>
|
|
* This creates a C major note.
|
|
* `;
|
|
*
|
|
* const xmlBlocks = extractXMLFromString(response);
|
|
* // Returns: ['<add_notes>\n <note>\n <pitch>C4</pitch>\n <start_beat>0</start_beat>\n <length>4</length>\n </note>\n</add_notes>']
|
|
* ```
|
|
*/
|
|
export function extractXMLFromString(input: string): string[] {
|
|
// Regex pattern to match complete XML blocks:
|
|
// - <([a-zA-Z_][a-zA-Z0-9_-]*) matches opening tag name (capture group 1)
|
|
// - [^>]* matches any attributes in the opening tag
|
|
// - [\s\S]*? matches any content (including newlines) non-greedily
|
|
// - <\/\1> matches the corresponding closing tag using backreference
|
|
const xmlPattern = /<([a-zA-Z_][a-zA-Z0-9_-]*)[^>]*>[\s\S]*?<\/\1>/g;
|
|
|
|
const matches: string[] = [];
|
|
let match: RegExpExecArray | null;
|
|
|
|
// Extract all XML blocks
|
|
while ((match = xmlPattern.exec(input)) !== null) {
|
|
const xmlBlock = match[0].trim();
|
|
if (xmlBlock.length > 0) {
|
|
matches.push(xmlBlock);
|
|
}
|
|
}
|
|
|
|
return matches;
|
|
}
|
|
|
|
/**
|
|
* Wrap XML blocks in content with fenced code blocks
|
|
* @param content - The input string that may contain XML blocks
|
|
* @returns The input string with XML blocks wrapped in fenced code blocks
|
|
*/
|
|
export const wrapXmlBlocksInContent = (content: string): string => {
|
|
if (!content) return content;
|
|
const xmlBlocks = extractXMLFromString(content);
|
|
if (!xmlBlocks || xmlBlocks.length === 0) return content;
|
|
let result = content;
|
|
for (const block of xmlBlocks) {
|
|
const fenced = '```xml\n' + block + '\n```';
|
|
result = result.split(block).join(fenced);
|
|
}
|
|
return result;
|
|
}; |