npm package: @scvzerng/template-parser
Description: 面向打印/报表场景的轻量级 HTML 模板解析与渲染库。通过在 HTML 表格中书写变量表达式，并提供业务上下文（Context），可完成变量替换、循环行扩展与统计汇总，最终输出可直接用于打印或导出的 HTML。
Latest version: 0.1.0

# template-parser

面向打印/报表场景的轻量级 HTML 模板解析与渲染库。通过在 HTML 表格中书写变量表达式，并提供业务上下文（Context），可完成变量替换、循环行扩展与统计汇总，最终输出可直接用于打印或导出的 HTML。


## 功能特性

- 所见即所得模板：直接使用标准 HTML（通常是 table）作为模板，易于维护
- 表达式解析：支持 `${变量名}` 与表达式语法，支持多表达式拼接
- 上下文驱动：通过 Context 提供变量值；支持自定义 Context 扩展集合与派生值
- 行模型：将表格解析为行/单元格模型，逐行渲染
- 循环/汇总：按约定识别「循环行」并扩展；支持专用统计语法与表达式汇总
- 轻量可扩展：核心抽象清晰，便于二次封装

## 安装

```bash
npm install template-parser
```

使用方式（ESM）：

```ts
import { NormalContext, Expression } from 'template-parser'
```

## 快速开始

### 1) 表达式解析与求值

```ts
import { NormalContext, Expression } from 'template-parser'

const tpl = 'Hello ${name}, your balance is ${amount.toFixed(2)}'
const context = new NormalContext({ name: 'John', amount: 100.5 })

const exps = Expression.parse(tpl) // 提取到 ["${name}", "${amount.toFixed(2)}"]

// 将表达式替换回字符串（示例化简实现）
let i = 0
let result = ''
for (const exp of exps) {
  result += tpl.slice(i, exp.getStartIndex())
  result += String(exp.eval(context))
  i = exp.getEndIndex()
}
result += tpl.slice(i)
// => Hello John, your balance is 100.50
```

### 2) 表格模板渲染（概念示例）

库中提供 HTML 表格到行/单元格模型的解析与渲染基类 HtmlTemplate，适合二次封装成你的业务模板。

HTML 模板（片段）：

```html
<table data-type="luckysheet_copy_action_table">
  <tbody>
    <tr>
      <td>客户</td>
      <td>${customerName}</td>
    </tr>
    <!-- 目标行（含集合变量） -->
    <tr>
      <td>${index + 1}</td>
      <td>${item.name}</td>
      <td>${item.qty}</td>
    </tr>
    <!-- 占位空行（用于循环复制） -->
    <tr>
      <td></td><td></td><td></td>
    </tr>
    <!-- 合计：专用语法 “字段,小数位” 或表达式 -->
    <tr>
      <td>合计</td>
      <td colspan="2">qty,2</td>
    </tr>
  </tbody>
</table>
```

简单的模板解析器实现：

```ts
import { HtmlTemplate, NormalContext } from 'template-parser'

class MyTableTemplate extends HtmlTemplate<NormalContext> {
  parse(ctx: NormalContext): string {
    // 逐行渲染（ExpressionCell 会用 ctx 替换表达式）
    let row = (this as any).rows[0] // rows 为受保护字段，示例中直接访问；在你的项目中可封装更友好的 API
    while (row) {
      row.eval(ctx)
      row = row.getNext()
    }
    // 返回模板根节点 HTML。此处 domFragment 为基类中的片段
    // 可根据需要改为 outerHTML 或 innerHTML
    return (this as any).domFragment.firstElementChild?.outerHTML ?? ''
  }
}
```

关于循环与占位：
- 目标行：包含集合相关变量（如 `${item.name}`），用于被复制
- 占位空行：目标行之后紧邻的“全空”行作为占位，渲染时按集合长度复制目标行并覆盖/追加

> 注意：集合与统计依赖 Context.getVector 提供集合数据；默认 NormalContext 未实现集合向量（会抛出异常）。你需要实现自定义 Context 以支持集合渲染与统计。

## 统计单元格（SummeryCell）

- 写法：`字段,小数位`（如 `qty,2`），等价于 `${qty.reduce((t,e)=>t+e,0).toFixed(2)}`。依赖 `Context.getVector(field)` 提供数组。

## 核心 API 速查

从 `template-parser` 导出的主要成员：

- 上下文
  - `Context` 接口：set/get/merge/has/getVariables/getVector
  - `NormalContext`：基础实现（未实现集合 getVector）
- 表达式
  - `Expression`：`parse(input: string)`、`eval(context)` 等
  - 常量：`EXP_START = '\${'`，`EXP_END = '}'`
- 模板与表格
  - `Template<T extends Context>`：`parse(context: T): string`
  - `HtmlTemplate<T extends Context>`：HTML 表格模板基类
  - `HtmlTableRow`/`TemplateRow`：行级操作（eval/append/remove/override/clone/isEmpty 等）
  - `TemplateCell` 及实现：`NormalCell`、`ExpressionCell`、`EmptyCell`、`SummeryCell`
  - `CellUtils`：辅助判断单元格类型（空/普通/表达式/统计）


## 自定义 Context（支持集合/循环）

```ts
import { NormalContext } from 'template-parser'

class ListContext extends NormalContext {
  getVector(key: string): (string | number)[] {
    const v = this.get(key)
    return Array.isArray(v) ? (v as any[]) : []
  }
}
```

## 开发指南

- 本地开发：`npm run dev`
- 构建：`npm run build`（会先跑测试，再打包 Vite 和类型）
- 测试：`npm run test`（Vitest + jsdom）
- Lint：`npm run lint` / `npm run lint:fix`

打包产物（见 package.json exports）：
- ESM：`dist/template-parser.es.js`
- UMD：`dist/template-parser.umd.js`
- 类型：`dist/types/index.d.ts`

## 兼容性与限制

- 需要 DOM 环境（浏览器或 jsdom）。Node 端纯字符串渲染可仅使用 Expression/Context。
- HtmlTemplate 解析要求根 table ，tbody 必须存在。
- 循环/统计依赖你提供的集合 Context 实现。