@ai-matrx/alchemy 0.16.0 → 0.17.5
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.
- package/CHANGELOG.md +43 -1
- package/dist/core.cjs.map +1 -1
- package/dist/core.js +2 -2
- package/dist/core.js.map +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/operate-capture.cjs +1 -1
- package/dist/operate-capture.cjs.map +1 -1
- package/dist/operate-capture.d.cts +30 -1
- package/dist/operate-capture.d.ts +30 -1
- package/dist/operate-capture.js +1 -1
- package/dist/operate-capture.js.map +1 -1
- package/dist/operate-document.cjs.map +1 -1
- package/dist/operate-document.js +1 -1
- package/dist/operate-document.js.map +1 -1
- package/dist/operate-pptx.cjs +151 -0
- package/dist/operate-pptx.cjs.map +1 -0
- package/dist/operate-pptx.d.cts +2695 -0
- package/dist/operate-pptx.d.ts +2695 -0
- package/dist/operate-pptx.js +151 -0
- package/dist/operate-pptx.js.map +1 -0
- package/dist/operate-read.cjs +2 -2
- package/dist/operate-read.cjs.map +1 -1
- package/dist/operate-read.js +2 -2
- package/dist/operate-read.js.map +1 -1
- package/dist/operate-rich-html.cjs.map +1 -1
- package/dist/operate-rich-html.js +1 -1
- package/dist/operate-rich-html.js.map +1 -1
- package/dist/operate-server.cjs.map +1 -1
- package/dist/operate-server.js +1 -1
- package/dist/operate-server.js.map +1 -1
- package/dist/operate-xlsx.cjs.map +1 -1
- package/dist/operate-xlsx.js +1 -1
- package/dist/operate-xlsx.js.map +1 -1
- package/dist/operate-zip.cjs +2 -0
- package/dist/operate-zip.cjs.map +1 -0
- package/dist/operate-zip.d.cts +35 -0
- package/dist/operate-zip.d.ts +35 -0
- package/dist/operate-zip.js +2 -0
- package/dist/operate-zip.js.map +1 -0
- package/dist/operate.cjs.map +1 -1
- package/dist/operate.js +2 -2
- package/dist/operate.js.map +1 -1
- package/package.json +26 -4
package/dist/operate-read.cjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"use strict";var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var operate_read_exports={};__export(operate_read_exports,{UnreadableFormatError:()=>UnreadableFormatError,fileFormatOf:()=>fileFormatOf,parseDelimited:()=>parseDelimited,readFile:()=>readFile,readWorkbook:()=>readWorkbook,toDelimitedText:()=>toDelimitedText});module.exports=__toCommonJS(operate_read_exports);var import_operate=require("@ai-matrx/alchemy/operate"),RECORD_SEP="",UNIT_SEP="",GUESS_DELIMITERS=[","," ","|",";",RECORD_SEP,UNIT_SEP],FLOAT=/^\s*-?(\d+\.?|\.\d+|\d+\.\d+)([eE][-+]?\d+)?\s*$/,ISO_DATE=/^((\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)))$/;function guessLinebreak(
|
|
1
|
+
"use strict";var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var operate_read_exports={};__export(operate_read_exports,{UnreadableFormatError:()=>UnreadableFormatError,fileFormatOf:()=>fileFormatOf,parseDelimited:()=>parseDelimited,readFile:()=>readFile,readWorkbook:()=>readWorkbook,toDelimitedText:()=>toDelimitedText});module.exports=__toCommonJS(operate_read_exports);var import_operate=require("@ai-matrx/alchemy/operate"),RECORD_SEP="",UNIT_SEP="",GUESS_DELIMITERS=[","," ","|",";",RECORD_SEP,UNIT_SEP],FLOAT=/^\s*-?(\d+\.?|\.\d+|\d+\.\d+)([eE][-+]?\d+)?\s*$/,ISO_DATE=/^((\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)))$/;function guessLinebreak(rawText){let text=rawText.includes('"')?rawText.replace(/"[^"]*"/g,'""'):rawText,crlf=text.indexOf(`\r
|
|
2
2
|
`),lf=text.indexOf(`
|
|
3
3
|
`),cr=text.indexOf("\r");return crlf!==-1&&crlf===cr?`\r
|
|
4
4
|
`:cr!==-1&&(lf===-1||cr<lf)?"\r":`
|
|
5
|
-
`}function tokenize(text,delimiter,linebreak,preview=1/0){let rows=[],errors=[];if(text==="")return{rows,errors};let row=[],i=0,n=text.length,dl=delimiter.length,ll=linebreak.length;for(;i<=n&&rows.length<preview;){if(text.startsWith('"',i)){let value="",j=i+1,closed=!1;for(;j<n;){let q=text.indexOf('"',j);if(q===-1)break;if(value+=text.slice(j,q),text[q+1]==='"'){value+='"',j=q+2;continue}let after=q+1;if(after>=n||text.startsWith(delimiter,after)||text.startsWith(linebreak,after)){closed=!0,j=after;break}errors.push({type:"Quotes",code:"InvalidQuotes",message:"Trailing quote on quoted field is malformed",row:rows.length}),value+='"',j=after}if(!closed)return errors.push({type:"Quotes",code:"MissingQuotes",message:"Quoted field unterminated",row:rows.length}),value+=text.slice(j),row.push(value),rows.push(row),{rows,errors};if(row.push(value),i=j,i>=n){rows.push(row);break}if(text.startsWith(delimiter,i)){if(i+=dl,i>=n){row.push(""),rows.push(row);break}continue}if(rows.push(row),row=[],i+=ll,i>=n){rows.push([""]);break}continue}let nextDelim=text.indexOf(delimiter,i),nextBreak=text.indexOf(linebreak,i);if(nextDelim!==-1&&(nextBreak===-1||nextDelim<nextBreak)){if(row.push(text.slice(i,nextDelim)),i=nextDelim+dl,i>=n){row.push(""),rows.push(row);break}continue}if(nextBreak!==-1){if(row.push(text.slice(i,nextBreak)),rows.push(row),row=[],i=nextBreak+ll,i>=n){rows.push([""]);break}continue}row.push(text.slice(i)),rows.push(row);break}return{rows,errors}}var isEmptyRow=(row,mode)=>mode==="greedy"?row.join("").trim()==="":row.length===1&&row[0].length===0;function guessDelimiter(text,linebreak,skip){let best,bestDelta,bestAvg;for(let delim of GUESS_DELIMITERS){let{rows}=tokenize(text,delim,linebreak,10),delta=0,avg=0,empty=0,prev;for(let row of rows){if(skip&&isEmptyRow(row,skip)){empty++;continue}let count=row.length;if(avg+=count,prev===void 0){prev=count;continue}count>0&&(delta+=Math.abs(count-prev),prev=count)}rows.length>0&&(avg/=rows.length-empty),(bestDelta===void 0||delta<=bestDelta)&&(bestAvg===void 0||avg>bestAvg)&&avg>1.99&&(bestDelta=delta,bestAvg=avg,best=delim)}return{delimiter:best??",",successful:best!==void 0}}function typed(value){if(value==="true"||value==="TRUE")return!0;if(value==="false"||value==="FALSE")return!1;if(FLOAT.test(value)){let num=parseFloat(value);return num>-(2**53)&&num<2**53?num:value}return ISO_DATE.test(value)?new Date(value):value===""?null:value}function parseDelimited(text,options={}){let input=text.charCodeAt(0)===65279?text.slice(1):text,linebreak=guessLinebreak(input),errors=[],delimiter=options.delimiter;if(!delimiter){let guess=guessDelimiter(input,linebreak,options.skipEmptyLines);delimiter=guess.delimiter,guess.successful||errors.push({type:"Delimiter",code:"UndetectableDelimiter",message:"Unable to auto-detect delimiting character; defaulted to ','"})}let tokens=tokenize(input,delimiter,linebreak);errors.unshift(...tokens.errors);let rows=tokens.rows;options.skipEmptyLines&&(rows=rows.filter(row=>!isEmptyRow(row,options.skipEmptyLines)));let meta={delimiter,linebreak};if(!options.header)return{data:options.dynamicTyping?rows.map(row=>row.map(typed)):rows,errors,meta};let[head=[],...body]=rows,seen=new Map,fields=head.map((raw,index)=>{let name=options.transformHeader?options.transformHeader(raw,index):raw,count=seen.get(name)??0;return seen.set(name,count+1),count===0?name:`${name}_${count}`});return meta.fields=fields,{data:body.map((row,index)=>{let record={};return row.forEach((value,j)=>{let cell=options.dynamicTyping?typed(value):value;j>=fields.length?(record.__parsed_extra??=[]).push(cell):record[fields[j]]=cell}),row.length>fields.length?errors.push({type:"FieldMismatch",code:"TooManyFields",message:`Too many fields: expected ${fields.length} fields but parsed ${row.length}`,row:index}):row.length<fields.length&&errors.push({type:"FieldMismatch",code:"TooFewFields",message:`Too few fields: expected ${fields.length} fields but parsed ${row.length}`,row:index}),record}),errors,meta}}function toDelimitedText(header,rows,options={}){let columns=header.map((label,i)=>({id:`c${i}`,label,path:`/c${i}`,visible:!0})),record=row=>{let out={};return header.forEach((label,i)=>{let v=Array.isArray(row)?row[i]:row[label];v!==void 0&&(out[`c${i}`]=v)}),out},captured=(0,import_operate.directSource)({kind:"rows",columns,rows:rows.map(record)}),draft=(0,import_operate.createDraft)((0,import_operate.projectSnapshot)(captured.snapshot,captured.projection));return(0,import_operate.serialize)(draft,options.format??"csv",{spreadsheetSafe:options.spreadsheetSafe??!1}).plainText}async function bytesOf(input){return typeof input=="string"?new TextEncoder().encode(input):input instanceof Uint8Array?input:input instanceof ArrayBuffer?new Uint8Array(input):new Uint8Array(await input.arrayBuffer())}async function readWorkbook(input,options={}){let bytes=await bytesOf(input),{readWorkbookBytes}=await import("@ai-matrx/alchemy/operate/formats/xlsx");return readWorkbookBytes(bytes,options)}var WORKBOOK_FORMATS=new Set(["xlsx","xls","ods","xlsm","xlsb"]),DELIMITED_FORMATS=new Set(["csv","tsv","txt","tab"]),UnreadableFormatError=class extends Error{constructor(format){super(`A "${format}" file is not a table we can read. Choose a CSV, TSV, or Excel file.`)
|
|
5
|
+
`}function tokenize(text,delimiter,linebreak,preview=1/0){let rows=[],errors=[];if(text==="")return{rows,errors};let row=[],i=0,n=text.length,dl=delimiter.length,ll=linebreak.length;for(;i<=n&&rows.length<preview;){if(text.startsWith('"',i)){let value="",j=i+1,closed=!1;for(;j<n;){let q=text.indexOf('"',j);if(q===-1)break;if(value+=text.slice(j,q),text[q+1]==='"'){value+='"',j=q+2;continue}let after=q+1;if(after>=n||text.startsWith(delimiter,after)||text.startsWith(linebreak,after)){closed=!0,j=after;break}errors.push({type:"Quotes",code:"InvalidQuotes",message:"Trailing quote on quoted field is malformed",row:rows.length}),value+='"',j=after}if(!closed)return errors.push({type:"Quotes",code:"MissingQuotes",message:"Quoted field unterminated",row:rows.length}),value+=text.slice(j),row.push(value),rows.push(row),{rows,errors};if(row.push(value),i=j,i>=n){rows.push(row);break}if(text.startsWith(delimiter,i)){if(i+=dl,i>=n){row.push(""),rows.push(row);break}continue}if(rows.push(row),row=[],i+=ll,i>=n){rows.push([""]);break}continue}let nextDelim=text.indexOf(delimiter,i),nextBreak=text.indexOf(linebreak,i);if(nextDelim!==-1&&(nextBreak===-1||nextDelim<nextBreak)){if(row.push(text.slice(i,nextDelim)),i=nextDelim+dl,i>=n){row.push(""),rows.push(row);break}continue}if(nextBreak!==-1){if(row.push(text.slice(i,nextBreak)),rows.push(row),row=[],i=nextBreak+ll,i>=n){rows.push([""]);break}continue}row.push(text.slice(i)),rows.push(row);break}return{rows,errors}}var isEmptyRow=(row,mode)=>mode==="greedy"?row.join("").trim()==="":row.length===1&&row[0].length===0;function guessDelimiter(text,linebreak,skip){let best,bestDelta,bestAvg;for(let delim of GUESS_DELIMITERS){let{rows}=tokenize(text,delim,linebreak,10),delta=0,avg=0,empty=0,prev;for(let row of rows){if(skip&&isEmptyRow(row,skip)){empty++;continue}let count=row.length;if(avg+=count,prev===void 0){prev=count;continue}count>0&&(delta+=Math.abs(count-prev),prev=count)}rows.length>0&&(avg/=rows.length-empty),(bestDelta===void 0||delta<=bestDelta)&&(bestAvg===void 0||avg>bestAvg)&&avg>1.99&&(bestDelta=delta,bestAvg=avg,best=delim)}return{delimiter:best??",",successful:best!==void 0}}function typed(value){if(value==="true"||value==="TRUE")return!0;if(value==="false"||value==="FALSE")return!1;if(FLOAT.test(value)){let num=parseFloat(value);return num>-(2**53)&&num<2**53?num:value}return ISO_DATE.test(value)?new Date(value):value===""?null:value}function parseDelimited(text,options={}){let input=text.charCodeAt(0)===65279?text.slice(1):text,linebreak=guessLinebreak(input),errors=[],delimiter=options.delimiter;if(!delimiter){let guess=guessDelimiter(input,linebreak,options.skipEmptyLines);delimiter=guess.delimiter,guess.successful||errors.push({type:"Delimiter",code:"UndetectableDelimiter",message:"Unable to auto-detect delimiting character; defaulted to ','"})}let tokens=tokenize(input,delimiter,linebreak);errors.unshift(...tokens.errors);let rows=tokens.rows;options.skipEmptyLines&&(rows=rows.filter(row=>!isEmptyRow(row,options.skipEmptyLines)));let meta={delimiter,linebreak};if(!options.header)return{data:options.dynamicTyping?rows.map(row=>row.map(typed)):rows,errors,meta};let[head=[],...body]=rows,seen=new Map,fields=head.map((raw,index)=>{let name=options.transformHeader?options.transformHeader(raw,index):raw,count=seen.get(name)??0;return seen.set(name,count+1),count===0?name:`${name}_${count}`});return meta.fields=fields,{data:body.map((row,index)=>{let record={};return row.forEach((value,j)=>{let cell=options.dynamicTyping?typed(value):value;j>=fields.length?(record.__parsed_extra??=[]).push(cell):record[fields[j]]=cell}),row.length>fields.length?errors.push({type:"FieldMismatch",code:"TooManyFields",message:`Too many fields: expected ${fields.length} fields but parsed ${row.length}`,row:index}):row.length<fields.length&&errors.push({type:"FieldMismatch",code:"TooFewFields",message:`Too few fields: expected ${fields.length} fields but parsed ${row.length}`,row:index}),record}),errors,meta}}function toDelimitedText(header,rows,options={}){let columns=header.map((label,i)=>({id:`c${i}`,label,path:`/c${i}`,visible:!0})),record=row=>{let out={};return header.forEach((label,i)=>{let v=Array.isArray(row)?row[i]:row[label];v!==void 0&&(out[`c${i}`]=v)}),out},captured=(0,import_operate.directSource)({kind:"rows",columns,rows:rows.map(record)}),draft=(0,import_operate.createDraft)((0,import_operate.projectSnapshot)(captured.snapshot,captured.projection));return(0,import_operate.serialize)(draft,options.format??"csv",{spreadsheetSafe:options.spreadsheetSafe??!1}).plainText}async function bytesOf(input){return typeof input=="string"?new TextEncoder().encode(input):input instanceof Uint8Array?input:input instanceof ArrayBuffer?new Uint8Array(input):new Uint8Array(await input.arrayBuffer())}async function readWorkbook(input,options={}){let bytes=await bytesOf(input),{readWorkbookBytes}=await import("@ai-matrx/alchemy/operate/formats/xlsx");return readWorkbookBytes(bytes,options)}var WORKBOOK_FORMATS=new Set(["xlsx","xls","ods","xlsm","xlsb"]),DELIMITED_FORMATS=new Set(["csv","tsv","txt","tab"]),UnreadableFormatError=class extends Error{format;constructor(format){super(`A "${format}" file is not a table we can read. Choose a CSV, TSV, or Excel file.`),this.format=format,this.name="UnreadableFormatError"}};function fileFormatOf(name){let m=/\.([a-z0-9]+)$/i.exec(name??"");return m?m[1].toLowerCase():""}async function readFile(input,options={}){let name=options.filename??(typeof Blob<"u"&&input instanceof Blob&&"name"in input?String(input.name):void 0),format=(options.format??fileFormatOf(name))||(typeof input=="string"?"csv":"");if(WORKBOOK_FORMATS.has(format)){let book=await readWorkbook(input,options.workbook);return{format:format==="xlsm"||format==="xlsb"?"xlsx":format,sheets:book.sheets.map(s=>({name:s.name,grid:s.grid(options.grid),merges:s.merges})),errors:[]}}if(!DELIMITED_FORMATS.has(format))throw new UnreadableFormatError(format||"unknown");let text=typeof input=="string"?input:new TextDecoder("utf-8").decode(await bytesOf(input)),parsed=parseDelimited(text,{...options,header:!1,...options.delimiter?{}:format==="tsv"||format==="tab"?{delimiter:" "}:{}});return{format:format==="tab"?"tsv":format,sheets:[{name:name?.replace(/\.[^.]+$/,"")||"Sheet1",grid:parsed.data,merges:[]}],errors:parsed.errors,delimiter:parsed.meta.delimiter}}
|
|
6
6
|
//# sourceMappingURL=operate-read.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/operate-read.ts","../src/operate/read.ts"],"sourcesContent":["/** `@ai-matrx/alchemy/operate/read` — the one parse door (CSV/TSV parser; workbooks through the lazy SheetJS leaf). */\nexport {\n UnreadableFormatError,\n fileFormatOf,\n parseDelimited,\n readFile,\n readWorkbook,\n toDelimitedText,\n type DelimitedCell,\n type DelimitedMeta,\n type DelimitedParseError,\n type DelimitedParseOptions,\n type DelimitedResult,\n type ReadFileOptions,\n type ReadInput,\n type TabularFile,\n type TabularFormat,\n type TabularSheet,\n type Workbook,\n type WorkbookCell,\n type WorkbookCellAddress,\n type WorkbookCellJson,\n type WorkbookCellRange,\n type WorkbookGridOptions,\n type WorkbookJson,\n type WorkbookReadOptions,\n type WorkbookRecordOptions,\n type WorkbookSheet,\n} from \"./operate/read\";\n","/**\n * `@ai-matrx/alchemy/operate/read` — the one parse door (ALC-19 T4–T12). Write has `buildFile`; read has `readFile`.\n *\n * One engine per format across read and write:\n * - CSV / TSV / delimited text: THIS module's parser (no library), the reverse of the `csv`/`tsv`\n * serializers in `./core`. `parseDelimited` is Papa Parse–compatible in the options the platform\n * uses (auto delimiter + line ending, quotes, `skipEmptyLines` true | \"greedy\", `header`,\n * `transformHeader`, `dynamicTyping`) and answers the same `{ data, errors, meta }` shape and\n * error codes, so a repointed importer keeps its behaviour.\n * - XLSX / XLS / ODS: SheetJS, the library the `xlsx` writer uses, loaded on first use from the\n * `/operate/formats/xlsx` leaf (`readWorkbook`). A caller that needs spreadsheet typing of a CSV\n * (dates, numbers, formulas as SheetJS reads them) reads it through `readWorkbook` too.\n */\n\n// Through the PUBLIC `/operate` subpath, so this entry shares the transfer core at runtime instead of bundling a copy.\nimport { createDraft, directSource, projectSnapshot, serialize, type Column, type Json } from \"@ai-matrx/alchemy/operate\";\n\n// ─── Delimited text ───────────────────────────────────────────────────────────────────────────────\n\nexport type DelimitedParseError = {\n type: \"Quotes\" | \"Delimiter\" | \"FieldMismatch\";\n code: \"MissingQuotes\" | \"InvalidQuotes\" | \"UndetectableDelimiter\" | \"TooFewFields\" | \"TooManyFields\";\n message: string;\n /** 0-based index of the data row the error belongs to (absent for whole-file errors). */\n row?: number;\n};\n\nexport type DelimitedParseOptions = {\n /** Field delimiter. Absent → guessed from `,` `\\t` `|` `;` and the ASCII record/unit separators; `,` when none fits. */\n delimiter?: string;\n /** true: an exactly empty line is dropped; \"greedy\": a line of only whitespace is dropped too. */\n skipEmptyLines?: boolean | \"greedy\";\n /** Numbers, `true`/`false` and ISO date-times become typed values; an empty field becomes null. */\n dynamicTyping?: boolean;\n /** Header mode only: rename a header (e.g. trim + lowercase). */\n transformHeader?(header: string, index: number): string;\n};\n\nexport type DelimitedCell = string | number | boolean | Date | null;\nexport type DelimitedMeta = { delimiter: string; linebreak: string; fields?: string[] };\nexport type DelimitedResult<T> = { data: T[]; errors: DelimitedParseError[]; meta: DelimitedMeta };\n\nconst RECORD_SEP = String.fromCharCode(30);\nconst UNIT_SEP = String.fromCharCode(31);\nconst GUESS_DELIMITERS = [\",\", \"\\t\", \"|\", \";\", RECORD_SEP, UNIT_SEP];\nconst FLOAT = /^\\s*-?(\\d+\\.?|\\.\\d+|\\d+\\.\\d+)([eE][-+]?\\d+)?\\s*$/;\nconst ISO_DATE =\n /^((\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z)))$/;\n\nfunction guessLinebreak(text: string): string {\n const crlf = text.indexOf(\"\\r\\n\");\n const lf = text.indexOf(\"\\n\");\n const cr = text.indexOf(\"\\r\");\n if (crlf !== -1 && crlf === cr) return \"\\r\\n\";\n if (cr !== -1 && (lf === -1 || cr < lf)) return \"\\r\";\n return \"\\n\";\n}\n\n/** Tokenize into rows of raw string fields; quote errors are reported against their data row. */\nfunction tokenize(text: string, delimiter: string, linebreak: string, preview = Infinity): { rows: string[][]; errors: DelimitedParseError[] } {\n const rows: string[][] = [];\n const errors: DelimitedParseError[] = [];\n if (text === \"\") return { rows, errors };\n let row: string[] = [];\n let i = 0;\n const n = text.length;\n const dl = delimiter.length;\n const ll = linebreak.length;\n while (i <= n && rows.length < preview) {\n if (text.startsWith('\"', i)) {\n // Quoted field: \"\" is a literal quote; the closing quote must precede a delimiter, a line break or the end.\n let value = \"\";\n let j = i + 1;\n let closed = false;\n while (j < n) {\n const q = text.indexOf('\"', j);\n if (q === -1) break;\n value += text.slice(j, q);\n if (text[q + 1] === '\"') {\n value += '\"';\n j = q + 2;\n continue;\n }\n const after = q + 1;\n if (after >= n || text.startsWith(delimiter, after) || text.startsWith(linebreak, after)) {\n closed = true;\n j = after;\n break;\n }\n errors.push({ type: \"Quotes\", code: \"InvalidQuotes\", message: \"Trailing quote on quoted field is malformed\", row: rows.length });\n value += '\"';\n j = after;\n }\n if (!closed) {\n errors.push({ type: \"Quotes\", code: \"MissingQuotes\", message: \"Quoted field unterminated\", row: rows.length });\n value += text.slice(j);\n row.push(value);\n rows.push(row);\n return { rows, errors };\n }\n row.push(value);\n i = j;\n if (i >= n) {\n rows.push(row);\n break;\n }\n if (text.startsWith(delimiter, i)) {\n i += dl;\n if (i >= n) {\n row.push(\"\");\n rows.push(row);\n break;\n }\n continue;\n }\n rows.push(row);\n row = [];\n i += ll;\n if (i >= n) {\n rows.push([\"\"]);\n break;\n }\n continue;\n }\n const nextDelim = text.indexOf(delimiter, i);\n const nextBreak = text.indexOf(linebreak, i);\n if (nextDelim !== -1 && (nextBreak === -1 || nextDelim < nextBreak)) {\n row.push(text.slice(i, nextDelim));\n i = nextDelim + dl;\n if (i >= n) {\n row.push(\"\");\n rows.push(row);\n break;\n }\n continue;\n }\n if (nextBreak !== -1) {\n row.push(text.slice(i, nextBreak));\n rows.push(row);\n row = [];\n i = nextBreak + ll;\n if (i >= n) {\n rows.push([\"\"]);\n break;\n }\n continue;\n }\n row.push(text.slice(i));\n rows.push(row);\n break;\n }\n return { rows, errors };\n}\n\nconst isEmptyRow = (row: readonly string[], mode: boolean | \"greedy\") =>\n mode === \"greedy\" ? row.join(\"\").trim() === \"\" : row.length === 1 && row[0]!.length === 0;\n\nfunction guessDelimiter(text: string, linebreak: string, skip: boolean | \"greedy\" | undefined): { delimiter: string; successful: boolean } {\n let best: string | undefined;\n let bestDelta: number | undefined;\n let bestAvg: number | undefined;\n for (const delim of GUESS_DELIMITERS) {\n const { rows } = tokenize(text, delim, linebreak, 10);\n let delta = 0;\n let avg = 0;\n let empty = 0;\n let prev: number | undefined;\n for (const row of rows) {\n if (skip && isEmptyRow(row, skip)) {\n empty++;\n continue;\n }\n const count = row.length;\n avg += count;\n if (prev === undefined) {\n prev = count;\n continue;\n }\n if (count > 0) {\n delta += Math.abs(count - prev);\n prev = count;\n }\n }\n if (rows.length > 0) avg /= rows.length - empty;\n if ((bestDelta === undefined || delta <= bestDelta) && (bestAvg === undefined || avg > bestAvg) && avg > 1.99) {\n bestDelta = delta;\n bestAvg = avg;\n best = delim;\n }\n }\n return { delimiter: best ?? \",\", successful: best !== undefined };\n}\n\nfunction typed(value: string): DelimitedCell {\n if (value === \"true\" || value === \"TRUE\") return true;\n if (value === \"false\" || value === \"FALSE\") return false;\n if (FLOAT.test(value)) {\n const num = parseFloat(value);\n if (num > -(2 ** 53) && num < 2 ** 53) return num;\n return value;\n }\n if (ISO_DATE.test(value)) return new Date(value);\n return value === \"\" ? null : value;\n}\n\n/** Parse delimited text into rows of fields (header row first). */\nexport function parseDelimited(text: string, options?: DelimitedParseOptions & { header?: false; dynamicTyping?: false }): DelimitedResult<string[]>;\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header?: false; dynamicTyping: true }): DelimitedResult<DelimitedCell[]>;\n/** Parse delimited text into records keyed by the (transformed) header row. Extra fields land in `__parsed_extra`. */\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header: true; dynamicTyping?: false }): DelimitedResult<Record<string, string>>;\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header: true; dynamicTyping: true }): DelimitedResult<Record<string, DelimitedCell>>;\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header?: boolean } = {}): DelimitedResult<unknown> {\n const input = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n const linebreak = guessLinebreak(input);\n const errors: DelimitedParseError[] = [];\n let delimiter = options.delimiter;\n if (!delimiter) {\n const guess = guessDelimiter(input, linebreak, options.skipEmptyLines);\n delimiter = guess.delimiter;\n if (!guess.successful)\n errors.push({ type: \"Delimiter\", code: \"UndetectableDelimiter\", message: \"Unable to auto-detect delimiting character; defaulted to ','\" });\n }\n const tokens = tokenize(input, delimiter, linebreak);\n errors.unshift(...tokens.errors);\n let rows = tokens.rows;\n if (options.skipEmptyLines) rows = rows.filter((row) => !isEmptyRow(row, options.skipEmptyLines!));\n const meta: DelimitedMeta = { delimiter, linebreak };\n if (!options.header) {\n return { data: options.dynamicTyping ? rows.map((row) => row.map(typed)) : rows, errors, meta };\n }\n const [head = [], ...body] = rows;\n const seen = new Map<string, number>();\n const fields = head.map((raw, index) => {\n const name = options.transformHeader ? options.transformHeader(raw, index) : raw;\n const count = seen.get(name) ?? 0;\n seen.set(name, count + 1);\n return count === 0 ? name : `${name}_${count}`;\n });\n meta.fields = fields;\n const data = body.map((row, index) => {\n const record: Record<string, unknown> = {};\n row.forEach((value, j) => {\n const cell = options.dynamicTyping ? typed(value) : value;\n if (j >= fields.length) {\n ((record.__parsed_extra as unknown[] | undefined) ??= []).push(cell);\n } else record[fields[j]!] = cell;\n });\n if (row.length > fields.length)\n errors.push({ type: \"FieldMismatch\", code: \"TooManyFields\", message: `Too many fields: expected ${fields.length} fields but parsed ${row.length}`, row: index });\n else if (row.length < fields.length)\n errors.push({ type: \"FieldMismatch\", code: \"TooFewFields\", message: `Too few fields: expected ${fields.length} fields but parsed ${row.length}`, row: index });\n return record;\n });\n return { data, errors, meta };\n}\n\n/**\n * Delimited text from a header row + body rows, through the SAME `csv`/`tsv` serializer the\n * Alchemy menu's copy and download use (quoting, nested values as compact JSON, `null` empty).\n * `spreadsheetSafe` (default false here) prefixes formula-looking text with `'`.\n */\nexport function toDelimitedText(\n header: readonly string[],\n rows: readonly (readonly (Json | undefined)[] | Readonly<Record<string, Json | undefined>>)[],\n options: { format?: \"csv\" | \"tsv\"; spreadsheetSafe?: boolean } = {},\n): string {\n const columns: Column[] = header.map((label, i) => ({ id: `c${i}`, label, path: `/c${i}`, visible: true }));\n const record = (row: (typeof rows)[number]): Record<string, Json> => {\n const out: Record<string, Json> = {};\n header.forEach((label, i) => {\n const v = Array.isArray(row) ? (row as readonly (Json | undefined)[])[i] : (row as Readonly<Record<string, Json | undefined>>)[label];\n if (v !== undefined) out[`c${i}`] = v;\n });\n return out;\n };\n const captured = directSource({ kind: \"rows\", columns, rows: rows.map(record) });\n const draft = createDraft(projectSnapshot(captured.snapshot, captured.projection));\n return serialize(draft, options.format ?? \"csv\", { spreadsheetSafe: options.spreadsheetSafe ?? false }).plainText;\n}\n\n// ─── Workbooks (the SheetJS leaf, loaded on first use) ───────────────────────────────────────────\n\nexport type WorkbookCellAddress = { r: number; c: number };\nexport type WorkbookCellRange = { s: WorkbookCellAddress; e: WorkbookCellAddress };\n/** A cell as the workbook engine reads it: type, raw value, formatted text, formula source. */\nexport type WorkbookCell = {\n t: \"s\" | \"n\" | \"b\" | \"d\" | \"e\" | \"z\";\n v?: string | number | boolean | Date;\n w?: string;\n f?: string;\n styled: boolean;\n};\n/** Grid shape options (header row included): `raw: false` → formatted text; `defval` fills gaps. */\nexport type WorkbookGridOptions = { raw?: boolean; defval?: unknown; blankrows?: boolean; dateNF?: string; rawNumbers?: boolean };\nexport type WorkbookRecordOptions = { raw?: boolean; defval?: unknown; blankrows?: boolean; dateNF?: string; rawNumbers?: boolean };\nexport interface WorkbookSheet {\n name: string;\n /** The used range, null for a sheet without one. */\n range: WorkbookCellRange | null;\n merges: WorkbookCellRange[];\n cell(r: number, c: number): WorkbookCell | null;\n cells(): Iterable<readonly [WorkbookCellAddress, WorkbookCell]>;\n grid(options?: WorkbookGridOptions): unknown[][];\n records(options?: WorkbookRecordOptions): Record<string, unknown>[];\n}\nexport interface Workbook {\n sheetNames: string[];\n sheets: WorkbookSheet[];\n sheet(name: string): WorkbookSheet | undefined;\n}\n/** `dates`: date cells as Date (`t: \"d\"`); `formulas`: keep formula source; `styles`: read cell styles. */\nexport type WorkbookReadOptions = { dates?: boolean; formulas?: boolean; styles?: boolean };\n\n/** A typed cell for the `xlsx-workbook` format; a bare string/number/boolean is a value cell. */\nexport type WorkbookCellJson = { t?: \"s\" | \"n\" | \"b\"; v?: string | number | boolean | null; f?: string };\n/** The `xlsx-workbook` payload value (`{ kind: \"registered\", format: \"workbook\", value }`). */\nexport type WorkbookJson = {\n sheets: { name: string; rows: (WorkbookCellJson | string | number | boolean | null)[][]; merges?: WorkbookCellRange[] }[];\n};\n\nexport type ReadInput = Blob | ArrayBuffer | Uint8Array | string;\n\nasync function bytesOf(input: ReadInput): Promise<Uint8Array> {\n if (typeof input === \"string\") return new TextEncoder().encode(input);\n if (input instanceof Uint8Array) return input;\n if (input instanceof ArrayBuffer) return new Uint8Array(input);\n return new Uint8Array(await input.arrayBuffer());\n}\n\n/** Read a spreadsheet (XLSX, XLS, ODS; CSV/TSV with spreadsheet typing) through the one workbook engine. */\nexport async function readWorkbook(input: ReadInput, options: WorkbookReadOptions = {}): Promise<Workbook> {\n const bytes = await bytesOf(input);\n const { readWorkbookBytes } = await import(\"@ai-matrx/alchemy/operate/formats/xlsx\");\n return readWorkbookBytes(bytes, options);\n}\n\n// ─── readFile: the door ──────────────────────────────────────────────────────────────────────────\n\nexport type TabularFormat = \"csv\" | \"tsv\" | \"txt\" | \"xlsx\" | \"xls\" | \"ods\";\nconst WORKBOOK_FORMATS: ReadonlySet<string> = new Set([\"xlsx\", \"xls\", \"ods\", \"xlsm\", \"xlsb\"]);\nconst DELIMITED_FORMATS: ReadonlySet<string> = new Set([\"csv\", \"tsv\", \"txt\", \"tab\"]);\n\nexport type ReadFileOptions = DelimitedParseOptions & {\n /** The file's format; absent → from `filename` (or a File's name) extension. */\n format?: TabularFormat | (string & {});\n filename?: string;\n /** Workbooks: the grid shape (`sheet_to_json` header-row semantics). */\n grid?: WorkbookGridOptions;\n /** Workbooks: read options. */\n workbook?: WorkbookReadOptions;\n};\nexport type TabularSheet = { name: string; grid: unknown[][]; merges: WorkbookCellRange[] };\nexport type TabularFile = { format: TabularFormat; sheets: TabularSheet[]; errors: DelimitedParseError[]; delimiter?: string };\n\nexport class UnreadableFormatError extends Error {\n constructor(readonly format: string) {\n super(`A \"${format}\" file is not a table we can read. Choose a CSV, TSV, or Excel file.`);\n this.name = \"UnreadableFormatError\";\n }\n}\n\n/** The format a file name declares (lower-case extension), or \"\" when it has none. */\nexport function fileFormatOf(name: string | undefined): string {\n const m = /\\.([a-z0-9]+)$/i.exec(name ?? \"\");\n return m ? m[1]!.toLowerCase() : \"\";\n}\n\n/**\n * Read a table file — CSV/TSV/plain text through the delimited parser, XLSX/XLS/ODS through the\n * workbook engine — into sheets of grids (header row first). Delimited text is one sheet.\n */\nexport async function readFile(input: ReadInput, options: ReadFileOptions = {}): Promise<TabularFile> {\n const name = options.filename ?? (typeof Blob !== \"undefined\" && input instanceof Blob && \"name\" in input ? String((input as File).name) : undefined);\n const format = (options.format ?? fileFormatOf(name)) || (typeof input === \"string\" ? \"csv\" : \"\");\n if (WORKBOOK_FORMATS.has(format)) {\n const book = await readWorkbook(input, options.workbook);\n return {\n format: (format === \"xlsm\" || format === \"xlsb\" ? \"xlsx\" : format) as TabularFormat,\n sheets: book.sheets.map((s) => ({ name: s.name, grid: s.grid(options.grid), merges: s.merges })),\n errors: [],\n };\n }\n if (!DELIMITED_FORMATS.has(format)) throw new UnreadableFormatError(format || \"unknown\");\n const text = typeof input === \"string\" ? input : new TextDecoder(\"utf-8\").decode(await bytesOf(input));\n const parsed = parseDelimited(text, {\n ...options,\n header: false,\n ...(options.delimiter ? {} : format === \"tsv\" || format === \"tab\" ? { delimiter: \"\\t\" } : {}),\n } as DelimitedParseOptions & { header: false; dynamicTyping?: false });\n return {\n format: (format === \"tab\" ? \"tsv\" : format) as TabularFormat,\n sheets: [{ name: name?.replace(/\\.[^.]+$/, \"\") || \"Sheet1\", grid: parsed.data, merges: [] }],\n errors: parsed.errors,\n delimiter: parsed.meta.delimiter,\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/operate-read.ts","../src/operate/read.ts"],"sourcesContent":["/** `@ai-matrx/alchemy/operate/read` — the one parse door (CSV/TSV parser; workbooks through the lazy SheetJS leaf). */\nexport {\n UnreadableFormatError,\n fileFormatOf,\n parseDelimited,\n readFile,\n readWorkbook,\n toDelimitedText,\n type DelimitedCell,\n type DelimitedMeta,\n type DelimitedParseError,\n type DelimitedParseOptions,\n type DelimitedResult,\n type ReadFileOptions,\n type ReadInput,\n type TabularFile,\n type TabularFormat,\n type TabularSheet,\n type Workbook,\n type WorkbookCell,\n type WorkbookCellAddress,\n type WorkbookCellJson,\n type WorkbookCellRange,\n type WorkbookGridOptions,\n type WorkbookJson,\n type WorkbookReadOptions,\n type WorkbookRecordOptions,\n type WorkbookSheet,\n} from \"./operate/read\";\n","/**\n * `@ai-matrx/alchemy/operate/read` — the one parse door (ALC-19 T4–T12). Write has `buildFile`; read has `readFile`.\n *\n * One engine per format across read and write:\n * - CSV / TSV / delimited text: THIS module's parser (no library), the reverse of the `csv`/`tsv`\n * serializers in `./core`. `parseDelimited` is Papa Parse–compatible in the options the platform\n * uses (auto delimiter + line ending, quotes, `skipEmptyLines` true | \"greedy\", `header`,\n * `transformHeader`, `dynamicTyping`) and answers the same `{ data, errors, meta }` shape and\n * error codes, so a repointed importer keeps its behaviour.\n * - XLSX / XLS / ODS: SheetJS, the library the `xlsx` writer uses, loaded on first use from the\n * `/operate/formats/xlsx` leaf (`readWorkbook`). A caller that needs spreadsheet typing of a CSV\n * (dates, numbers, formulas as SheetJS reads them) reads it through `readWorkbook` too.\n */\n\n// Through the PUBLIC `/operate` subpath, so this entry shares the transfer core at runtime instead of bundling a copy.\nimport { createDraft, directSource, projectSnapshot, serialize, type Column, type Json } from \"@ai-matrx/alchemy/operate\";\n\n// ─── Delimited text ───────────────────────────────────────────────────────────────────────────────\n\nexport type DelimitedParseError = {\n type: \"Quotes\" | \"Delimiter\" | \"FieldMismatch\";\n code: \"MissingQuotes\" | \"InvalidQuotes\" | \"UndetectableDelimiter\" | \"TooFewFields\" | \"TooManyFields\";\n message: string;\n /** 0-based index of the data row the error belongs to (absent for whole-file errors). */\n row?: number;\n};\n\nexport type DelimitedParseOptions = {\n /** Field delimiter. Absent → guessed from `,` `\\t` `|` `;` and the ASCII record/unit separators; `,` when none fits. */\n delimiter?: string;\n /** true: an exactly empty line is dropped; \"greedy\": a line of only whitespace is dropped too. */\n skipEmptyLines?: boolean | \"greedy\";\n /** Numbers, `true`/`false` and ISO date-times become typed values; an empty field becomes null. */\n dynamicTyping?: boolean;\n /** Header mode only: rename a header (e.g. trim + lowercase). */\n transformHeader?(header: string, index: number): string;\n};\n\nexport type DelimitedCell = string | number | boolean | Date | null;\nexport type DelimitedMeta = { delimiter: string; linebreak: string; fields?: string[] };\nexport type DelimitedResult<T> = { data: T[]; errors: DelimitedParseError[]; meta: DelimitedMeta };\n\nconst RECORD_SEP = String.fromCharCode(30);\nconst UNIT_SEP = String.fromCharCode(31);\nconst GUESS_DELIMITERS = [\",\", \"\\t\", \"|\", \";\", RECORD_SEP, UNIT_SEP];\nconst FLOAT = /^\\s*-?(\\d+\\.?|\\.\\d+|\\d+\\.\\d+)([eE][-+]?\\d+)?\\s*$/;\nconst ISO_DATE =\n /^((\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z)))$/;\n\nfunction guessLinebreak(rawText: string): string {\n // Quoted cells may hold any line break; only the ones between records say what the file uses.\n const text = rawText.includes('\"') ? rawText.replace(/\"[^\"]*\"/g, '\"\"') : rawText;\n const crlf = text.indexOf(\"\\r\\n\");\n const lf = text.indexOf(\"\\n\");\n const cr = text.indexOf(\"\\r\");\n if (crlf !== -1 && crlf === cr) return \"\\r\\n\";\n if (cr !== -1 && (lf === -1 || cr < lf)) return \"\\r\";\n return \"\\n\";\n}\n\n/** Tokenize into rows of raw string fields; quote errors are reported against their data row. */\nfunction tokenize(text: string, delimiter: string, linebreak: string, preview = Infinity): { rows: string[][]; errors: DelimitedParseError[] } {\n const rows: string[][] = [];\n const errors: DelimitedParseError[] = [];\n if (text === \"\") return { rows, errors };\n let row: string[] = [];\n let i = 0;\n const n = text.length;\n const dl = delimiter.length;\n const ll = linebreak.length;\n while (i <= n && rows.length < preview) {\n if (text.startsWith('\"', i)) {\n // Quoted field: \"\" is a literal quote; the closing quote must precede a delimiter, a line break or the end.\n let value = \"\";\n let j = i + 1;\n let closed = false;\n while (j < n) {\n const q = text.indexOf('\"', j);\n if (q === -1) break;\n value += text.slice(j, q);\n if (text[q + 1] === '\"') {\n value += '\"';\n j = q + 2;\n continue;\n }\n const after = q + 1;\n if (after >= n || text.startsWith(delimiter, after) || text.startsWith(linebreak, after)) {\n closed = true;\n j = after;\n break;\n }\n errors.push({ type: \"Quotes\", code: \"InvalidQuotes\", message: \"Trailing quote on quoted field is malformed\", row: rows.length });\n value += '\"';\n j = after;\n }\n if (!closed) {\n errors.push({ type: \"Quotes\", code: \"MissingQuotes\", message: \"Quoted field unterminated\", row: rows.length });\n value += text.slice(j);\n row.push(value);\n rows.push(row);\n return { rows, errors };\n }\n row.push(value);\n i = j;\n if (i >= n) {\n rows.push(row);\n break;\n }\n if (text.startsWith(delimiter, i)) {\n i += dl;\n if (i >= n) {\n row.push(\"\");\n rows.push(row);\n break;\n }\n continue;\n }\n rows.push(row);\n row = [];\n i += ll;\n if (i >= n) {\n rows.push([\"\"]);\n break;\n }\n continue;\n }\n const nextDelim = text.indexOf(delimiter, i);\n const nextBreak = text.indexOf(linebreak, i);\n if (nextDelim !== -1 && (nextBreak === -1 || nextDelim < nextBreak)) {\n row.push(text.slice(i, nextDelim));\n i = nextDelim + dl;\n if (i >= n) {\n row.push(\"\");\n rows.push(row);\n break;\n }\n continue;\n }\n if (nextBreak !== -1) {\n row.push(text.slice(i, nextBreak));\n rows.push(row);\n row = [];\n i = nextBreak + ll;\n if (i >= n) {\n rows.push([\"\"]);\n break;\n }\n continue;\n }\n row.push(text.slice(i));\n rows.push(row);\n break;\n }\n return { rows, errors };\n}\n\nconst isEmptyRow = (row: readonly string[], mode: boolean | \"greedy\") =>\n mode === \"greedy\" ? row.join(\"\").trim() === \"\" : row.length === 1 && row[0]!.length === 0;\n\nfunction guessDelimiter(text: string, linebreak: string, skip: boolean | \"greedy\" | undefined): { delimiter: string; successful: boolean } {\n let best: string | undefined;\n let bestDelta: number | undefined;\n let bestAvg: number | undefined;\n for (const delim of GUESS_DELIMITERS) {\n const { rows } = tokenize(text, delim, linebreak, 10);\n let delta = 0;\n let avg = 0;\n let empty = 0;\n let prev: number | undefined;\n for (const row of rows) {\n if (skip && isEmptyRow(row, skip)) {\n empty++;\n continue;\n }\n const count = row.length;\n avg += count;\n if (prev === undefined) {\n prev = count;\n continue;\n }\n if (count > 0) {\n delta += Math.abs(count - prev);\n prev = count;\n }\n }\n if (rows.length > 0) avg /= rows.length - empty;\n if ((bestDelta === undefined || delta <= bestDelta) && (bestAvg === undefined || avg > bestAvg) && avg > 1.99) {\n bestDelta = delta;\n bestAvg = avg;\n best = delim;\n }\n }\n return { delimiter: best ?? \",\", successful: best !== undefined };\n}\n\nfunction typed(value: string): DelimitedCell {\n if (value === \"true\" || value === \"TRUE\") return true;\n if (value === \"false\" || value === \"FALSE\") return false;\n if (FLOAT.test(value)) {\n const num = parseFloat(value);\n if (num > -(2 ** 53) && num < 2 ** 53) return num;\n return value;\n }\n if (ISO_DATE.test(value)) return new Date(value);\n return value === \"\" ? null : value;\n}\n\n/** Parse delimited text into rows of fields (header row first). */\nexport function parseDelimited(text: string, options?: DelimitedParseOptions & { header?: false; dynamicTyping?: false }): DelimitedResult<string[]>;\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header?: false; dynamicTyping: true }): DelimitedResult<DelimitedCell[]>;\n/** Parse delimited text into records keyed by the (transformed) header row. Extra fields land in `__parsed_extra`. */\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header: true; dynamicTyping?: false }): DelimitedResult<Record<string, string>>;\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header: true; dynamicTyping: true }): DelimitedResult<Record<string, DelimitedCell>>;\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header?: boolean } = {}): DelimitedResult<unknown> {\n const input = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n const linebreak = guessLinebreak(input);\n const errors: DelimitedParseError[] = [];\n let delimiter = options.delimiter;\n if (!delimiter) {\n const guess = guessDelimiter(input, linebreak, options.skipEmptyLines);\n delimiter = guess.delimiter;\n if (!guess.successful)\n errors.push({ type: \"Delimiter\", code: \"UndetectableDelimiter\", message: \"Unable to auto-detect delimiting character; defaulted to ','\" });\n }\n const tokens = tokenize(input, delimiter, linebreak);\n errors.unshift(...tokens.errors);\n let rows = tokens.rows;\n if (options.skipEmptyLines) rows = rows.filter((row) => !isEmptyRow(row, options.skipEmptyLines!));\n const meta: DelimitedMeta = { delimiter, linebreak };\n if (!options.header) {\n return { data: options.dynamicTyping ? rows.map((row) => row.map(typed)) : rows, errors, meta };\n }\n const [head = [], ...body] = rows;\n const seen = new Map<string, number>();\n const fields = head.map((raw, index) => {\n const name = options.transformHeader ? options.transformHeader(raw, index) : raw;\n const count = seen.get(name) ?? 0;\n seen.set(name, count + 1);\n return count === 0 ? name : `${name}_${count}`;\n });\n meta.fields = fields;\n const data = body.map((row, index) => {\n const record: Record<string, unknown> = {};\n row.forEach((value, j) => {\n const cell = options.dynamicTyping ? typed(value) : value;\n if (j >= fields.length) {\n ((record.__parsed_extra as unknown[] | undefined) ??= []).push(cell);\n } else record[fields[j]!] = cell;\n });\n if (row.length > fields.length)\n errors.push({ type: \"FieldMismatch\", code: \"TooManyFields\", message: `Too many fields: expected ${fields.length} fields but parsed ${row.length}`, row: index });\n else if (row.length < fields.length)\n errors.push({ type: \"FieldMismatch\", code: \"TooFewFields\", message: `Too few fields: expected ${fields.length} fields but parsed ${row.length}`, row: index });\n return record;\n });\n return { data, errors, meta };\n}\n\n/**\n * Delimited text from a header row + body rows, through the SAME `csv`/`tsv` serializer the\n * Alchemy menu's copy and download use (quoting, nested values as compact JSON, `null` empty).\n * `spreadsheetSafe` (default false here) prefixes formula-looking text with `'`.\n */\nexport function toDelimitedText(\n header: readonly string[],\n rows: readonly (readonly (Json | undefined)[] | Readonly<Record<string, Json | undefined>>)[],\n options: { format?: \"csv\" | \"tsv\"; spreadsheetSafe?: boolean } = {},\n): string {\n const columns: Column[] = header.map((label, i) => ({ id: `c${i}`, label, path: `/c${i}`, visible: true }));\n const record = (row: (typeof rows)[number]): Record<string, Json> => {\n const out: Record<string, Json> = {};\n header.forEach((label, i) => {\n const v = Array.isArray(row) ? (row as readonly (Json | undefined)[])[i] : (row as Readonly<Record<string, Json | undefined>>)[label];\n if (v !== undefined) out[`c${i}`] = v;\n });\n return out;\n };\n const captured = directSource({ kind: \"rows\", columns, rows: rows.map(record) });\n const draft = createDraft(projectSnapshot(captured.snapshot, captured.projection));\n return serialize(draft, options.format ?? \"csv\", { spreadsheetSafe: options.spreadsheetSafe ?? false }).plainText;\n}\n\n// ─── Workbooks (the SheetJS leaf, loaded on first use) ───────────────────────────────────────────\n\nexport type WorkbookCellAddress = { r: number; c: number };\nexport type WorkbookCellRange = { s: WorkbookCellAddress; e: WorkbookCellAddress };\n/** A cell as the workbook engine reads it: type, raw value, formatted text, formula source. */\nexport type WorkbookCell = {\n t: \"s\" | \"n\" | \"b\" | \"d\" | \"e\" | \"z\";\n v?: string | number | boolean | Date;\n w?: string;\n f?: string;\n styled: boolean;\n};\n/** Grid shape options (header row included): `raw: false` → formatted text; `defval` fills gaps. */\nexport type WorkbookGridOptions = { raw?: boolean; defval?: unknown; blankrows?: boolean; dateNF?: string; rawNumbers?: boolean };\nexport type WorkbookRecordOptions = { raw?: boolean; defval?: unknown; blankrows?: boolean; dateNF?: string; rawNumbers?: boolean };\nexport interface WorkbookSheet {\n name: string;\n /** The used range, null for a sheet without one. */\n range: WorkbookCellRange | null;\n merges: WorkbookCellRange[];\n cell(r: number, c: number): WorkbookCell | null;\n cells(): Iterable<readonly [WorkbookCellAddress, WorkbookCell]>;\n grid(options?: WorkbookGridOptions): unknown[][];\n records(options?: WorkbookRecordOptions): Record<string, unknown>[];\n}\nexport interface Workbook {\n sheetNames: string[];\n sheets: WorkbookSheet[];\n sheet(name: string): WorkbookSheet | undefined;\n}\n/** `dates`: date cells as Date (`t: \"d\"`); `formulas`: keep formula source; `styles`: read cell styles. */\nexport type WorkbookReadOptions = { dates?: boolean; formulas?: boolean; styles?: boolean };\n\n/** A typed cell for the `xlsx-workbook` format; a bare string/number/boolean is a value cell. */\nexport type WorkbookCellJson = { t?: \"s\" | \"n\" | \"b\"; v?: string | number | boolean | null; f?: string };\n/** The `xlsx-workbook` payload value (`{ kind: \"registered\", format: \"workbook\", value }`). */\nexport type WorkbookJson = {\n sheets: { name: string; rows: (WorkbookCellJson | string | number | boolean | null)[][]; merges?: WorkbookCellRange[] }[];\n};\n\nexport type ReadInput = Blob | ArrayBuffer | Uint8Array | string;\n\nasync function bytesOf(input: ReadInput): Promise<Uint8Array> {\n if (typeof input === \"string\") return new TextEncoder().encode(input);\n if (input instanceof Uint8Array) return input;\n if (input instanceof ArrayBuffer) return new Uint8Array(input);\n return new Uint8Array(await input.arrayBuffer());\n}\n\n/** Read a spreadsheet (XLSX, XLS, ODS; CSV/TSV with spreadsheet typing) through the one workbook engine. */\nexport async function readWorkbook(input: ReadInput, options: WorkbookReadOptions = {}): Promise<Workbook> {\n const bytes = await bytesOf(input);\n const { readWorkbookBytes } = await import(\"@ai-matrx/alchemy/operate/formats/xlsx\");\n return readWorkbookBytes(bytes, options);\n}\n\n// ─── readFile: the door ──────────────────────────────────────────────────────────────────────────\n\nexport type TabularFormat = \"csv\" | \"tsv\" | \"txt\" | \"xlsx\" | \"xls\" | \"ods\";\nconst WORKBOOK_FORMATS: ReadonlySet<string> = new Set([\"xlsx\", \"xls\", \"ods\", \"xlsm\", \"xlsb\"]);\nconst DELIMITED_FORMATS: ReadonlySet<string> = new Set([\"csv\", \"tsv\", \"txt\", \"tab\"]);\n\nexport type ReadFileOptions = DelimitedParseOptions & {\n /** The file's format; absent → from `filename` (or a File's name) extension. */\n format?: TabularFormat | (string & {});\n filename?: string;\n /** Workbooks: the grid shape (`sheet_to_json` header-row semantics). */\n grid?: WorkbookGridOptions;\n /** Workbooks: read options. */\n workbook?: WorkbookReadOptions;\n};\nexport type TabularSheet = { name: string; grid: unknown[][]; merges: WorkbookCellRange[] };\nexport type TabularFile = { format: TabularFormat; sheets: TabularSheet[]; errors: DelimitedParseError[]; delimiter?: string };\n\nexport class UnreadableFormatError extends Error {\n readonly format: string;\n constructor(format: string) {\n super(`A \"${format}\" file is not a table we can read. Choose a CSV, TSV, or Excel file.`);\n this.format = format;\n this.name = \"UnreadableFormatError\";\n }\n}\n\n/** The format a file name declares (lower-case extension), or \"\" when it has none. */\nexport function fileFormatOf(name: string | undefined): string {\n const m = /\\.([a-z0-9]+)$/i.exec(name ?? \"\");\n return m ? m[1]!.toLowerCase() : \"\";\n}\n\n/**\n * Read a table file — CSV/TSV/plain text through the delimited parser, XLSX/XLS/ODS through the\n * workbook engine — into sheets of grids (header row first). Delimited text is one sheet.\n */\nexport async function readFile(input: ReadInput, options: ReadFileOptions = {}): Promise<TabularFile> {\n const name = options.filename ?? (typeof Blob !== \"undefined\" && input instanceof Blob && \"name\" in input ? String((input as File).name) : undefined);\n const format = (options.format ?? fileFormatOf(name)) || (typeof input === \"string\" ? \"csv\" : \"\");\n if (WORKBOOK_FORMATS.has(format)) {\n const book = await readWorkbook(input, options.workbook);\n return {\n format: (format === \"xlsm\" || format === \"xlsb\" ? \"xlsx\" : format) as TabularFormat,\n sheets: book.sheets.map((s) => ({ name: s.name, grid: s.grid(options.grid), merges: s.merges })),\n errors: [],\n };\n }\n if (!DELIMITED_FORMATS.has(format)) throw new UnreadableFormatError(format || \"unknown\");\n const text = typeof input === \"string\" ? input : new TextDecoder(\"utf-8\").decode(await bytesOf(input));\n const parsed = parseDelimited(text, {\n ...options,\n header: false,\n ...(options.delimiter ? {} : format === \"tsv\" || format === \"tab\" ? { delimiter: \"\\t\" } : {}),\n } as DelimitedParseOptions & { header: false; dynamicTyping?: false });\n return {\n format: (format === \"tab\" ? \"tsv\" : format) as TabularFormat,\n sheets: [{ name: name?.replace(/\\.[^.]+$/, \"\") || \"Sheet1\", grid: parsed.data, merges: [] }],\n errors: parsed.errors,\n delimiter: parsed.meta.delimiter,\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/operate-read.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{createDraft,directSource,projectSnapshot,serialize}from"@ai-matrx/alchemy/operate";var RECORD_SEP="",UNIT_SEP="",GUESS_DELIMITERS=[","," ","|",";",RECORD_SEP,UNIT_SEP],FLOAT=/^\s*-?(\d+\.?|\.\d+|\d+\.\d+)([eE][-+]?\d+)?\s*$/,ISO_DATE=/^((\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)))$/;function guessLinebreak(
|
|
1
|
+
import{createDraft,directSource,projectSnapshot,serialize}from"@ai-matrx/alchemy/operate";var RECORD_SEP="",UNIT_SEP="",GUESS_DELIMITERS=[","," ","|",";",RECORD_SEP,UNIT_SEP],FLOAT=/^\s*-?(\d+\.?|\.\d+|\d+\.\d+)([eE][-+]?\d+)?\s*$/,ISO_DATE=/^((\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)))$/;function guessLinebreak(rawText){let text=rawText.includes('"')?rawText.replace(/"[^"]*"/g,'""'):rawText,crlf=text.indexOf(`\r
|
|
2
2
|
`),lf=text.indexOf(`
|
|
3
3
|
`),cr=text.indexOf("\r");return crlf!==-1&&crlf===cr?`\r
|
|
4
4
|
`:cr!==-1&&(lf===-1||cr<lf)?"\r":`
|
|
5
|
-
`}function tokenize(text,delimiter,linebreak,preview=1/0){let rows=[],errors=[];if(text==="")return{rows,errors};let row=[],i=0,n=text.length,dl=delimiter.length,ll=linebreak.length;for(;i<=n&&rows.length<preview;){if(text.startsWith('"',i)){let value="",j=i+1,closed=!1;for(;j<n;){let q=text.indexOf('"',j);if(q===-1)break;if(value+=text.slice(j,q),text[q+1]==='"'){value+='"',j=q+2;continue}let after=q+1;if(after>=n||text.startsWith(delimiter,after)||text.startsWith(linebreak,after)){closed=!0,j=after;break}errors.push({type:"Quotes",code:"InvalidQuotes",message:"Trailing quote on quoted field is malformed",row:rows.length}),value+='"',j=after}if(!closed)return errors.push({type:"Quotes",code:"MissingQuotes",message:"Quoted field unterminated",row:rows.length}),value+=text.slice(j),row.push(value),rows.push(row),{rows,errors};if(row.push(value),i=j,i>=n){rows.push(row);break}if(text.startsWith(delimiter,i)){if(i+=dl,i>=n){row.push(""),rows.push(row);break}continue}if(rows.push(row),row=[],i+=ll,i>=n){rows.push([""]);break}continue}let nextDelim=text.indexOf(delimiter,i),nextBreak=text.indexOf(linebreak,i);if(nextDelim!==-1&&(nextBreak===-1||nextDelim<nextBreak)){if(row.push(text.slice(i,nextDelim)),i=nextDelim+dl,i>=n){row.push(""),rows.push(row);break}continue}if(nextBreak!==-1){if(row.push(text.slice(i,nextBreak)),rows.push(row),row=[],i=nextBreak+ll,i>=n){rows.push([""]);break}continue}row.push(text.slice(i)),rows.push(row);break}return{rows,errors}}var isEmptyRow=(row,mode)=>mode==="greedy"?row.join("").trim()==="":row.length===1&&row[0].length===0;function guessDelimiter(text,linebreak,skip){let best,bestDelta,bestAvg;for(let delim of GUESS_DELIMITERS){let{rows}=tokenize(text,delim,linebreak,10),delta=0,avg=0,empty=0,prev;for(let row of rows){if(skip&&isEmptyRow(row,skip)){empty++;continue}let count=row.length;if(avg+=count,prev===void 0){prev=count;continue}count>0&&(delta+=Math.abs(count-prev),prev=count)}rows.length>0&&(avg/=rows.length-empty),(bestDelta===void 0||delta<=bestDelta)&&(bestAvg===void 0||avg>bestAvg)&&avg>1.99&&(bestDelta=delta,bestAvg=avg,best=delim)}return{delimiter:best??",",successful:best!==void 0}}function typed(value){if(value==="true"||value==="TRUE")return!0;if(value==="false"||value==="FALSE")return!1;if(FLOAT.test(value)){let num=parseFloat(value);return num>-(2**53)&&num<2**53?num:value}return ISO_DATE.test(value)?new Date(value):value===""?null:value}function parseDelimited(text,options={}){let input=text.charCodeAt(0)===65279?text.slice(1):text,linebreak=guessLinebreak(input),errors=[],delimiter=options.delimiter;if(!delimiter){let guess=guessDelimiter(input,linebreak,options.skipEmptyLines);delimiter=guess.delimiter,guess.successful||errors.push({type:"Delimiter",code:"UndetectableDelimiter",message:"Unable to auto-detect delimiting character; defaulted to ','"})}let tokens=tokenize(input,delimiter,linebreak);errors.unshift(...tokens.errors);let rows=tokens.rows;options.skipEmptyLines&&(rows=rows.filter(row=>!isEmptyRow(row,options.skipEmptyLines)));let meta={delimiter,linebreak};if(!options.header)return{data:options.dynamicTyping?rows.map(row=>row.map(typed)):rows,errors,meta};let[head=[],...body]=rows,seen=new Map,fields=head.map((raw,index)=>{let name=options.transformHeader?options.transformHeader(raw,index):raw,count=seen.get(name)??0;return seen.set(name,count+1),count===0?name:`${name}_${count}`});return meta.fields=fields,{data:body.map((row,index)=>{let record={};return row.forEach((value,j)=>{let cell=options.dynamicTyping?typed(value):value;j>=fields.length?(record.__parsed_extra??=[]).push(cell):record[fields[j]]=cell}),row.length>fields.length?errors.push({type:"FieldMismatch",code:"TooManyFields",message:`Too many fields: expected ${fields.length} fields but parsed ${row.length}`,row:index}):row.length<fields.length&&errors.push({type:"FieldMismatch",code:"TooFewFields",message:`Too few fields: expected ${fields.length} fields but parsed ${row.length}`,row:index}),record}),errors,meta}}function toDelimitedText(header,rows,options={}){let columns=header.map((label,i)=>({id:`c${i}`,label,path:`/c${i}`,visible:!0})),record=row=>{let out={};return header.forEach((label,i)=>{let v=Array.isArray(row)?row[i]:row[label];v!==void 0&&(out[`c${i}`]=v)}),out},captured=directSource({kind:"rows",columns,rows:rows.map(record)}),draft=createDraft(projectSnapshot(captured.snapshot,captured.projection));return serialize(draft,options.format??"csv",{spreadsheetSafe:options.spreadsheetSafe??!1}).plainText}async function bytesOf(input){return typeof input=="string"?new TextEncoder().encode(input):input instanceof Uint8Array?input:input instanceof ArrayBuffer?new Uint8Array(input):new Uint8Array(await input.arrayBuffer())}async function readWorkbook(input,options={}){let bytes=await bytesOf(input),{readWorkbookBytes}=await import("@ai-matrx/alchemy/operate/formats/xlsx");return readWorkbookBytes(bytes,options)}var WORKBOOK_FORMATS=new Set(["xlsx","xls","ods","xlsm","xlsb"]),DELIMITED_FORMATS=new Set(["csv","tsv","txt","tab"]),UnreadableFormatError=class extends Error{constructor(format){super(`A "${format}" file is not a table we can read. Choose a CSV, TSV, or Excel file.`)
|
|
5
|
+
`}function tokenize(text,delimiter,linebreak,preview=1/0){let rows=[],errors=[];if(text==="")return{rows,errors};let row=[],i=0,n=text.length,dl=delimiter.length,ll=linebreak.length;for(;i<=n&&rows.length<preview;){if(text.startsWith('"',i)){let value="",j=i+1,closed=!1;for(;j<n;){let q=text.indexOf('"',j);if(q===-1)break;if(value+=text.slice(j,q),text[q+1]==='"'){value+='"',j=q+2;continue}let after=q+1;if(after>=n||text.startsWith(delimiter,after)||text.startsWith(linebreak,after)){closed=!0,j=after;break}errors.push({type:"Quotes",code:"InvalidQuotes",message:"Trailing quote on quoted field is malformed",row:rows.length}),value+='"',j=after}if(!closed)return errors.push({type:"Quotes",code:"MissingQuotes",message:"Quoted field unterminated",row:rows.length}),value+=text.slice(j),row.push(value),rows.push(row),{rows,errors};if(row.push(value),i=j,i>=n){rows.push(row);break}if(text.startsWith(delimiter,i)){if(i+=dl,i>=n){row.push(""),rows.push(row);break}continue}if(rows.push(row),row=[],i+=ll,i>=n){rows.push([""]);break}continue}let nextDelim=text.indexOf(delimiter,i),nextBreak=text.indexOf(linebreak,i);if(nextDelim!==-1&&(nextBreak===-1||nextDelim<nextBreak)){if(row.push(text.slice(i,nextDelim)),i=nextDelim+dl,i>=n){row.push(""),rows.push(row);break}continue}if(nextBreak!==-1){if(row.push(text.slice(i,nextBreak)),rows.push(row),row=[],i=nextBreak+ll,i>=n){rows.push([""]);break}continue}row.push(text.slice(i)),rows.push(row);break}return{rows,errors}}var isEmptyRow=(row,mode)=>mode==="greedy"?row.join("").trim()==="":row.length===1&&row[0].length===0;function guessDelimiter(text,linebreak,skip){let best,bestDelta,bestAvg;for(let delim of GUESS_DELIMITERS){let{rows}=tokenize(text,delim,linebreak,10),delta=0,avg=0,empty=0,prev;for(let row of rows){if(skip&&isEmptyRow(row,skip)){empty++;continue}let count=row.length;if(avg+=count,prev===void 0){prev=count;continue}count>0&&(delta+=Math.abs(count-prev),prev=count)}rows.length>0&&(avg/=rows.length-empty),(bestDelta===void 0||delta<=bestDelta)&&(bestAvg===void 0||avg>bestAvg)&&avg>1.99&&(bestDelta=delta,bestAvg=avg,best=delim)}return{delimiter:best??",",successful:best!==void 0}}function typed(value){if(value==="true"||value==="TRUE")return!0;if(value==="false"||value==="FALSE")return!1;if(FLOAT.test(value)){let num=parseFloat(value);return num>-(2**53)&&num<2**53?num:value}return ISO_DATE.test(value)?new Date(value):value===""?null:value}function parseDelimited(text,options={}){let input=text.charCodeAt(0)===65279?text.slice(1):text,linebreak=guessLinebreak(input),errors=[],delimiter=options.delimiter;if(!delimiter){let guess=guessDelimiter(input,linebreak,options.skipEmptyLines);delimiter=guess.delimiter,guess.successful||errors.push({type:"Delimiter",code:"UndetectableDelimiter",message:"Unable to auto-detect delimiting character; defaulted to ','"})}let tokens=tokenize(input,delimiter,linebreak);errors.unshift(...tokens.errors);let rows=tokens.rows;options.skipEmptyLines&&(rows=rows.filter(row=>!isEmptyRow(row,options.skipEmptyLines)));let meta={delimiter,linebreak};if(!options.header)return{data:options.dynamicTyping?rows.map(row=>row.map(typed)):rows,errors,meta};let[head=[],...body]=rows,seen=new Map,fields=head.map((raw,index)=>{let name=options.transformHeader?options.transformHeader(raw,index):raw,count=seen.get(name)??0;return seen.set(name,count+1),count===0?name:`${name}_${count}`});return meta.fields=fields,{data:body.map((row,index)=>{let record={};return row.forEach((value,j)=>{let cell=options.dynamicTyping?typed(value):value;j>=fields.length?(record.__parsed_extra??=[]).push(cell):record[fields[j]]=cell}),row.length>fields.length?errors.push({type:"FieldMismatch",code:"TooManyFields",message:`Too many fields: expected ${fields.length} fields but parsed ${row.length}`,row:index}):row.length<fields.length&&errors.push({type:"FieldMismatch",code:"TooFewFields",message:`Too few fields: expected ${fields.length} fields but parsed ${row.length}`,row:index}),record}),errors,meta}}function toDelimitedText(header,rows,options={}){let columns=header.map((label,i)=>({id:`c${i}`,label,path:`/c${i}`,visible:!0})),record=row=>{let out={};return header.forEach((label,i)=>{let v=Array.isArray(row)?row[i]:row[label];v!==void 0&&(out[`c${i}`]=v)}),out},captured=directSource({kind:"rows",columns,rows:rows.map(record)}),draft=createDraft(projectSnapshot(captured.snapshot,captured.projection));return serialize(draft,options.format??"csv",{spreadsheetSafe:options.spreadsheetSafe??!1}).plainText}async function bytesOf(input){return typeof input=="string"?new TextEncoder().encode(input):input instanceof Uint8Array?input:input instanceof ArrayBuffer?new Uint8Array(input):new Uint8Array(await input.arrayBuffer())}async function readWorkbook(input,options={}){let bytes=await bytesOf(input),{readWorkbookBytes}=await import("@ai-matrx/alchemy/operate/formats/xlsx");return readWorkbookBytes(bytes,options)}var WORKBOOK_FORMATS=new Set(["xlsx","xls","ods","xlsm","xlsb"]),DELIMITED_FORMATS=new Set(["csv","tsv","txt","tab"]),UnreadableFormatError=class extends Error{format;constructor(format){super(`A "${format}" file is not a table we can read. Choose a CSV, TSV, or Excel file.`),this.format=format,this.name="UnreadableFormatError"}};function fileFormatOf(name){let m=/\.([a-z0-9]+)$/i.exec(name??"");return m?m[1].toLowerCase():""}async function readFile(input,options={}){let name=options.filename??(typeof Blob<"u"&&input instanceof Blob&&"name"in input?String(input.name):void 0),format=(options.format??fileFormatOf(name))||(typeof input=="string"?"csv":"");if(WORKBOOK_FORMATS.has(format)){let book=await readWorkbook(input,options.workbook);return{format:format==="xlsm"||format==="xlsb"?"xlsx":format,sheets:book.sheets.map(s=>({name:s.name,grid:s.grid(options.grid),merges:s.merges})),errors:[]}}if(!DELIMITED_FORMATS.has(format))throw new UnreadableFormatError(format||"unknown");let text=typeof input=="string"?input:new TextDecoder("utf-8").decode(await bytesOf(input)),parsed=parseDelimited(text,{...options,header:!1,...options.delimiter?{}:format==="tsv"||format==="tab"?{delimiter:" "}:{}});return{format:format==="tab"?"tsv":format,sheets:[{name:name?.replace(/\.[^.]+$/,"")||"Sheet1",grid:parsed.data,merges:[]}],errors:parsed.errors,delimiter:parsed.meta.delimiter}}export{UnreadableFormatError,fileFormatOf,parseDelimited,readFile,readWorkbook,toDelimitedText};
|
|
6
6
|
//# sourceMappingURL=operate-read.js.map
|
package/dist/operate-read.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/operate/read.ts"],"sourcesContent":["/**\n * `@ai-matrx/alchemy/operate/read` — the one parse door (ALC-19 T4–T12). Write has `buildFile`; read has `readFile`.\n *\n * One engine per format across read and write:\n * - CSV / TSV / delimited text: THIS module's parser (no library), the reverse of the `csv`/`tsv`\n * serializers in `./core`. `parseDelimited` is Papa Parse–compatible in the options the platform\n * uses (auto delimiter + line ending, quotes, `skipEmptyLines` true | \"greedy\", `header`,\n * `transformHeader`, `dynamicTyping`) and answers the same `{ data, errors, meta }` shape and\n * error codes, so a repointed importer keeps its behaviour.\n * - XLSX / XLS / ODS: SheetJS, the library the `xlsx` writer uses, loaded on first use from the\n * `/operate/formats/xlsx` leaf (`readWorkbook`). A caller that needs spreadsheet typing of a CSV\n * (dates, numbers, formulas as SheetJS reads them) reads it through `readWorkbook` too.\n */\n\n// Through the PUBLIC `/operate` subpath, so this entry shares the transfer core at runtime instead of bundling a copy.\nimport { createDraft, directSource, projectSnapshot, serialize, type Column, type Json } from \"@ai-matrx/alchemy/operate\";\n\n// ─── Delimited text ───────────────────────────────────────────────────────────────────────────────\n\nexport type DelimitedParseError = {\n type: \"Quotes\" | \"Delimiter\" | \"FieldMismatch\";\n code: \"MissingQuotes\" | \"InvalidQuotes\" | \"UndetectableDelimiter\" | \"TooFewFields\" | \"TooManyFields\";\n message: string;\n /** 0-based index of the data row the error belongs to (absent for whole-file errors). */\n row?: number;\n};\n\nexport type DelimitedParseOptions = {\n /** Field delimiter. Absent → guessed from `,` `\\t` `|` `;` and the ASCII record/unit separators; `,` when none fits. */\n delimiter?: string;\n /** true: an exactly empty line is dropped; \"greedy\": a line of only whitespace is dropped too. */\n skipEmptyLines?: boolean | \"greedy\";\n /** Numbers, `true`/`false` and ISO date-times become typed values; an empty field becomes null. */\n dynamicTyping?: boolean;\n /** Header mode only: rename a header (e.g. trim + lowercase). */\n transformHeader?(header: string, index: number): string;\n};\n\nexport type DelimitedCell = string | number | boolean | Date | null;\nexport type DelimitedMeta = { delimiter: string; linebreak: string; fields?: string[] };\nexport type DelimitedResult<T> = { data: T[]; errors: DelimitedParseError[]; meta: DelimitedMeta };\n\nconst RECORD_SEP = String.fromCharCode(30);\nconst UNIT_SEP = String.fromCharCode(31);\nconst GUESS_DELIMITERS = [\",\", \"\\t\", \"|\", \";\", RECORD_SEP, UNIT_SEP];\nconst FLOAT = /^\\s*-?(\\d+\\.?|\\.\\d+|\\d+\\.\\d+)([eE][-+]?\\d+)?\\s*$/;\nconst ISO_DATE =\n /^((\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z)))$/;\n\nfunction guessLinebreak(text: string): string {\n const crlf = text.indexOf(\"\\r\\n\");\n const lf = text.indexOf(\"\\n\");\n const cr = text.indexOf(\"\\r\");\n if (crlf !== -1 && crlf === cr) return \"\\r\\n\";\n if (cr !== -1 && (lf === -1 || cr < lf)) return \"\\r\";\n return \"\\n\";\n}\n\n/** Tokenize into rows of raw string fields; quote errors are reported against their data row. */\nfunction tokenize(text: string, delimiter: string, linebreak: string, preview = Infinity): { rows: string[][]; errors: DelimitedParseError[] } {\n const rows: string[][] = [];\n const errors: DelimitedParseError[] = [];\n if (text === \"\") return { rows, errors };\n let row: string[] = [];\n let i = 0;\n const n = text.length;\n const dl = delimiter.length;\n const ll = linebreak.length;\n while (i <= n && rows.length < preview) {\n if (text.startsWith('\"', i)) {\n // Quoted field: \"\" is a literal quote; the closing quote must precede a delimiter, a line break or the end.\n let value = \"\";\n let j = i + 1;\n let closed = false;\n while (j < n) {\n const q = text.indexOf('\"', j);\n if (q === -1) break;\n value += text.slice(j, q);\n if (text[q + 1] === '\"') {\n value += '\"';\n j = q + 2;\n continue;\n }\n const after = q + 1;\n if (after >= n || text.startsWith(delimiter, after) || text.startsWith(linebreak, after)) {\n closed = true;\n j = after;\n break;\n }\n errors.push({ type: \"Quotes\", code: \"InvalidQuotes\", message: \"Trailing quote on quoted field is malformed\", row: rows.length });\n value += '\"';\n j = after;\n }\n if (!closed) {\n errors.push({ type: \"Quotes\", code: \"MissingQuotes\", message: \"Quoted field unterminated\", row: rows.length });\n value += text.slice(j);\n row.push(value);\n rows.push(row);\n return { rows, errors };\n }\n row.push(value);\n i = j;\n if (i >= n) {\n rows.push(row);\n break;\n }\n if (text.startsWith(delimiter, i)) {\n i += dl;\n if (i >= n) {\n row.push(\"\");\n rows.push(row);\n break;\n }\n continue;\n }\n rows.push(row);\n row = [];\n i += ll;\n if (i >= n) {\n rows.push([\"\"]);\n break;\n }\n continue;\n }\n const nextDelim = text.indexOf(delimiter, i);\n const nextBreak = text.indexOf(linebreak, i);\n if (nextDelim !== -1 && (nextBreak === -1 || nextDelim < nextBreak)) {\n row.push(text.slice(i, nextDelim));\n i = nextDelim + dl;\n if (i >= n) {\n row.push(\"\");\n rows.push(row);\n break;\n }\n continue;\n }\n if (nextBreak !== -1) {\n row.push(text.slice(i, nextBreak));\n rows.push(row);\n row = [];\n i = nextBreak + ll;\n if (i >= n) {\n rows.push([\"\"]);\n break;\n }\n continue;\n }\n row.push(text.slice(i));\n rows.push(row);\n break;\n }\n return { rows, errors };\n}\n\nconst isEmptyRow = (row: readonly string[], mode: boolean | \"greedy\") =>\n mode === \"greedy\" ? row.join(\"\").trim() === \"\" : row.length === 1 && row[0]!.length === 0;\n\nfunction guessDelimiter(text: string, linebreak: string, skip: boolean | \"greedy\" | undefined): { delimiter: string; successful: boolean } {\n let best: string | undefined;\n let bestDelta: number | undefined;\n let bestAvg: number | undefined;\n for (const delim of GUESS_DELIMITERS) {\n const { rows } = tokenize(text, delim, linebreak, 10);\n let delta = 0;\n let avg = 0;\n let empty = 0;\n let prev: number | undefined;\n for (const row of rows) {\n if (skip && isEmptyRow(row, skip)) {\n empty++;\n continue;\n }\n const count = row.length;\n avg += count;\n if (prev === undefined) {\n prev = count;\n continue;\n }\n if (count > 0) {\n delta += Math.abs(count - prev);\n prev = count;\n }\n }\n if (rows.length > 0) avg /= rows.length - empty;\n if ((bestDelta === undefined || delta <= bestDelta) && (bestAvg === undefined || avg > bestAvg) && avg > 1.99) {\n bestDelta = delta;\n bestAvg = avg;\n best = delim;\n }\n }\n return { delimiter: best ?? \",\", successful: best !== undefined };\n}\n\nfunction typed(value: string): DelimitedCell {\n if (value === \"true\" || value === \"TRUE\") return true;\n if (value === \"false\" || value === \"FALSE\") return false;\n if (FLOAT.test(value)) {\n const num = parseFloat(value);\n if (num > -(2 ** 53) && num < 2 ** 53) return num;\n return value;\n }\n if (ISO_DATE.test(value)) return new Date(value);\n return value === \"\" ? null : value;\n}\n\n/** Parse delimited text into rows of fields (header row first). */\nexport function parseDelimited(text: string, options?: DelimitedParseOptions & { header?: false; dynamicTyping?: false }): DelimitedResult<string[]>;\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header?: false; dynamicTyping: true }): DelimitedResult<DelimitedCell[]>;\n/** Parse delimited text into records keyed by the (transformed) header row. Extra fields land in `__parsed_extra`. */\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header: true; dynamicTyping?: false }): DelimitedResult<Record<string, string>>;\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header: true; dynamicTyping: true }): DelimitedResult<Record<string, DelimitedCell>>;\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header?: boolean } = {}): DelimitedResult<unknown> {\n const input = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n const linebreak = guessLinebreak(input);\n const errors: DelimitedParseError[] = [];\n let delimiter = options.delimiter;\n if (!delimiter) {\n const guess = guessDelimiter(input, linebreak, options.skipEmptyLines);\n delimiter = guess.delimiter;\n if (!guess.successful)\n errors.push({ type: \"Delimiter\", code: \"UndetectableDelimiter\", message: \"Unable to auto-detect delimiting character; defaulted to ','\" });\n }\n const tokens = tokenize(input, delimiter, linebreak);\n errors.unshift(...tokens.errors);\n let rows = tokens.rows;\n if (options.skipEmptyLines) rows = rows.filter((row) => !isEmptyRow(row, options.skipEmptyLines!));\n const meta: DelimitedMeta = { delimiter, linebreak };\n if (!options.header) {\n return { data: options.dynamicTyping ? rows.map((row) => row.map(typed)) : rows, errors, meta };\n }\n const [head = [], ...body] = rows;\n const seen = new Map<string, number>();\n const fields = head.map((raw, index) => {\n const name = options.transformHeader ? options.transformHeader(raw, index) : raw;\n const count = seen.get(name) ?? 0;\n seen.set(name, count + 1);\n return count === 0 ? name : `${name}_${count}`;\n });\n meta.fields = fields;\n const data = body.map((row, index) => {\n const record: Record<string, unknown> = {};\n row.forEach((value, j) => {\n const cell = options.dynamicTyping ? typed(value) : value;\n if (j >= fields.length) {\n ((record.__parsed_extra as unknown[] | undefined) ??= []).push(cell);\n } else record[fields[j]!] = cell;\n });\n if (row.length > fields.length)\n errors.push({ type: \"FieldMismatch\", code: \"TooManyFields\", message: `Too many fields: expected ${fields.length} fields but parsed ${row.length}`, row: index });\n else if (row.length < fields.length)\n errors.push({ type: \"FieldMismatch\", code: \"TooFewFields\", message: `Too few fields: expected ${fields.length} fields but parsed ${row.length}`, row: index });\n return record;\n });\n return { data, errors, meta };\n}\n\n/**\n * Delimited text from a header row + body rows, through the SAME `csv`/`tsv` serializer the\n * Alchemy menu's copy and download use (quoting, nested values as compact JSON, `null` empty).\n * `spreadsheetSafe` (default false here) prefixes formula-looking text with `'`.\n */\nexport function toDelimitedText(\n header: readonly string[],\n rows: readonly (readonly (Json | undefined)[] | Readonly<Record<string, Json | undefined>>)[],\n options: { format?: \"csv\" | \"tsv\"; spreadsheetSafe?: boolean } = {},\n): string {\n const columns: Column[] = header.map((label, i) => ({ id: `c${i}`, label, path: `/c${i}`, visible: true }));\n const record = (row: (typeof rows)[number]): Record<string, Json> => {\n const out: Record<string, Json> = {};\n header.forEach((label, i) => {\n const v = Array.isArray(row) ? (row as readonly (Json | undefined)[])[i] : (row as Readonly<Record<string, Json | undefined>>)[label];\n if (v !== undefined) out[`c${i}`] = v;\n });\n return out;\n };\n const captured = directSource({ kind: \"rows\", columns, rows: rows.map(record) });\n const draft = createDraft(projectSnapshot(captured.snapshot, captured.projection));\n return serialize(draft, options.format ?? \"csv\", { spreadsheetSafe: options.spreadsheetSafe ?? false }).plainText;\n}\n\n// ─── Workbooks (the SheetJS leaf, loaded on first use) ───────────────────────────────────────────\n\nexport type WorkbookCellAddress = { r: number; c: number };\nexport type WorkbookCellRange = { s: WorkbookCellAddress; e: WorkbookCellAddress };\n/** A cell as the workbook engine reads it: type, raw value, formatted text, formula source. */\nexport type WorkbookCell = {\n t: \"s\" | \"n\" | \"b\" | \"d\" | \"e\" | \"z\";\n v?: string | number | boolean | Date;\n w?: string;\n f?: string;\n styled: boolean;\n};\n/** Grid shape options (header row included): `raw: false` → formatted text; `defval` fills gaps. */\nexport type WorkbookGridOptions = { raw?: boolean; defval?: unknown; blankrows?: boolean; dateNF?: string; rawNumbers?: boolean };\nexport type WorkbookRecordOptions = { raw?: boolean; defval?: unknown; blankrows?: boolean; dateNF?: string; rawNumbers?: boolean };\nexport interface WorkbookSheet {\n name: string;\n /** The used range, null for a sheet without one. */\n range: WorkbookCellRange | null;\n merges: WorkbookCellRange[];\n cell(r: number, c: number): WorkbookCell | null;\n cells(): Iterable<readonly [WorkbookCellAddress, WorkbookCell]>;\n grid(options?: WorkbookGridOptions): unknown[][];\n records(options?: WorkbookRecordOptions): Record<string, unknown>[];\n}\nexport interface Workbook {\n sheetNames: string[];\n sheets: WorkbookSheet[];\n sheet(name: string): WorkbookSheet | undefined;\n}\n/** `dates`: date cells as Date (`t: \"d\"`); `formulas`: keep formula source; `styles`: read cell styles. */\nexport type WorkbookReadOptions = { dates?: boolean; formulas?: boolean; styles?: boolean };\n\n/** A typed cell for the `xlsx-workbook` format; a bare string/number/boolean is a value cell. */\nexport type WorkbookCellJson = { t?: \"s\" | \"n\" | \"b\"; v?: string | number | boolean | null; f?: string };\n/** The `xlsx-workbook` payload value (`{ kind: \"registered\", format: \"workbook\", value }`). */\nexport type WorkbookJson = {\n sheets: { name: string; rows: (WorkbookCellJson | string | number | boolean | null)[][]; merges?: WorkbookCellRange[] }[];\n};\n\nexport type ReadInput = Blob | ArrayBuffer | Uint8Array | string;\n\nasync function bytesOf(input: ReadInput): Promise<Uint8Array> {\n if (typeof input === \"string\") return new TextEncoder().encode(input);\n if (input instanceof Uint8Array) return input;\n if (input instanceof ArrayBuffer) return new Uint8Array(input);\n return new Uint8Array(await input.arrayBuffer());\n}\n\n/** Read a spreadsheet (XLSX, XLS, ODS; CSV/TSV with spreadsheet typing) through the one workbook engine. */\nexport async function readWorkbook(input: ReadInput, options: WorkbookReadOptions = {}): Promise<Workbook> {\n const bytes = await bytesOf(input);\n const { readWorkbookBytes } = await import(\"@ai-matrx/alchemy/operate/formats/xlsx\");\n return readWorkbookBytes(bytes, options);\n}\n\n// ─── readFile: the door ──────────────────────────────────────────────────────────────────────────\n\nexport type TabularFormat = \"csv\" | \"tsv\" | \"txt\" | \"xlsx\" | \"xls\" | \"ods\";\nconst WORKBOOK_FORMATS: ReadonlySet<string> = new Set([\"xlsx\", \"xls\", \"ods\", \"xlsm\", \"xlsb\"]);\nconst DELIMITED_FORMATS: ReadonlySet<string> = new Set([\"csv\", \"tsv\", \"txt\", \"tab\"]);\n\nexport type ReadFileOptions = DelimitedParseOptions & {\n /** The file's format; absent → from `filename` (or a File's name) extension. */\n format?: TabularFormat | (string & {});\n filename?: string;\n /** Workbooks: the grid shape (`sheet_to_json` header-row semantics). */\n grid?: WorkbookGridOptions;\n /** Workbooks: read options. */\n workbook?: WorkbookReadOptions;\n};\nexport type TabularSheet = { name: string; grid: unknown[][]; merges: WorkbookCellRange[] };\nexport type TabularFile = { format: TabularFormat; sheets: TabularSheet[]; errors: DelimitedParseError[]; delimiter?: string };\n\nexport class UnreadableFormatError extends Error {\n constructor(readonly format: string) {\n super(`A \"${format}\" file is not a table we can read. Choose a CSV, TSV, or Excel file.`);\n this.name = \"UnreadableFormatError\";\n }\n}\n\n/** The format a file name declares (lower-case extension), or \"\" when it has none. */\nexport function fileFormatOf(name: string | undefined): string {\n const m = /\\.([a-z0-9]+)$/i.exec(name ?? \"\");\n return m ? m[1]!.toLowerCase() : \"\";\n}\n\n/**\n * Read a table file — CSV/TSV/plain text through the delimited parser, XLSX/XLS/ODS through the\n * workbook engine — into sheets of grids (header row first). Delimited text is one sheet.\n */\nexport async function readFile(input: ReadInput, options: ReadFileOptions = {}): Promise<TabularFile> {\n const name = options.filename ?? (typeof Blob !== \"undefined\" && input instanceof Blob && \"name\" in input ? String((input as File).name) : undefined);\n const format = (options.format ?? fileFormatOf(name)) || (typeof input === \"string\" ? \"csv\" : \"\");\n if (WORKBOOK_FORMATS.has(format)) {\n const book = await readWorkbook(input, options.workbook);\n return {\n format: (format === \"xlsm\" || format === \"xlsb\" ? \"xlsx\" : format) as TabularFormat,\n sheets: book.sheets.map((s) => ({ name: s.name, grid: s.grid(options.grid), merges: s.merges })),\n errors: [],\n };\n }\n if (!DELIMITED_FORMATS.has(format)) throw new UnreadableFormatError(format || \"unknown\");\n const text = typeof input === \"string\" ? input : new TextDecoder(\"utf-8\").decode(await bytesOf(input));\n const parsed = parseDelimited(text, {\n ...options,\n header: false,\n ...(options.delimiter ? {} : format === \"tsv\" || format === \"tab\" ? { delimiter: \"\\t\" } : {}),\n } as DelimitedParseOptions & { header: false; dynamicTyping?: false });\n return {\n format: (format === \"tab\" ? \"tsv\" : format) as TabularFormat,\n sheets: [{ name: name?.replace(/\\.[^.]+$/, \"\") || \"Sheet1\", grid: parsed.data, merges: [] }],\n errors: parsed.errors,\n delimiter: parsed.meta.delimiter,\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/operate/read.ts"],"sourcesContent":["/**\n * `@ai-matrx/alchemy/operate/read` — the one parse door (ALC-19 T4–T12). Write has `buildFile`; read has `readFile`.\n *\n * One engine per format across read and write:\n * - CSV / TSV / delimited text: THIS module's parser (no library), the reverse of the `csv`/`tsv`\n * serializers in `./core`. `parseDelimited` is Papa Parse–compatible in the options the platform\n * uses (auto delimiter + line ending, quotes, `skipEmptyLines` true | \"greedy\", `header`,\n * `transformHeader`, `dynamicTyping`) and answers the same `{ data, errors, meta }` shape and\n * error codes, so a repointed importer keeps its behaviour.\n * - XLSX / XLS / ODS: SheetJS, the library the `xlsx` writer uses, loaded on first use from the\n * `/operate/formats/xlsx` leaf (`readWorkbook`). A caller that needs spreadsheet typing of a CSV\n * (dates, numbers, formulas as SheetJS reads them) reads it through `readWorkbook` too.\n */\n\n// Through the PUBLIC `/operate` subpath, so this entry shares the transfer core at runtime instead of bundling a copy.\nimport { createDraft, directSource, projectSnapshot, serialize, type Column, type Json } from \"@ai-matrx/alchemy/operate\";\n\n// ─── Delimited text ───────────────────────────────────────────────────────────────────────────────\n\nexport type DelimitedParseError = {\n type: \"Quotes\" | \"Delimiter\" | \"FieldMismatch\";\n code: \"MissingQuotes\" | \"InvalidQuotes\" | \"UndetectableDelimiter\" | \"TooFewFields\" | \"TooManyFields\";\n message: string;\n /** 0-based index of the data row the error belongs to (absent for whole-file errors). */\n row?: number;\n};\n\nexport type DelimitedParseOptions = {\n /** Field delimiter. Absent → guessed from `,` `\\t` `|` `;` and the ASCII record/unit separators; `,` when none fits. */\n delimiter?: string;\n /** true: an exactly empty line is dropped; \"greedy\": a line of only whitespace is dropped too. */\n skipEmptyLines?: boolean | \"greedy\";\n /** Numbers, `true`/`false` and ISO date-times become typed values; an empty field becomes null. */\n dynamicTyping?: boolean;\n /** Header mode only: rename a header (e.g. trim + lowercase). */\n transformHeader?(header: string, index: number): string;\n};\n\nexport type DelimitedCell = string | number | boolean | Date | null;\nexport type DelimitedMeta = { delimiter: string; linebreak: string; fields?: string[] };\nexport type DelimitedResult<T> = { data: T[]; errors: DelimitedParseError[]; meta: DelimitedMeta };\n\nconst RECORD_SEP = String.fromCharCode(30);\nconst UNIT_SEP = String.fromCharCode(31);\nconst GUESS_DELIMITERS = [\",\", \"\\t\", \"|\", \";\", RECORD_SEP, UNIT_SEP];\nconst FLOAT = /^\\s*-?(\\d+\\.?|\\.\\d+|\\d+\\.\\d+)([eE][-+]?\\d+)?\\s*$/;\nconst ISO_DATE =\n /^((\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d+([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z))|(\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z)))$/;\n\nfunction guessLinebreak(rawText: string): string {\n // Quoted cells may hold any line break; only the ones between records say what the file uses.\n const text = rawText.includes('\"') ? rawText.replace(/\"[^\"]*\"/g, '\"\"') : rawText;\n const crlf = text.indexOf(\"\\r\\n\");\n const lf = text.indexOf(\"\\n\");\n const cr = text.indexOf(\"\\r\");\n if (crlf !== -1 && crlf === cr) return \"\\r\\n\";\n if (cr !== -1 && (lf === -1 || cr < lf)) return \"\\r\";\n return \"\\n\";\n}\n\n/** Tokenize into rows of raw string fields; quote errors are reported against their data row. */\nfunction tokenize(text: string, delimiter: string, linebreak: string, preview = Infinity): { rows: string[][]; errors: DelimitedParseError[] } {\n const rows: string[][] = [];\n const errors: DelimitedParseError[] = [];\n if (text === \"\") return { rows, errors };\n let row: string[] = [];\n let i = 0;\n const n = text.length;\n const dl = delimiter.length;\n const ll = linebreak.length;\n while (i <= n && rows.length < preview) {\n if (text.startsWith('\"', i)) {\n // Quoted field: \"\" is a literal quote; the closing quote must precede a delimiter, a line break or the end.\n let value = \"\";\n let j = i + 1;\n let closed = false;\n while (j < n) {\n const q = text.indexOf('\"', j);\n if (q === -1) break;\n value += text.slice(j, q);\n if (text[q + 1] === '\"') {\n value += '\"';\n j = q + 2;\n continue;\n }\n const after = q + 1;\n if (after >= n || text.startsWith(delimiter, after) || text.startsWith(linebreak, after)) {\n closed = true;\n j = after;\n break;\n }\n errors.push({ type: \"Quotes\", code: \"InvalidQuotes\", message: \"Trailing quote on quoted field is malformed\", row: rows.length });\n value += '\"';\n j = after;\n }\n if (!closed) {\n errors.push({ type: \"Quotes\", code: \"MissingQuotes\", message: \"Quoted field unterminated\", row: rows.length });\n value += text.slice(j);\n row.push(value);\n rows.push(row);\n return { rows, errors };\n }\n row.push(value);\n i = j;\n if (i >= n) {\n rows.push(row);\n break;\n }\n if (text.startsWith(delimiter, i)) {\n i += dl;\n if (i >= n) {\n row.push(\"\");\n rows.push(row);\n break;\n }\n continue;\n }\n rows.push(row);\n row = [];\n i += ll;\n if (i >= n) {\n rows.push([\"\"]);\n break;\n }\n continue;\n }\n const nextDelim = text.indexOf(delimiter, i);\n const nextBreak = text.indexOf(linebreak, i);\n if (nextDelim !== -1 && (nextBreak === -1 || nextDelim < nextBreak)) {\n row.push(text.slice(i, nextDelim));\n i = nextDelim + dl;\n if (i >= n) {\n row.push(\"\");\n rows.push(row);\n break;\n }\n continue;\n }\n if (nextBreak !== -1) {\n row.push(text.slice(i, nextBreak));\n rows.push(row);\n row = [];\n i = nextBreak + ll;\n if (i >= n) {\n rows.push([\"\"]);\n break;\n }\n continue;\n }\n row.push(text.slice(i));\n rows.push(row);\n break;\n }\n return { rows, errors };\n}\n\nconst isEmptyRow = (row: readonly string[], mode: boolean | \"greedy\") =>\n mode === \"greedy\" ? row.join(\"\").trim() === \"\" : row.length === 1 && row[0]!.length === 0;\n\nfunction guessDelimiter(text: string, linebreak: string, skip: boolean | \"greedy\" | undefined): { delimiter: string; successful: boolean } {\n let best: string | undefined;\n let bestDelta: number | undefined;\n let bestAvg: number | undefined;\n for (const delim of GUESS_DELIMITERS) {\n const { rows } = tokenize(text, delim, linebreak, 10);\n let delta = 0;\n let avg = 0;\n let empty = 0;\n let prev: number | undefined;\n for (const row of rows) {\n if (skip && isEmptyRow(row, skip)) {\n empty++;\n continue;\n }\n const count = row.length;\n avg += count;\n if (prev === undefined) {\n prev = count;\n continue;\n }\n if (count > 0) {\n delta += Math.abs(count - prev);\n prev = count;\n }\n }\n if (rows.length > 0) avg /= rows.length - empty;\n if ((bestDelta === undefined || delta <= bestDelta) && (bestAvg === undefined || avg > bestAvg) && avg > 1.99) {\n bestDelta = delta;\n bestAvg = avg;\n best = delim;\n }\n }\n return { delimiter: best ?? \",\", successful: best !== undefined };\n}\n\nfunction typed(value: string): DelimitedCell {\n if (value === \"true\" || value === \"TRUE\") return true;\n if (value === \"false\" || value === \"FALSE\") return false;\n if (FLOAT.test(value)) {\n const num = parseFloat(value);\n if (num > -(2 ** 53) && num < 2 ** 53) return num;\n return value;\n }\n if (ISO_DATE.test(value)) return new Date(value);\n return value === \"\" ? null : value;\n}\n\n/** Parse delimited text into rows of fields (header row first). */\nexport function parseDelimited(text: string, options?: DelimitedParseOptions & { header?: false; dynamicTyping?: false }): DelimitedResult<string[]>;\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header?: false; dynamicTyping: true }): DelimitedResult<DelimitedCell[]>;\n/** Parse delimited text into records keyed by the (transformed) header row. Extra fields land in `__parsed_extra`. */\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header: true; dynamicTyping?: false }): DelimitedResult<Record<string, string>>;\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header: true; dynamicTyping: true }): DelimitedResult<Record<string, DelimitedCell>>;\nexport function parseDelimited(text: string, options: DelimitedParseOptions & { header?: boolean } = {}): DelimitedResult<unknown> {\n const input = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;\n const linebreak = guessLinebreak(input);\n const errors: DelimitedParseError[] = [];\n let delimiter = options.delimiter;\n if (!delimiter) {\n const guess = guessDelimiter(input, linebreak, options.skipEmptyLines);\n delimiter = guess.delimiter;\n if (!guess.successful)\n errors.push({ type: \"Delimiter\", code: \"UndetectableDelimiter\", message: \"Unable to auto-detect delimiting character; defaulted to ','\" });\n }\n const tokens = tokenize(input, delimiter, linebreak);\n errors.unshift(...tokens.errors);\n let rows = tokens.rows;\n if (options.skipEmptyLines) rows = rows.filter((row) => !isEmptyRow(row, options.skipEmptyLines!));\n const meta: DelimitedMeta = { delimiter, linebreak };\n if (!options.header) {\n return { data: options.dynamicTyping ? rows.map((row) => row.map(typed)) : rows, errors, meta };\n }\n const [head = [], ...body] = rows;\n const seen = new Map<string, number>();\n const fields = head.map((raw, index) => {\n const name = options.transformHeader ? options.transformHeader(raw, index) : raw;\n const count = seen.get(name) ?? 0;\n seen.set(name, count + 1);\n return count === 0 ? name : `${name}_${count}`;\n });\n meta.fields = fields;\n const data = body.map((row, index) => {\n const record: Record<string, unknown> = {};\n row.forEach((value, j) => {\n const cell = options.dynamicTyping ? typed(value) : value;\n if (j >= fields.length) {\n ((record.__parsed_extra as unknown[] | undefined) ??= []).push(cell);\n } else record[fields[j]!] = cell;\n });\n if (row.length > fields.length)\n errors.push({ type: \"FieldMismatch\", code: \"TooManyFields\", message: `Too many fields: expected ${fields.length} fields but parsed ${row.length}`, row: index });\n else if (row.length < fields.length)\n errors.push({ type: \"FieldMismatch\", code: \"TooFewFields\", message: `Too few fields: expected ${fields.length} fields but parsed ${row.length}`, row: index });\n return record;\n });\n return { data, errors, meta };\n}\n\n/**\n * Delimited text from a header row + body rows, through the SAME `csv`/`tsv` serializer the\n * Alchemy menu's copy and download use (quoting, nested values as compact JSON, `null` empty).\n * `spreadsheetSafe` (default false here) prefixes formula-looking text with `'`.\n */\nexport function toDelimitedText(\n header: readonly string[],\n rows: readonly (readonly (Json | undefined)[] | Readonly<Record<string, Json | undefined>>)[],\n options: { format?: \"csv\" | \"tsv\"; spreadsheetSafe?: boolean } = {},\n): string {\n const columns: Column[] = header.map((label, i) => ({ id: `c${i}`, label, path: `/c${i}`, visible: true }));\n const record = (row: (typeof rows)[number]): Record<string, Json> => {\n const out: Record<string, Json> = {};\n header.forEach((label, i) => {\n const v = Array.isArray(row) ? (row as readonly (Json | undefined)[])[i] : (row as Readonly<Record<string, Json | undefined>>)[label];\n if (v !== undefined) out[`c${i}`] = v;\n });\n return out;\n };\n const captured = directSource({ kind: \"rows\", columns, rows: rows.map(record) });\n const draft = createDraft(projectSnapshot(captured.snapshot, captured.projection));\n return serialize(draft, options.format ?? \"csv\", { spreadsheetSafe: options.spreadsheetSafe ?? false }).plainText;\n}\n\n// ─── Workbooks (the SheetJS leaf, loaded on first use) ───────────────────────────────────────────\n\nexport type WorkbookCellAddress = { r: number; c: number };\nexport type WorkbookCellRange = { s: WorkbookCellAddress; e: WorkbookCellAddress };\n/** A cell as the workbook engine reads it: type, raw value, formatted text, formula source. */\nexport type WorkbookCell = {\n t: \"s\" | \"n\" | \"b\" | \"d\" | \"e\" | \"z\";\n v?: string | number | boolean | Date;\n w?: string;\n f?: string;\n styled: boolean;\n};\n/** Grid shape options (header row included): `raw: false` → formatted text; `defval` fills gaps. */\nexport type WorkbookGridOptions = { raw?: boolean; defval?: unknown; blankrows?: boolean; dateNF?: string; rawNumbers?: boolean };\nexport type WorkbookRecordOptions = { raw?: boolean; defval?: unknown; blankrows?: boolean; dateNF?: string; rawNumbers?: boolean };\nexport interface WorkbookSheet {\n name: string;\n /** The used range, null for a sheet without one. */\n range: WorkbookCellRange | null;\n merges: WorkbookCellRange[];\n cell(r: number, c: number): WorkbookCell | null;\n cells(): Iterable<readonly [WorkbookCellAddress, WorkbookCell]>;\n grid(options?: WorkbookGridOptions): unknown[][];\n records(options?: WorkbookRecordOptions): Record<string, unknown>[];\n}\nexport interface Workbook {\n sheetNames: string[];\n sheets: WorkbookSheet[];\n sheet(name: string): WorkbookSheet | undefined;\n}\n/** `dates`: date cells as Date (`t: \"d\"`); `formulas`: keep formula source; `styles`: read cell styles. */\nexport type WorkbookReadOptions = { dates?: boolean; formulas?: boolean; styles?: boolean };\n\n/** A typed cell for the `xlsx-workbook` format; a bare string/number/boolean is a value cell. */\nexport type WorkbookCellJson = { t?: \"s\" | \"n\" | \"b\"; v?: string | number | boolean | null; f?: string };\n/** The `xlsx-workbook` payload value (`{ kind: \"registered\", format: \"workbook\", value }`). */\nexport type WorkbookJson = {\n sheets: { name: string; rows: (WorkbookCellJson | string | number | boolean | null)[][]; merges?: WorkbookCellRange[] }[];\n};\n\nexport type ReadInput = Blob | ArrayBuffer | Uint8Array | string;\n\nasync function bytesOf(input: ReadInput): Promise<Uint8Array> {\n if (typeof input === \"string\") return new TextEncoder().encode(input);\n if (input instanceof Uint8Array) return input;\n if (input instanceof ArrayBuffer) return new Uint8Array(input);\n return new Uint8Array(await input.arrayBuffer());\n}\n\n/** Read a spreadsheet (XLSX, XLS, ODS; CSV/TSV with spreadsheet typing) through the one workbook engine. */\nexport async function readWorkbook(input: ReadInput, options: WorkbookReadOptions = {}): Promise<Workbook> {\n const bytes = await bytesOf(input);\n const { readWorkbookBytes } = await import(\"@ai-matrx/alchemy/operate/formats/xlsx\");\n return readWorkbookBytes(bytes, options);\n}\n\n// ─── readFile: the door ──────────────────────────────────────────────────────────────────────────\n\nexport type TabularFormat = \"csv\" | \"tsv\" | \"txt\" | \"xlsx\" | \"xls\" | \"ods\";\nconst WORKBOOK_FORMATS: ReadonlySet<string> = new Set([\"xlsx\", \"xls\", \"ods\", \"xlsm\", \"xlsb\"]);\nconst DELIMITED_FORMATS: ReadonlySet<string> = new Set([\"csv\", \"tsv\", \"txt\", \"tab\"]);\n\nexport type ReadFileOptions = DelimitedParseOptions & {\n /** The file's format; absent → from `filename` (or a File's name) extension. */\n format?: TabularFormat | (string & {});\n filename?: string;\n /** Workbooks: the grid shape (`sheet_to_json` header-row semantics). */\n grid?: WorkbookGridOptions;\n /** Workbooks: read options. */\n workbook?: WorkbookReadOptions;\n};\nexport type TabularSheet = { name: string; grid: unknown[][]; merges: WorkbookCellRange[] };\nexport type TabularFile = { format: TabularFormat; sheets: TabularSheet[]; errors: DelimitedParseError[]; delimiter?: string };\n\nexport class UnreadableFormatError extends Error {\n readonly format: string;\n constructor(format: string) {\n super(`A \"${format}\" file is not a table we can read. Choose a CSV, TSV, or Excel file.`);\n this.format = format;\n this.name = \"UnreadableFormatError\";\n }\n}\n\n/** The format a file name declares (lower-case extension), or \"\" when it has none. */\nexport function fileFormatOf(name: string | undefined): string {\n const m = /\\.([a-z0-9]+)$/i.exec(name ?? \"\");\n return m ? m[1]!.toLowerCase() : \"\";\n}\n\n/**\n * Read a table file — CSV/TSV/plain text through the delimited parser, XLSX/XLS/ODS through the\n * workbook engine — into sheets of grids (header row first). Delimited text is one sheet.\n */\nexport async function readFile(input: ReadInput, options: ReadFileOptions = {}): Promise<TabularFile> {\n const name = options.filename ?? (typeof Blob !== \"undefined\" && input instanceof Blob && \"name\" in input ? String((input as File).name) : undefined);\n const format = (options.format ?? fileFormatOf(name)) || (typeof input === \"string\" ? \"csv\" : \"\");\n if (WORKBOOK_FORMATS.has(format)) {\n const book = await readWorkbook(input, options.workbook);\n return {\n format: (format === \"xlsm\" || format === \"xlsb\" ? \"xlsx\" : format) as TabularFormat,\n sheets: book.sheets.map((s) => ({ name: s.name, grid: s.grid(options.grid), merges: s.merges })),\n errors: [],\n };\n }\n if (!DELIMITED_FORMATS.has(format)) throw new UnreadableFormatError(format || \"unknown\");\n const text = typeof input === \"string\" ? input : new TextDecoder(\"utf-8\").decode(await bytesOf(input));\n const parsed = parseDelimited(text, {\n ...options,\n header: false,\n ...(options.delimiter ? {} : format === \"tsv\" || format === \"tab\" ? { delimiter: \"\\t\" } : {}),\n } as DelimitedParseOptions & { header: false; dynamicTyping?: false });\n return {\n format: (format === \"tab\" ? \"tsv\" : format) as TabularFormat,\n sheets: [{ name: name?.replace(/\\.[^.]+$/, \"\") || \"Sheet1\", grid: parsed.data, merges: [] }],\n errors: parsed.errors,\n delimiter: parsed.meta.delimiter,\n };\n}\n"],"mappings":"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","names":[]}
|