一个以 .xlsx 或 .xlsm 保存的工作簿,底层是一个 OOXML 容器包。里面除了工作表、样式、图表这些服务表格本身的部件,还可以带上若干份与任何单元格都无关的 XML 数据。
它们不出现在工作表上,不参与公式运算,桌面 Excel 打开时也看不到,却会随文件一起保存、一起分发,再打开时依然在。这类部件在规范里叫 Custom XML,惯常由宏、加载项或文档工作流在背后读写,用来给一份文件附加一段结构化业务信息。
服务端程序想维护这类数据,此前一直缺一个正规入口。在 GcExcel V9.2 之前,读写 Custom XML 只能绕过产品,把文件当作压缩包直接解开、改完再重新打包。V9.2 提供了工作簿级的 API,把这层数据收拢成一份可以新增、取回、遍历、删除的集合,绕行不再必要。
一张表之外,还有一层数据
Custom XML 不是 V9.2 才有的概念,而是 OOXML 格式从一开始就预留的一类部件。每个工作簿包里可以并存若干份这样的 XML:每一份都是独立部件,内容是一段完整 XML,另配一个标识,落盘时写在 /customXml/itemN.xml 这类标准位置上,不挂在任何工作表或单元格之下。标识与内容各自独立,标识在部件生成时定下、之后只读,供在文件内外引用这份数据;内容则是开放的,由持有方按需填写和改写。Excel 不渲染它们,用户在工作表上也看不到,但打开含这些部件的文件时,它们会被原样保留下来。
真正在维护这些 XML 的,通常是宏和加载项。启用宏的工作簿(.xlsm)常把运行时要参考的配置与状态写成一段 XML 放进文件,宏执行时读入、结束时更新;模板类文件也借助这种部件随身携带结构化元数据,比如一份单据的来源系统、审批节点或流转记录。文档在生成、审核、归档之间走动时,需要一段跟文件走、又不受单元格布局限制的数据载体,Custom XML 承担的正是这个角色。
这一类部件与工作表在包内是平级的。增删工作表、改动单元格、重算公式,都不会碰动它们;反过来,写入或更新 Custom XML 也不影响任何表格内容,这使得服务端可以把它当作独立于表格的旁路数据来维护。内容本身是一段带命名空间的 XML,结构和长度由使用方自己决定,引擎不预设格式。由于每份部件都自带标识,同一个工作簿里可以同时放多份用途不同的数据------一份记录审批进度,一份留给回执或外部系统标记------彼此互不干扰。

不必再去撬开那个压缩包
这个需求在真实集成里并不冷门。V9.2 的这条 API 就来自一个欧洲客户的流水线:他们的 Spring Boot 工作流先从模板生成启用宏的 .xlsm 文件,下发之前,服务端要把一段 XML 负载写进工作簿;用户下载后在桌面 Excel 里编辑,文件内的宏会同步更新这段 XML;等文件传回服务器,服务端再读出更新后的内容,交给后续环节处理。整条链路上,Custom XML 是服务端与桌面宏共用、随文件往返的同一段数据。
卡点出在服务端这一侧。此前 GcExcel 没有面向工作簿的读写入口,客户只能把 .xlsm 当压缩包直接解开,在 OPC 部件层面把 XML 塞回去,再重新打包。改动稍有偏离 OOXML 结构,文件就可能损坏;字段有调整、产品要升级,都得重写一遍手工拆包逻辑;产品也无法对绕过它产生的文件负责。绕行的方案能跑通,却始终是一件需要小心伺候的易碎品。
V9.2 把这条通路移到了工作簿对象本身上,.NET 与 Java 两个平台都提供同一套集合。要存数据就新增一份 Custom XML,引擎随即自动生成一个 UUID 作为它的标识,这一行为与 VSTO 的接口保持一致;要取回,按标识或按序号都可以;可以遍历当前全部部件,也能按标识或序号删掉不再需要的那份。新增之后拿到的是这份部件本身,接着填入内容、记下标识,之后的写入与回读都以它为对象。写入、取回、清理都在这一个集合上完成,压缩包不再需要被打开。
按标识取与按序号取各有各的用途。标识在新增那一刻就确定下来,适合在系统之间传递时作为稳定的引用,文件回传后凭它定位同一份负载;序号反映部件在集合里的排列顺序,适合从头到尾清点一遍。常见的集成写法是:服务端把模板工作簿读入内存,往集合里新增一份部件、填入字节内容,再保存为 .xlsm 下发;文件回传后再次读入,凭标识取出那一段数据,转给后续环节。全过程不再有手工拆包的动作。

