UNPKG

@xwordly/xword-parser

Version:

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

431 lines (429 loc) 14.2 kB
import { MAX_GRID_WIDTH, MAX_GRID_HEIGHT } from './chunk-GKUILQIP.mjs'; import { IpuzParseError, UnsupportedPuzzleTypeError } from './chunk-O732NFC7.mjs'; // src/ipuz.ts var CellType = /* @__PURE__ */ ((CellType2) => { CellType2["NORMAL"] = "normal"; CellType2["BLOCK"] = "block"; CellType2["NULL"] = "null"; return CellType2; })(CellType || {}); function parseCellFromIpuz(cellData, solutionData) { const cell = { type: "normal" /* NORMAL */ }; if (cellData === null || cellData === "null") { cell.type = "null" /* NULL */; return cell; } if (cellData === "#") { cell.type = "block" /* BLOCK */; return cell; } if (typeof cellData === "object" && cellData !== null) { const cellObj = cellData; if ("cell" in cellObj && cellObj.cell !== void 0) { const baseValue = cellObj.cell; if (baseValue === "#") { cell.type = "block" /* BLOCK */; } else if (baseValue === "null" || baseValue === null) { cell.type = "null" /* NULL */; } else if (typeof baseValue === "number" || typeof baseValue === "string") { if (baseValue !== 0 && baseValue !== "0") { cell.number = baseValue; } } } if ("style" in cellObj && cellObj.style && typeof cellObj.style === "object") { cell.style = {}; const styleFields = [ "shapebg", "highlight", "named", "border", "divided", "label", "mark", "imagebg", "color", "colortext", "colorborder", "colorbar", "barred", "dotted", "dashed", "lessthan", "greaterthan", "equal", "handler", "handlerdata" ]; for (const field of styleFields) { if (field in cellObj.style) { const value = cellObj.style[field]; if (value !== void 0) { cell.style[field] = value; } } } } if ("value" in cellObj && cellObj.value !== void 0) { cell.value = cellObj.value; } if ("continued" in cellObj && cellObj.continued !== void 0) { cell.continued = cellObj.continued; } if ("directions" in cellObj && cellObj.directions !== void 0) { cell.directions = cellObj.directions; } if ("given" in cellObj && cellObj.given !== void 0) { cell.given = cellObj.given; } } else if (typeof cellData === "number" || typeof cellData === "string") { if (typeof cellData === "number" && cellData > 0) { cell.number = cellData; } else if (typeof cellData === "string") { if (cellData === "0" || cellData === "#" || cellData === "null") { if (cellData === "#") { cell.type = "block" /* BLOCK */; } } else { const numVal = Number.parseInt(cellData, 10); if (!Number.isNaN(numVal) && numVal > 0) { cell.number = cellData; } else { cell.value = cellData; } } } } if (solutionData) { if (typeof solutionData === "string" && solutionData !== "#" && solutionData !== "null" && solutionData !== null) { cell.solution = solutionData; } } return cell; } function parseCluesFromIpuz(cluesData) { const clues = {}; for (const [direction, clueList] of Object.entries(cluesData)) { if (!Array.isArray(clueList)) { continue; } clues[direction] = []; for (const clueItem of clueList) { let clue = null; if (Array.isArray(clueItem) && clueItem.length >= 2) { clue = { number: clueItem[0], text: clueItem[1] }; if (clueItem.length > 2) { for (let i = 2; i < clueItem.length; i++) { const extra = clueItem[i]; if (typeof extra === "object" && extra !== null) { const extraObj = extra; if ("references" in extraObj && extraObj.references) { clue.references = extraObj.references; } if ("continued" in extraObj && extraObj.continued) { clue.continued = extraObj.continued; } if ("highlight" in extraObj && extraObj.highlight) { clue.highlight = extraObj.highlight; } if ("image" in extraObj && extraObj.image) { clue.image = extraObj.image; } } } } } else if (typeof clueItem === "object" && clueItem !== null) { const clueObj = clueItem; clue = { number: clueObj.number || "", text: clueObj.clue || "" }; if (clueObj.cells) { clue.cells = clueObj.cells; } if (clueObj.references) { clue.references = clueObj.references; } if (clueObj.continued) { clue.continued = clueObj.continued; } if (clueObj.highlight !== void 0) { clue.highlight = clueObj.highlight; } if (clueObj.image) { clue.image = clueObj.image; } } if (clue) { clues[direction].push(clue); } } } return clues; } function parseIpuz(content, options) { const stringContent = typeof content === "string" ? content : content.toString("utf-8"); let jsonContent = stringContent.trim(); if (jsonContent.startsWith("ipuz(") && jsonContent.endsWith(")")) { jsonContent = jsonContent.slice(5, -1); } let data; try { data = JSON.parse(jsonContent); } catch (e) { throw new IpuzParseError( `Invalid JSON: ${e instanceof Error ? e.message : "Unknown error"}`, "IPUZ_INVALID_JSON" /* IPUZ_INVALID_JSON */, void 0, e ); } if (!data.kind || !Array.isArray(data.kind) || !data.kind.some((k) => k.includes("crossword"))) { throw new UnsupportedPuzzleTypeError("Non-crossword"); } if (!data.dimensions || typeof data.dimensions !== "object") { throw new IpuzParseError( "Missing or invalid dimensions field", "IPUZ_MISSING_REQUIRED_FIELD" /* IPUZ_MISSING_REQUIRED_FIELD */ ); } if (!data.dimensions.width || !data.dimensions.height) { throw new IpuzParseError( "Missing width or height in dimensions", "IPUZ_MISSING_REQUIRED_FIELD" /* IPUZ_MISSING_REQUIRED_FIELD */ ); } if (typeof data.dimensions.width !== "number" || typeof data.dimensions.height !== "number") { throw new IpuzParseError("Width and height must be numbers", "IPUZ_INVALID_DATA_TYPE" /* IPUZ_INVALID_DATA_TYPE */); } if (!Number.isInteger(data.dimensions.width) || !Number.isInteger(data.dimensions.height)) { throw new IpuzParseError("Width and height must be integers", "IPUZ_INVALID_DATA_TYPE" /* IPUZ_INVALID_DATA_TYPE */); } if (data.dimensions.width <= 0 || data.dimensions.height <= 0) { throw new IpuzParseError( "Width and height must be positive numbers", "IPUZ_INVALID_GRID_SIZE" /* IPUZ_INVALID_GRID_SIZE */ ); } const maxWidth = options?.maxGridSize?.width ?? MAX_GRID_WIDTH; const maxHeight = options?.maxGridSize?.height ?? MAX_GRID_HEIGHT; if (data.dimensions.width > maxWidth || data.dimensions.height > maxHeight) { throw new IpuzParseError( `Grid dimensions too large: ${data.dimensions.width}x${data.dimensions.height}. Maximum supported size is ${maxWidth}x${maxHeight}`, "IPUZ_INVALID_GRID_SIZE" /* IPUZ_INVALID_GRID_SIZE */ ); } if (!data.puzzle || !Array.isArray(data.puzzle)) { throw new IpuzParseError( "Missing or invalid puzzle grid", "IPUZ_MISSING_REQUIRED_FIELD" /* IPUZ_MISSING_REQUIRED_FIELD */ ); } const puzzle = { version: data.version || "", kind: data.kind || [], dimensions: data.dimensions, puzzle: [], clues: {}, extensions: {} }; const simpleFields = [ "title", "author", "copyright", "publisher", "publication", "url", "intro", "explanation", "annotation", "notes", "difficulty", "origin", "date", "empty", "charset", "block", "answer" ]; for (const field of simpleFields) { if (field in data && data[field] !== void 0) { puzzle[field] = data[field]; } } if ("uniqueid" in data && data.uniqueid !== void 0) { puzzle.uniqueId = data.uniqueid; } if ("showenumerations" in data && data.showenumerations !== void 0) { puzzle.showEnumerations = data.showenumerations; } if ("clueplacement" in data && data.clueplacement !== void 0) { puzzle.cluePlacement = data.clueplacement; } if ("answers" in data && data.answers) { puzzle.answers = data.answers; } if ("enumeration" in data && data.enumeration !== void 0) { puzzle.enumeration = data.enumeration; } if ("enumerations" in data && data.enumerations) { puzzle.enumerations = data.enumerations; } if ("volatile" in data && data.volatile) { puzzle.volatile = data.volatile; } if ("checksum" in data && data.checksum) { puzzle.checksum = data.checksum; } if ("zones" in data && data.zones) { puzzle.zones = data.zones; } if ("styles" in data && data.styles) { puzzle.styles = data.styles; } if ("misses" in data && data.misses) { puzzle.misses = data.misses; } const solutionGrid = data.solution || []; if (solutionGrid.length > 0) { puzzle.solution = solutionGrid; } if ("saved" in data && data.saved) { puzzle.saved = data.saved; } const puzzleGrid = data.puzzle || []; for (let rowIdx = 0; rowIdx < puzzleGrid.length; rowIdx++) { const row = puzzleGrid[rowIdx]; if (!row) continue; const cellRow = []; for (let colIdx = 0; colIdx < row.length; colIdx++) { const cellData = row[colIdx]; let solutionVal = void 0; if (solutionGrid.length > rowIdx && solutionGrid[rowIdx] && solutionGrid[rowIdx].length > colIdx) { solutionVal = solutionGrid[rowIdx][colIdx]; } const cell = parseCellFromIpuz(cellData, solutionVal); cellRow.push(cell); } puzzle.puzzle.push(cellRow); } if ("clues" in data && data.clues) { puzzle.clues = parseCluesFromIpuz(data.clues); } for (const [key, value] of Object.entries(data)) { if (key.startsWith("http://") || key.startsWith("https://") || key.includes(":")) { puzzle.extensions[key] = value; } } return puzzle; } function convertIpuzToUnified(puzzle) { const grid = { width: puzzle.dimensions.width, height: puzzle.dimensions.height, cells: [] }; for (let y = 0; y < puzzle.dimensions.height; y++) { const row = []; for (let x = 0; x < puzzle.dimensions.width; x++) { const ipuzCell = puzzle.puzzle[y]?.[x]; const solutionCell = puzzle.solution?.[y]?.[x]; const cellNumber = ipuzCell?.number ? typeof ipuzCell.number === "string" ? parseInt(ipuzCell.number) : ipuzCell.number : void 0; const isBlack = ipuzCell?.type === "block" /* BLOCK */; const solution = typeof solutionCell === "string" ? solutionCell : ipuzCell?.value || void 0; const cell = { solution, number: cellNumber, isBlack }; if (ipuzCell?.style && typeof ipuzCell.style === "object" && ipuzCell.style.shapebg === "circle") { cell.isCircled = true; } if (ipuzCell?.style || ipuzCell?.continued || ipuzCell?.directions || ipuzCell?.given) { cell.additionalProperties = {}; if (ipuzCell.style && typeof ipuzCell.style === "object" && (Object.keys(ipuzCell.style).length > 1 || ipuzCell.style.shapebg !== "circle")) { cell.additionalProperties.style = ipuzCell.style; } if (ipuzCell.continued) { cell.additionalProperties.continued = ipuzCell.continued; } if (ipuzCell.directions) { cell.additionalProperties.directions = ipuzCell.directions; } if (ipuzCell.given !== void 0) { cell.additionalProperties.given = ipuzCell.given; } if (Object.keys(cell.additionalProperties).length === 0) { delete cell.additionalProperties; } } row.push(cell); } grid.cells.push(row); } const clues = { across: [], down: [] }; if (puzzle.clues?.Across) { for (const clue of puzzle.clues.Across) { const clueNumber = typeof clue.number === "string" ? parseInt(clue.number) : clue.number; if (!Number.isNaN(clueNumber) && clueNumber > 0) { clues.across.push({ number: clueNumber, text: clue.text || "" }); } } } if (puzzle.clues?.Down) { for (const clue of puzzle.clues.Down) { const clueNumber = typeof clue.number === "string" ? parseInt(clue.number) : clue.number; if (!Number.isNaN(clueNumber) && clueNumber > 0) { clues.down.push({ number: clueNumber, text: clue.text || "" }); } } } const result = { title: puzzle.title, author: puzzle.author, copyright: puzzle.copyright, notes: puzzle.notes, date: puzzle.date, grid, clues }; const additionalProps = {}; if (puzzle.difficulty) additionalProps.difficulty = puzzle.difficulty; if (puzzle.publisher) additionalProps.publisher = puzzle.publisher; if (puzzle.publication) additionalProps.publication = puzzle.publication; if (puzzle.url) additionalProps.url = puzzle.url; if (puzzle.uniqueId) additionalProps.uniqueId = puzzle.uniqueId; if (puzzle.intro) additionalProps.intro = puzzle.intro; if (puzzle.explanation) additionalProps.explanation = puzzle.explanation; if (puzzle.annotation) additionalProps.annotation = puzzle.annotation; if (puzzle.styles && Object.keys(puzzle.styles).length > 0) { additionalProps.styles = puzzle.styles; } if (puzzle.zones && puzzle.zones.length > 0) { additionalProps.zones = puzzle.zones; } if (Object.keys(puzzle.extensions).length > 0) { additionalProps.extensions = puzzle.extensions; } if (Object.keys(additionalProps).length > 0) { result.additionalProperties = additionalProps; } return result; } export { CellType, convertIpuzToUnified, parseIpuz }; //# sourceMappingURL=chunk-VJXS4EY5.mjs.map //# sourceMappingURL=chunk-VJXS4EY5.mjs.map