JQuick-Excel JContext 与 trans 实战将字典值带入行转换tags: #JQuickExcel #Java #JContext #Excel导入更多示例: JQuick-Excel 官网示例 https://www.jquick.org/jquick-excel/#demo-formulas0005简介业务系统通常保存性别、状态、类型等编码Excel 模板通常展示可读文本。JQuick-Excel 已确认支持通过JContext提供外部字典再在TRANSFORM中调用trans(${dict},${gender})。本文只基于 README-CN.md 与测试 XML 已出现的JContext、trans、${field}和${key}语义说明上下文键、当前行字段和导入导出字典方向如何配合。前言把字典翻译散落在 Java 循环中会使 XML 模板只剩表头信息真正的展示规则又分散到调用方。JQuick-Excel 的做法是让 Java 在解析器创建前把外部值放入JContext让 XML 的TRANSFORM保留逐行转换规则。这样字典的来源仍由业务代码控制字段如何在工作簿流转则可以在配置中审查。README 明确说明${field}读取当前行字段${key}可以从JContext取值。于是trans(${dict},${gender})的第一个参数来自上下文第二个参数来自当前行。测试 XML 的导出规则和导入规则都采用该形式。这里不应假设trans会自动发现某个 Map也不应把dict当作工作簿列名二者必须由调用方和 XML 显式约定。最关键的业务点是字典方向。导入时用户在工作簿填写“男”“女”应用通常要保存“1”“0”字典应为“展示文本到编码”。导出时当前行通常保存“1”“0”用户希望看到“男”“女”字典应为“编码到展示文本”。两个场景都可以使用同一个函数表达式但字典内容不能不经确认地复用。环境与依赖本文使用 JDK 8 或更高版本、Maven 和xls或xlsx工作簿。Maven 坐标如下dependencygroupIdio.github.paohaijiao/groupIdartifactIdjquick-excel/artifactIdversion3.6.0/version/dependencyXML 位于类路径例如src/main/resources/jquick-excel.xml。导入解析器JQuickExcelImportXmlParseFactory接收JContext和输入流README 中的基础导出示例则以行数据和输出流创建JQuickExcelExportXmlParseFactory。本文不会杜撰其他构造器或私有调用流程字典传递只使用 README 已展示的context.put(dict, gender)模式。服务接口的全限定名应与 XMLnamespace对应接口方法名应与excel name对应。输入模板需满足SHEET和HEADER所声明的工作表与表头契约。若字典由数据库、配置中心或远程服务提供取值、缓存和权限仍是应用层职责传入JContext前应保证内容已经满足当前导入或导出的方向要求。代码示例以下是导入规则。工作簿“学生表”包含“姓名”“性别”两列MAPPING将它们写入name、gender字段TRANSFORM用上下文字典将“男”“女”还原为内部编码。?xml version1.0 encodingUTF-8?!DOCTYPEexcelsPUBLIC-//PAOHAIJIAO//DTD API EXCEL 1.0//ENclasspath:paohaijiao/dtd/Jquick-excel.dtdexcelsnamespacecom.example.StudentImportServiceexcelnameimportStudentsreturnClassjava.util.List![CDATA[ IMPORT WITH SHEET学生表, HEADERtrue, MAPPING{姓名:name,性别:gender}, TRANSFORM{gender:trans(${dict},${gender})} ]]/excel/excelsJava 先构造“展示文本到编码”的字典再把它放到键为dict的上下文中。键名与${dict}必须相同。importcom.github.paohaijiao.context.JContext;importjava.util.HashMap;importjava.util.Map;MapString,ObjectgendernewHashMap();gender.put(男,1);gender.put(女,0);JContextcontextnewJContext();context.put(dict,gender);完整导入调用保持 README 的代理创建方式importcom.github.paohaijiao.context.JContext;importcom.github.paohaijiao.statement.JQuickRow;importcom.github.paohaijiao.xml.JQuickFactory;importcom.github.paohaijiao.xml.JQuickXmlFactory;importcom.github.paohaijiao.xml.parse.JQuickParseHandler;importcom.github.paohaijiao.xml.parse.excel.JQuickExcelImportXmlParseFactory;importcom.github.paohaijiao.xml.param.Param;importjava.io.InputStream;importjava.util.List;publicinterfaceStudentImportService{ListJQuickRowimportStudents(Param(field)Stringfield,Param(value)Stringvalue);}try(InputStreaminputApp.class.getClassLoader().getResourceAsStream(students.xlsx)){JQuickParseHandlerparsernewJQuickExcelImportXmlParseFactory(context,input);JQuickFactoryfactorynewJQuickXmlFactory(parser,jquick-excel.xml);ListJQuickRowrowsfactory.createApi(StudentImportService.class).importStudents(field,value);}导出时可保持同一表达式trans(${dict},${gender})但字典需反向准备例如键为1、0值为“男”“女”。这是数据契约变化不是 XML 函数名称变化。将两份字典显式命名为导入字典和导出字典有助于代码评审时发现方向错误。原理说明JContext是 Java 与 DSL 之间传递外部值的容器。代码执行context.put(dict, gender)后表达式解析${dict}可以取得该对象表达式解析${gender}时则从正在处理的当前行读取字段。trans在两个参数完成求值后以字典和当前值完成转换。这个模型的价值在于外部数据与行数据来源不同但可以在一个声明式表达式中被组合。导入链路中HEADERtrue使第一行作为表头MAPPING先将“性别”列对应到gender字段。随后每一行执行TRANSFORM因此${gender}取得的是该行已经映射到的字段值。字典转换不负责识别工作簿列也不负责决定标题名称列归属始终由MAPPING定义。把这两层职责分开模板调整时更容易定位问题。导出链路也是同样的逐行模型只是当前行值与字典方向相反。若当前数据是编码字典把编码翻译为展示文本若当前数据已经是展示文本又使用编码到文本字典结果就不会符合预期。函数本身并不会推断“这是导入还是导出”因此业务代码必须在创建上下文前确定字典方向。FORMAT与这一过程独立。trans属于TRANSFORM它决定每行写入或导入后的字段值FORMAT只控制导出单元格的显示格式。字典翻译不是日期、货币等显示格式也不能用格式规则替代。类似地字典成员是否允许、某个编码是否已停用、是否有权限使用某值仍需在基础转换之后由校验和应用层规则处理。注意事项第一上下文键和表达式键必须完全一致。context.put(dict, gender)对应${dict}如果 Java 使用了别的键表达式不会从期望位置读取字典。当前行字段也必须一致导入映射右侧是gender转换键和${gender}同样应为gender。表头“性别”只放在导入映射左侧不能直接当作表达式字段。第二导入与导出都要为字典未命中制定明确策略。本文只说明已确认的转换表达式不杜撰未文档化的默认值行为。业务可以在导入前补齐模板可选值、在导入后拒绝无法映射的行或由调用方按业务需求保留原值但不能把未命中悄悄当成成功。测试至少应覆盖字典命中、字典缺项和输入为空三类数据。第三不要让单行字典翻译承担跨行或跨系统约束。批次内重复、用户权限、引用数据存在性、状态是否可迁移等都依赖业务上下文或数据库应在得到ListJQuickRow后处理。若需要基础单元格约束只使用 README 已确认的VALIDATION范围和规则并把范围限制在实际数据区。第四模板升级时应同时检查工作表名、表头和映射字段。交换列顺序在表头未变时通常不影响映射但表头名称、全半角、空格或历史别名的变化都会改变契约。建议用包含“男”“女”以及未命中值的小工作簿回归测试检查结果行的gender字段而不是只观察文件是否被成功读取。第五流生命周期由调用方管理。创建解析器、创建代理和调用方法应在输入流仍打开时完成。若出现没有转换、字段为空或读错列的情况按顺序检查 XML 是否在类路径、接口与 namespace 是否一致、节点名与方法是否一致、映射是否命中、上下文键和字段名是否一致。先证明契约正确再考虑数据量和性能问题。补充实践字典转换应先定义数据方向再写表达式。导入模板中用户填写的是展示值时传入 JContext 的字典需要能以展示值找到内部值导出行中保存的是内部值时字典需要能以内部值找到展示值。两种场景可以复用trans(${dict},${gender})的函数形态却不能不加检查地共用同一份键值方向。函数调用不包含“导入”或“导出”的标记因此方向判断只能由业务代码和字典内容承担。上下文键需要形成明确约定。Java 调用context.put(dict, gender)后XML 中才使用${dict}键名改变时Java 与 XML 必须同步修改。与之相对${gender}来自当前行字段。导入场景中该字段由MAPPING的右侧名称产生导出场景中它来自待写出的行数据。dict和gender即使都写在${...}中来源也不同一个来自 JContext一个来自当前行。将二者分开理解是维护 trans 表达式的关键。建议把字典准备过程限制在调用边界。数据库、配置文件或远端接口如何提供字典属于业务应用职责在创建JQuickExcelImportXmlParseFactory前将已经准备好的 Map 放进 JContext。XML 只表达行值要如何通过字典转换。这样模板能够被审查而数据加载、缓存有效期和权限控制仍留在合适的应用层。README 已展示的调用方式足以表达这个边界不需要为本文假设额外的上下文生命周期或自动加载机制。验收时至少要检查三类契约上下文键与${dict}是否一致MAPPING 右侧、TRANSFORM 键与${gender}是否一致字典方向是否符合本次导入或导出。对于字典未命中的处理本文不宣称框架具有某种默认值或异常策略因为现有材料没有给出该承诺。项目应使用小样本在目标版本中验证实际结果并由应用层制定可见的业务处置而不是把未命中静默解释为已经翻译成功。延伸检查同一个工作簿可能同时有多类字典例如性别、班级、状态。项目应让每类上下文键表达清楚含义并使 XML 的${key}与 Java 的context.put保持一一对应。不要因为所有字典都存放在 JContext 中就把它们混成无法辨认的通用对象表达式的可读性依赖于调用方和模板对键名的共同约定。模板回归时除了检查可命中的值还应保留一条未命中值作为观察样本。现有材料确认了 trans 的调用形式却没有为本文提供未命中时的返回或报错承诺因此不应把某次运行观察到的现象包装成固定规则。应用层需要根据目标业务明确后续处置并通过所用版本的实际测试确认。这样的边界说明比假设一个默认行为更可靠。trans 只负责当前行值与上下文字典的组合不应承担数据来源的安全控制。字典内容来自哪里、是否可被当前用户使用、何时刷新、是否需要审计都是应用层需要控制的事项。XML 负责声明转换Java 负责提供已准备好的上下文两端各自职责稳定后字典展示规则才不会散落在不同的业务循环中。字典契约核对字典转换的可测试单位应是一条明确的键值关系而不是一次“文件成功导入”或“文件成功导出”的笼统结论。对于导入可选择工作簿文本“男”并断言结果行的gender为内部编码对于导出则使用内部编码并断言目标单元格为展示文本。两种断言分别验证了同一表达式在不同方向上的输入契约。当一个模板需要多个字典时上下文键应继续保持精确。例如性别、状态和类型分别由不同 key 提供时表达式中的${key}应能直接反映使用哪一份共享数据。JContext提供的是外部值入口不会根据当前行字段名自动选择字典为不同字典保留清晰的键名能让 XML 审查者检查每一项转换的来源。字典内容也应在调用边界完成快照式准备。本文不对JContext的具体实现或更新机制作额外推断但从表达式职责看转换期间应消费与本次任务相匹配的字典。若业务要求按租户、组织或有效状态选择字典应先由应用层筛选再放入上下文。这样trans仍然只处理“当前行值加已提供字典”的事实而不会隐藏业务选择条件。排查结果差异时应先将问题拆成“当前行字段是否正确”和“上下文字典是否正确”两部分。例如性别列没有得到预期文本先检查导入映射是否已把该单元格写入gender再检查${dict}对应的 key 是否存在于调用方放入的字典。两个来源分别验证后才需要检查trans表达式本身。这样的顺序不依赖未确认的框架默认行为并能避免因表头、字段名和上下文键混用而扩大排查范围。总结JContext为 DSL 提供外部字典trans(${dict},${gender})则将该字典与当前行字段组合为逐行转换。上下文键必须与表达式键一致字段引用必须与映射后的业务字段一致字典来源和模板中的行数据来源不同配置时不应混为同一概念。导入与导出的表达式形式可以相同字典方向却应根据本次数据流明确准备展示文本转内部编码或内部编码转展示文本。字典未命中、权限、有效状态和跨记录校验均不由trans自动解决调用方应以小样本验证映射结果并在应用层明确后续处置。