xml-diff-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/utils.ts","../src/normalize.ts","../src/parse.ts","../src/text-diff.ts","../src/diff.ts","../src/serialize.ts","../src/format.ts","../src/patch.ts"],"sourcesContent":["/**\r\n * Public entrypoint for xml-diff-kit.\r\n *\r\n * This file intentionally re-exports a small, stable API surface so consumers\r\n * can import everything from `xml-diff-kit` without depending on internal file\r\n * structure. Internal modules may be reorganized in future versions without\r\n * changing these exports.\r\n */\r\nexport { diffXml } from './diff.js';\r\nexport { formatDiff } from './format.js';\r\nexport { normalizeXml } from './normalize.js';\r\nexport { parseXml } from './parse.js';\r\nexport { patchXml } from './patch.js';\r\nexport { serializeXml } from './serialize.js';\r\nexport { diffText } from './text-diff.js';\r\n\r\nexport type {\r\n DiffSummaryItem,\r\n FormatDiffOptions,\r\n SerializeOptions,\r\n TextChange,\r\n TextDiffSegment,\r\n XmlCommentNode,\r\n XmlDiffOp,\r\n XmlDiffOptions,\r\n XmlElementNode,\r\n XmlNode,\r\n XmlTextNode,\r\n} from './types.js';\r\n","import type { XmlElementNode, XmlNode } from './types.js';\r\n\r\n/** Return true when a normalized XML node is an element node. */\r\nexport function isElementNode(node: XmlNode): node is XmlElementNode {\r\n return node.type === 'element';\r\n}\r\n\r\n/**\r\n * Deep-clone a normalized XML node.\r\n *\r\n * `structuredClone` is available in the supported Node.js versions and modern\r\n * browsers. The AST is plain data, so structured cloning is sufficient and keeps\r\n * the library free of an additional cloning dependency.\r\n */\r\nexport function cloneXmlNode<T extends XmlNode>(node: T): T {\r\n return structuredClone(node) as T;\r\n}\r\n\r\n/** Return a new record sorted by key for deterministic serialization/diffing. */\r\nexport function sortRecord(record: Record<string, string>): Record<string, string> {\r\n return Object.fromEntries(\r\n Object.entries(record).sort(([left], [right]) => left.localeCompare(right)),\r\n );\r\n}\r\n\r\n/** Escape XML text-node content. */\r\nexport function escapeXmlText(value: string): string {\r\n return value\r\n .replaceAll('&', '&amp;')\r\n .replaceAll('<', '&lt;')\r\n .replaceAll('>', '&gt;');\r\n}\r\n\r\n/** Escape XML attribute values. */\r\nexport function escapeXmlAttr(value: string): string {\r\n return escapeXmlText(value).replaceAll('\"', '&quot;');\r\n}\r\n\r\n/**\r\n * Build a stable semantic key for an element using the first matching key attr.\r\n *\r\n * The key includes the element name to avoid treating `<step id=\"1\"/>` and\r\n * `<figure id=\"1\"/>` as the same logical node.\r\n */\r\nexport function getNodeKey(node: XmlNode, keyAttrs: string[] = []): string | undefined {\r\n if (!isElementNode(node)) return undefined;\r\n\r\n for (const keyAttr of keyAttrs) {\r\n const value = node.attrs[keyAttr];\r\n if (value !== undefined) {\r\n return `${node.name}:${keyAttr}=${value}`;\r\n }\r\n }\r\n\r\n return undefined;\r\n}\r\n\r\n/**\r\n * Format one absolute-path segment for a node.\r\n *\r\n * Every segment includes a numeric sibling index, which is the executable part\r\n * used by patching. When a key attribute is available, the segment also includes\r\n * a readable key hint such as `step[@id=\"s1\"][0]`.\r\n */\r\nexport function formatPathSegment(node: XmlNode, index: number, keyAttrs: string[] = []): string {\r\n if (node.type === 'text') return `text()[${index}]`;\r\n if (node.type === 'comment') return `comment()[${index}]`;\r\n\r\n const keyAttr = keyAttrs.find((attr) => node.attrs[attr] !== undefined);\r\n const keyValue = keyAttr ? node.attrs[keyAttr] : undefined;\r\n const keyPart = keyAttr && keyValue !== undefined ? `[@${keyAttr}=\"${keyValue}\"]` : '';\r\n\r\n return `${node.name}${keyPart}[${index}]`;\r\n}\r\n","import type { XmlDiffOptions, XmlNode } from './types.js';\r\nimport { cloneXmlNode, sortRecord } from './utils.js';\r\n\r\n/**\r\n * Normalize an XML AST before diffing or patching.\r\n *\r\n * Normalization is where callers define which XML differences are meaningful.\r\n * For example, formatted indentation can be ignored with `ignoreWhitespaceText`,\r\n * comments can be ignored with `ignoreComments`, and attribute output can be\r\n * made deterministic with `sortAttributes`.\r\n *\r\n * The input node is cloned first. Callers can safely reuse their original AST\r\n * after normalization without worrying about mutation.\r\n */\r\nexport function normalizeXml(node: XmlNode, options: XmlDiffOptions = {}): XmlNode {\r\n const cloned = cloneXmlNode(node);\r\n const normalized = normalizeNode(cloned, options);\r\n\r\n if (!normalized) {\r\n throw new Error('Cannot normalize an empty XML document.');\r\n }\r\n\r\n return normalized;\r\n}\r\n\r\n/**\r\n * Normalize one node recursively.\r\n *\r\n * The function returns `null` for nodes that should be removed from the logical\r\n * comparison, such as whitespace-only text nodes or ignored comments.\r\n */\r\nfunction normalizeNode(node: XmlNode, options: XmlDiffOptions): XmlNode | null {\r\n if (node.type === 'text') {\r\n const text = options.trimText ? node.text.trim() : node.text;\r\n\r\n if (options.ignoreWhitespaceText && text.trim() === '') {\r\n return null;\r\n }\r\n\r\n return {\r\n type: 'text',\r\n text,\r\n };\r\n }\r\n\r\n if (node.type === 'comment') {\r\n if (options.ignoreComments) return null;\r\n\r\n return node;\r\n }\r\n\r\n const children = node.children\r\n .map((child) => normalizeNode(child, options))\r\n .filter((child): child is XmlNode => Boolean(child));\r\n\r\n return {\r\n ...node,\r\n attrs: options.sortAttributes ? sortRecord(node.attrs) : { ...node.attrs },\r\n children,\r\n };\r\n}\r\n","import { DOMParser } from '@xmldom/xmldom';\r\n\r\nimport type { XmlNode } from './types.js';\r\n\r\n/**\r\n * Parse an XML string into the library's normalized XML AST.\r\n *\r\n * This function is intentionally stricter than the default `@xmldom/xmldom`\r\n * behavior: parser warnings are treated as invalid XML. This is important for\r\n * a diff/patch library because silently recovering malformed XML can produce\r\n * surprising paths and operations.\r\n *\r\n * Only element, text, and comment nodes are currently preserved. Processing\r\n * instructions, doctypes, and other DOM node types are ignored during child\r\n * traversal until the public AST grows explicit representations for them.\r\n */\r\nexport function parseXml(xml: string): XmlNode {\r\n const doc = new DOMParser({\r\n errorHandler: {\r\n warning: (message) => {\r\n throw new Error(`Invalid XML: ${message}`);\r\n },\r\n error: (message) => {\r\n throw new Error(`Invalid XML: ${message}`);\r\n },\r\n fatalError: (message) => {\r\n throw new Error(`Invalid XML: ${message}`);\r\n },\r\n },\r\n }).parseFromString(xml, 'application/xml');\r\n\r\n if (!doc.documentElement) {\r\n throw new Error('Invalid XML: missing document element.');\r\n }\r\n\r\n return fromDomNode(doc.documentElement);\r\n}\r\n\r\n/** Convert a DOM node into the serializable `XmlNode` AST shape. */\r\nfunction fromDomNode(node: Node): XmlNode {\r\n if (node.nodeType === 1) {\r\n const element = node as Element;\r\n const attrs: Record<string, string> = {};\r\n\r\n // Preserve attribute names and values exactly as reported by the parser.\r\n // Attribute ordering can be made deterministic later by `normalizeXml`.\r\n for (let index = 0; index < element.attributes.length; index += 1) {\r\n const attr = element.attributes.item(index);\r\n if (attr) attrs[attr.name] = attr.value;\r\n }\r\n\r\n const children: XmlNode[] = [];\r\n\r\n // Preserve only node kinds currently represented by the public AST.\r\n for (let index = 0; index < element.childNodes.length; index += 1) {\r\n const child = element.childNodes.item(index);\r\n\r\n if (child.nodeType === 1 || child.nodeType === 3 || child.nodeType === 8) {\r\n children.push(fromDomNode(child));\r\n }\r\n }\r\n\r\n return {\r\n type: 'element',\r\n name: element.nodeName,\r\n namespaceURI: element.namespaceURI ?? null,\r\n attrs,\r\n children,\r\n };\r\n }\r\n\r\n if (node.nodeType === 3) {\r\n return {\r\n type: 'text',\r\n text: node.nodeValue ?? '',\r\n };\r\n }\r\n\r\n if (node.nodeType === 8) {\r\n return {\r\n type: 'comment',\r\n text: node.nodeValue ?? '',\r\n };\r\n }\r\n\r\n throw new Error(`Unsupported XML node type: ${node.nodeType}`);\r\n}\r\n","import type { TextChange, TextDiffSegment } from './types.js';\r\n\r\n/**\r\n * Diff two text values and return both patch-oriented and display-oriented data.\r\n *\r\n * `changes` is suitable for patch/review workflows because each item includes\r\n * an offset in the old text. `segments` is suitable for UI rendering because it\r\n * preserves a readable sequence of equal/insert/delete chunks.\r\n *\r\n * The implementation works on Unicode code points via `Array.from`, so offsets\r\n * are more predictable for emoji and other non-BMP characters than raw\r\n * JavaScript UTF-16 indexes.\r\n */\r\nexport function diffText(\r\n oldValue: string,\r\n newValue: string,\r\n): {\r\n changes: TextChange[];\r\n segments: TextDiffSegment[];\r\n} {\r\n if (oldValue === newValue) {\r\n return {\r\n changes: [],\r\n segments: [{ type: 'equal', text: oldValue }],\r\n };\r\n }\r\n\r\n const oldChars = Array.from(oldValue);\r\n const newChars = Array.from(newValue);\r\n const segments = buildDiffSegments(oldChars, newChars);\r\n\r\n return {\r\n changes: segmentsToChanges(segments),\r\n segments,\r\n };\r\n}\r\n\r\n/**\r\n * Build display segments using a classic LCS dynamic-programming table.\r\n *\r\n * This is intentionally simple and deterministic for the first version. It is\r\n * not the most memory-efficient diff algorithm for very large text nodes, but\r\n * it produces stable, easy-to-understand segments for typical XML text content\r\n * such as paragraphs, titles, and short inline text nodes.\r\n */\r\nfunction buildDiffSegments(oldChars: string[], newChars: string[]): TextDiffSegment[] {\r\n const rows = oldChars.length + 1;\r\n const cols = newChars.length + 1;\r\n const dp: number[][] = Array.from({ length: rows }, () => Array.from({ length: cols }, () => 0));\r\n\r\n // Fill the LCS length table from bottom-right to top-left.\r\n for (let oldIndex = oldChars.length - 1; oldIndex >= 0; oldIndex -= 1) {\r\n for (let newIndex = newChars.length - 1; newIndex >= 0; newIndex -= 1) {\r\n if (oldChars[oldIndex] === newChars[newIndex]) {\r\n dp[oldIndex]![newIndex] = dp[oldIndex + 1]![newIndex + 1]! + 1;\r\n } else {\r\n dp[oldIndex]![newIndex] = Math.max(dp[oldIndex + 1]![newIndex]!, dp[oldIndex]![newIndex + 1]!);\r\n }\r\n }\r\n }\r\n\r\n const segments: TextDiffSegment[] = [];\r\n let oldIndex = 0;\r\n let newIndex = 0;\r\n\r\n // Walk the table to produce an edit script. When both options are equal,\r\n // deletion is preferred first so a delete+insert pair can later become a\r\n // replaceTextRange operation.\r\n while (oldIndex < oldChars.length && newIndex < newChars.length) {\r\n if (oldChars[oldIndex] === newChars[newIndex]) {\r\n pushSegment(segments, { type: 'equal', text: oldChars[oldIndex]! });\r\n oldIndex += 1;\r\n newIndex += 1;\r\n } else if (dp[oldIndex + 1]![newIndex]! >= dp[oldIndex]![newIndex + 1]!) {\r\n pushSegment(segments, { type: 'delete', text: oldChars[oldIndex]! });\r\n oldIndex += 1;\r\n } else {\r\n pushSegment(segments, { type: 'insert', text: newChars[newIndex]! });\r\n newIndex += 1;\r\n }\r\n }\r\n\r\n while (oldIndex < oldChars.length) {\r\n pushSegment(segments, { type: 'delete', text: oldChars[oldIndex]! });\r\n oldIndex += 1;\r\n }\r\n\r\n while (newIndex < newChars.length) {\r\n pushSegment(segments, { type: 'insert', text: newChars[newIndex]! });\r\n newIndex += 1;\r\n }\r\n\r\n return segments;\r\n}\r\n\r\n/** Convert display segments into offset-based patch changes. */\r\nfunction segmentsToChanges(segments: TextDiffSegment[]): TextChange[] {\r\n const changes: TextChange[] = [];\r\n let offset = 0;\r\n let index = 0;\r\n\r\n while (index < segments.length) {\r\n const segment = segments[index]!;\r\n\r\n if (segment.type === 'equal') {\r\n offset += Array.from(segment.text).length;\r\n index += 1;\r\n continue;\r\n }\r\n\r\n if (segment.type === 'delete') {\r\n const next = segments[index + 1];\r\n\r\n // Adjacent delete+insert is a replacement, which is easier for review\r\n // workflows to display than two independent operations.\r\n if (next?.type === 'insert') {\r\n changes.push({\r\n op: 'replaceTextRange',\r\n offset,\r\n oldText: segment.text,\r\n newText: next.text,\r\n });\r\n offset += Array.from(segment.text).length;\r\n index += 2;\r\n continue;\r\n }\r\n\r\n changes.push({\r\n op: 'deleteText',\r\n offset,\r\n oldText: segment.text,\r\n });\r\n offset += Array.from(segment.text).length;\r\n index += 1;\r\n continue;\r\n }\r\n\r\n // Insertions do not advance the old-text offset because no old text is\r\n // consumed by an insertion.\r\n changes.push({\r\n op: 'insertText',\r\n offset,\r\n text: segment.text,\r\n });\r\n index += 1;\r\n }\r\n\r\n return changes;\r\n}\r\n\r\n/** Append a segment, merging it with the previous segment when possible. */\r\nfunction pushSegment(segments: TextDiffSegment[], next: TextDiffSegment): void {\r\n const previous = segments.at(-1);\r\n\r\n if (previous?.type === next.type) {\r\n previous.text += next.text;\r\n return;\r\n }\r\n\r\n segments.push({ ...next });\r\n}\r\n","import { normalizeXml } from './normalize.js';\r\nimport { parseXml } from './parse.js';\r\nimport { diffText } from './text-diff.js';\r\nimport type { XmlDiffOp, XmlDiffOptions, XmlNode } from './types.js';\r\nimport { formatPathSegment, getNodeKey } from './utils.js';\r\n\r\n/**\r\n * Compare two XML inputs and return structured, machine-readable operations.\r\n *\r\n * Inputs can be raw XML strings or pre-parsed `XmlNode` ASTs. Both sides are\r\n * normalized before comparison, so options such as `ignoreWhitespaceText` and\r\n * `ignoreComments` are applied consistently to old and new documents.\r\n *\r\n * The returned operations use absolute paths from the XML root node. The path\r\n * format is intentionally deterministic and readable, for example:\r\n * `/procedure[0]/step[@id=\"s1\"][0]/text()[0]`.\r\n */\r\nexport function diffXml(\r\n oldInput: string | XmlNode,\r\n newInput: string | XmlNode,\r\n options: XmlDiffOptions = {},\r\n): XmlDiffOp[] {\r\n const oldNode = normalizeXml(typeof oldInput === 'string' ? parseXml(oldInput) : oldInput, options);\r\n const newNode = normalizeXml(typeof newInput === 'string' ? parseXml(newInput) : newInput, options);\r\n\r\n const ops: XmlDiffOp[] = [];\r\n diffNode(oldNode, newNode, `/${formatPathSegment(oldNode, 0, options.keyAttrs)}`, options, ops);\r\n return ops;\r\n}\r\n\r\n/** Compare two nodes that are expected to represent the same path. */\r\nfunction diffNode(\r\n oldNode: XmlNode,\r\n newNode: XmlNode,\r\n path: string,\r\n options: XmlDiffOptions,\r\n ops: XmlDiffOp[],\r\n): void {\r\n if (oldNode.type !== newNode.type) {\r\n ops.push({ op: 'replaceNode', path, oldValue: oldNode, newValue: newNode });\r\n return;\r\n }\r\n\r\n if (oldNode.type === 'text' && newNode.type === 'text') {\r\n if (oldNode.text !== newNode.text) {\r\n const textDiff = diffText(oldNode.text, newNode.text);\r\n ops.push({\r\n op: 'replaceText',\r\n path,\r\n oldValue: oldNode.text,\r\n newValue: newNode.text,\r\n changes: textDiff.changes,\r\n segments: textDiff.segments,\r\n });\r\n }\r\n return;\r\n }\r\n\r\n if (oldNode.type === 'comment' && newNode.type === 'comment') {\r\n if (oldNode.text !== newNode.text) {\r\n ops.push({ op: 'replaceNode', path, oldValue: oldNode, newValue: newNode });\r\n }\r\n return;\r\n }\r\n\r\n if (oldNode.type !== 'element' || newNode.type !== 'element') return;\r\n\r\n // Different element names or namespace URIs are treated as whole-node replacement.\r\n // This keeps the first version simple and avoids ambiguous rename semantics.\r\n if (oldNode.name !== newNode.name || oldNode.namespaceURI !== newNode.namespaceURI) {\r\n ops.push({ op: 'replaceNode', path, oldValue: oldNode, newValue: newNode });\r\n return;\r\n }\r\n\r\n diffAttributes(oldNode.attrs, newNode.attrs, path, ops);\r\n diffChildren(oldNode.children, newNode.children, path, options, ops);\r\n}\r\n\r\n/** Compare attribute maps on the same element path. */\r\nfunction diffAttributes(\r\n oldAttrs: Record<string, string>,\r\n newAttrs: Record<string, string>,\r\n path: string,\r\n ops: XmlDiffOp[],\r\n): void {\r\n for (const [name, oldValue] of Object.entries(oldAttrs)) {\r\n const newValue = newAttrs[name];\r\n\r\n if (newValue === undefined) {\r\n ops.push({ op: 'removeAttr', path, name, oldValue });\r\n } else if (newValue !== oldValue) {\r\n ops.push({ op: 'updateAttr', path, name, oldValue, newValue });\r\n }\r\n }\r\n\r\n for (const [name, value] of Object.entries(newAttrs)) {\r\n if (oldAttrs[name] === undefined) {\r\n ops.push({ op: 'addAttr', path, name, value });\r\n }\r\n }\r\n}\r\n\r\n/**\r\n * Compare child nodes.\r\n *\r\n * If `keyAttrs` is provided and at least one sibling has a key, keyed children\r\n * are aligned by key first. This reduces false positives for document models\r\n * where stable IDs are more meaningful than sibling indexes. Unkeyed children\r\n * still fall back to index-based comparison.\r\n */\r\nfunction diffChildren(\r\n oldChildren: XmlNode[],\r\n newChildren: XmlNode[],\r\n parentPath: string,\r\n options: XmlDiffOptions,\r\n ops: XmlDiffOp[],\r\n): void {\r\n const keyAttrs = options.keyAttrs ?? [];\r\n\r\n if (keyAttrs.length > 0) {\r\n const oldKeyed = new Map<string, { node: XmlNode; index: number }>();\r\n const newKeyed = new Map<string, { node: XmlNode; index: number }>();\r\n\r\n oldChildren.forEach((node, index) => {\r\n const key = getNodeKey(node, keyAttrs);\r\n if (key) oldKeyed.set(key, { node, index });\r\n });\r\n\r\n newChildren.forEach((node, index) => {\r\n const key = getNodeKey(node, keyAttrs);\r\n if (key) newKeyed.set(key, { node, index });\r\n });\r\n\r\n if (oldKeyed.size > 0 || newKeyed.size > 0) {\r\n for (const [key, { node, index }] of oldKeyed) {\r\n const next = newKeyed.get(key);\r\n const childPath = `${parentPath}/${formatPathSegment(node, index, keyAttrs)}`;\r\n\r\n if (!next) {\r\n ops.push({ op: 'removeNode', path: childPath, oldValue: node });\r\n } else {\r\n const newPath = `${parentPath}/${formatPathSegment(next.node, next.index, keyAttrs)}`;\r\n\r\n // Move detection is opt-in because move application changes sibling order.\r\n // Consumers that only need semantic comparison can keep the conservative default.\r\n if (options.detectMoves === true && index !== next.index) {\r\n ops.push({\r\n op: 'moveNode',\r\n path: childPath,\r\n fromPath: childPath,\r\n toPath: newPath,\r\n value: next.node,\r\n });\r\n }\r\n\r\n diffNode(node, next.node, childPath, options, ops);\r\n }\r\n }\r\n\r\n for (const [, { node, index }] of newKeyed) {\r\n const key = getNodeKey(node, keyAttrs);\r\n if (key && !oldKeyed.has(key)) {\r\n const childPath = `${parentPath}/${formatPathSegment(node, index, keyAttrs)}`;\r\n ops.push({ op: 'addNode', path: childPath, value: node });\r\n }\r\n }\r\n\r\n const oldUnkeyed = oldChildren\r\n .map((node, index) => ({ node, index }))\r\n .filter(({ node }) => !getNodeKey(node, keyAttrs));\r\n const newUnkeyed = newChildren\r\n .map((node, index) => ({ node, index }))\r\n .filter(({ node }) => !getNodeKey(node, keyAttrs));\r\n\r\n diffChildrenByIndex(oldUnkeyed, newUnkeyed, parentPath, options, ops);\r\n return;\r\n }\r\n }\r\n\r\n diffChildrenByIndex(\r\n oldChildren.map((node, index) => ({ node, index })),\r\n newChildren.map((node, index) => ({ node, index })),\r\n parentPath,\r\n options,\r\n ops,\r\n );\r\n}\r\n\r\n/** Compare child arrays by their current sibling indexes. */\r\nfunction diffChildrenByIndex(\r\n oldChildren: Array<{ node: XmlNode; index: number }>,\r\n newChildren: Array<{ node: XmlNode; index: number }>,\r\n parentPath: string,\r\n options: XmlDiffOptions,\r\n ops: XmlDiffOp[],\r\n): void {\r\n const max = Math.max(oldChildren.length, newChildren.length);\r\n\r\n for (let index = 0; index < max; index += 1) {\r\n const oldItem = oldChildren[index];\r\n const newItem = newChildren[index];\r\n\r\n if (!oldItem && newItem) {\r\n const childPath = `${parentPath}/${formatPathSegment(newItem.node, newItem.index, options.keyAttrs)}`;\r\n ops.push({ op: 'addNode', path: childPath, value: newItem.node });\r\n continue;\r\n }\r\n\r\n if (oldItem && !newItem) {\r\n const childPath = `${parentPath}/${formatPathSegment(oldItem.node, oldItem.index, options.keyAttrs)}`;\r\n ops.push({ op: 'removeNode', path: childPath, oldValue: oldItem.node });\r\n continue;\r\n }\r\n\r\n if (oldItem && newItem) {\r\n const childPath = `${parentPath}/${formatPathSegment(oldItem.node, oldItem.index, options.keyAttrs)}`;\r\n diffNode(oldItem.node, newItem.node, childPath, options, ops);\r\n }\r\n }\r\n}\r\n","import type { SerializeOptions, XmlNode } from './types.js';\r\nimport { escapeXmlAttr, escapeXmlText } from './utils.js';\r\n\r\n/**\r\n * Serialize a normalized XML AST back to XML text.\r\n *\r\n * The serializer is intentionally small and deterministic. It does not attempt\r\n * to preserve the original lexical formatting of the input XML; instead, it\r\n * serializes the current AST state. Use `pretty` when human-readable nested\r\n * output is preferred.\r\n */\r\nexport function serializeXml(node: XmlNode, options: SerializeOptions = {}): string {\r\n return serializeNode(node, options, 0);\r\n}\r\n\r\n/** Serialize one node recursively. */\r\nfunction serializeNode(node: XmlNode, options: SerializeOptions, level: number): string {\r\n if (node.type === 'text') {\r\n return escapeXmlText(node.text);\r\n }\r\n\r\n if (node.type === 'comment') {\r\n return `<!--${node.text}-->`;\r\n }\r\n\r\n const attrs = Object.entries(node.attrs)\r\n .map(([name, value]) => ` ${name}=\"${escapeXmlAttr(value)}\"`)\r\n .join('');\r\n\r\n if (node.children.length === 0) {\r\n return `${indent(options, level)}<${node.name}${attrs}/>`;\r\n }\r\n\r\n // Keep text-only elements inline even in pretty mode so simple content like\r\n // `<title>Hello</title>` does not become unnecessarily noisy.\r\n if (!options.pretty || node.children.every((child) => child.type === 'text')) {\r\n const children = node.children.map((child) => serializeNode(child, options, level + 1)).join('');\r\n return `${indent(options, level)}<${node.name}${attrs}>${children}</${node.name}>`;\r\n }\r\n\r\n const children = node.children\r\n .map((child) => serializeNode(child, options, level + 1))\r\n .join('\\n');\r\n\r\n return `${indent(options, level)}<${node.name}${attrs}>\\n${children}\\n${indent(\r\n options,\r\n level,\r\n )}</${node.name}>`;\r\n}\r\n\r\n/** Return indentation for a nesting level when pretty output is enabled. */\r\nfunction indent(options: SerializeOptions, level: number): string {\r\n if (!options.pretty) return '';\r\n\r\n return (options.indent ?? ' ').repeat(level);\r\n}\r\n","import { serializeXml } from './serialize.js';\r\nimport type { DiffSummaryItem, FormatDiffOptions, XmlDiffOp, XmlNode } from './types.js';\r\n\r\nexport function formatDiff(ops: XmlDiffOp[], options: FormatDiffOptions & { format: 'markdown' }): string;\r\nexport function formatDiff(ops: XmlDiffOp[], options?: FormatDiffOptions): DiffSummaryItem[];\r\n/**\r\n * Format structured diff operations for humans.\r\n *\r\n * By default, this returns a typed summary array that is easy to consume in UI\r\n * code. With `{ format: 'markdown' }`, it returns a markdown report suitable\r\n * for logs, pull request comments, review notes, or exported change summaries.\r\n */\r\nexport function formatDiff(\r\n ops: XmlDiffOp[],\r\n options: FormatDiffOptions = {},\r\n): DiffSummaryItem[] | string {\r\n if (options.format === 'markdown') {\r\n return toMarkdown(ops);\r\n }\r\n\r\n return ops.map(toSummaryItem);\r\n}\r\n\r\n/** Build a full markdown report from all operations. */\r\nfunction toMarkdown(ops: XmlDiffOp[]): string {\r\n if (ops.length === 0) {\r\n return '# XML Diff\\n\\nNo XML differences.';\r\n }\r\n\r\n return [\r\n '# XML Diff',\r\n '',\r\n `Total changes: ${ops.length}`,\r\n '',\r\n ...ops.flatMap((op, index) => formatMarkdownOp(op, index + 1)),\r\n ].join('\\n');\r\n}\r\n\r\n/** Format one operation as a markdown section. */\r\nfunction formatMarkdownOp(op: XmlDiffOp, index: number): string[] {\r\n switch (op.op) {\r\n case 'addNode':\r\n return [\r\n `## ${index}. Added node`,\r\n '',\r\n `- Path: \\`${op.path}\\``,\r\n '',\r\n codeBlock('xml', serializeForMarkdown(op.value)),\r\n '',\r\n ];\r\n\r\n case 'removeNode':\r\n return [\r\n `## ${index}. Removed node`,\r\n '',\r\n `- Path: \\`${op.path}\\``,\r\n '',\r\n codeBlock('xml', serializeForMarkdown(op.oldValue)),\r\n '',\r\n ];\r\n\r\n case 'replaceNode':\r\n return [\r\n `## ${index}. Replaced node`,\r\n '',\r\n `- Path: \\`${op.path}\\``,\r\n '',\r\n '**Before**',\r\n '',\r\n codeBlock('xml', serializeForMarkdown(op.oldValue)),\r\n '',\r\n '**After**',\r\n '',\r\n codeBlock('xml', serializeForMarkdown(op.newValue)),\r\n '',\r\n ];\r\n\r\n case 'moveNode':\r\n return [\r\n `## ${index}. Moved node`,\r\n '',\r\n `- From: \\`${op.fromPath}\\``,\r\n `- To: \\`${op.toPath}\\``,\r\n '',\r\n codeBlock('xml', serializeForMarkdown(op.value)),\r\n '',\r\n ];\r\n\r\n case 'replaceText':\r\n return [\r\n `## ${index}. Changed text`,\r\n '',\r\n `- Path: \\`${op.path}\\``,\r\n '',\r\n '**Before**',\r\n '',\r\n codeBlock('text', op.oldValue),\r\n '',\r\n '**After**',\r\n '',\r\n codeBlock('text', op.newValue),\r\n '',\r\n '**Text segments**',\r\n '',\r\n ...op.segments.map((segment) => `- ${segment.type}: \\`${escapeInlineCode(segment.text)}\\``),\r\n '',\r\n ];\r\n\r\n case 'addAttr':\r\n return [\r\n `## ${index}. Added attribute`,\r\n '',\r\n `- Path: \\`${op.path}\\``,\r\n `- Name: \\`${op.name}\\``,\r\n `- Value: \\`${escapeInlineCode(op.value)}\\``,\r\n '',\r\n ];\r\n\r\n case 'updateAttr':\r\n return [\r\n `## ${index}. Updated attribute`,\r\n '',\r\n `- Path: \\`${op.path}\\``,\r\n `- Name: \\`${op.name}\\``,\r\n `- Before: \\`${escapeInlineCode(op.oldValue)}\\``,\r\n `- After: \\`${escapeInlineCode(op.newValue)}\\``,\r\n '',\r\n ];\r\n\r\n case 'removeAttr':\r\n return [\r\n `## ${index}. Removed attribute`,\r\n '',\r\n `- Path: \\`${op.path}\\``,\r\n `- Name: \\`${op.name}\\``,\r\n `- Old value: \\`${escapeInlineCode(op.oldValue)}\\``,\r\n '',\r\n ];\r\n }\r\n}\r\n\r\n/** Convert one operation into a typed summary item. */\r\nfunction toSummaryItem(op: XmlDiffOp): DiffSummaryItem {\r\n switch (op.op) {\r\n case 'addNode':\r\n return {\r\n type: 'nodeAdded',\r\n path: op.path,\r\n message: `Added node at ${op.path}`,\r\n after: op.value,\r\n };\r\n\r\n case 'removeNode':\r\n return {\r\n type: 'nodeRemoved',\r\n path: op.path,\r\n message: `Removed node at ${op.path}`,\r\n before: op.oldValue,\r\n };\r\n\r\n case 'replaceNode':\r\n return {\r\n type: 'nodeReplaced',\r\n path: op.path,\r\n message: `Replaced node at ${op.path}`,\r\n before: op.oldValue,\r\n after: op.newValue,\r\n };\r\n\r\n case 'moveNode':\r\n return {\r\n type: 'nodeMoved',\r\n path: op.path,\r\n message: `Moved node from ${op.fromPath} to ${op.toPath}`,\r\n before: op.fromPath,\r\n after: op.toPath,\r\n };\r\n\r\n case 'replaceText':\r\n return {\r\n type: 'textChanged',\r\n path: op.path,\r\n message: `Changed text at ${op.path}`,\r\n before: op.oldValue,\r\n after: op.newValue,\r\n };\r\n\r\n case 'addAttr':\r\n return {\r\n type: 'attrAdded',\r\n path: op.path,\r\n message: `Added attribute ${op.name} at ${op.path}`,\r\n after: op.value,\r\n };\r\n\r\n case 'updateAttr':\r\n return {\r\n type: 'attrUpdated',\r\n path: op.path,\r\n message: `Updated attribute ${op.name} at ${op.path}`,\r\n before: op.oldValue,\r\n after: op.newValue,\r\n };\r\n\r\n case 'removeAttr':\r\n return {\r\n type: 'attrRemoved',\r\n path: op.path,\r\n message: `Removed attribute ${op.name} at ${op.path}`,\r\n before: op.oldValue,\r\n };\r\n }\r\n}\r\n\r\n/** Serialize a node for markdown code blocks, using pretty XML for elements. */\r\nfunction serializeForMarkdown(node: XmlNode): string {\r\n if (node.type === 'text' || node.type === 'comment') {\r\n return serializeXml(node);\r\n }\r\n\r\n return serializeXml(node, {\r\n pretty: true,\r\n });\r\n}\r\n\r\n/** Create a fenced markdown code block and escape nested fences. */\r\nfunction codeBlock(language: string, value: string): string {\r\n return `\\`\\`\\`${language}\\n${escapeCodeFence(value)}\\n\\`\\`\\``;\r\n}\r\n\r\n/** Prevent embedded triple-backticks from breaking the markdown report. */\r\nfunction escapeCodeFence(value: string): string {\r\n return value.replaceAll('```', '`\\u200b``');\r\n}\r\n\r\n/** Escape backticks inside inline-code spans. */\r\nfunction escapeInlineCode(value: string): string {\r\n return value.replaceAll('`', '\\\\`');\r\n}\r\n","import { normalizeXml } from './normalize.js';\r\nimport { parseXml } from './parse.js';\r\nimport { serializeXml } from './serialize.js';\r\nimport type { SerializeOptions, XmlDiffOp, XmlNode } from './types.js';\r\nimport { cloneXmlNode } from './utils.js';\r\n\r\n/** Internal target descriptor used while applying path-based operations. */\r\ntype PatchTarget = {\r\n /** The node selected by the operation path. */\r\n node: XmlNode;\r\n\r\n /** Parent element for non-root targets. Root targets intentionally omit this. */\r\n parent?: Extract<XmlNode, { type: 'element' }>;\r\n\r\n /** Index of `node` inside `parent.children`; root targets use index 0. */\r\n index: number;\r\n};\r\n\r\nexport function patchXml(input: string, ops: XmlDiffOp[], options?: SerializeOptions): string;\r\nexport function patchXml(input: XmlNode, ops: XmlDiffOp[], options?: SerializeOptions): XmlNode;\r\n/**\r\n * Apply structured diff operations to an XML string or AST.\r\n *\r\n * When the input is a string, the result is serialized XML. When the input is an\r\n * `XmlNode`, the result is a patched `XmlNode`. The input is cloned before any\r\n * operation is applied, so callers do not need to worry about accidental\r\n * mutation of their original XML AST.\r\n *\r\n * Patch application uses the absolute path information stored in each operation.\r\n * The current path parser reads numeric `[index]` parts and ignores optional\r\n * key hints such as `[@id=\"s1\"]`; those hints are useful for readability and\r\n * alignment during diffing, while the numeric indexes remain the executable\r\n * addressing component for patching.\r\n */\r\nexport function patchXml(\r\n input: string | XmlNode,\r\n ops: XmlDiffOp[],\r\n options: SerializeOptions = {},\r\n): string | XmlNode {\r\n const root = cloneXmlNode(typeof input === 'string' ? parseXml(input) : input);\r\n\r\n for (const op of ops) {\r\n applyOp(root, op);\r\n }\r\n\r\n const normalized = normalizeXml(root, { sortAttributes: true });\r\n\r\n if (typeof input === 'string') {\r\n return serializeXml(normalized, options);\r\n }\r\n\r\n return normalized;\r\n}\r\n\r\n/** Apply one operation to the mutable cloned root node. */\r\nfunction applyOp(root: XmlNode, op: XmlDiffOp): void {\r\n if (op.op === 'replaceNode' && isRootPath(op.path)) {\r\n Object.assign(root, cloneXmlNode(op.newValue));\r\n return;\r\n }\r\n\r\n // The added node does not exist in the old tree, so resolving op.path first\r\n // would fail. Resolve the parent path instead, then insert at the final index.\r\n if (op.op === 'addNode') {\r\n const parentPath = getParentPath(op.path);\r\n const parent = getTarget(root, parentPath).node;\r\n assertElement(parent, parentPath);\r\n const insertIndex = getLastIndex(op.path);\r\n parent.children.splice(insertIndex, 0, cloneXmlNode(op.value));\r\n return;\r\n }\r\n\r\n if (op.op === 'moveNode') {\r\n moveNode(root, op.fromPath, op.toPath);\r\n return;\r\n }\r\n\r\n const target = getTarget(root, op.path);\r\n\r\n switch (op.op) {\r\n case 'addAttr':\r\n assertElement(target.node, op.path);\r\n target.node.attrs[op.name] = op.value;\r\n break;\r\n\r\n case 'updateAttr':\r\n assertElement(target.node, op.path);\r\n target.node.attrs[op.name] = op.newValue;\r\n break;\r\n\r\n case 'removeAttr':\r\n assertElement(target.node, op.path);\r\n delete target.node.attrs[op.name];\r\n break;\r\n\r\n case 'replaceText':\r\n if (target.node.type !== 'text') {\r\n throw new Error(`Cannot replace text at non-text path: ${op.path}`);\r\n }\r\n target.node.text = op.newValue;\r\n break;\r\n\r\n case 'replaceNode':\r\n if (!target.parent) throw new Error('Cannot replace root node without root replace path.');\r\n target.parent.children[target.index] = cloneXmlNode(op.newValue);\r\n break;\r\n\r\n case 'removeNode':\r\n if (!target.parent) throw new Error('Cannot remove root node.');\r\n target.parent.children.splice(target.index, 1);\r\n break;\r\n }\r\n}\r\n\r\n/** Move an existing node from one absolute path to another. */\r\nfunction moveNode(root: XmlNode, fromPath: string, toPath: string): void {\r\n const source = getTarget(root, fromPath);\r\n\r\n if (!source.parent) {\r\n throw new Error('Cannot move root node.');\r\n }\r\n\r\n const [removed] = source.parent.children.splice(source.index, 1);\r\n if (!removed) {\r\n throw new Error(`Path not found: ${fromPath}`);\r\n }\r\n\r\n const parentPath = getParentPath(toPath);\r\n const targetParent = getTarget(root, parentPath).node;\r\n assertElement(targetParent, parentPath);\r\n\r\n const targetIndex = getLastIndex(toPath);\r\n targetParent.children.splice(targetIndex, 0, removed);\r\n}\r\n\r\n/** Resolve an absolute XML path to a node and, when applicable, its parent. */\r\nfunction getTarget(root: XmlNode, path: string): PatchTarget {\r\n const indexes = getPathIndexes(path);\r\n\r\n if (indexes.length === 0) {\r\n return {\r\n node: root,\r\n index: 0,\r\n };\r\n }\r\n\r\n let current = root;\r\n let parent: Extract<XmlNode, { type: 'element' }> | undefined;\r\n let currentIndex = 0;\r\n\r\n // The first numeric index belongs to the root path segment. Since `root` is\r\n // already selected, traversal starts from the second index.\r\n for (const index of indexes.slice(1)) {\r\n assertElement(current, path);\r\n parent = current;\r\n currentIndex = index;\r\n\r\n const child = current.children[index];\r\n if (!child) {\r\n throw new Error(`Path not found: ${path}`);\r\n }\r\n\r\n current = child;\r\n }\r\n\r\n const result: PatchTarget = {\r\n node: current,\r\n index: currentIndex,\r\n };\r\n\r\n if (parent) {\r\n result.parent = parent;\r\n }\r\n\r\n return result;\r\n}\r\n\r\n/** Extract all numeric `[index]` parts from a path. */\r\nfunction getPathIndexes(path: string): number[] {\r\n const matches = path.match(/\\[(\\d+)\\]/g) ?? [];\r\n return matches.map((match) => Number.parseInt(match.slice(1, -1), 10));\r\n}\r\n\r\n/** Return the final numeric path index, usually the insertion or target index. */\r\nfunction getLastIndex(path: string): number {\r\n const indexes = getPathIndexes(path);\r\n const index = indexes.at(-1);\r\n\r\n if (index === undefined) {\r\n throw new Error(`Path has no numeric index: ${path}`);\r\n }\r\n\r\n return index;\r\n}\r\n\r\n/** Return the absolute parent path for a node path. */\r\nfunction getParentPath(path: string): string {\r\n const parts = path.split('/').filter(Boolean);\r\n\r\n if (parts.length <= 1) {\r\n return path;\r\n }\r\n\r\n return `/${parts.slice(0, -1).join('/')}`;\r\n}\r\n\r\n/** A root path has exactly one path segment, for example `/root[0]`. */\r\nfunction isRootPath(path: string): boolean {\r\n return path.split('/').filter(Boolean).length === 1;\r\n}\r\n\r\n/** Type guard used before mutating element-only fields such as attrs/children. */\r\nfunction assertElement(node: XmlNode, path: string): asserts node is Extract<XmlNode, { type: 'element' }> {\r\n if (node.type !== 'element') {\r\n throw new Error(`Expected element node at path: ${path}`);\r\n }\r\n}\r\n"],"mappings":"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","names":["oldIndex","newIndex","children"]}
@@ -0,0 +1,267 @@
1
+ /**
2
+ * A normalized XML node used by xml-diff-kit.
3
+ *
4
+ * The library intentionally uses a small custom AST instead of exposing DOM nodes.
5
+ * This keeps diff operations serializable, easy to store, and usable in both
6
+ * Node.js and browser runtimes.
7
+ */
8
+ type XmlNode = XmlElementNode | XmlTextNode | XmlCommentNode;
9
+ /**
10
+ * Normalized XML element node.
11
+ *
12
+ * `name` currently preserves the parsed element name, including any prefix
13
+ * exposed by the XML parser. `namespaceURI` is normalized to `null` when the
14
+ * parser does not provide a namespace URI, which makes equality checks stable.
15
+ */
16
+ interface XmlElementNode {
17
+ /** Discriminator used by TypeScript union narrowing. */
18
+ type: 'element';
19
+ /** Element name as parsed from XML, for example `procedure`, `step`, or `rdf:Description`. */
20
+ name: string;
21
+ /** Namespace URI when available; `null` when the element has no namespace. */
22
+ namespaceURI?: string | null;
23
+ /** Attribute map. Attribute order is not represented semantically. */
24
+ attrs: Record<string, string>;
25
+ /** Child nodes in document order. XML element order is treated as significant by default. */
26
+ children: XmlNode[];
27
+ }
28
+ /** Normalized XML text node. */
29
+ interface XmlTextNode {
30
+ /** Discriminator used by TypeScript union narrowing. */
31
+ type: 'text';
32
+ /** Raw text content after optional normalization. */
33
+ text: string;
34
+ }
35
+ /** Normalized XML comment node. */
36
+ interface XmlCommentNode {
37
+ /** Discriminator used by TypeScript union narrowing. */
38
+ type: 'comment';
39
+ /** Comment body without `<!--` and `-->`. */
40
+ text: string;
41
+ }
42
+ /** Options shared by parsing-normalization and diffing. */
43
+ interface XmlDiffOptions {
44
+ /** Drop text nodes that contain only whitespace after trimming. */
45
+ ignoreWhitespaceText?: boolean;
46
+ /** Trim leading and trailing whitespace from all text nodes before diffing. */
47
+ trimText?: boolean;
48
+ /** Drop XML comment nodes before diffing. */
49
+ ignoreComments?: boolean;
50
+ /** Sort attributes by name during normalization for deterministic output. */
51
+ sortAttributes?: boolean;
52
+ /**
53
+ * Attribute names used to align sibling elements semantically.
54
+ *
55
+ * Without `keyAttrs`, child nodes are compared by index. With `keyAttrs`,
56
+ * elements such as `<step id="s1"/>` can be matched by `id` even if their
57
+ * sibling position changes.
58
+ */
59
+ keyAttrs?: string[];
60
+ /**
61
+ * Detect keyed sibling reorders as `moveNode` operations.
62
+ *
63
+ * This is opt-in because the current patch algorithm is conservative and the
64
+ * safest default for early versions is to avoid changing node order unless the
65
+ * caller explicitly asks for move detection.
66
+ */
67
+ detectMoves?: boolean;
68
+ }
69
+ /** Options used when serializing the normalized XML AST back to XML text. */
70
+ interface SerializeOptions {
71
+ /** Format nested element children across multiple lines. */
72
+ pretty?: boolean;
73
+ /** Indentation string used when `pretty` is enabled. Defaults to two spaces. */
74
+ indent?: string;
75
+ }
76
+ /**
77
+ * Fine-grained text patch inside a `replaceText` XML operation.
78
+ *
79
+ * Offsets are based on the old text value and measured by Unicode code points
80
+ * through `Array.from`, not by UTF-16 code units. This makes offsets safer for
81
+ * non-BMP characters such as emoji.
82
+ */
83
+ type TextChange = {
84
+ /** Insert text at `offset` in the old text. */
85
+ op: 'insertText';
86
+ offset: number;
87
+ text: string;
88
+ } | {
89
+ /** Delete `oldText` starting at `offset` in the old text. */
90
+ op: 'deleteText';
91
+ offset: number;
92
+ oldText: string;
93
+ } | {
94
+ /** Replace `oldText` with `newText` starting at `offset` in the old text. */
95
+ op: 'replaceTextRange';
96
+ offset: number;
97
+ oldText: string;
98
+ newText: string;
99
+ };
100
+ /**
101
+ * Display-oriented text diff segment.
102
+ *
103
+ * Segments are useful for rendering review UIs or markdown reports. They are
104
+ * less convenient than `TextChange` for applying patches because they do not
105
+ * carry explicit offsets.
106
+ */
107
+ type TextDiffSegment = {
108
+ type: 'equal';
109
+ text: string;
110
+ } | {
111
+ type: 'insert';
112
+ text: string;
113
+ } | {
114
+ type: 'delete';
115
+ text: string;
116
+ };
117
+ /**
118
+ * Structured XML diff operation.
119
+ *
120
+ * All `path`, `fromPath`, and `toPath` values are absolute paths from the XML
121
+ * root node, for example `/procedure[0]/step[@id="s1"][0]/text()[0]`.
122
+ */
123
+ type XmlDiffOp = {
124
+ /** Add a node at `path`. */
125
+ op: 'addNode';
126
+ path: string;
127
+ value: XmlNode;
128
+ } | {
129
+ /** Remove the node at `path`. */
130
+ op: 'removeNode';
131
+ path: string;
132
+ oldValue: XmlNode;
133
+ } | {
134
+ /** Replace the entire node at `path`. */
135
+ op: 'replaceNode';
136
+ path: string;
137
+ oldValue: XmlNode;
138
+ newValue: XmlNode;
139
+ } | {
140
+ /** Move a node from `fromPath` to `toPath`. */
141
+ op: 'moveNode';
142
+ path: string;
143
+ fromPath: string;
144
+ toPath: string;
145
+ value: XmlNode;
146
+ } | {
147
+ /** Replace a text node value, with optional fine-grained text details. */
148
+ op: 'replaceText';
149
+ path: string;
150
+ oldValue: string;
151
+ newValue: string;
152
+ changes: TextChange[];
153
+ segments: TextDiffSegment[];
154
+ } | {
155
+ /** Add an attribute to the element at `path`. */
156
+ op: 'addAttr';
157
+ path: string;
158
+ name: string;
159
+ value: string;
160
+ } | {
161
+ /** Update an existing attribute on the element at `path`. */
162
+ op: 'updateAttr';
163
+ path: string;
164
+ name: string;
165
+ oldValue: string;
166
+ newValue: string;
167
+ } | {
168
+ /** Remove an attribute from the element at `path`. */
169
+ op: 'removeAttr';
170
+ path: string;
171
+ name: string;
172
+ oldValue: string;
173
+ };
174
+ /** Options for converting structured diff operations into human-readable output. */
175
+ interface FormatDiffOptions {
176
+ /** `summary` returns objects; `markdown` returns a markdown report string. */
177
+ format?: 'summary' | 'markdown';
178
+ }
179
+ /** Human-readable summary item generated from an `XmlDiffOp`. */
180
+ interface DiffSummaryItem {
181
+ /** High-level operation category suitable for UI filtering. */
182
+ type: 'nodeAdded' | 'nodeRemoved' | 'nodeReplaced' | 'nodeMoved' | 'textChanged' | 'attrAdded' | 'attrUpdated' | 'attrRemoved';
183
+ /** Absolute XML path associated with the change. */
184
+ path: string;
185
+ /** Short human-readable description. */
186
+ message: string;
187
+ /** Optional old value, path, or node, depending on operation type. */
188
+ before?: unknown;
189
+ /** Optional new value, path, or node, depending on operation type. */
190
+ after?: unknown;
191
+ }
192
+
193
+ /**
194
+ * Compare two XML inputs and return structured, machine-readable operations.
195
+ *
196
+ * Inputs can be raw XML strings or pre-parsed `XmlNode` ASTs. Both sides are
197
+ * normalized before comparison, so options such as `ignoreWhitespaceText` and
198
+ * `ignoreComments` are applied consistently to old and new documents.
199
+ *
200
+ * The returned operations use absolute paths from the XML root node. The path
201
+ * format is intentionally deterministic and readable, for example:
202
+ * `/procedure[0]/step[@id="s1"][0]/text()[0]`.
203
+ */
204
+ declare function diffXml(oldInput: string | XmlNode, newInput: string | XmlNode, options?: XmlDiffOptions): XmlDiffOp[];
205
+
206
+ declare function formatDiff(ops: XmlDiffOp[], options: FormatDiffOptions & {
207
+ format: 'markdown';
208
+ }): string;
209
+ declare function formatDiff(ops: XmlDiffOp[], options?: FormatDiffOptions): DiffSummaryItem[];
210
+
211
+ /**
212
+ * Normalize an XML AST before diffing or patching.
213
+ *
214
+ * Normalization is where callers define which XML differences are meaningful.
215
+ * For example, formatted indentation can be ignored with `ignoreWhitespaceText`,
216
+ * comments can be ignored with `ignoreComments`, and attribute output can be
217
+ * made deterministic with `sortAttributes`.
218
+ *
219
+ * The input node is cloned first. Callers can safely reuse their original AST
220
+ * after normalization without worrying about mutation.
221
+ */
222
+ declare function normalizeXml(node: XmlNode, options?: XmlDiffOptions): XmlNode;
223
+
224
+ /**
225
+ * Parse an XML string into the library's normalized XML AST.
226
+ *
227
+ * This function is intentionally stricter than the default `@xmldom/xmldom`
228
+ * behavior: parser warnings are treated as invalid XML. This is important for
229
+ * a diff/patch library because silently recovering malformed XML can produce
230
+ * surprising paths and operations.
231
+ *
232
+ * Only element, text, and comment nodes are currently preserved. Processing
233
+ * instructions, doctypes, and other DOM node types are ignored during child
234
+ * traversal until the public AST grows explicit representations for them.
235
+ */
236
+ declare function parseXml(xml: string): XmlNode;
237
+
238
+ declare function patchXml(input: string, ops: XmlDiffOp[], options?: SerializeOptions): string;
239
+ declare function patchXml(input: XmlNode, ops: XmlDiffOp[], options?: SerializeOptions): XmlNode;
240
+
241
+ /**
242
+ * Serialize a normalized XML AST back to XML text.
243
+ *
244
+ * The serializer is intentionally small and deterministic. It does not attempt
245
+ * to preserve the original lexical formatting of the input XML; instead, it
246
+ * serializes the current AST state. Use `pretty` when human-readable nested
247
+ * output is preferred.
248
+ */
249
+ declare function serializeXml(node: XmlNode, options?: SerializeOptions): string;
250
+
251
+ /**
252
+ * Diff two text values and return both patch-oriented and display-oriented data.
253
+ *
254
+ * `changes` is suitable for patch/review workflows because each item includes
255
+ * an offset in the old text. `segments` is suitable for UI rendering because it
256
+ * preserves a readable sequence of equal/insert/delete chunks.
257
+ *
258
+ * The implementation works on Unicode code points via `Array.from`, so offsets
259
+ * are more predictable for emoji and other non-BMP characters than raw
260
+ * JavaScript UTF-16 indexes.
261
+ */
262
+ declare function diffText(oldValue: string, newValue: string): {
263
+ changes: TextChange[];
264
+ segments: TextDiffSegment[];
265
+ };
266
+
267
+ export { type DiffSummaryItem, type FormatDiffOptions, type SerializeOptions, type TextChange, type TextDiffSegment, type XmlCommentNode, type XmlDiffOp, type XmlDiffOptions, type XmlElementNode, type XmlNode, type XmlTextNode, diffText, diffXml, formatDiff, normalizeXml, parseXml, patchXml, serializeXml };
@@ -0,0 +1,267 @@
1
+ /**
2
+ * A normalized XML node used by xml-diff-kit.
3
+ *
4
+ * The library intentionally uses a small custom AST instead of exposing DOM nodes.
5
+ * This keeps diff operations serializable, easy to store, and usable in both
6
+ * Node.js and browser runtimes.
7
+ */
8
+ type XmlNode = XmlElementNode | XmlTextNode | XmlCommentNode;
9
+ /**
10
+ * Normalized XML element node.
11
+ *
12
+ * `name` currently preserves the parsed element name, including any prefix
13
+ * exposed by the XML parser. `namespaceURI` is normalized to `null` when the
14
+ * parser does not provide a namespace URI, which makes equality checks stable.
15
+ */
16
+ interface XmlElementNode {
17
+ /** Discriminator used by TypeScript union narrowing. */
18
+ type: 'element';
19
+ /** Element name as parsed from XML, for example `procedure`, `step`, or `rdf:Description`. */
20
+ name: string;
21
+ /** Namespace URI when available; `null` when the element has no namespace. */
22
+ namespaceURI?: string | null;
23
+ /** Attribute map. Attribute order is not represented semantically. */
24
+ attrs: Record<string, string>;
25
+ /** Child nodes in document order. XML element order is treated as significant by default. */
26
+ children: XmlNode[];
27
+ }
28
+ /** Normalized XML text node. */
29
+ interface XmlTextNode {
30
+ /** Discriminator used by TypeScript union narrowing. */
31
+ type: 'text';
32
+ /** Raw text content after optional normalization. */
33
+ text: string;
34
+ }
35
+ /** Normalized XML comment node. */
36
+ interface XmlCommentNode {
37
+ /** Discriminator used by TypeScript union narrowing. */
38
+ type: 'comment';
39
+ /** Comment body without `<!--` and `-->`. */
40
+ text: string;
41
+ }
42
+ /** Options shared by parsing-normalization and diffing. */
43
+ interface XmlDiffOptions {
44
+ /** Drop text nodes that contain only whitespace after trimming. */
45
+ ignoreWhitespaceText?: boolean;
46
+ /** Trim leading and trailing whitespace from all text nodes before diffing. */
47
+ trimText?: boolean;
48
+ /** Drop XML comment nodes before diffing. */
49
+ ignoreComments?: boolean;
50
+ /** Sort attributes by name during normalization for deterministic output. */
51
+ sortAttributes?: boolean;
52
+ /**
53
+ * Attribute names used to align sibling elements semantically.
54
+ *
55
+ * Without `keyAttrs`, child nodes are compared by index. With `keyAttrs`,
56
+ * elements such as `<step id="s1"/>` can be matched by `id` even if their
57
+ * sibling position changes.
58
+ */
59
+ keyAttrs?: string[];
60
+ /**
61
+ * Detect keyed sibling reorders as `moveNode` operations.
62
+ *
63
+ * This is opt-in because the current patch algorithm is conservative and the
64
+ * safest default for early versions is to avoid changing node order unless the
65
+ * caller explicitly asks for move detection.
66
+ */
67
+ detectMoves?: boolean;
68
+ }
69
+ /** Options used when serializing the normalized XML AST back to XML text. */
70
+ interface SerializeOptions {
71
+ /** Format nested element children across multiple lines. */
72
+ pretty?: boolean;
73
+ /** Indentation string used when `pretty` is enabled. Defaults to two spaces. */
74
+ indent?: string;
75
+ }
76
+ /**
77
+ * Fine-grained text patch inside a `replaceText` XML operation.
78
+ *
79
+ * Offsets are based on the old text value and measured by Unicode code points
80
+ * through `Array.from`, not by UTF-16 code units. This makes offsets safer for
81
+ * non-BMP characters such as emoji.
82
+ */
83
+ type TextChange = {
84
+ /** Insert text at `offset` in the old text. */
85
+ op: 'insertText';
86
+ offset: number;
87
+ text: string;
88
+ } | {
89
+ /** Delete `oldText` starting at `offset` in the old text. */
90
+ op: 'deleteText';
91
+ offset: number;
92
+ oldText: string;
93
+ } | {
94
+ /** Replace `oldText` with `newText` starting at `offset` in the old text. */
95
+ op: 'replaceTextRange';
96
+ offset: number;
97
+ oldText: string;
98
+ newText: string;
99
+ };
100
+ /**
101
+ * Display-oriented text diff segment.
102
+ *
103
+ * Segments are useful for rendering review UIs or markdown reports. They are
104
+ * less convenient than `TextChange` for applying patches because they do not
105
+ * carry explicit offsets.
106
+ */
107
+ type TextDiffSegment = {
108
+ type: 'equal';
109
+ text: string;
110
+ } | {
111
+ type: 'insert';
112
+ text: string;
113
+ } | {
114
+ type: 'delete';
115
+ text: string;
116
+ };
117
+ /**
118
+ * Structured XML diff operation.
119
+ *
120
+ * All `path`, `fromPath`, and `toPath` values are absolute paths from the XML
121
+ * root node, for example `/procedure[0]/step[@id="s1"][0]/text()[0]`.
122
+ */
123
+ type XmlDiffOp = {
124
+ /** Add a node at `path`. */
125
+ op: 'addNode';
126
+ path: string;
127
+ value: XmlNode;
128
+ } | {
129
+ /** Remove the node at `path`. */
130
+ op: 'removeNode';
131
+ path: string;
132
+ oldValue: XmlNode;
133
+ } | {
134
+ /** Replace the entire node at `path`. */
135
+ op: 'replaceNode';
136
+ path: string;
137
+ oldValue: XmlNode;
138
+ newValue: XmlNode;
139
+ } | {
140
+ /** Move a node from `fromPath` to `toPath`. */
141
+ op: 'moveNode';
142
+ path: string;
143
+ fromPath: string;
144
+ toPath: string;
145
+ value: XmlNode;
146
+ } | {
147
+ /** Replace a text node value, with optional fine-grained text details. */
148
+ op: 'replaceText';
149
+ path: string;
150
+ oldValue: string;
151
+ newValue: string;
152
+ changes: TextChange[];
153
+ segments: TextDiffSegment[];
154
+ } | {
155
+ /** Add an attribute to the element at `path`. */
156
+ op: 'addAttr';
157
+ path: string;
158
+ name: string;
159
+ value: string;
160
+ } | {
161
+ /** Update an existing attribute on the element at `path`. */
162
+ op: 'updateAttr';
163
+ path: string;
164
+ name: string;
165
+ oldValue: string;
166
+ newValue: string;
167
+ } | {
168
+ /** Remove an attribute from the element at `path`. */
169
+ op: 'removeAttr';
170
+ path: string;
171
+ name: string;
172
+ oldValue: string;
173
+ };
174
+ /** Options for converting structured diff operations into human-readable output. */
175
+ interface FormatDiffOptions {
176
+ /** `summary` returns objects; `markdown` returns a markdown report string. */
177
+ format?: 'summary' | 'markdown';
178
+ }
179
+ /** Human-readable summary item generated from an `XmlDiffOp`. */
180
+ interface DiffSummaryItem {
181
+ /** High-level operation category suitable for UI filtering. */
182
+ type: 'nodeAdded' | 'nodeRemoved' | 'nodeReplaced' | 'nodeMoved' | 'textChanged' | 'attrAdded' | 'attrUpdated' | 'attrRemoved';
183
+ /** Absolute XML path associated with the change. */
184
+ path: string;
185
+ /** Short human-readable description. */
186
+ message: string;
187
+ /** Optional old value, path, or node, depending on operation type. */
188
+ before?: unknown;
189
+ /** Optional new value, path, or node, depending on operation type. */
190
+ after?: unknown;
191
+ }
192
+
193
+ /**
194
+ * Compare two XML inputs and return structured, machine-readable operations.
195
+ *
196
+ * Inputs can be raw XML strings or pre-parsed `XmlNode` ASTs. Both sides are
197
+ * normalized before comparison, so options such as `ignoreWhitespaceText` and
198
+ * `ignoreComments` are applied consistently to old and new documents.
199
+ *
200
+ * The returned operations use absolute paths from the XML root node. The path
201
+ * format is intentionally deterministic and readable, for example:
202
+ * `/procedure[0]/step[@id="s1"][0]/text()[0]`.
203
+ */
204
+ declare function diffXml(oldInput: string | XmlNode, newInput: string | XmlNode, options?: XmlDiffOptions): XmlDiffOp[];
205
+
206
+ declare function formatDiff(ops: XmlDiffOp[], options: FormatDiffOptions & {
207
+ format: 'markdown';
208
+ }): string;
209
+ declare function formatDiff(ops: XmlDiffOp[], options?: FormatDiffOptions): DiffSummaryItem[];
210
+
211
+ /**
212
+ * Normalize an XML AST before diffing or patching.
213
+ *
214
+ * Normalization is where callers define which XML differences are meaningful.
215
+ * For example, formatted indentation can be ignored with `ignoreWhitespaceText`,
216
+ * comments can be ignored with `ignoreComments`, and attribute output can be
217
+ * made deterministic with `sortAttributes`.
218
+ *
219
+ * The input node is cloned first. Callers can safely reuse their original AST
220
+ * after normalization without worrying about mutation.
221
+ */
222
+ declare function normalizeXml(node: XmlNode, options?: XmlDiffOptions): XmlNode;
223
+
224
+ /**
225
+ * Parse an XML string into the library's normalized XML AST.
226
+ *
227
+ * This function is intentionally stricter than the default `@xmldom/xmldom`
228
+ * behavior: parser warnings are treated as invalid XML. This is important for
229
+ * a diff/patch library because silently recovering malformed XML can produce
230
+ * surprising paths and operations.
231
+ *
232
+ * Only element, text, and comment nodes are currently preserved. Processing
233
+ * instructions, doctypes, and other DOM node types are ignored during child
234
+ * traversal until the public AST grows explicit representations for them.
235
+ */
236
+ declare function parseXml(xml: string): XmlNode;
237
+
238
+ declare function patchXml(input: string, ops: XmlDiffOp[], options?: SerializeOptions): string;
239
+ declare function patchXml(input: XmlNode, ops: XmlDiffOp[], options?: SerializeOptions): XmlNode;
240
+
241
+ /**
242
+ * Serialize a normalized XML AST back to XML text.
243
+ *
244
+ * The serializer is intentionally small and deterministic. It does not attempt
245
+ * to preserve the original lexical formatting of the input XML; instead, it
246
+ * serializes the current AST state. Use `pretty` when human-readable nested
247
+ * output is preferred.
248
+ */
249
+ declare function serializeXml(node: XmlNode, options?: SerializeOptions): string;
250
+
251
+ /**
252
+ * Diff two text values and return both patch-oriented and display-oriented data.
253
+ *
254
+ * `changes` is suitable for patch/review workflows because each item includes
255
+ * an offset in the old text. `segments` is suitable for UI rendering because it
256
+ * preserves a readable sequence of equal/insert/delete chunks.
257
+ *
258
+ * The implementation works on Unicode code points via `Array.from`, so offsets
259
+ * are more predictable for emoji and other non-BMP characters than raw
260
+ * JavaScript UTF-16 indexes.
261
+ */
262
+ declare function diffText(oldValue: string, newValue: string): {
263
+ changes: TextChange[];
264
+ segments: TextDiffSegment[];
265
+ };
266
+
267
+ export { type DiffSummaryItem, type FormatDiffOptions, type SerializeOptions, type TextChange, type TextDiffSegment, type XmlCommentNode, type XmlDiffOp, type XmlDiffOptions, type XmlElementNode, type XmlNode, type XmlTextNode, diffText, diffXml, formatDiff, normalizeXml, parseXml, patchXml, serializeXml };