UNPKG

@xwordly/xword-parser

Version:

Fast, type-safe TypeScript library for parsing crossword puzzles (PUZ, iPUZ, JPZ, XD)

1 lines 7.62 kB
{"version":3,"sources":["../src/detect.ts"],"names":[],"mappings":"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","file":"chunk-Q5B2K4C6.mjs","sourcesContent":["export type FormatType = 'ipuz' | 'puz' | 'jpz' | 'xd' | 'unknown';\n\nexport interface FormatHint {\n format: FormatType;\n confidence: 'high' | 'medium' | 'low';\n}\n\nexport function detectFormatFromContent(content: string | Buffer, filename?: string): FormatHint[] {\n const hints: FormatHint[] = [];\n\n // Check filename extension first\n if (filename) {\n const ext = filename.split('.').pop()?.toLowerCase();\n if (ext === 'puz') {\n hints.push({ format: 'puz', confidence: 'high' });\n } else if (ext === 'ipuz') {\n hints.push({ format: 'ipuz', confidence: 'high' });\n } else if (ext === 'jpz') {\n hints.push({ format: 'jpz', confidence: 'high' });\n } else if (ext === 'xd') {\n hints.push({ format: 'xd', confidence: 'high' });\n }\n }\n\n // Content-based detection\n if (typeof content === 'string') {\n const trimmed = content.trim();\n\n // iPUZ detection\n if (\n trimmed.startsWith('ipuz({') ||\n (trimmed.startsWith('{') && trimmed.includes('\"version\"'))\n ) {\n if (!hints.some((h) => h.format === 'ipuz')) {\n hints.push({ format: 'ipuz', confidence: 'high' });\n }\n }\n\n // JPZ detection (XML)\n if (\n content.includes('<?xml') ||\n content.includes('<crossword') ||\n content.includes('<puzzle')\n ) {\n if (!hints.some((h) => h.format === 'jpz')) {\n hints.push({ format: 'jpz', confidence: 'high' });\n }\n }\n\n // XD detection\n const lines = content.split('\\n');\n const hasXdHeaders = lines.some((line) =>\n /^(Title|Author|Editor|Copyright|Date|Rebus|Notepad|Notes?):/i.test(line),\n );\n if (hasXdHeaders) {\n if (!hints.some((h) => h.format === 'xd')) {\n hints.push({ format: 'xd', confidence: 'high' });\n }\n }\n } else {\n // Binary content - likely PUZ\n if (!hints.some((h) => h.format === 'puz')) {\n hints.push({ format: 'puz', confidence: 'medium' });\n }\n\n // But could also be text encoded as Buffer\n const textContent = content.toString('utf-8');\n const trimmed = textContent.trim();\n\n if (\n trimmed.startsWith('ipuz({') ||\n (trimmed.startsWith('{') && trimmed.includes('\"version\"'))\n ) {\n if (!hints.some((h) => h.format === 'ipuz')) {\n hints.push({ format: 'ipuz', confidence: 'medium' });\n }\n }\n\n if (\n textContent.includes('<?xml') ||\n textContent.includes('<crossword') ||\n textContent.includes('<puzzle')\n ) {\n if (!hints.some((h) => h.format === 'jpz')) {\n hints.push({ format: 'jpz', confidence: 'medium' });\n }\n }\n\n const lines = textContent.split('\\n');\n const hasXdHeaders = lines.some((line) =>\n /^(Title|Author|Editor|Copyright|Date|Rebus|Notepad|Notes?):/i.test(line),\n );\n if (hasXdHeaders) {\n if (!hints.some((h) => h.format === 'xd')) {\n hints.push({ format: 'xd', confidence: 'medium' });\n }\n }\n }\n\n // Sort by confidence (high first)\n hints.sort((a, b) => {\n const confidenceOrder = { high: 0, medium: 1, low: 2 };\n return confidenceOrder[a.confidence] - confidenceOrder[b.confidence];\n });\n\n return hints;\n}\n\nexport function getOrderedFormatsToTry(content: string | Buffer, filename?: string): FormatType[] {\n const hints = detectFormatFromContent(content, filename);\n const formats: FormatType[] = [];\n\n // Add hinted formats first\n for (const hint of hints) {\n if (!formats.includes(hint.format)) {\n formats.push(hint.format);\n }\n }\n\n // Add remaining formats\n const allFormats: FormatType[] = ['ipuz', 'puz', 'jpz', 'xd'];\n for (const format of allFormats) {\n if (!formats.includes(format)) {\n formats.push(format);\n }\n }\n\n return formats;\n}\n"]}