llmxml
Version:
Convert between markdown and LLM-friendly pseudo-XML
106 lines (96 loc) • 2.84 kB
text/typescript
import { ASTNode, TagNode, TextNode, HeadingNode } from '../types';
import logger from '../utils/logger';
/**
* Transforms LLM-XML AST back into Markdown AST format
*/
export class LLMToMarkdownTransformer {
/**
* Convert LLM-XML AST to Markdown AST
*
* @param nodes - Array of LLM-XML AST nodes
* @returns Array of Markdown AST nodes
*/
public transform(nodes: ASTNode[]): ASTNode[] {
const result: ASTNode[] = [];
for (const node of nodes) {
const converted = this.convertNode(node);
if (Array.isArray(converted)) {
result.push(...converted);
} else if (converted) {
result.push(converted);
}
}
return result;
}
/**
* Convert a single LLM-XML AST node to Markdown AST node(s)
*
* @param node - LLM-XML AST node
* @returns Single node, array of nodes, or null if node should be skipped
*/
private convertNode(node: ASTNode): ASTNode | ASTNode[] | null {
switch (node.type) {
case 'tag':
return this.convertTagNode(node as TagNode);
case 'text':
return this.convertTextNode(node as TextNode);
default:
logger.warn('Unknown node type', { type: node.type });
return null;
}
}
/**
* Convert a tag node to Markdown AST nodes
*
* @param node - LLM-XML tag node
* @returns Array of Markdown AST nodes
*/
private convertTagNode(node: TagNode): ASTNode[] {
const result: ASTNode[] = [];
// Create heading from tag
const heading: HeadingNode = {
type: 'heading',
depth: parseInt(node.attributes?.hlevel || '1', 10),
text: node.attributes?.title || node.name,
children: [],
};
result.push(heading);
// Convert children
if (node.children?.length) {
for (const child of node.children) {
if (child.type === 'tag') {
// For tag nodes, ensure they have the correct heading level
const tagNode = child as TagNode;
if (!tagNode.attributes) {
tagNode.attributes = {};
}
// If hlevel is not set, use parent's level + 1
if (!tagNode.attributes.hlevel) {
tagNode.attributes.hlevel = String(heading.depth + 1);
}
}
const converted = this.convertNode(child);
if (Array.isArray(converted)) {
result.push(...converted);
} else if (converted) {
result.push(converted);
}
}
}
return result;
}
/**
* Convert a text node to Markdown AST node
*
* @param node - LLM-XML text node
* @returns Text node
*/
private convertTextNode(node: TextNode): TextNode {
return {
type: 'text',
value: node.value || '',
};
}
}
// Export a singleton instance
export const llmToMarkdown = new LLMToMarkdownTransformer();