@mitre/hdf-utilities 3.1.0 → 3.2.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/csv/index.ts"],"mappings":";;;;;;UAiBiB,eAAA;EACf,MAAA;EACA,cAAA;EACA,eAAA,IAAmB,MAAA,UAAgB,KAAA;EACnC,aAAA;EACA,SAAA;EACA,OAAA;AAAA;;;;UAMe,eAAA;EACf,MAAA;EACA,OAAA;EACA,cAAA;EACA,SAAA;AAAA;AAwEF;;;;;;;AAAA,iBAAgB,QAAA,KAAa,MAAA,kBAAA,CAC3B,GAAA,UACA,OAAA,GAAU,OAAA,CAAQ,eAAA,IACjB,CAAA;;;;;;;;iBAoDa,aAAA,CACd,GAAA,UACA,OAAA,GAAU,OAAA,CAAQ,eAAA;;;AAFpB;;;;;;;;iBAmCgB,QAAA,KAAa,MAAA,kBAAA,CAC3B,IAAA,EAAM,CAAA,IACN,OAAA,GAAU,OAAA,CAAQ,eAAA;EAAqB,QAAA;AAAA;;;;;;;;;;;iBA2CzB,aAAA,CACd,IAAA,cACA,OAAA,GAAU,OAAA,CAAQ,eAAA;EAAqB,QAAA;AAAA;;;;;;iBA2BzB,UAAA,CAAW,GAAA;;;;;;;;;;iBA0BX,gBAAA,CAAiB,KAAA;AA1BjC;;;;;AA0BA;AA1BA,iBAyCgB,gBAAA,CAAiB,MAAA;;;;AAAjC;;;iBAUgB,iBAAA,WAA4B,MAAA,kBAAA,CAC1C,GAAA,EAAK,CAAA,GACJ,MAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/csv/index.ts"],"mappings":";;;;;;UAiBiB,eAAA;EACf,MAAA;EACA,cAAA;EACA,eAAA,IAAmB,MAAA,UAAgB,KAAA;EACnC,aAAA;EACA,SAAA;EACA,OAAA;AAAA;;;;UAMe,eAAA;EACf,MAAA;EACA,OAAA;EACA,cAAA;EACA,SAAA;AAAA;AAwEF;;;;;;;AAAA,iBAAgB,QAAA,KAAa,MAAA,kBAAA,CAC3B,GAAA,UACA,OAAA,GAAU,OAAA,CAAQ,eAAA,IACjB,CAAA;;;;;;;;iBAoDa,aAAA,CACd,GAAA,UACA,OAAA,GAAU,OAAO,CAAC,eAAA;;AAtDhB;AAoDJ;;;;;;;;iBAmCgB,QAAA,KAAa,MAAA,kBAAA,CAC3B,IAAA,EAAM,CAAA,IACN,OAAA,GAAU,OAAA,CAAQ,eAAA;EAAqB,QAAA;AAAA;;;;;;;;;;;iBA2CzB,aAAA,CACd,IAAA,cACA,OAAA,GAAU,OAAO,CAAC,eAAA;EAAqB,QAAA;AAAA;;;;;AA7CoB;iBAwE7C,UAAA,CAAW,GAAW;;;;;;;;;;iBA0BtB,gBAAA,CAAiB,KAAc;AA1B/C;;;;AAAsC;AA0BtC;AA1BA,iBAyCgB,gBAAA,CAAiB,MAAiB;;;AAfH;AAe/C;;;iBAUgB,iBAAA,WAA4B,MAAA,kBAAA,CAC1C,GAAA,EAAK,CAAA,GACJ,MAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/csv/index.ts"],"sourcesContent":["/**\n * CSV parsing and generation utilities using d3-dsv\n *\n * SECURITY WARNING: CSV files opened in Excel/LibreOffice may execute formulas.\n * Use sanitization options when exporting data that may contain user-controlled content.\n */\n\nimport { dsvFormat } from 'd3-dsv';\n\nexport {\n extractColumn as extractCsvColumn,\n findRows as findCsvRows,\n} from '../object/index.js';\n\n/**\n * Options for parsing CSV\n */\nexport interface CsvParseOptions {\n header?: boolean;\n skipEmptyLines?: boolean;\n transformHeader?: (header: string, index: number) => string;\n dynamicTyping?: boolean;\n delimiter?: string;\n maxSize?: number;\n}\n\n/**\n * Options for building CSV\n */\nexport interface CsvBuildOptions {\n header?: boolean;\n newline?: string;\n skipEmptyLines?: boolean;\n delimiter?: string;\n}\n\n/**\n * Default options for parsing CSV with headers\n */\nconst DEFAULT_PARSE_OPTIONS: CsvParseOptions = {\n header: true,\n skipEmptyLines: true,\n transformHeader: undefined,\n dynamicTyping: false,\n};\n\n/**\n * Default options for parsing CSV without headers\n */\nconst DEFAULT_PARSE_ARRAY_OPTIONS: CsvParseOptions = {\n header: false,\n skipEmptyLines: true,\n dynamicTyping: false,\n};\n\n/**\n * Default options for building CSV\n */\nconst DEFAULT_BUILD_OPTIONS: CsvBuildOptions = {\n header: true,\n newline: '\\n',\n skipEmptyLines: true,\n};\n\n/**\n * Validate CSV quoting — throws if unclosed quotes are found\n */\nfunction validateQuoting(csv: string): void {\n let inQuotes = false;\n let row = 0;\n for (let i = 0; i < csv.length; i++) {\n if (csv[i] === '\"') {\n if (inQuotes && i + 1 < csv.length && csv[i + 1] === '\"') {\n i++; // skip escaped quote \"\"\n } else {\n inQuotes = !inQuotes;\n }\n } else if (csv[i] === '\\n' && !inQuotes) {\n row++;\n }\n }\n if (inQuotes) {\n throw new Error(`CSV parsing failed: Row ${row}: Unclosed quote`);\n }\n}\n\n/**\n * Convert string value to number or boolean when appropriate\n */\nfunction dynamicTypeConvert(value: string): unknown {\n if (value === '') return value;\n if (value === 'true') return true;\n if (value === 'false') return false;\n const num = Number(value);\n if (!isNaN(num) && value.trim() !== '') return num;\n return value;\n}\n\n/**\n * Parse CSV string into array of objects with headers as keys\n * @param csv - CSV string to parse\n * @param options - Optional parse configuration\n * @returns Array of objects representing rows\n * @throws Error if parsing fails\n */\nexport function parseCsv<T = Record<string, unknown>>(\n csv: string,\n options?: Partial<CsvParseOptions>\n): T[] {\n if (options?.maxSize !== undefined && csv.length > options.maxSize) {\n throw new Error(\n `Input exceeds maximum allowed size: ${csv.length} bytes exceeds limit of ${options.maxSize} bytes`\n );\n }\n\n const opts = { ...DEFAULT_PARSE_OPTIONS, ...options };\n const trimmed = csv.trim();\n\n validateQuoting(trimmed);\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n const parsed = dsv.parse(trimmed);\n const { columns } = parsed;\n\n // Pre-compute transformed headers\n const headers = opts.transformHeader\n ? columns.map((col, i) => opts.transformHeader!(col, i))\n : columns;\n\n let rows: Record<string, unknown>[] = parsed.map((row) => {\n const obj: Record<string, unknown> = {};\n columns.forEach((col, i) => {\n let value: unknown = row[col] ?? '';\n if (opts.dynamicTyping && typeof value === 'string') {\n value = dynamicTypeConvert(value);\n }\n const key = headers[i];\n if (key !== undefined) {\n obj[key] = value;\n }\n });\n return obj;\n });\n\n if (opts.skipEmptyLines) {\n rows = rows.filter((row) =>\n Object.values(row).some((v) => v !== '' && v !== null && v !== undefined)\n );\n }\n\n return rows as T[];\n}\n\n/**\n * Parse CSV string into array of arrays without treating first row as headers\n * @param csv - CSV string to parse\n * @param options - Optional parse configuration\n * @returns Array of arrays representing rows\n * @throws Error if parsing fails\n */\nexport function parseCsvArray(\n csv: string,\n options?: Partial<CsvParseOptions>\n): string[][] {\n if (options?.maxSize !== undefined && csv.length > options.maxSize) {\n throw new Error(\n `Input exceeds maximum allowed size: ${csv.length} bytes exceeds limit of ${options.maxSize} bytes`\n );\n }\n\n const opts = { ...DEFAULT_PARSE_ARRAY_OPTIONS, ...options };\n const trimmed = csv.trim();\n\n validateQuoting(trimmed);\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n let rows = dsv.parseRows(trimmed);\n\n if (opts.skipEmptyLines) {\n rows = rows.filter((row) => !(row.length === 1 && row[0] === ''));\n }\n\n return rows;\n}\n\n/**\n * Build CSV string from array of objects\n *\n * SECURITY: Set sanitize=true when data may contain user-controlled content\n * to prevent CSV formula injection attacks in Excel/LibreOffice.\n *\n * @param data - Array of objects to convert to CSV\n * @param options - Optional build configuration and sanitize flag\n * @returns CSV string\n */\nexport function buildCsv<T = Record<string, unknown>>(\n data: T[],\n options?: Partial<CsvBuildOptions> & { sanitize?: boolean }\n): string {\n const { sanitize, ...buildOpts } = options || {};\n const opts = { ...DEFAULT_BUILD_OPTIONS, ...buildOpts };\n\n if (data.length === 0) return '';\n\n let processedData = data;\n if (sanitize) {\n processedData = data.map((row) =>\n sanitizeCsvObject(row as Record<string, unknown>)\n ) as T[];\n }\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n let result: string;\n\n if (opts.header === false) {\n const rows = processedData.map((row) =>\n Object.values(row as Record<string, unknown>).map(String)\n );\n result = dsv.formatRows(rows);\n } else {\n result = dsv.format(processedData as Record<string, unknown>[]);\n }\n\n if (opts.newline && opts.newline !== '\\n') {\n result = result.replace(/\\n/g, opts.newline);\n }\n\n return result;\n}\n\n/**\n * Build CSV string from array of arrays\n *\n * SECURITY: Set sanitize=true when data may contain user-controlled content\n * to prevent CSV formula injection attacks in Excel/LibreOffice.\n *\n * @param data - Array of arrays to convert to CSV\n * @param options - Optional build configuration and sanitize flag\n * @returns CSV string\n */\nexport function buildCsvArray(\n data: string[][],\n options?: Partial<CsvBuildOptions> & { sanitize?: boolean }\n): string {\n const { sanitize, ...buildOpts } = options || {};\n const opts = { ...DEFAULT_BUILD_OPTIONS, header: false, ...buildOpts };\n\n if (data.length === 0) return '';\n\n let processedData = data;\n if (sanitize) {\n processedData = data.map((row) => sanitizeCsvArray(row));\n }\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n let result = dsv.formatRows(processedData);\n\n if (opts.newline && opts.newline !== '\\n') {\n result = result.replace(/\\n/g, opts.newline);\n }\n\n return result;\n}\n\n/**\n * Validate CSV format\n * @param csv - CSV string to validate\n * @returns True if valid CSV, false otherwise\n */\nexport function isValidCsv(csv: string): boolean {\n if (!csv || csv.trim().length === 0) return false;\n\n const trimmed = csv.trim();\n\n // CSV must have at least one delimiter (comma) to be considered CSV structure\n if (!trimmed.includes(',')) return false;\n\n try {\n validateQuoting(trimmed);\n } catch {\n return false;\n }\n\n return true;\n}\n\n/**\n * Sanitize a single CSV value to prevent formula injection\n *\n * Prefixes values starting with =, +, -, @, |, or % with a single quote\n * to prevent Excel/LibreOffice from interpreting them as formulas.\n *\n * @param value - Value to sanitize\n * @returns Sanitized value\n */\nexport function sanitizeCsvValue(value: unknown): string {\n const str = String(value);\n // Check if string starts with formula trigger characters\n if (/^[=+\\-@|%]/.test(str)) {\n return `'${str}`;\n }\n return str;\n}\n\n/**\n * Sanitize all values in an array to prevent formula injection\n *\n * @param values - Array of values to sanitize\n * @returns Array with sanitized values\n */\nexport function sanitizeCsvArray(values: unknown[]): string[] {\n return values.map(sanitizeCsvValue);\n}\n\n/**\n * Sanitize all values in an object to prevent formula injection\n *\n * @param obj - Object with values to sanitize\n * @returns New object with sanitized values\n */\nexport function sanitizeCsvObject<T extends Record<string, unknown>>(\n obj: T\n): Record<string, string> {\n const sanitized: Record<string, string> = {};\n for (const [key, value] of Object.entries(obj)) {\n sanitized[key] = sanitizeCsvValue(value);\n }\n return sanitized;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/csv/index.ts"],"sourcesContent":["/**\n * CSV parsing and generation utilities using d3-dsv\n *\n * SECURITY WARNING: CSV files opened in Excel/LibreOffice may execute formulas.\n * Use sanitization options when exporting data that may contain user-controlled content.\n */\n\nimport { dsvFormat } from 'd3-dsv';\n\nexport {\n extractColumn as extractCsvColumn,\n findRows as findCsvRows,\n} from '../object/index.js';\n\n/**\n * Options for parsing CSV\n */\nexport interface CsvParseOptions {\n header?: boolean;\n skipEmptyLines?: boolean;\n transformHeader?: (header: string, index: number) => string;\n dynamicTyping?: boolean;\n delimiter?: string;\n maxSize?: number;\n}\n\n/**\n * Options for building CSV\n */\nexport interface CsvBuildOptions {\n header?: boolean;\n newline?: string;\n skipEmptyLines?: boolean;\n delimiter?: string;\n}\n\n/**\n * Default options for parsing CSV with headers\n */\nconst DEFAULT_PARSE_OPTIONS: CsvParseOptions = {\n header: true,\n skipEmptyLines: true,\n transformHeader: undefined,\n dynamicTyping: false,\n};\n\n/**\n * Default options for parsing CSV without headers\n */\nconst DEFAULT_PARSE_ARRAY_OPTIONS: CsvParseOptions = {\n header: false,\n skipEmptyLines: true,\n dynamicTyping: false,\n};\n\n/**\n * Default options for building CSV\n */\nconst DEFAULT_BUILD_OPTIONS: CsvBuildOptions = {\n header: true,\n newline: '\\n',\n skipEmptyLines: true,\n};\n\n/**\n * Validate CSV quoting — throws if unclosed quotes are found\n */\nfunction validateQuoting(csv: string): void {\n let inQuotes = false;\n let row = 0;\n for (let i = 0; i < csv.length; i++) {\n if (csv[i] === '\"') {\n if (inQuotes && i + 1 < csv.length && csv[i + 1] === '\"') {\n i++; // skip escaped quote \"\"\n } else {\n inQuotes = !inQuotes;\n }\n } else if (csv[i] === '\\n' && !inQuotes) {\n row++;\n }\n }\n if (inQuotes) {\n throw new Error(`CSV parsing failed: Row ${row}: Unclosed quote`);\n }\n}\n\n/**\n * Convert string value to number or boolean when appropriate\n */\nfunction dynamicTypeConvert(value: string): unknown {\n if (value === '') return value;\n if (value === 'true') return true;\n if (value === 'false') return false;\n const num = Number(value);\n if (!isNaN(num) && value.trim() !== '') return num;\n return value;\n}\n\n/**\n * Parse CSV string into array of objects with headers as keys\n * @param csv - CSV string to parse\n * @param options - Optional parse configuration\n * @returns Array of objects representing rows\n * @throws Error if parsing fails\n */\nexport function parseCsv<T = Record<string, unknown>>(\n csv: string,\n options?: Partial<CsvParseOptions>\n): T[] {\n if (options?.maxSize !== undefined && csv.length > options.maxSize) {\n throw new Error(\n `Input exceeds maximum allowed size: ${csv.length} bytes exceeds limit of ${options.maxSize} bytes`\n );\n }\n\n const opts = { ...DEFAULT_PARSE_OPTIONS, ...options };\n const trimmed = csv.trim();\n\n validateQuoting(trimmed);\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n const parsed = dsv.parse(trimmed);\n const { columns } = parsed;\n\n // Pre-compute transformed headers\n const headers = opts.transformHeader\n ? columns.map((col, i) => opts.transformHeader!(col, i))\n : columns;\n\n let rows: Record<string, unknown>[] = parsed.map((row) => {\n const obj: Record<string, unknown> = {};\n columns.forEach((col, i) => {\n let value: unknown = row[col] ?? '';\n if (opts.dynamicTyping && typeof value === 'string') {\n value = dynamicTypeConvert(value);\n }\n const key = headers[i];\n if (key !== undefined) {\n obj[key] = value;\n }\n });\n return obj;\n });\n\n if (opts.skipEmptyLines) {\n rows = rows.filter((row) =>\n Object.values(row).some((v) => v !== '' && v !== null && v !== undefined)\n );\n }\n\n return rows as T[];\n}\n\n/**\n * Parse CSV string into array of arrays without treating first row as headers\n * @param csv - CSV string to parse\n * @param options - Optional parse configuration\n * @returns Array of arrays representing rows\n * @throws Error if parsing fails\n */\nexport function parseCsvArray(\n csv: string,\n options?: Partial<CsvParseOptions>\n): string[][] {\n if (options?.maxSize !== undefined && csv.length > options.maxSize) {\n throw new Error(\n `Input exceeds maximum allowed size: ${csv.length} bytes exceeds limit of ${options.maxSize} bytes`\n );\n }\n\n const opts = { ...DEFAULT_PARSE_ARRAY_OPTIONS, ...options };\n const trimmed = csv.trim();\n\n validateQuoting(trimmed);\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n let rows = dsv.parseRows(trimmed);\n\n if (opts.skipEmptyLines) {\n rows = rows.filter((row) => !(row.length === 1 && row[0] === ''));\n }\n\n return rows;\n}\n\n/**\n * Build CSV string from array of objects\n *\n * SECURITY: Set sanitize=true when data may contain user-controlled content\n * to prevent CSV formula injection attacks in Excel/LibreOffice.\n *\n * @param data - Array of objects to convert to CSV\n * @param options - Optional build configuration and sanitize flag\n * @returns CSV string\n */\nexport function buildCsv<T = Record<string, unknown>>(\n data: T[],\n options?: Partial<CsvBuildOptions> & { sanitize?: boolean }\n): string {\n const { sanitize, ...buildOpts } = options || {};\n const opts = { ...DEFAULT_BUILD_OPTIONS, ...buildOpts };\n\n if (data.length === 0) return '';\n\n let processedData = data;\n if (sanitize) {\n processedData = data.map((row) =>\n sanitizeCsvObject(row as Record<string, unknown>)\n ) as T[];\n }\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n let result: string;\n\n if (opts.header === false) {\n const rows = processedData.map((row) =>\n Object.values(row as Record<string, unknown>).map(String)\n );\n result = dsv.formatRows(rows);\n } else {\n result = dsv.format(processedData as Record<string, unknown>[]);\n }\n\n if (opts.newline && opts.newline !== '\\n') {\n result = result.replace(/\\n/g, opts.newline);\n }\n\n return result;\n}\n\n/**\n * Build CSV string from array of arrays\n *\n * SECURITY: Set sanitize=true when data may contain user-controlled content\n * to prevent CSV formula injection attacks in Excel/LibreOffice.\n *\n * @param data - Array of arrays to convert to CSV\n * @param options - Optional build configuration and sanitize flag\n * @returns CSV string\n */\nexport function buildCsvArray(\n data: string[][],\n options?: Partial<CsvBuildOptions> & { sanitize?: boolean }\n): string {\n const { sanitize, ...buildOpts } = options || {};\n const opts = { ...DEFAULT_BUILD_OPTIONS, header: false, ...buildOpts };\n\n if (data.length === 0) return '';\n\n let processedData = data;\n if (sanitize) {\n processedData = data.map((row) => sanitizeCsvArray(row));\n }\n\n const dsv = dsvFormat(opts.delimiter ?? ',');\n let result = dsv.formatRows(processedData);\n\n if (opts.newline && opts.newline !== '\\n') {\n result = result.replace(/\\n/g, opts.newline);\n }\n\n return result;\n}\n\n/**\n * Validate CSV format\n * @param csv - CSV string to validate\n * @returns True if valid CSV, false otherwise\n */\nexport function isValidCsv(csv: string): boolean {\n if (!csv || csv.trim().length === 0) return false;\n\n const trimmed = csv.trim();\n\n // CSV must have at least one delimiter (comma) to be considered CSV structure\n if (!trimmed.includes(',')) return false;\n\n try {\n validateQuoting(trimmed);\n } catch {\n return false;\n }\n\n return true;\n}\n\n/**\n * Sanitize a single CSV value to prevent formula injection\n *\n * Prefixes values starting with =, +, -, @, |, or % with a single quote\n * to prevent Excel/LibreOffice from interpreting them as formulas.\n *\n * @param value - Value to sanitize\n * @returns Sanitized value\n */\nexport function sanitizeCsvValue(value: unknown): string {\n const str = String(value);\n // Check if string starts with formula trigger characters\n if (/^[=+\\-@|%]/.test(str)) {\n return `'${str}`;\n }\n return str;\n}\n\n/**\n * Sanitize all values in an array to prevent formula injection\n *\n * @param values - Array of values to sanitize\n * @returns Array with sanitized values\n */\nexport function sanitizeCsvArray(values: unknown[]): string[] {\n return values.map(sanitizeCsvValue);\n}\n\n/**\n * Sanitize all values in an object to prevent formula injection\n *\n * @param obj - Object with values to sanitize\n * @returns New object with sanitized values\n */\nexport function sanitizeCsvObject<T extends Record<string, unknown>>(\n obj: T\n): Record<string, string> {\n const sanitized: Record<string, string> = {};\n for (const [key, value] of Object.entries(obj)) {\n sanitized[key] = sanitizeCsvValue(value);\n }\n return sanitized;\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/hash/index.ts"],"mappings":";;AASA;;;;;AAKA;;KALY,aAAA;;;AAUZ;KALY,YAAA;;;;UAKK,WAAA;EAIf;EAFA,SAAA,GAAY,aAAA;EAEW;EAAvB,QAAA,GAAW,YAAA;AAAA;;;;;;;;;;AA8Db;;;;iBApBsB,YAAA,CAAa,IAAA,UAAc,OAAA,GAAS,WAAA,GAAmB,OAAA;AAoC7E;;;;;AAmBA;;;;;;;AAnBA,iBAhBsB,MAAA,CAAO,IAAA,WAAe,OAAA;;;AAwD5C;;;;;;;;;;iBAxCsB,MAAA,CAAO,IAAA,WAAe,OAAA;;;;;;;;;;;;;;;;iBAmBtB,UAAA,CAAW,GAAA,WAAc,OAAA,GAAS,WAAA,GAAmB,OAAA;;;;;;;;;;;;;;;;iBAqBrD,UAAA,CACpB,IAAA,UACA,YAAA,UACA,OAAA,GAAS,WAAA,GACR,OAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/hash/index.ts"],"mappings":";;AASA;;;;AAAyB;AAKzB;;AALyB,KAAb,aAAA;;AAKY;AAKxB;KALY,YAAA;;;;UAKK,WAAA;EAIf;EAFA,SAAA,GAAY,aAAA;EAEW;EAAvB,QAAA,GAAW,YAAY;AAAA;;;;;;;;;AA0C2D;AAoBpF;;;;iBApBsB,YAAA,CAAa,IAAA,UAAc,OAAA,GAAS,WAAA,GAAmB,OAAO;AAoCpF;;;;AAAmD;AAmBnD;;;;;;;AAnBA,iBAhBsB,MAAA,CAAO,IAAA,WAAe,OAAO;;AAmC+B;AAqBlF;;;;;;;;;;iBAxCsB,MAAA,CAAO,IAAA,WAAe,OAAO;;;;;;;;;;;;;;;;iBAmB7B,UAAA,CAAW,GAAA,WAAc,OAAA,GAAS,WAAA,GAAmB,OAAO;;;;;;;;;;;;;;;;iBAqB5D,UAAA,CACpB,IAAA,UACA,YAAA,UACA,OAAA,GAAS,WAAA,GACR,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/hash/index.ts"],"sourcesContent":["/**\n * Hash utilities for HDF content\n * Provides cryptographic hashing functions for content identification and integrity verification\n * Uses the Web Crypto API (globalThis.crypto.subtle) for browser and Node.js compatibility\n */\n\n/**\n * Supported hash algorithms\n */\nexport type HashAlgorithm = 'sha256' | 'sha512';\n\n/**\n * Supported output encodings\n */\nexport type HashEncoding = 'hex' | 'base64';\n\n/**\n * Options for hash generation\n */\nexport interface HashOptions {\n /** Hash algorithm (default: 'sha256') */\n algorithm?: HashAlgorithm;\n /** Output encoding (default: 'hex') */\n encoding?: HashEncoding;\n}\n\n/** Map our algorithm names to Web Crypto algorithm identifiers */\nconst ALGORITHM_MAP: Record<HashAlgorithm, string> = {\n sha256: 'SHA-256',\n sha512: 'SHA-512',\n};\n\n/** Convert an ArrayBuffer to a hex string */\nfunction bufferToHex(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let hex = '';\n for (const b of bytes) {\n hex += b.toString(16).padStart(2, '0');\n }\n return hex;\n}\n\n/** Convert an ArrayBuffer to a base64 string */\nfunction bufferToBase64(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (const b of bytes) {\n binary += String.fromCharCode(b);\n }\n return btoa(binary);\n}\n\n/**\n * Generate a hash of the given data\n *\n * @param data - String data to hash\n * @param options - Hash generation options\n * @returns Hex-encoded hash string\n *\n * @example\n * ```typescript\n * const hash = await generateHash('hello world');\n * // Returns: 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'\n * ```\n */\nexport async function generateHash(data: string, options: HashOptions = {}): Promise<string> {\n const { algorithm = 'sha256', encoding = 'hex' } = options;\n const webAlgorithm = ALGORITHM_MAP[algorithm];\n const encoded = new TextEncoder().encode(data);\n const hashBuffer = await globalThis.crypto.subtle.digest(webAlgorithm, encoded);\n return encoding === 'base64' ? bufferToBase64(hashBuffer) : bufferToHex(hashBuffer);\n}\n\n/**\n * Generate a SHA-256 hash (recommended for most use cases)\n *\n * @param data - String data to hash\n * @returns Hex-encoded SHA-256 hash\n *\n * @example\n * ```typescript\n * const hash = await sha256('hello world');\n * // Returns: 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'\n * ```\n */\nexport async function sha256(data: string): Promise<string> {\n return generateHash(data, { algorithm: 'sha256' });\n}\n\n/**\n * Generate a SHA-512 hash (enhanced security for sensitive data)\n *\n * @param data - String data to hash\n * @returns Hex-encoded SHA-512 hash\n *\n * @example\n * ```typescript\n * const hash = await sha512('hello world');\n * // Returns: '309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f989dd35bc5ff499670da34255b45b0cfd830e81f605dcf7dc5542e93ae9cd76f'\n * ```\n */\nexport async function sha512(data: string): Promise<string> {\n return generateHash(data, { algorithm: 'sha512' });\n}\n\n/**\n * Generate a hash of an object by stringifying it first\n * Useful for hashing HDF control objects or other structured data\n *\n * @param obj - Object to hash\n * @param options - Hash generation options\n * @returns Hex-encoded hash string\n *\n * @example\n * ```typescript\n * const control = { id: 'V-12345', title: 'Test Control' };\n * const hash = await hashObject(control);\n * // Returns hash of the JSON-stringified object\n * ```\n */\nexport async function hashObject(obj: unknown, options: HashOptions = {}): Promise<string> {\n // JSON.stringify returns undefined for undefined, so handle it explicitly\n const stringified = obj === undefined ? 'undefined' : JSON.stringify(obj);\n return generateHash(stringified, options);\n}\n\n/**\n * Verify that data matches a given hash\n *\n * @param data - String data to verify\n * @param expectedHash - Expected hash value\n * @param options - Hash generation options (must match hash generation)\n * @returns True if data matches the hash\n *\n * @example\n * ```typescript\n * const data = 'hello world';\n * const hash = await sha256(data);\n * const isValid = await verifyHash(data, hash); // true\n * ```\n */\nexport async function verifyHash(\n data: string,\n expectedHash: string,\n options: HashOptions = {}\n): Promise<boolean> {\n const actualHash = await generateHash(data, options);\n return actualHash === expectedHash;\n}\n"],"mappings":";;AA2BA,MAAM,gBAA+C;CACnD,QAAQ;CACR,QAAQ;CACT;;AAGD,SAAS,YAAY,QAA6B;CAChD,MAAM,QAAQ,IAAI,WAAW,OAAO;CACpC,IAAI,MAAM;AACV,MAAK,MAAM,KAAK,MACd,QAAO,EAAE,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI;AAExC,QAAO;;;AAIT,SAAS,eAAe,QAA6B;CACnD,MAAM,QAAQ,IAAI,WAAW,OAAO;CACpC,IAAI,SAAS;AACb,MAAK,MAAM,KAAK,MACd,WAAU,OAAO,aAAa,EAAE;AAElC,QAAO,KAAK,OAAO;;;;;;;;;;;;;;;AAgBrB,eAAsB,aAAa,MAAc,UAAuB,EAAE,EAAmB;CAC3F,MAAM,EAAE,YAAY,UAAU,WAAW,UAAU;CACnD,MAAM,eAAe,cAAc;CACnC,MAAM,UAAU,IAAI,aAAa,CAAC,OAAO,KAAK;CAC9C,MAAM,aAAa,MAAM,WAAW,OAAO,OAAO,OAAO,cAAc,QAAQ;AAC/E,QAAO,aAAa,WAAW,eAAe,WAAW,GAAG,YAAY,WAAW;;;;;;;;;;;;;;AAerF,eAAsB,OAAO,MAA+B;AAC1D,QAAO,aAAa,MAAM,EAAE,WAAW,UAAU,CAAC;;;;;;;;;;;;;;AAepD,eAAsB,OAAO,MAA+B;AAC1D,QAAO,aAAa,MAAM,EAAE,WAAW,UAAU,CAAC;;;;;;;;;;;;;;;;;AAkBpD,eAAsB,WAAW,KAAc,UAAuB,EAAE,EAAmB;AAGzF,QAAO,aADa,QAAQ,KAAA,IAAY,cAAc,KAAK,UAAU,IAAI,EACxC,QAAQ;;;;;;;;;;;;;;;;;AAkB3C,eAAsB,WACpB,MACA,cACA,UAAuB,EAAE,EACP;AAElB,QADmB,MAAM,aAAa,MAAM,QAAQ,KAC9B"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/hash/index.ts"],"sourcesContent":["/**\n * Hash utilities for HDF content\n * Provides cryptographic hashing functions for content identification and integrity verification\n * Uses the Web Crypto API (globalThis.crypto.subtle) for browser and Node.js compatibility\n */\n\n/**\n * Supported hash algorithms\n */\nexport type HashAlgorithm = 'sha256' | 'sha512';\n\n/**\n * Supported output encodings\n */\nexport type HashEncoding = 'hex' | 'base64';\n\n/**\n * Options for hash generation\n */\nexport interface HashOptions {\n /** Hash algorithm (default: 'sha256') */\n algorithm?: HashAlgorithm;\n /** Output encoding (default: 'hex') */\n encoding?: HashEncoding;\n}\n\n/** Map our algorithm names to Web Crypto algorithm identifiers */\nconst ALGORITHM_MAP: Record<HashAlgorithm, string> = {\n sha256: 'SHA-256',\n sha512: 'SHA-512',\n};\n\n/** Convert an ArrayBuffer to a hex string */\nfunction bufferToHex(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let hex = '';\n for (const b of bytes) {\n hex += b.toString(16).padStart(2, '0');\n }\n return hex;\n}\n\n/** Convert an ArrayBuffer to a base64 string */\nfunction bufferToBase64(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = '';\n for (const b of bytes) {\n binary += String.fromCharCode(b);\n }\n return btoa(binary);\n}\n\n/**\n * Generate a hash of the given data\n *\n * @param data - String data to hash\n * @param options - Hash generation options\n * @returns Hex-encoded hash string\n *\n * @example\n * ```typescript\n * const hash = await generateHash('hello world');\n * // Returns: 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'\n * ```\n */\nexport async function generateHash(data: string, options: HashOptions = {}): Promise<string> {\n const { algorithm = 'sha256', encoding = 'hex' } = options;\n const webAlgorithm = ALGORITHM_MAP[algorithm];\n const encoded = new TextEncoder().encode(data);\n const hashBuffer = await globalThis.crypto.subtle.digest(webAlgorithm, encoded);\n return encoding === 'base64' ? bufferToBase64(hashBuffer) : bufferToHex(hashBuffer);\n}\n\n/**\n * Generate a SHA-256 hash (recommended for most use cases)\n *\n * @param data - String data to hash\n * @returns Hex-encoded SHA-256 hash\n *\n * @example\n * ```typescript\n * const hash = await sha256('hello world');\n * // Returns: 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'\n * ```\n */\nexport async function sha256(data: string): Promise<string> {\n return generateHash(data, { algorithm: 'sha256' });\n}\n\n/**\n * Generate a SHA-512 hash (enhanced security for sensitive data)\n *\n * @param data - String data to hash\n * @returns Hex-encoded SHA-512 hash\n *\n * @example\n * ```typescript\n * const hash = await sha512('hello world');\n * // Returns: '309ecc489c12d6eb4cc40f50c902f2b4d0ed77ee511a7c7a9bcd3ca86d4cd86f989dd35bc5ff499670da34255b45b0cfd830e81f605dcf7dc5542e93ae9cd76f'\n * ```\n */\nexport async function sha512(data: string): Promise<string> {\n return generateHash(data, { algorithm: 'sha512' });\n}\n\n/**\n * Generate a hash of an object by stringifying it first\n * Useful for hashing HDF control objects or other structured data\n *\n * @param obj - Object to hash\n * @param options - Hash generation options\n * @returns Hex-encoded hash string\n *\n * @example\n * ```typescript\n * const control = { id: 'V-12345', title: 'Test Control' };\n * const hash = await hashObject(control);\n * // Returns hash of the JSON-stringified object\n * ```\n */\nexport async function hashObject(obj: unknown, options: HashOptions = {}): Promise<string> {\n // JSON.stringify returns undefined for undefined, so handle it explicitly\n const stringified = obj === undefined ? 'undefined' : JSON.stringify(obj);\n return generateHash(stringified, options);\n}\n\n/**\n * Verify that data matches a given hash\n *\n * @param data - String data to verify\n * @param expectedHash - Expected hash value\n * @param options - Hash generation options (must match hash generation)\n * @returns True if data matches the hash\n *\n * @example\n * ```typescript\n * const data = 'hello world';\n * const hash = await sha256(data);\n * const isValid = await verifyHash(data, hash); // true\n * ```\n */\nexport async function verifyHash(\n data: string,\n expectedHash: string,\n options: HashOptions = {}\n): Promise<boolean> {\n const actualHash = await generateHash(data, options);\n return actualHash === expectedHash;\n}\n"],"mappings":";;AA2BA,MAAM,gBAA+C;CACnD,QAAQ;CACR,QAAQ;AACV;;AAGA,SAAS,YAAY,QAA6B;CAChD,MAAM,QAAQ,IAAI,WAAW,MAAM;CACnC,IAAI,MAAM;CACV,KAAK,MAAM,KAAK,OACd,OAAO,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;CAEvC,OAAO;AACT;;AAGA,SAAS,eAAe,QAA6B;CACnD,MAAM,QAAQ,IAAI,WAAW,MAAM;CACnC,IAAI,SAAS;CACb,KAAK,MAAM,KAAK,OACd,UAAU,OAAO,aAAa,CAAC;CAEjC,OAAO,KAAK,MAAM;AACpB;;;;;;;;;;;;;;AAeA,eAAsB,aAAa,MAAc,UAAuB,CAAC,GAAoB;CAC3F,MAAM,EAAE,YAAY,UAAU,WAAW,UAAU;CACnD,MAAM,eAAe,cAAc;CACnC,MAAM,UAAU,IAAI,YAAY,EAAE,OAAO,IAAI;CAC7C,MAAM,aAAa,MAAM,WAAW,OAAO,OAAO,OAAO,cAAc,OAAO;CAC9E,OAAO,aAAa,WAAW,eAAe,UAAU,IAAI,YAAY,UAAU;AACpF;;;;;;;;;;;;;AAcA,eAAsB,OAAO,MAA+B;CAC1D,OAAO,aAAa,MAAM,EAAE,WAAW,SAAS,CAAC;AACnD;;;;;;;;;;;;;AAcA,eAAsB,OAAO,MAA+B;CAC1D,OAAO,aAAa,MAAM,EAAE,WAAW,SAAS,CAAC;AACnD;;;;;;;;;;;;;;;;AAiBA,eAAsB,WAAW,KAAc,UAAuB,CAAC,GAAoB;CAGzF,OAAO,aADa,QAAQ,KAAA,IAAY,cAAc,KAAK,UAAU,GAAG,GACvC,OAAO;AAC1C;;;;;;;;;;;;;;;;AAiBA,eAAsB,WACpB,MACA,cACA,UAAuB,CAAC,GACN;CAElB,OAAO,MADkB,aAAa,MAAM,OAAO,MAC7B;AACxB"}
package/dist/index.d.ts CHANGED
@@ -23,17 +23,21 @@ import { parseTimestamp, stripHtml } from "./string/index.js";
23
23
  /**
24
24
  * Map a severity string to an impact score.
25
25
  *
26
- * @param severity - Severity level (case-insensitive)
27
- * @returns Impact score between 0.0 and 1.0; defaults to 0.5 for unrecognized values
26
+ * @param severity - Severity level (case-insensitive), or null for no severity assessment
27
+ * @returns Impact score between 0.0 and 1.0; null when input is null; defaults to 0.5 for unrecognized values
28
28
  */
29
+ declare function severityToImpact(severity: null): null;
29
30
  declare function severityToImpact(severity: string): number;
31
+ declare function severityToImpact(severity: string | null): number | null;
30
32
  /**
31
33
  * Map an impact score to a severity string.
32
34
  *
33
- * @param impact - Impact score (0.0 to 1.0)
34
- * @returns Severity level string
35
+ * @param impact - Impact score (0.0 to 1.0), or null for no impact assessment
36
+ * @returns Severity level string, or null when input is null
35
37
  */
38
+ declare function impactToSeverity(impact: null): null;
36
39
  declare function impactToSeverity(impact: number): string;
40
+ declare function impactToSeverity(impact: number | null): string | null;
37
41
  //#endregion
38
42
  export { type CsvBuildOptions, type CsvParseOptions, type HashAlgorithm, type HashEncoding, type HashOptions, type StringifyOptions, buildCsv, buildCsvArray, buildXml, extractColumn, extractColumn as extractCsvColumn, extractTextFromXml, findRows as findCsvRows, findRows, findValuesByKey as findJsonValues, findValuesByKey, findValuesByKey as findXmlValues, generateHash, hashObject, impactToSeverity, isValidCsv, isValidJSON, isValidXml, parseCsv, parseCsvArray, parseJSON, parseTimestamp, parseXml, parseXmlWithArrays, sanitizeCsvArray, sanitizeCsvObject, sanitizeCsvValue, severityToImpact, sha256, sha512, stringifyJSON, stripHtml, verifyHash };
39
43
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/severity/index.ts"],"mappings":";;;;;;;;;;;;;;;;AAgCA;;;;;AAWA;;;;;;;iBAXgB,gBAAA,CAAiB,QAAA;;;;;;;iBAWjB,gBAAA,CAAiB,MAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/severity/index.ts"],"mappings":";;;;;;;;;;;;;;;;AAgCA;;;;AAA+C;AAC/C;;;;AAAiD;AACjD;;iBAFgB,gBAAA,CAAiB,QAAc;AAAA,iBAC/B,gBAAA,CAAiB,QAAgB;AAAA,iBACjC,gBAAA,CAAiB,QAAuB;AAaxD;;;;AAA6C;AAC7C;AADA,iBAAgB,gBAAA,CAAiB,MAAY;AAAA,iBAC7B,gBAAA,CAAiB,MAAc;AAAA,iBAC/B,gBAAA,CAAiB,MAAqB"}
package/dist/index.js CHANGED
@@ -29,22 +29,12 @@ const severityMap = {
29
29
  informational: 0,
30
30
  information: 0
31
31
  };
32
- /**
33
- * Map a severity string to an impact score.
34
- *
35
- * @param severity - Severity level (case-insensitive)
36
- * @returns Impact score between 0.0 and 1.0; defaults to 0.5 for unrecognized values
37
- */
38
32
  function severityToImpact(severity) {
33
+ if (severity === null) return null;
39
34
  return severityMap[severity.toLowerCase()] ?? .5;
40
35
  }
41
- /**
42
- * Map an impact score to a severity string.
43
- *
44
- * @param impact - Impact score (0.0 to 1.0)
45
- * @returns Severity level string
46
- */
47
36
  function impactToSeverity(impact) {
37
+ if (impact === null) return null;
48
38
  if (impact >= .9) return "critical";
49
39
  if (impact >= .7) return "high";
50
40
  if (impact >= .4) return "medium";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/severity/index.ts"],"sourcesContent":["/**\n * Severity-to-impact mapping utilities.\n *\n * These define the canonical bidirectional mapping between severity labels\n * (critical, high, medium, low, informational) and HDF impact scores (0.0–1.0),\n * aligned with CVSS 3.x severity bands normalized to 0–1.\n *\n * Threshold ranges (used by impactToSeverity):\n * >=0.9 = critical (CVSS 9.0–10.0)\n * [0.7, 0.9) = high (CVSS 7.0–8.9)\n * [0.4, 0.7) = medium (CVSS 4.0–6.9)\n * (0.0, 0.4) = low (CVSS 0.1–3.9)\n * 0.0 = informational (CVSS 0.0)\n */\n\nconst severityMap: Record<string, number> = {\n critical: 0.9,\n high: 0.7,\n medium: 0.5,\n low: 0.3,\n info: 0.0,\n none: 0.0,\n informational: 0.0,\n information: 0.0,\n};\n\n/**\n * Map a severity string to an impact score.\n *\n * @param severity - Severity level (case-insensitive)\n * @returns Impact score between 0.0 and 1.0; defaults to 0.5 for unrecognized values\n */\nexport function severityToImpact(severity: string): number {\n const normalized = severity.toLowerCase();\n return severityMap[normalized] ?? 0.5;\n}\n\n/**\n * Map an impact score to a severity string.\n *\n * @param impact - Impact score (0.0 to 1.0)\n * @returns Severity level string\n */\nexport function impactToSeverity(impact: number): string {\n if (impact >= 0.9) return 'critical';\n if (impact >= 0.7) return 'high';\n if (impact >= 0.4) return 'medium';\n if (impact > 0.0) return 'low';\n return 'informational';\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAeA,MAAM,cAAsC;CAC1C,UAAU;CACV,MAAM;CACN,QAAQ;CACR,KAAK;CACL,MAAM;CACN,MAAM;CACN,eAAe;CACf,aAAa;CACd;;;;;;;AAQD,SAAgB,iBAAiB,UAA0B;AAEzD,QAAO,YADY,SAAS,aAAa,KACP;;;;;;;;AASpC,SAAgB,iBAAiB,QAAwB;AACvD,KAAI,UAAU,GAAK,QAAO;AAC1B,KAAI,UAAU,GAAK,QAAO;AAC1B,KAAI,UAAU,GAAK,QAAO;AAC1B,KAAI,SAAS,EAAK,QAAO;AACzB,QAAO"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/severity/index.ts"],"sourcesContent":["/**\n * Severity-to-impact mapping utilities.\n *\n * These define the canonical bidirectional mapping between severity labels\n * (critical, high, medium, low, informational) and HDF impact scores (0.0–1.0),\n * aligned with CVSS 3.x severity bands normalized to 0–1.\n *\n * Threshold ranges (used by impactToSeverity):\n * >=0.9 = critical (CVSS 9.0–10.0)\n * [0.7, 0.9) = high (CVSS 7.0–8.9)\n * [0.4, 0.7) = medium (CVSS 4.0–6.9)\n * (0.0, 0.4) = low (CVSS 0.1–3.9)\n * 0.0 = informational (CVSS 0.0)\n */\n\nconst severityMap: Record<string, number> = {\n critical: 0.9,\n high: 0.7,\n medium: 0.5,\n low: 0.3,\n info: 0.0,\n none: 0.0,\n informational: 0.0,\n information: 0.0,\n};\n\n/**\n * Map a severity string to an impact score.\n *\n * @param severity - Severity level (case-insensitive), or null for no severity assessment\n * @returns Impact score between 0.0 and 1.0; null when input is null; defaults to 0.5 for unrecognized values\n */\nexport function severityToImpact(severity: null): null;\nexport function severityToImpact(severity: string): number;\nexport function severityToImpact(severity: string | null): number | null;\nexport function severityToImpact(severity: string | null): number | null {\n if (severity === null) return null;\n const normalized = severity.toLowerCase();\n return severityMap[normalized] ?? 0.5;\n}\n\n/**\n * Map an impact score to a severity string.\n *\n * @param impact - Impact score (0.0 to 1.0), or null for no impact assessment\n * @returns Severity level string, or null when input is null\n */\nexport function impactToSeverity(impact: null): null;\nexport function impactToSeverity(impact: number): string;\nexport function impactToSeverity(impact: number | null): string | null;\nexport function impactToSeverity(impact: number | null): string | null {\n if (impact === null) return null;\n if (impact >= 0.9) return 'critical';\n if (impact >= 0.7) return 'high';\n if (impact >= 0.4) return 'medium';\n if (impact > 0.0) return 'low';\n return 'informational';\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAeA,MAAM,cAAsC;CAC1C,UAAU;CACV,MAAM;CACN,QAAQ;CACR,KAAK;CACL,MAAM;CACN,MAAM;CACN,eAAe;CACf,aAAa;AACf;AAWA,SAAgB,iBAAiB,UAAwC;CACvE,IAAI,aAAa,MAAM,OAAO;CAE9B,OAAO,YADY,SAAS,YACA,MAAM;AACpC;AAWA,SAAgB,iBAAiB,QAAsC;CACrE,IAAI,WAAW,MAAM,OAAO;CAC5B,IAAI,UAAU,IAAK,OAAO;CAC1B,IAAI,UAAU,IAAK,OAAO;CAC1B,IAAI,UAAU,IAAK,OAAO;CAC1B,IAAI,SAAS,GAAK,OAAO;CACzB,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/json/index.ts"],"mappings":";;;;;AAKA;UAAiB,gBAAA;;EAEf,MAAA;EAEM;EAAN,MAAA;AAAA;;;;;;;iBASc,SAAA,aAAA,CAAuB,KAAA,WAAgB,CAAA;AAuBvD;;;;;;;AAAA,iBAAgB,aAAA,CAAc,KAAA,WAAgB,OAAA,GAAS,gBAAA;;AAkBvD;;;;iBAAgB,WAAA,CAAY,KAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/json/index.ts"],"mappings":";;;;;AAKA;UAAiB,gBAAA;;EAEf,MAAA;EAEM;EAAN,MAAM;AAAA;;;;;;;iBASQ,SAAA,aAAA,CAAuB,KAAA,WAAgB,CAAC;AAuBxD;;;;;;;AAAA,iBAAgB,aAAA,CAAc,KAAA,WAAgB,OAAA,GAAS,gBAAqB;AAAA;AAkB5E;;;;AAlB4E,iBAkB5D,WAAA,CAAY,KAAc"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/json/index.ts"],"sourcesContent":["export { findValuesByKey as findJsonValues } from '../object/index.js';\n\n/**\n * Options for JSON stringification\n */\nexport interface StringifyOptions {\n /** Enable pretty printing with indentation */\n pretty?: boolean;\n /** Number of spaces for indentation (default: 2) */\n indent?: number;\n}\n\n/**\n * Safely parse a JSON string\n * @param input - JSON string to parse\n * @returns Parsed JSON value\n * @throws Error if input is not valid JSON\n */\nexport function parseJSON<T = unknown>(input: string): T {\n if (typeof input !== 'string') {\n throw new Error('Input must be a string');\n }\n\n if (input.trim() === '') {\n throw new Error('Input cannot be empty');\n }\n\n try {\n return JSON.parse(input) as T;\n } catch (error) {\n throw new Error(`Invalid JSON: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\n/**\n * Safely stringify a value to JSON\n * @param value - Value to stringify\n * @param options - Stringification options\n * @returns JSON string\n * @throws Error if value contains circular references or cannot be stringified\n */\nexport function stringifyJSON(value: unknown, options: StringifyOptions = {}): string {\n const { pretty = false, indent = 2 } = options;\n\n try {\n if (pretty) {\n return JSON.stringify(value, null, indent);\n }\n return JSON.stringify(value);\n } catch (error) {\n throw new Error(`Failed to stringify JSON: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\n/**\n * Check if a string is valid JSON\n * @param input - String to validate\n * @returns true if input is valid JSON, false otherwise\n */\nexport function isValidJSON(input: unknown): boolean {\n if (typeof input !== 'string') {\n return false;\n }\n\n if (input.trim() === '') {\n return false;\n }\n\n try {\n JSON.parse(input);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":";;;;;;;;AAkBA,SAAgB,UAAuB,OAAkB;AACvD,KAAI,OAAO,UAAU,SACnB,OAAM,IAAI,MAAM,yBAAyB;AAG3C,KAAI,MAAM,MAAM,KAAK,GACnB,OAAM,IAAI,MAAM,wBAAwB;AAG1C,KAAI;AACF,SAAO,KAAK,MAAM,MAAM;UACjB,OAAO;AACd,QAAM,IAAI,MAAM,iBAAiB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GAAG;;;;;;;;;;AAW9F,SAAgB,cAAc,OAAgB,UAA4B,EAAE,EAAU;CACpF,MAAM,EAAE,SAAS,OAAO,SAAS,MAAM;AAEvC,KAAI;AACF,MAAI,OACF,QAAO,KAAK,UAAU,OAAO,MAAM,OAAO;AAE5C,SAAO,KAAK,UAAU,MAAM;UACrB,OAAO;AACd,QAAM,IAAI,MAAM,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GAAG;;;;;;;;AAS1G,SAAgB,YAAY,OAAyB;AACnD,KAAI,OAAO,UAAU,SACnB,QAAO;AAGT,KAAI,MAAM,MAAM,KAAK,GACnB,QAAO;AAGT,KAAI;AACF,OAAK,MAAM,MAAM;AACjB,SAAO;SACD;AACN,SAAO"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/json/index.ts"],"sourcesContent":["export { findValuesByKey as findJsonValues } from '../object/index.js';\n\n/**\n * Options for JSON stringification\n */\nexport interface StringifyOptions {\n /** Enable pretty printing with indentation */\n pretty?: boolean;\n /** Number of spaces for indentation (default: 2) */\n indent?: number;\n}\n\n/**\n * Safely parse a JSON string\n * @param input - JSON string to parse\n * @returns Parsed JSON value\n * @throws Error if input is not valid JSON\n */\nexport function parseJSON<T = unknown>(input: string): T {\n if (typeof input !== 'string') {\n throw new Error('Input must be a string');\n }\n\n if (input.trim() === '') {\n throw new Error('Input cannot be empty');\n }\n\n try {\n return JSON.parse(input) as T;\n } catch (error) {\n throw new Error(`Invalid JSON: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\n/**\n * Safely stringify a value to JSON\n * @param value - Value to stringify\n * @param options - Stringification options\n * @returns JSON string\n * @throws Error if value contains circular references or cannot be stringified\n */\nexport function stringifyJSON(value: unknown, options: StringifyOptions = {}): string {\n const { pretty = false, indent = 2 } = options;\n\n try {\n if (pretty) {\n return JSON.stringify(value, null, indent);\n }\n return JSON.stringify(value);\n } catch (error) {\n throw new Error(`Failed to stringify JSON: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n\n/**\n * Check if a string is valid JSON\n * @param input - String to validate\n * @returns true if input is valid JSON, false otherwise\n */\nexport function isValidJSON(input: unknown): boolean {\n if (typeof input !== 'string') {\n return false;\n }\n\n if (input.trim() === '') {\n return false;\n }\n\n try {\n JSON.parse(input);\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":";;;;;;;;AAkBA,SAAgB,UAAuB,OAAkB;CACvD,IAAI,OAAO,UAAU,UACnB,MAAM,IAAI,MAAM,wBAAwB;CAG1C,IAAI,MAAM,KAAK,MAAM,IACnB,MAAM,IAAI,MAAM,uBAAuB;CAGzC,IAAI;EACF,OAAO,KAAK,MAAM,KAAK;CACzB,SAAS,OAAO;EACd,MAAM,IAAI,MAAM,iBAAiB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG;CAC3F;AACF;;;;;;;;AASA,SAAgB,cAAc,OAAgB,UAA4B,CAAC,GAAW;CACpF,MAAM,EAAE,SAAS,OAAO,SAAS,MAAM;CAEvC,IAAI;EACF,IAAI,QACF,OAAO,KAAK,UAAU,OAAO,MAAM,MAAM;EAE3C,OAAO,KAAK,UAAU,KAAK;CAC7B,SAAS,OAAO;EACd,MAAM,IAAI,MAAM,6BAA6B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG;CACvG;AACF;;;;;;AAOA,SAAgB,YAAY,OAAyB;CACnD,IAAI,OAAO,UAAU,UACnB,OAAO;CAGT,IAAI,MAAM,KAAK,MAAM,IACnB,OAAO;CAGT,IAAI;EACF,KAAK,MAAM,KAAK;EAChB,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/object/index.ts"],"mappings":";;AAmBA;;;;;;;;;AAkDA;;;;;;;;iBAlDgB,eAAA,CACd,GAAA,WACA,GAAA,UACA,QAAA;AAgEF;;;;;;;;;AAAA,iBAjBgB,aAAA,CACd,IAAA,EAAM,MAAA,qBACN,MAAA;;;;;;;;;;;iBAec,QAAA,CACd,IAAA,EAAM,MAAA,qBACN,MAAA,UACA,KAAA,YACC,MAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/object/index.ts"],"mappings":";;AAmBA;;;;;;;;AAGgB;AA+ChB;;;;;;;;iBAlDgB,eAAA,CACd,GAAA,WACA,GAAA,UACA,QAAA;AAgEF;;;;;;;;;AAAA,iBAjBgB,aAAA,CACd,IAAA,EAAM,MAAM,qBACZ,MAAA;AAmBO;;;;;;;;;;AAAA,iBAJO,QAAA,CACd,IAAA,EAAM,MAAA,qBACN,MAAA,UACA,KAAA,YACC,MAAM"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/object/index.ts"],"sourcesContent":["/**\n * Generic object query utilities for traversing parsed data structures.\n *\n * These functions work on any plain JS objects — parsed XML, JSON, or CSV rows\n * are all objects by the time converters work with them.\n */\n\n/**\n * Recursively find all values for a given key anywhere in a nested object tree.\n *\n * Walks the entire tree depth-first. When a property matching `key` is found,\n * its value is collected. Recurse continues into all child values (objects and\n * arrays) regardless of whether the current level matched.\n *\n * @param obj - Object tree to search (parsed XML, JSON, etc.)\n * @param key - Property name to search for\n * @param maxDepth - Maximum recursion depth (default 100). Nodes beyond this depth are silently skipped.\n * @returns Flat array of all matched values\n */\nexport function findValuesByKey(\n obj: unknown,\n key: string,\n maxDepth = 100,\n): unknown[] {\n const results: unknown[] = [];\n walk(obj, key, results, 0, maxDepth);\n return results;\n}\n\nfunction walk(\n node: unknown,\n key: string,\n results: unknown[],\n depth: number,\n maxDepth: number,\n): void {\n if (depth > maxDepth) {\n return;\n }\n\n if (node === null || node === undefined || typeof node !== 'object') {\n return;\n }\n\n if (Array.isArray(node)) {\n for (const item of node) {\n walk(item, key, results, depth + 1, maxDepth);\n }\n return;\n }\n\n const record = node as Record<string, unknown>;\n if (key in record) {\n results.push(record[key]);\n }\n for (const value of Object.values(record)) {\n walk(value, key, results, depth + 1, maxDepth);\n }\n}\n\n/**\n * Extract all values for a named field from an array of objects.\n *\n * Rows where the column is `undefined` are skipped.\n *\n * @param rows - Array of objects (e.g. parsed CSV rows)\n * @param column - Property name to extract\n * @returns Array of values for that column\n */\nexport function extractColumn(\n rows: Record<string, unknown>[],\n column: string,\n): unknown[] {\n return rows.map((r) => r[column]).filter((v) => v !== undefined);\n}\n\n/**\n * Filter an array of objects to rows where a column matches a value.\n *\n * Uses strict equality (`===`).\n *\n * @param rows - Array of objects to filter\n * @param column - Property name to check\n * @param value - Value to match against\n * @returns Filtered array of matching rows\n */\nexport function findRows(\n rows: Record<string, unknown>[],\n column: string,\n value: unknown,\n): Record<string, unknown>[] {\n return rows.filter((r) => r[column] === value);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,gBACd,KACA,KACA,WAAW,KACA;CACX,MAAM,UAAqB,EAAE;AAC7B,MAAK,KAAK,KAAK,SAAS,GAAG,SAAS;AACpC,QAAO;;AAGT,SAAS,KACP,MACA,KACA,SACA,OACA,UACM;AACN,KAAI,QAAQ,SACV;AAGF,KAAI,SAAS,QAAQ,SAAS,KAAA,KAAa,OAAO,SAAS,SACzD;AAGF,KAAI,MAAM,QAAQ,KAAK,EAAE;AACvB,OAAK,MAAM,QAAQ,KACjB,MAAK,MAAM,KAAK,SAAS,QAAQ,GAAG,SAAS;AAE/C;;CAGF,MAAM,SAAS;AACf,KAAI,OAAO,OACT,SAAQ,KAAK,OAAO,KAAK;AAE3B,MAAK,MAAM,SAAS,OAAO,OAAO,OAAO,CACvC,MAAK,OAAO,KAAK,SAAS,QAAQ,GAAG,SAAS;;;;;;;;;;;AAalD,SAAgB,cACd,MACA,QACW;AACX,QAAO,KAAK,KAAK,MAAM,EAAE,QAAQ,CAAC,QAAQ,MAAM,MAAM,KAAA,EAAU;;;;;;;;;;;;AAalE,SAAgB,SACd,MACA,QACA,OAC2B;AAC3B,QAAO,KAAK,QAAQ,MAAM,EAAE,YAAY,MAAM"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/object/index.ts"],"sourcesContent":["/**\n * Generic object query utilities for traversing parsed data structures.\n *\n * These functions work on any plain JS objects — parsed XML, JSON, or CSV rows\n * are all objects by the time converters work with them.\n */\n\n/**\n * Recursively find all values for a given key anywhere in a nested object tree.\n *\n * Walks the entire tree depth-first. When a property matching `key` is found,\n * its value is collected. Recurse continues into all child values (objects and\n * arrays) regardless of whether the current level matched.\n *\n * @param obj - Object tree to search (parsed XML, JSON, etc.)\n * @param key - Property name to search for\n * @param maxDepth - Maximum recursion depth (default 100). Nodes beyond this depth are silently skipped.\n * @returns Flat array of all matched values\n */\nexport function findValuesByKey(\n obj: unknown,\n key: string,\n maxDepth = 100,\n): unknown[] {\n const results: unknown[] = [];\n walk(obj, key, results, 0, maxDepth);\n return results;\n}\n\nfunction walk(\n node: unknown,\n key: string,\n results: unknown[],\n depth: number,\n maxDepth: number,\n): void {\n if (depth > maxDepth) {\n return;\n }\n\n if (node === null || node === undefined || typeof node !== 'object') {\n return;\n }\n\n if (Array.isArray(node)) {\n for (const item of node) {\n walk(item, key, results, depth + 1, maxDepth);\n }\n return;\n }\n\n const record = node as Record<string, unknown>;\n if (key in record) {\n results.push(record[key]);\n }\n for (const value of Object.values(record)) {\n walk(value, key, results, depth + 1, maxDepth);\n }\n}\n\n/**\n * Extract all values for a named field from an array of objects.\n *\n * Rows where the column is `undefined` are skipped.\n *\n * @param rows - Array of objects (e.g. parsed CSV rows)\n * @param column - Property name to extract\n * @returns Array of values for that column\n */\nexport function extractColumn(\n rows: Record<string, unknown>[],\n column: string,\n): unknown[] {\n return rows.map((r) => r[column]).filter((v) => v !== undefined);\n}\n\n/**\n * Filter an array of objects to rows where a column matches a value.\n *\n * Uses strict equality (`===`).\n *\n * @param rows - Array of objects to filter\n * @param column - Property name to check\n * @param value - Value to match against\n * @returns Filtered array of matching rows\n */\nexport function findRows(\n rows: Record<string, unknown>[],\n column: string,\n value: unknown,\n): Record<string, unknown>[] {\n return rows.filter((r) => r[column] === value);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,gBACd,KACA,KACA,WAAW,KACA;CACX,MAAM,UAAqB,CAAC;CAC5B,KAAK,KAAK,KAAK,SAAS,GAAG,QAAQ;CACnC,OAAO;AACT;AAEA,SAAS,KACP,MACA,KACA,SACA,OACA,UACM;CACN,IAAI,QAAQ,UACV;CAGF,IAAI,SAAS,QAAQ,SAAS,KAAA,KAAa,OAAO,SAAS,UACzD;CAGF,IAAI,MAAM,QAAQ,IAAI,GAAG;EACvB,KAAK,MAAM,QAAQ,MACjB,KAAK,MAAM,KAAK,SAAS,QAAQ,GAAG,QAAQ;EAE9C;CACF;CAEA,MAAM,SAAS;CACf,IAAI,OAAO,QACT,QAAQ,KAAK,OAAO,IAAI;CAE1B,KAAK,MAAM,SAAS,OAAO,OAAO,MAAM,GACtC,KAAK,OAAO,KAAK,SAAS,QAAQ,GAAG,QAAQ;AAEjD;;;;;;;;;;AAWA,SAAgB,cACd,MACA,QACW;CACX,OAAO,KAAK,KAAK,MAAM,EAAE,OAAO,EAAE,QAAQ,MAAM,MAAM,KAAA,CAAS;AACjE;;;;;;;;;;;AAYA,SAAgB,SACd,MACA,QACA,OAC2B;CAC3B,OAAO,KAAK,QAAQ,MAAM,EAAE,YAAY,KAAK;AAC/C"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/string/index.ts"],"mappings":";;AA2BA;;;;;AA0BA;;;;;;;;;;;;;;;;;;;;iBA1BgB,SAAA,CAAU,IAAA;;;;;;;;;;;;;;;;;;;;;;iBA0BV,cAAA,CAAe,CAAA,WAAY,IAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/string/index.ts"],"mappings":";;AA2BA;;;;AAAsC;AA0BtC;;;;AAA+C;;;;;;;;;;;;;;;;iBA1B/B,SAAA,CAAU,IAAY;;;;;;;;;;;;;;;;;;;;;;iBA0BtB,cAAA,CAAe,CAAA,WAAY,IAAI"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/string/index.ts"],"sourcesContent":["/**\n * String manipulation utilities for HDF converters.\n *\n * These handle common operations on security tool output strings:\n * HTML stripping for STIG descriptions and SonarQube messages,\n * and multi-format timestamp parsing for tool output dates.\n */\n\n/**\n * Remove HTML tags from a string and normalize whitespace.\n *\n * Strips all HTML/XML tags, collapses multiple whitespace characters\n * into single spaces, and trims leading/trailing whitespace.\n *\n * For XML documents, use {@link extractTextFromXml} from the xml module instead.\n * This function is for inline HTML in non-XML strings (STIG descriptions,\n * SonarQube messages, etc.).\n *\n * @param html - String potentially containing HTML tags\n * @returns Plain text with tags removed and whitespace normalized\n *\n * @example\n * ```typescript\n * stripHtml('<p>hello</p> <b>world</b>'); // 'hello world'\n * stripHtml('no tags here'); // 'no tags here'\n * ```\n */\nexport function stripHtml(html: string): string {\n const stripped = html.replace(/<[^>]*>/g, ' ');\n return stripped.replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * Parse a timestamp string in various common formats.\n *\n * Attempts to parse the input using the JavaScript Date constructor, which\n * supports ISO 8601, RFC 2822, and several other formats natively.\n *\n * Returns `null` if the input is empty or cannot be parsed.\n *\n * Equivalent to the Go `ParseTimestamp` in shared/go/converterutil.go.\n *\n * @param s - Timestamp string to parse\n * @returns Parsed Date or null if unparseable\n *\n * @example\n * ```typescript\n * parseTimestamp('2024-01-15T10:30:00Z'); // Date object\n * parseTimestamp('Mon, 15 Jan 2024 10:30:00 UTC'); // Date object\n * parseTimestamp('not a date'); // null\n * parseTimestamp(''); // null\n * ```\n */\nexport function parseTimestamp(s: string): Date | null {\n if (!s || s.trim().length === 0) {\n return null;\n }\n\n const parsed = new Date(s);\n if (isNaN(parsed.getTime())) {\n return null;\n }\n\n return parsed;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,UAAU,MAAsB;AAE9C,QADiB,KAAK,QAAQ,YAAY,IAAI,CAC9B,QAAQ,QAAQ,IAAI,CAAC,MAAM;;;;;;;;;;;;;;;;;;;;;;;AAwB7C,SAAgB,eAAe,GAAwB;AACrD,KAAI,CAAC,KAAK,EAAE,MAAM,CAAC,WAAW,EAC5B,QAAO;CAGT,MAAM,SAAS,IAAI,KAAK,EAAE;AAC1B,KAAI,MAAM,OAAO,SAAS,CAAC,CACzB,QAAO;AAGT,QAAO"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/string/index.ts"],"sourcesContent":["/**\n * String manipulation utilities for HDF converters.\n *\n * These handle common operations on security tool output strings:\n * HTML stripping for STIG descriptions and SonarQube messages,\n * and multi-format timestamp parsing for tool output dates.\n */\n\n/**\n * Remove HTML tags from a string and normalize whitespace.\n *\n * Strips all HTML/XML tags, collapses multiple whitespace characters\n * into single spaces, and trims leading/trailing whitespace.\n *\n * For XML documents, use {@link extractTextFromXml} from the xml module instead.\n * This function is for inline HTML in non-XML strings (STIG descriptions,\n * SonarQube messages, etc.).\n *\n * @param html - String potentially containing HTML tags\n * @returns Plain text with tags removed and whitespace normalized\n *\n * @example\n * ```typescript\n * stripHtml('<p>hello</p> <b>world</b>'); // 'hello world'\n * stripHtml('no tags here'); // 'no tags here'\n * ```\n */\nexport function stripHtml(html: string): string {\n const stripped = html.replace(/<[^>]*>/g, ' ');\n return stripped.replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * Parse a timestamp string in various common formats.\n *\n * Attempts to parse the input using the JavaScript Date constructor, which\n * supports ISO 8601, RFC 2822, and several other formats natively.\n *\n * Returns `null` if the input is empty or cannot be parsed.\n *\n * Equivalent to the Go `ParseTimestamp` in shared/go/converterutil.go.\n *\n * @param s - Timestamp string to parse\n * @returns Parsed Date or null if unparseable\n *\n * @example\n * ```typescript\n * parseTimestamp('2024-01-15T10:30:00Z'); // Date object\n * parseTimestamp('Mon, 15 Jan 2024 10:30:00 UTC'); // Date object\n * parseTimestamp('not a date'); // null\n * parseTimestamp(''); // null\n * ```\n */\nexport function parseTimestamp(s: string): Date | null {\n if (!s || s.trim().length === 0) {\n return null;\n }\n\n const parsed = new Date(s);\n if (isNaN(parsed.getTime())) {\n return null;\n }\n\n return parsed;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,UAAU,MAAsB;CAE9C,OADiB,KAAK,QAAQ,YAAY,GAC5B,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK;AAC5C;;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,eAAe,GAAwB;CACrD,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,WAAW,GAC5B,OAAO;CAGT,MAAM,SAAS,IAAI,KAAK,CAAC;CACzB,IAAI,MAAM,OAAO,QAAQ,CAAC,GACxB,OAAO;CAGT,OAAO;AACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/xml/index.ts"],"mappings":";;;;;;;;;;;;;;;;;;iBAuDgB,QAAA,CACd,GAAA,UACA,OAAA,GAAU,OAAA,CAAQ,UAAA;EAAgB,OAAA;AAAA,IACjC,MAAA;;;;;;;;;;;;;AA8DH;;iBA1BgB,QAAA,CACd,GAAA,EAAK,MAAA,mBACL,OAAA,GAAU,OAAA,CAAQ,iBAAA;;;AAiDpB;;;;;;;;;;;iBAzBgB,UAAA,CAAW,GAAA;;;;;;AAiE3B;;;;;;;;;;;iBAxCgB,kBAAA,CACd,GAAA,UACA,SAAA,YACA,OAAA,GAAU,OAAA,CAAQ,UAAA;EAAgB,OAAA;AAAA,IACjC,MAAA;;;;;;;;;;;;;;iBAoCa,kBAAA,CAAmB,GAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/xml/index.ts"],"mappings":";;;;;;;;;;;;;;;;;AA0DS;iBAHO,QAAA,CACd,GAAA,UACA,OAAA,GAAU,OAAA,CAAQ,UAAA;EAAgB,OAAA;AAAA,IACjC,MAAA;;;;;;;;;;;;AAsCmC;AAwBtC;;iBA1BgB,QAAA,CACd,GAAA,EAAK,MAAA,mBACL,OAAA,GAAU,OAAA,CAAQ,iBAAA;;AAwBkB;AAyBtC;;;;;;;;;;;iBAzBgB,UAAA,CAAW,GAAW;;;;;AA6B7B;AAoCT;;;;AAA8C;;;;;;;iBAxC9B,kBAAA,CACd,GAAA,UACA,SAAA,YACA,OAAA,GAAU,OAAA,CAAQ,UAAA;EAAgB,OAAA;AAAA,IACjC,MAAA;;;;;;;;;;;;;;iBAoCa,kBAAA,CAAmB,GAAW"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/xml/index.ts"],"sourcesContent":["/**\n * XML parsing utilities for HDF converters\n * Provides XML parsing with sensible defaults for security tool output\n */\n\nimport { XMLParser, XMLBuilder, XMLValidator } from 'fast-xml-parser';\nimport type { X2jOptions, XmlBuilderOptions } from 'fast-xml-parser';\n\nexport { findValuesByKey as findXmlValues } from '../object/index.js';\n\n/**\n * Default options for XML parsing optimized for security tool converters.\n *\n * **XXE Safety**: fast-xml-parser v5.x does not process DTD or external entities\n * by default. The `processEntities: false` option is set as defense-in-depth to\n * ensure entity expansion is disabled even if future versions change defaults.\n * See: https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/docs/v5/2.XMLparseOptions.md\n */\nconst DEFAULT_PARSE_OPTIONS: Partial<X2jOptions> = {\n attributeNamePrefix: '',\n textNodeName: '#text',\n ignoreAttributes: false,\n ignoreDeclaration: true,\n parseAttributeValue: false,\n parseTagValue: false,\n removeNSPrefix: true,\n processEntities: false,\n};\n\n/**\n * Default options for XML building\n */\nconst DEFAULT_BUILD_OPTIONS: Partial<XmlBuilderOptions> = {\n attributeNamePrefix: '',\n textNodeName: '#text',\n ignoreAttributes: false,\n format: true,\n indentBy: ' ',\n suppressEmptyNode: false,\n};\n\n/**\n * Parse XML string to JavaScript object\n *\n * @param xml - XML string to parse\n * @param options - Optional parser configuration (merged with defaults)\n * @returns Parsed JavaScript object\n *\n * @example\n * ```typescript\n * const xmlString = '<root><item id=\"1\">Test</item></root>';\n * const result = parseXml(xmlString);\n * // Returns: { root: { item: { id: '1', '#text': 'Test' } } }\n * ```\n */\nexport function parseXml(\n xml: string,\n options?: Partial<X2jOptions> & { maxSize?: number }\n): Record<string, unknown> {\n if (options?.maxSize !== undefined && xml.length > options.maxSize) {\n throw new Error(\n `Input exceeds maximum allowed size: ${xml.length} bytes exceeds limit of ${options.maxSize} bytes`\n );\n }\n\n // Validate XML first\n const validation = XMLValidator.validate(xml);\n if (validation !== true) {\n throw new Error(`Invalid XML: ${validation.err.msg}`);\n }\n\n const mergedOptions = {\n ...DEFAULT_PARSE_OPTIONS,\n ...options,\n };\n\n const parser = new XMLParser(mergedOptions);\n return parser.parse(xml) as Record<string, unknown>;\n}\n\n/**\n * Build XML string from JavaScript object\n *\n * @param obj - JavaScript object to convert to XML\n * @param options - Optional builder configuration (merged with defaults)\n * @returns XML string\n *\n * @example\n * ```typescript\n * const obj = { root: { item: { id: '1', '#text': 'Test' } } };\n * const xml = buildXml(obj);\n * // Returns formatted XML string\n * ```\n */\nexport function buildXml(\n obj: Record<string, unknown>,\n options?: Partial<XmlBuilderOptions>\n): string {\n const mergedOptions = {\n ...DEFAULT_BUILD_OPTIONS,\n ...options,\n };\n\n const builder = new XMLBuilder(mergedOptions);\n return builder.build(obj) as string;\n}\n\n/**\n * Validate if a string is well-formed XML\n *\n * @param xml - String to validate\n * @returns True if valid XML, false otherwise\n *\n * @example\n * ```typescript\n * isValidXml('<root><item>Test</item></root>'); // true\n * isValidXml('<root><item>Test</root>'); // false (mismatched tags)\n * isValidXml('not xml'); // false\n * ```\n */\nexport function isValidXml(xml: string): boolean {\n if (!xml || xml.trim().length === 0) {\n return false;\n }\n\n const result = XMLValidator.validate(xml);\n return result === true;\n}\n\n/**\n * Parse XML with array handling for repeated elements\n * Forces specified tag names to always be arrays, even if only one element exists\n *\n * @param xml - XML string to parse\n * @param arrayTags - Array of tag names that should always be parsed as arrays\n * @returns Parsed JavaScript object with forced arrays\n *\n * @example\n * ```typescript\n * const xml = '<root><item>Test</item></root>';\n * const result = parseXmlWithArrays(xml, ['item']);\n * // Returns: { root: { item: ['Test'] } }\n * // Without arrayTags, would return: { root: { item: 'Test' } }\n * ```\n */\nexport function parseXmlWithArrays(\n xml: string,\n arrayTags: string[],\n options?: Partial<X2jOptions> & { maxSize?: number }\n): Record<string, unknown> {\n if (options?.maxSize !== undefined && xml.length > options.maxSize) {\n throw new Error(\n `Input exceeds maximum allowed size: ${xml.length} bytes exceeds limit of ${options.maxSize} bytes`\n );\n }\n\n // Validate XML first (consistency with parseXml)\n const validation = XMLValidator.validate(xml);\n if (validation !== true) {\n throw new Error(`Invalid XML: ${validation.err.msg}`);\n }\n\n const mergedOptions: Partial<X2jOptions> = {\n ...DEFAULT_PARSE_OPTIONS,\n ...options,\n isArray: (tagName: string) => arrayTags.includes(tagName),\n };\n\n const parser = new XMLParser(mergedOptions);\n return parser.parse(xml) as Record<string, unknown>;\n}\n\n/**\n * Extract text content from XML, stripping all tags\n *\n * @param xml - XML string\n * @returns Plain text content with tags removed\n *\n * @example\n * ```typescript\n * const xml = '<root><b>Bold</b> and <i>italic</i></root>';\n * const text = extractTextFromXml(xml);\n * // Returns: 'Bold and italic'\n * ```\n */\nexport function extractTextFromXml(xml: string): string {\n if (!isValidXml(xml)) {\n return '';\n }\n\n try {\n const parsed = parseXml(xml, { ignoreAttributes: true });\n return extractTextRecursive(parsed).trim();\n } catch {\n return '';\n }\n}\n\n/**\n * Recursively extract text from parsed XML object\n * @private\n */\nfunction extractTextRecursive(obj: unknown): string {\n if (typeof obj === 'string' || typeof obj === 'number') {\n return String(obj);\n }\n\n if (Array.isArray(obj)) {\n return obj.map(extractTextRecursive).join(' ');\n }\n\n if (typeof obj === 'object' && obj !== null) {\n const record = obj as Record<string, unknown>;\n\n // Recursively extract text from all properties\n return Object.values(record)\n .map(extractTextRecursive)\n .filter(text => text.length > 0)\n .join(' ');\n }\n\n return '';\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkBA,MAAM,wBAA6C;CACjD,qBAAqB;CACrB,cAAc;CACd,kBAAkB;CAClB,mBAAmB;CACnB,qBAAqB;CACrB,eAAe;CACf,gBAAgB;CAChB,iBAAiB;CAClB;;;;AAKD,MAAM,wBAAoD;CACxD,qBAAqB;CACrB,cAAc;CACd,kBAAkB;CAClB,QAAQ;CACR,UAAU;CACV,mBAAmB;CACpB;;;;;;;;;;;;;;;AAgBD,SAAgB,SACd,KACA,SACyB;AACzB,KAAI,SAAS,YAAY,KAAA,KAAa,IAAI,SAAS,QAAQ,QACzD,OAAM,IAAI,MACR,uCAAuC,IAAI,OAAO,0BAA0B,QAAQ,QAAQ,QAC7F;CAIH,MAAM,aAAa,aAAa,SAAS,IAAI;AAC7C,KAAI,eAAe,KACjB,OAAM,IAAI,MAAM,gBAAgB,WAAW,IAAI,MAAM;AASvD,QADe,IAAI,UALG;EACpB,GAAG;EACH,GAAG;EACJ,CAE0C,CAC7B,MAAM,IAAI;;;;;;;;;;;;;;;;AAiB1B,SAAgB,SACd,KACA,SACQ;AAOR,QADgB,IAAI,WALE;EACpB,GAAG;EACH,GAAG;EACJ,CAE4C,CAC9B,MAAM,IAAI;;;;;;;;;;;;;;;AAgB3B,SAAgB,WAAW,KAAsB;AAC/C,KAAI,CAAC,OAAO,IAAI,MAAM,CAAC,WAAW,EAChC,QAAO;AAIT,QADe,aAAa,SAAS,IAAI,KACvB;;;;;;;;;;;;;;;;;;AAmBpB,SAAgB,mBACd,KACA,WACA,SACyB;AACzB,KAAI,SAAS,YAAY,KAAA,KAAa,IAAI,SAAS,QAAQ,QACzD,OAAM,IAAI,MACR,uCAAuC,IAAI,OAAO,0BAA0B,QAAQ,QAAQ,QAC7F;CAIH,MAAM,aAAa,aAAa,SAAS,IAAI;AAC7C,KAAI,eAAe,KACjB,OAAM,IAAI,MAAM,gBAAgB,WAAW,IAAI,MAAM;AAUvD,QADe,IAAI,UANwB;EACzC,GAAG;EACH,GAAG;EACH,UAAU,YAAoB,UAAU,SAAS,QAAQ;EAC1D,CAE0C,CAC7B,MAAM,IAAI;;;;;;;;;;;;;;;AAgB1B,SAAgB,mBAAmB,KAAqB;AACtD,KAAI,CAAC,WAAW,IAAI,CAClB,QAAO;AAGT,KAAI;AAEF,SAAO,qBADQ,SAAS,KAAK,EAAE,kBAAkB,MAAM,CAAC,CACrB,CAAC,MAAM;SACpC;AACN,SAAO;;;;;;;AAQX,SAAS,qBAAqB,KAAsB;AAClD,KAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,SAC5C,QAAO,OAAO,IAAI;AAGpB,KAAI,MAAM,QAAQ,IAAI,CACpB,QAAO,IAAI,IAAI,qBAAqB,CAAC,KAAK,IAAI;AAGhD,KAAI,OAAO,QAAQ,YAAY,QAAQ,KAIrC,QAAO,OAAO,OAHC,IAGa,CACzB,IAAI,qBAAqB,CACzB,QAAO,SAAQ,KAAK,SAAS,EAAE,CAC/B,KAAK,IAAI;AAGd,QAAO"}
1
+ {"version":3,"file":"index.js","names":["record"],"sources":["../../src/xml/index.ts"],"sourcesContent":["/**\n * XML parsing utilities for HDF converters\n * Provides XML parsing with sensible defaults for security tool output\n */\n\nimport { XMLParser, XMLBuilder, XMLValidator } from 'fast-xml-parser';\nimport type { X2jOptions, XmlBuilderOptions } from 'fast-xml-parser';\n\nexport { findValuesByKey as findXmlValues } from '../object/index.js';\n\n/**\n * Default options for XML parsing optimized for security tool converters.\n *\n * **XXE Safety**: fast-xml-parser v5.x does not process DTD or external entities\n * by default. The `processEntities: false` option is set as defense-in-depth to\n * ensure entity expansion is disabled even if future versions change defaults.\n * See: https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/docs/v5/2.XMLparseOptions.md\n */\nconst DEFAULT_PARSE_OPTIONS: Partial<X2jOptions> = {\n attributeNamePrefix: '',\n textNodeName: '#text',\n ignoreAttributes: false,\n ignoreDeclaration: true,\n parseAttributeValue: false,\n parseTagValue: false,\n removeNSPrefix: true,\n processEntities: false,\n};\n\n/**\n * Default options for XML building\n */\nconst DEFAULT_BUILD_OPTIONS: Partial<XmlBuilderOptions> = {\n attributeNamePrefix: '',\n textNodeName: '#text',\n ignoreAttributes: false,\n format: true,\n indentBy: ' ',\n suppressEmptyNode: false,\n};\n\n/**\n * Parse XML string to JavaScript object\n *\n * @param xml - XML string to parse\n * @param options - Optional parser configuration (merged with defaults)\n * @returns Parsed JavaScript object\n *\n * @example\n * ```typescript\n * const xmlString = '<root><item id=\"1\">Test</item></root>';\n * const result = parseXml(xmlString);\n * // Returns: { root: { item: { id: '1', '#text': 'Test' } } }\n * ```\n */\nexport function parseXml(\n xml: string,\n options?: Partial<X2jOptions> & { maxSize?: number }\n): Record<string, unknown> {\n if (options?.maxSize !== undefined && xml.length > options.maxSize) {\n throw new Error(\n `Input exceeds maximum allowed size: ${xml.length} bytes exceeds limit of ${options.maxSize} bytes`\n );\n }\n\n // Validate XML first\n const validation = XMLValidator.validate(xml);\n if (validation !== true) {\n throw new Error(`Invalid XML: ${validation.err.msg}`);\n }\n\n const mergedOptions = {\n ...DEFAULT_PARSE_OPTIONS,\n ...options,\n };\n\n const parser = new XMLParser(mergedOptions);\n return parser.parse(xml) as Record<string, unknown>;\n}\n\n/**\n * Build XML string from JavaScript object\n *\n * @param obj - JavaScript object to convert to XML\n * @param options - Optional builder configuration (merged with defaults)\n * @returns XML string\n *\n * @example\n * ```typescript\n * const obj = { root: { item: { id: '1', '#text': 'Test' } } };\n * const xml = buildXml(obj);\n * // Returns formatted XML string\n * ```\n */\nexport function buildXml(\n obj: Record<string, unknown>,\n options?: Partial<XmlBuilderOptions>\n): string {\n const mergedOptions = {\n ...DEFAULT_BUILD_OPTIONS,\n ...options,\n };\n\n const builder = new XMLBuilder(mergedOptions);\n return builder.build(obj) as string;\n}\n\n/**\n * Validate if a string is well-formed XML\n *\n * @param xml - String to validate\n * @returns True if valid XML, false otherwise\n *\n * @example\n * ```typescript\n * isValidXml('<root><item>Test</item></root>'); // true\n * isValidXml('<root><item>Test</root>'); // false (mismatched tags)\n * isValidXml('not xml'); // false\n * ```\n */\nexport function isValidXml(xml: string): boolean {\n if (!xml || xml.trim().length === 0) {\n return false;\n }\n\n const result = XMLValidator.validate(xml);\n return result === true;\n}\n\n/**\n * Parse XML with array handling for repeated elements\n * Forces specified tag names to always be arrays, even if only one element exists\n *\n * @param xml - XML string to parse\n * @param arrayTags - Array of tag names that should always be parsed as arrays\n * @returns Parsed JavaScript object with forced arrays\n *\n * @example\n * ```typescript\n * const xml = '<root><item>Test</item></root>';\n * const result = parseXmlWithArrays(xml, ['item']);\n * // Returns: { root: { item: ['Test'] } }\n * // Without arrayTags, would return: { root: { item: 'Test' } }\n * ```\n */\nexport function parseXmlWithArrays(\n xml: string,\n arrayTags: string[],\n options?: Partial<X2jOptions> & { maxSize?: number }\n): Record<string, unknown> {\n if (options?.maxSize !== undefined && xml.length > options.maxSize) {\n throw new Error(\n `Input exceeds maximum allowed size: ${xml.length} bytes exceeds limit of ${options.maxSize} bytes`\n );\n }\n\n // Validate XML first (consistency with parseXml)\n const validation = XMLValidator.validate(xml);\n if (validation !== true) {\n throw new Error(`Invalid XML: ${validation.err.msg}`);\n }\n\n const mergedOptions: Partial<X2jOptions> = {\n ...DEFAULT_PARSE_OPTIONS,\n ...options,\n isArray: (tagName: string) => arrayTags.includes(tagName),\n };\n\n const parser = new XMLParser(mergedOptions);\n return parser.parse(xml) as Record<string, unknown>;\n}\n\n/**\n * Extract text content from XML, stripping all tags\n *\n * @param xml - XML string\n * @returns Plain text content with tags removed\n *\n * @example\n * ```typescript\n * const xml = '<root><b>Bold</b> and <i>italic</i></root>';\n * const text = extractTextFromXml(xml);\n * // Returns: 'Bold and italic'\n * ```\n */\nexport function extractTextFromXml(xml: string): string {\n if (!isValidXml(xml)) {\n return '';\n }\n\n try {\n const parsed = parseXml(xml, { ignoreAttributes: true });\n return extractTextRecursive(parsed).trim();\n } catch {\n return '';\n }\n}\n\n/**\n * Recursively extract text from parsed XML object\n * @private\n */\nfunction extractTextRecursive(obj: unknown): string {\n if (typeof obj === 'string' || typeof obj === 'number') {\n return String(obj);\n }\n\n if (Array.isArray(obj)) {\n return obj.map(extractTextRecursive).join(' ');\n }\n\n if (typeof obj === 'object' && obj !== null) {\n const record = obj as Record<string, unknown>;\n\n // Recursively extract text from all properties\n return Object.values(record)\n .map(extractTextRecursive)\n .filter(text => text.length > 0)\n .join(' ');\n }\n\n return '';\n}\n"],"mappings":";;;;;;;;;;;;;;;AAkBA,MAAM,wBAA6C;CACjD,qBAAqB;CACrB,cAAc;CACd,kBAAkB;CAClB,mBAAmB;CACnB,qBAAqB;CACrB,eAAe;CACf,gBAAgB;CAChB,iBAAiB;AACnB;;;;AAKA,MAAM,wBAAoD;CACxD,qBAAqB;CACrB,cAAc;CACd,kBAAkB;CAClB,QAAQ;CACR,UAAU;CACV,mBAAmB;AACrB;;;;;;;;;;;;;;;AAgBA,SAAgB,SACd,KACA,SACyB;CACzB,IAAI,SAAS,YAAY,KAAA,KAAa,IAAI,SAAS,QAAQ,SACzD,MAAM,IAAI,MACR,uCAAuC,IAAI,OAAO,0BAA0B,QAAQ,QAAQ,OAC9F;CAIF,MAAM,aAAa,aAAa,SAAS,GAAG;CAC5C,IAAI,eAAe,MACjB,MAAM,IAAI,MAAM,gBAAgB,WAAW,IAAI,KAAK;CAStD,OAAO,IADY,UAAU;EAJ3B,GAAG;EACH,GAAG;CAGoC,CAC7B,EAAE,MAAM,GAAG;AACzB;;;;;;;;;;;;;;;AAgBA,SAAgB,SACd,KACA,SACQ;CAOR,OAAO,IADa,WAAW;EAJ7B,GAAG;EACH,GAAG;CAGsC,CAC9B,EAAE,MAAM,GAAG;AAC1B;;;;;;;;;;;;;;AAeA,SAAgB,WAAW,KAAsB;CAC/C,IAAI,CAAC,OAAO,IAAI,KAAK,EAAE,WAAW,GAChC,OAAO;CAIT,OADe,aAAa,SAAS,GACzB,MAAM;AACpB;;;;;;;;;;;;;;;;;AAkBA,SAAgB,mBACd,KACA,WACA,SACyB;CACzB,IAAI,SAAS,YAAY,KAAA,KAAa,IAAI,SAAS,QAAQ,SACzD,MAAM,IAAI,MACR,uCAAuC,IAAI,OAAO,0BAA0B,QAAQ,QAAQ,OAC9F;CAIF,MAAM,aAAa,aAAa,SAAS,GAAG;CAC5C,IAAI,eAAe,MACjB,MAAM,IAAI,MAAM,gBAAgB,WAAW,IAAI,KAAK;CAUtD,OAAO,IADY,UAAU;EAL3B,GAAG;EACH,GAAG;EACH,UAAU,YAAoB,UAAU,SAAS,OAAO;CAGjB,CAC7B,EAAE,MAAM,GAAG;AACzB;;;;;;;;;;;;;;AAeA,SAAgB,mBAAmB,KAAqB;CACtD,IAAI,CAAC,WAAW,GAAG,GACjB,OAAO;CAGT,IAAI;EAEF,OAAO,qBADQ,SAAS,KAAK,EAAE,kBAAkB,KAAK,CACrB,CAAC,EAAE,KAAK;CAC3C,QAAQ;EACN,OAAO;CACT;AACF;;;;;AAMA,SAAS,qBAAqB,KAAsB;CAClD,IAAI,OAAO,QAAQ,YAAY,OAAO,QAAQ,UAC5C,OAAO,OAAO,GAAG;CAGnB,IAAI,MAAM,QAAQ,GAAG,GACnB,OAAO,IAAI,IAAI,oBAAoB,EAAE,KAAK,GAAG;CAG/C,IAAI,OAAO,QAAQ,YAAY,QAAQ,MAIrC,OAAO,OAAO,OAAOA,GAAM,EACxB,IAAI,oBAAoB,EACxB,QAAO,SAAQ,KAAK,SAAS,CAAC,EAC9B,KAAK,GAAG;CAGb,OAAO;AACT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mitre/hdf-utilities",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Utility functions for HDF libraries (JSON parsing, validation helpers)",
5
5
  "publishConfig": {
6
6
  "access": "public"