@zenbase/llml
Version:
Lightweight Language Markup Language - Convert nested data structures to human-readable, XML-like markup
123 lines (105 loc) • 3.73 kB
text/typescript
import dedent from "dedent"
import type { Formatters } from "../types"
export const isObject = (v: unknown): boolean =>
typeof v === "object" && v !== null && !Array.isArray(v) && !(v instanceof Date)
export const formatObject = (
value: unknown,
llml: (data: unknown, formatters: Formatters) => string,
formatters: Formatters,
): string => {
// Check if object has a custom toString method (not the default Object.prototype.toString)
if (value && typeof value.toString === "function" && value.toString !== Object.prototype.toString) {
return value.toString()
}
const entries = Object.entries(value as Record<string, unknown>)
if (entries.length === 0) {
// Plain empty objects should return empty string
// Other objects with no enumerable properties should use String(value)
if (Object.getPrototypeOf(value) === Object.prototype) {
return ""
}
return String(value)
}
const results: string[] = []
for (let i = 0; i < entries.length; i++) {
const [key, val] = entries[i]
if (i > 0) results.push("\n")
// Format key-value pair with proper XML structure
const formattedContent = formatKeyValue(key, val, llml, formatters)
results.push(formattedContent)
}
return results.join("")
}
/**
* Helper function to format key-value pairs in VibeXML style
*/
function formatKeyValue(
key: string,
value: unknown,
llml: (data: unknown, formatters: Formatters) => string,
formatters: Formatters,
): string {
if (Array.isArray(value)) {
if (value.length === 0) return ""
const wrapperTag = key
let result = `<${wrapperTag}>\n`
for (let i = 0; i < value.length; i++) {
const item = value[i]
const itemTag = `${key}-${i + 1}`
if (typeof item === "object" && item !== null && !Array.isArray(item)) {
const itemFormatted = llml(item, formatters)
if (itemFormatted.includes("\n")) {
result += ` <${itemTag}>\n`
const indentedContent = itemFormatted
.split("\n")
.map(line => (line ? ` ${line}` : ""))
.join("\n")
result += indentedContent
result += `\n </${itemTag}>\n`
} else {
result += ` <${itemTag}>${itemFormatted}</${itemTag}>\n`
}
} else {
if (typeof item === "string" && item.includes("\n")) {
const cleanText = dedent(item).trim()
const lines = cleanText.split("\n")
result += ` <${itemTag}>`
for (let j = 0; j < lines.length; j++) {
if (j > 0) {
result += "\n "
} else {
result += " "
}
result += lines[j]
}
result += `</${itemTag}>\n`
} else {
const formatted = llml(item, formatters)
result += ` <${itemTag}>${formatted}</${itemTag}>\n`
}
}
}
result += `</${wrapperTag}>`
return result
}
if (typeof value === "object" && value !== null) {
const formatted = llml(value, formatters)
if (formatted.includes("\n")) {
const indentedContent = formatted
.split("\n")
.map(line => (line ? ` ${line}` : ""))
.join("\n")
return `<${key}>\n${indentedContent}\n</${key}>`
}
return `<${key}>${formatted}</${key}>`
}
if (typeof value === "string" && value.includes("\n")) {
const cleanText = dedent(value).trim()
const lines = cleanText.split("\n")
const formattedLines = lines.map(line => ` ${line}`)
const formattedContent = formattedLines.join("\n")
return `<${key}>\n${formattedContent}\n</${key}>`
}
const formatted = llml(value, formatters)
return `<${key}>${formatted}</${key}>`
}