legal-markdown-js
Version:
Node.js implementation of LegalMarkdown for processing legal documents with markdown and YAML - Complete feature parity with Ruby version
194 lines • 7.06 kB
TypeScript
/**
* Phase 2: String-Level Transformations
*
* This module contains transformations that MUST run on raw string content
* BEFORE remark AST parsing. These transformations cannot work as remark plugins
* because the AST fragments multi-line patterns across multiple nodes.
*
* Why String Transformations Exist:
* ─────────────────────────────────────
* When remark parses markdown into an AST, it splits content into nodes (paragraphs,
* text, strong, emphasis, etc.). Multi-line patterns get fragmented across these nodes,
* making it impossible for AST plugins to match complete patterns.
*
* Example: Multi-line Optional Clause
* ```markdown
* [l. **Warranties**
*
* The seller provides warranties.]{includeWarranties}
* ```
*
* As String (BEFORE remark):
* ✅ Regex can match: /\[(.*?)\]\{(.*?)\}/s
*
* As AST (AFTER remark parsing):
* ❌ Plugin cannot see complete pattern - it's fragmented across:
* - Paragraph with Text "[", Text "l. ", Strong "Warranties", Text "]{", ...
*
* Transformation Order:
* ────────────────────
* 0. Collect escaped template literals: \{{...}} → __LMESC_N__ placeholder
* (Must happen before everything - prevents \{{}} from being processed)
*
* 1. Normalize field patterns: |field| → {{field}}
* (Must happen before Handlebars compilation)
*
* 2. Process optional clauses: [content]{condition}
* (Multi-line content with markdown formatting)
*
* 3. Process template loops: {{#each}}, {{#if}}, etc.
* (Handlebars blocks that span multiple lines)
*
* @module
* @see docs/architecture/string-transformations.md
* @see Issue #149 - https://github.com/petalo/legal-markdown-js/issues/149
*/
import type { YamlValue } from '../../types/index.js';
/**
* Options for string-level transformations
*/
interface StringTransformationOptions {
/** Document metadata for condition evaluation and variable expansion */
metadata: Record<string, YamlValue>;
/** Enable debug logging */
debug?: boolean;
/** Enable field tracking (passed to template loops) */
enableFieldTracking?: boolean;
/** Use AST-first tracking tokens instead of direct spans in Phase 2 */
astFieldTracking?: boolean;
/** Highlight winner branches for conditional logic */
logicBranchHighlighting?: boolean;
/** Disable optional clause processing */
noClauses?: boolean;
/** Custom field patterns to normalize (e.g., ['<<(.+?)>>', '|(.+?)|']) */
fieldPatterns?: string[];
}
/**
* Result of string transformations
*/
interface StringTransformationResult {
/** Transformed content ready for remark AST parsing */
content: string;
/** Updated metadata (includes field mappings and other tracked data) */
metadata: Record<string, YamlValue>;
/**
* Literal template strings collected from \{{...}} escape sequences.
* Index N corresponds to placeholder `${prefix}${N}__` in content.
* Must be restored after remark processing.
*/
escapedTemplates: string[];
}
/**
* Apply all string-level transformations in the correct order
*
* This is the main entry point for Phase 2 string transformations.
* Transformations are applied in a specific order to ensure dependencies:
*
* 1. Field pattern normalization (|field| → {{field}})
* 2. Optional clauses processing ([content]{condition})
* 3. Template loops ({{#each}}, {{#if}})
*
* @param content - Raw markdown content (without YAML frontmatter)
* @param options - Transformation options
* @returns Transformed content and updated metadata
*
* @example
* ```typescript
* const result = await applyStringTransformations(
* content,
* {
* metadata: { items: [...], includeWarranty: true },
* debug: true,
* enableFieldTracking: true
* }
* );
*
* // result.content is ready for remark AST parsing
* // result.metadata includes field mappings
* ```
*/
export declare function applyStringTransformations(content: string, options: StringTransformationOptions): Promise<StringTransformationResult>;
/**
* Normalize custom field patterns to standard {{field}} format
*
* This function converts custom field patterns (e.g., <<field>>, |field|)
* into the standard {{field}} format so all fields use consistent syntax
* before Handlebars compilation.
*
* @param content - The content to normalize
* @param fieldPatterns - Array of regex patterns to normalize (e.g., ['<<(.+?)>>', '|(.+?)|'])
* @param debug - Enable debug logging
* @returns Normalized content and field mappings
*
* @example
* ```typescript
* const { content, mappings } = normalizeFieldPatterns(
* "Template: {{name1}} and custom: <<name2>>",
* ['<<(.+?)>>'],
* true
* );
* // content: "Template: {{name1}} and custom: {{name2}}"
* // mappings: Map { "{{name2}}" => "<<name2>>" }
* ```
*/
declare function normalizeFieldPatterns(content: string, fieldPatterns?: string[], debug?: boolean): {
content: string;
mappings: Map<string, string>;
};
/**
* Pre-process optional clauses [content]{condition} before remark parsing
*
* This function processes optional clauses BEFORE the content is parsed by remark,
* which allows clauses with multi-line content and markdown formatting to work correctly.
* Without this pre-processing, remark would split the clause across multiple AST nodes,
* making it impossible for an AST plugin to find and process them.
*
* Why This Cannot Be a Remark Plugin:
* ───────────────────────────────────
* Multi-line optional clauses with markdown formatting get fragmented in the AST:
*
* Input String:
* ```
* [l. **Warranties**
*
* The seller provides warranties.]{includeWarranties}
* ```
*
* After Remark Parsing (AST):
* ```
* Paragraph {
* children: [
* Text("["),
* Text("l. "),
* Strong("Warranties"),
* Text("]{"),
* Text("includeWarranties"),
* Text("}")
* ]
* }
* Paragraph {
* children: [
* Text("The seller provides warranties.")
* ]
* }
* ```
*
* The pattern is split across multiple nodes and paragraphs, making it impossible
* for a plugin to match the complete pattern.
*
* @param content - The markdown content with optional clauses
* @param metadata - Document metadata for evaluating conditions
* @param debug - Enable debug logging
* @returns Content with optional clauses processed
*
* @example
* ```typescript
* const content = "[Optional content]{showThis}";
* const metadata = { showThis: true };
* const result = preprocessOptionalClauses(content, metadata);
* // result: "Optional content"
* ```
*/
declare function preprocessOptionalClauses(content: string, metadata: Record<string, YamlValue>, debug?: boolean): string;
export { normalizeFieldPatterns as _normalizeFieldPatterns, preprocessOptionalClauses as _preprocessOptionalClauses, };
//# sourceMappingURL=string-transformations.d.ts.map