原样进,原样出
对以字节为生的服务端集成来说,最要紧的是内容不被额外加工。Custom XML 的数据按原始字节存取:写进去是什么编码、什么字节序,取回来就是什么,不经转码,也不改写内容。取回的内容是一份拷贝,直接改动它不会改到部件本身,要更新仍需把新内容再写回去。每份部件持有一个只读标识和一段可读写的数据,标识在新增时生成,内容则在保存时随工作簿一同落到 /customXml/itemN.xml 这类标准 OOXML 部件里。反过来,一份由桌面 Excel 生成的、自带 Custom XML 的工作簿,被 GcExcel 打开时,这些部件也会自动载入集合,供程序按同样的方式取用。
字节级往返的价值体现在两端。对写的一端,服务端放进什么,桌面宏读到的就是什么,命名空间、缩进、属性顺序都不会被悄悄改写,宏按自己约定的结构去解析即可;对读的一端,用户在 Excel 里由宏更新过的内容,回到服务端时仍是原始字节,后续无论是转存数据库还是转发给其他系统,拿到的都是用户侧真实的最终值。数据在这条链路上没有第二个解释者。
这份状态能跟着文件走。含 Custom XML 的工作簿保存为 .xlsx 或 .xlsm 后,部件以标准部件形式留在包内;下次再打开这个文件,引擎会自动把它们重新载入,不需要额外步骤。标识同样持久化------保存、关闭、再打开,凭新增时拿到的那个标识仍能取到同一份部件。只要文件还在流转,里面的 XML 和它的身份就不会丢,服务端在任何一个环节都能用同一个标识把它找回来。
边界写在明处
职责要分清。GcExcel 负责存取,不负责理解。引擎不校验写入内容是不是合法 XML,也不做 schema 校验;它不会把 XML 映射到单元格,更不会去执行工作簿里引用这些数据的宏------宏的运行发生在桌面 Excel 里,不在服务端。Custom XML 是一条数据通道,引擎保证它存得进去、取得回来,内容的语义由使用它的宏和业务代码负责。
因此有一处限制要如实说明。既然内容原样保存、不加校验,一旦放进文件的是不合法的 XML,Excel 打开该文件时可能失败。产品不会替写入方拦下标签没闭合、属性少了引号、混入非法字符这类手误,这类错误要等文件在 Excel 里打开时才暴露。稳妥的做法是写入前先让内容经过服务端已有的 XML 解析工具确认是良构的,再交给工作簿。集成方始终要在写入一侧自行保证内容的合法性,这份校验职责本来就该由服务端承担。
把这几条边界合起来看,Custom XML 在 GcExcel 里始终只是被搬运的数据。涉及内容校验、结构映射、宏逻辑的部分仍留在各自原本的位置:宏在桌面 Excel 里运行,业务规则在服务端代码里定义,引擎负责让数据随文件完整地往返,并把能力边界如实摊开。
把这条能力放回文档流转的语境里,它给自动化集成团队多了一种随文件携带数据的方式。审批状态、审核记录、回执、来源系统标识这类结构化元数据,可以由服务端写入,在桌面端被宏就地更新,再随文件回到服务端校验。过去为了传一小段数据去维护一张旁路数据库,或在关键时刻手工拆包,在这些需求面前都不再必要。文件走到哪个环节,这段数据就跟到哪个环节,服务端始终能按同一个标识把它读回来。