{"_id":"@alex-roc/xlsform2json","name":"@alex-roc/xlsform2json","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@alex-roc/xlsform2json","version":"0.1.0","description":"Parse XLSForm Excel files and convert them to a JSON structure consumable by React Hook Form + Yup","type":"module","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.cjs"},"./yup":{"types":"./dist/yup.d.ts","import":"./dist/yup.mjs","require":"./dist/yup.cjs"}},"main":"./dist/index.cjs","module":"./dist/index.mjs","types":"./dist/index.d.ts","license":"MIT","repository":{"type":"git","url":"git+https://github.com/alex-roc/xlsform2json.git"},"keywords":["xlsform","xls","form","json","react-hook-form","yup","survey"],"sideEffects":false,"scripts":{"build":"tsup","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit","prepublishOnly":"npm run build && npm run typecheck"},"dependencies":{"exceljs":"^4.4.0"},"peerDependencies":{"yup":">=1.0.0"},"peerDependenciesMeta":{"yup":{"optional":true}},"devDependencies":{"@types/node":"^20.x","typescript":"^5.x","tsup":"^8.x","vitest":"^1.x","yup":"^1.x"},"_id":"@alex-roc/xlsform2json@0.1.0","gitHead":"3d5ca307835ec7726ac90504f36545593d7e7446","bugs":{"url":"https://github.com/alex-roc/xlsform2json/issues"},"homepage":"https://github.com/alex-roc/xlsform2json#readme","_nodeVersion":"22.15.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-w2/zMW+3qjtLS45mKIGLillBx5BsaP7TKFVcWD0zj0TtsOziZcRgXNMpG8o0EClcmJG7i64G4OwTu5R4mucrUA==","shasum":"dd264f453791c7a2414ff44d31dd0a59ad2ba111","tarball":"https://registry.npmjs.org/@alex-roc/xlsform2json/-/xlsform2json-0.1.0.tgz","fileCount":17,"unpackedSize":91165,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCecGU8jukUwpZcOi4SZIo/hnwbmO3Soes4nmTNCyPnQwIgYJbRA1uDNXffuMVtFPYWsGTlDVQQH1y+mzYXOqj0q5c="}]},"_npmUser":{"name":"alex-roc","email":"alex.r.ojeda@gmail.com"},"directories":{},"maintainers":[{"name":"alex-roc","email":"alex.r.ojeda@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/xlsform2json_0.1.0_1773103302442_0.2824201377781246"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-10T00:41:42.345Z","0.1.0":"2026-03-10T00:41:42.580Z","modified":"2026-03-10T00:41:42.778Z"},"maintainers":[{"name":"alex-roc","email":"alex.r.ojeda@gmail.com"}],"description":"Parse XLSForm Excel files and convert them to a JSON structure consumable by React Hook Form + Yup","homepage":"https://github.com/alex-roc/xlsform2json#readme","keywords":["xlsform","xls","form","json","react-hook-form","yup","survey"],"repository":{"type":"git","url":"git+https://github.com/alex-roc/xlsform2json.git"},"bugs":{"url":"https://github.com/alex-roc/xlsform2json/issues"},"license":"MIT","readme":"# xlsform2json\n\nParse [XLSForm](https://xlsform.org) Excel files (`.xlsx`) and convert them to a JSON structure ready for use with **React Hook Form** + **Yup**.\n\n```bash\nnpm install @alex-roc/xlsform2json\n```\n\n---\n\n## Quick start\n\n```ts\nimport { convertXLSForm } from '@alex-roc/xlsform2json'\n\nconst form = await convertXLSForm('./my-form.xlsx')\n// or pass a Buffer / ArrayBuffer for browser / server use\n```\n\n**Output:**\n\n```json\n{\n  \"version\": \"1\",\n  \"fields\": [\n    {\n      \"type\": \"integer\",\n      \"name\": \"patient_age\",\n      \"label\": \"Patient age\",\n      \"hint\": \"In full years\",\n      \"visibleWhen\": null,\n      \"validation\": {\n        \"required\": true,\n        \"constraint\": \". >= 0 and . <= 150\",\n        \"constraintMessage\": \"Age must be 0-150\"\n      }\n    },\n    {\n      \"type\": \"select_one\",\n      \"name\": \"gender\",\n      \"label\": \"Gender\",\n      \"hint\": null,\n      \"listName\": \"gender\",\n      \"visibleWhen\": null,\n      \"validation\": { \"required\": true, \"constraint\": null, \"constraintMessage\": null }\n    }\n  ],\n  \"choices\": {\n    \"gender\": [\n      { \"value\": \"male\", \"label\": \"Male\" },\n      { \"value\": \"female\", \"label\": \"Female\" }\n    ]\n  }\n}\n```\n\n---\n\n## API\n\n### `convertXLSForm(source)`\n\n```ts\nimport { convertXLSForm } from '@alex-roc/xlsform2json'\n\nasync function convertXLSForm(\n  source: string | Buffer | ArrayBuffer\n): Promise<XLSFormJSON>\n```\n\n| Argument | Description |\n|---|---|\n| `string` | Absolute or relative file path (Node.js only) |\n| `Buffer` | Node.js Buffer of the `.xlsx` file |\n| `ArrayBuffer` | Browser `ArrayBuffer` (e.g. from `FileReader` or `fetch`) |\n\nThrows `XLSFormParseError` if required worksheets or columns are missing, or if an unsupported field type is encountered.\n\n---\n\n### `buildYupSchema(form)` — separate entry point\n\nRequires `yup >= 1.0.0` as a peer dependency.\n\n```ts\nimport { convertXLSForm } from '@alex-roc/xlsform2json'\nimport { buildYupSchema } from '@alex-roc/xlsform2json/yup'\nimport { useForm } from 'react-hook-form'\nimport { yupResolver } from '@hookform/resolvers/yup'\n\nconst form = await convertXLSForm('./my-form.xlsx')\nconst schema = buildYupSchema(form)\n\nconst { register, handleSubmit } = useForm({ resolver: yupResolver(schema) })\n```\n\n> **Note:** XLSForm `constraint` expressions are raw XPath strings and cannot be converted to Yup rules automatically in v1. A `console.warn` is emitted for each field that has a non-null `constraint` so you can add the validation manually.\n\n---\n\n## Supported field types\n\n| XLSForm type | JSON `type` | Extra fields |\n|---|---|---|\n| `text` | `text` | — |\n| `integer` | `integer` | — |\n| `decimal` | `decimal` | — |\n| `date` | `date` | — |\n| `time` | `time` | — |\n| `dateTime` | `dateTime` | — |\n| `geopoint` | `geopoint` | — |\n| `acknowledge` | `acknowledge` | — |\n| `note` | `note` | — |\n| `select_one <list>` | `select_one` | `listName` |\n| `select_multiple <list>` | `select_multiple` | `listName` |\n| `select_one_from_file <file>` | `select_one_from_file` | `sourceFile` |\n| `select_multiple_from_file <file>` | `select_multiple_from_file` | `sourceFile` |\n| `component <Name>` | `component` | `componentName`, `props` |\n\n---\n\n## Survey columns parsed\n\n| Column | Notes |\n|---|---|\n| `type` | Required |\n| `name` | Required. Used as the field identifier |\n| `label` | Required. Display text |\n| `hint` | Optional. `null` if empty |\n| `required` | `\"yes\"` → `true`, anything else → `false` |\n| `constraint` | Stored as raw string or `null` |\n| `constraint_message` | Stored as raw string or `null` |\n| `relevant` | Parsed into `visibleWhen` (see below) |\n| `parameters` | Parsed into `props` for `component` fields |\n\n---\n\n## `visibleWhen` — parsing the `relevant` column\n\nSimple and compound expressions with `and` / `or` are parsed automatically.\n\n**Single condition:**\n```json\n\"visibleWhen\": {\n  \"raw\": \"${likes_pizza} = 'yes'\",\n  \"conditions\": [{ \"field\": \"likes_pizza\", \"operator\": \"=\", \"value\": \"yes\" }],\n  \"logic\": null\n}\n```\n\n**Compound AND / OR:**\n```json\n\"visibleWhen\": {\n  \"raw\": \"${age} >= 18 and ${gender} = 'male'\",\n  \"conditions\": [\n    { \"field\": \"age\", \"operator\": \">=\", \"value\": \"18\" },\n    { \"field\": \"gender\", \"operator\": \"=\", \"value\": \"male\" }\n  ],\n  \"logic\": \"and\"\n}\n```\n\n**Unparseable expression** (complex XPath): `conditions` and `logic` are `null`, only `raw` is preserved.\n\n**No `relevant` column / empty cell:** `visibleWhen` is `null`.\n\nSupported operators: `=`, `!=`, `>`, `<`, `>=`, `<=`\n\n---\n\n## `component` type\n\nUse the `component` type to embed custom React components in your form. Define the component name and pass props via the `parameters` column using space-separated `key=value` pairs.\n\n**XLSForm row:**\n\n| type | name | label | parameters |\n|---|---|---|---|\n| `component ConsentBanner` | `consent_banner` | Informed Consent | `variant=warning imageUrl=consent.png` |\n\n**Output:**\n\n```json\n{\n  \"type\": \"component\",\n  \"name\": \"consent_banner\",\n  \"label\": \"Informed Consent\",\n  \"componentName\": \"ConsentBanner\",\n  \"props\": { \"variant\": \"warning\", \"imageUrl\": \"consent.png\" }\n}\n```\n\n---\n\n## Error handling\n\n```ts\nimport { convertXLSForm, XLSFormParseError } from '@alex-roc/xlsform2json'\n\ntry {\n  const form = await convertXLSForm('./my-form.xlsx')\n} catch (err) {\n  if (err instanceof XLSFormParseError) {\n    console.error(`Parse error at row ${err.rowNumber}: ${err.message}`)\n  }\n}\n```\n\n`XLSFormParseError` properties:\n\n| Property | Type | Description |\n|---|---|---|\n| `message` | `string` | Human-readable description |\n| `rowNumber` | `number \\| null` | Excel row number (1-based), if applicable |\n| `column` | `string \\| null` | Column name, if applicable |\n\n---\n\n## TypeScript types\n\nAll types are exported from the main entry point:\n\n```ts\nimport type {\n  XLSFormJSON,\n  FieldDefinition,\n  ValidationDescriptor,\n  VisibleWhen,\n  VisibleWhenCondition,\n  ChoiceOption,\n  ChoicesMap,\n  // Specific field types:\n  TextField,\n  IntegerField,\n  DecimalField,\n  SelectOneField,\n  SelectMultipleField,\n  SelectOneFromFileField,\n  SelectMultipleFromFileField,\n  ComponentField,\n} from '@alex-roc/xlsform2json'\n```\n\n`FieldDefinition` is a discriminated union — narrow by `field.type`:\n\n```ts\nfor (const field of form.fields) {\n  if (field.type === 'select_one') {\n    console.log(field.listName) // TypeScript knows this exists\n  }\n  if (field.type === 'component') {\n    console.log(field.componentName, field.props)\n  }\n}\n```\n\n---\n\n## v1 limitations\n\n- **Single language only.** Multi-language `label::English` columns are not supported yet.\n- **No XPath constraint evaluation.** `constraint` is preserved as a raw string.\n- **No `begin_group` / `begin_repeat` nesting.** Structural rows are skipped; all fields are returned flat.\n- **No media columns** (`image`, `audio`, `video`).\n\n---\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-8e490a0784db7cbe47d33207f13b150a"}