脚注是 Word 文档中常用的注释工具,用于在页面底部对正文内容进行补充说明或标注引用来源。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接完成脚注的插入、格式设置与移除操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
本文介绍三个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。
插入脚注是在文档中添加注释信息的基础操作,适合对特定术语、引用来源或补充说明进行标注。Spire.Doc for JavaScript 通过 AppendFootnote 方法创建脚注,并允许开发者精确控制脚注插入的位置、脚注正文的文本内容以及脚注标记(上标数字)的显示样式。
function App() {
const insertFootnote = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 检查模块是否就绪
if (!wasmModule) {
alert('Spire.Doc is not ready yet');
return;
}
const inputFileName = 'SampleB_2.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// 加载文档
const doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// 查找文档中第一个匹配的字符串
let selection = doc.FindString("Spire.Doc", false, true);
// 获取匹配文本所在的 TextRange
let textRange = selection.GetAsOneRange();
// 获取匹配文本所在的段落
let paragraph = textRange.OwnerParagraph;
// 获取 TextRange 在段落中的索引位置
let index = paragraph.ChildObjects.IndexOf(textRange);
// 在段落中追加一个脚注
let footnote = paragraph.AppendFootnote({ type: docModule.FootnoteType.Footnote });
// 将脚注插入到匹配文本之后
paragraph.ChildObjects.Insert(index + 1, footnote);
// 在脚注文本体中添加内容
textRange = footnote.TextBody.AddParagraph().AppendText("欢迎来评估Spire.Doc");
// 设置脚注内容的文字格式
textRange.CharacterFormat.FontName = "Arial Black";
textRange.CharacterFormat.FontSize = 10;
textRange.CharacterFormat.TextColor = docModule.Color.get_DarkGray();
// 设置脚注标记(上标数字)的格式
footnote.MarkerCharacterFormat.FontName = "Calibri";
footnote.MarkerCharacterFormat.FontSize = 12;
footnote.MarkerCharacterFormat.Bold = true;
footnote.MarkerCharacterFormat.TextColor = docModule.Color.get_DarkGreen();
// 定义输出文件名
const outputFileName = "InsertFootnote.docx";
// 保存文档
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// 释放资源
doc.Dispose();
// 从 VFS 读取生成的文件,触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>插入脚注。</h1>
<button onClick={insertFootnote}>
Generate
</button>
</div>
);
}
export default App;
通过 AppendFootnote 方法插入脚注后,页面底部会显示注释内容,同时正文中的对应位置出现上标脚注标记。

Word 文档的脚注默认采用阿拉伯数字(1, 2, 3...)编号,并在每页底部连续显示。但在学术论文、技术手册或排版要求严格的出版物中,往往需要按章节或页面重新编号、使用字母或罗马数字等不同的编号格式,甚至将脚注集中放置在节(Section)的末尾而非页面底部。Spire.Doc for JavaScript 通过 FootnoteOptions 对象提供了灵活的控制能力,允许开发者针对文档中的每个节单独设置编号格式、重新开始规则以及显示位置,以满足不同排版规范的需求。
function App() {
const setFootnoteFormat = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 检查模块是否就绪
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
const inputFileName = 'Footnote.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// 加载文档
const doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// 获取第一个节
let sec = doc.Sections.get_Item(0);
// 设置脚注编号格式为大写字母
sec.FootnoteOptions.NumberFormat = docModule.FootnoteNumberFormat.UpperCaseLetter;
// 设置脚注重新开始规则为每页重新编号
sec.FootnoteOptions.RestartRule = docModule.FootnoteRestartRule.RestartPage;
// 设置脚注位置为节的末尾
sec.FootnoteOptions.Position = docModule.FootnotePosition.PrintAsEndOfSection;
// 定义输出文件名
const outputFileName = "SetPositionAndNumberFormat.docx";
// 保存文档
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// 释放资源
doc.Dispose();
// 从 VFS 读取生成的文件,触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>设置脚注格式。</h1>
<button onClick={setFootnoteFormat}>
Generate
</button>
</div>
);
}
export default App;
通过 FootnoteOptions 对象修改编号格式和位置后,文档中的脚注会按照指定的样式重新编排。例如,将编号格式设为 UpperCaseLetter(大写字母)并将位置设为 PrintAsEndOfSection(节末尾),适用于附录或分章节编写的文档,让脚注在每节结束时统一呈现。设置 RestartRule 为 RestartPage 则可以让每页的脚注编号从 A(或 1)重新开始,避免跨页后编号持续增长。这些配置按节独立生效,同一文档中不同节可以拥有不同的脚注规则,非常适合多章节、多作者协作的复杂文档场景。

当文档经过多轮审阅或内容更新后,某些脚注可能不再适用或需要删除。Spire.Doc for JavaScript 通过遍历节(Section)中所有段落(Paragraph)的子对象,使用 instanceof 判断每个子对象是否为 Footnote 类型,找到后调用 RemoveAt 方法将其从段落的子对象集合中移除。这种逐段遍历的方式可以精准定位文档中任意位置的脚注,而无需预先知道脚注的具体页码或索引号。
function App() {
const removeFootnote = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 检查模块是否就绪
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
const inputFileName = 'Footnote.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// 加载文档
const doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// 获取第一个节
let section = doc.Sections.get_Item(0);
// 遍历节中所有段落,查找并移除脚注
for (let p = 0; p < section.Paragraphs.Count; p++) {
let para = section.Paragraphs.get_Item(p);
let index = -1;
// 检查段落中的每个子对象是否为脚注
for (let i = 0, cnt = para.ChildObjects.Count; i < cnt; i++) {
let pBase = para.ChildObjects.get_Item(i);
if (pBase instanceof docModule.Footnote) {
index = i;
break;
}
}
// 如果找到脚注,则将其从段落中移除
if (index > -1) {
para.ChildObjects.RemoveAt(index);
}
}
// 定义输出文件名
const outputFileName = "RemoveFootnote.docx";
// 保存文档
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// 释放资源
doc.Dispose();
// 从 VFS 读取生成的文件,触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>移除脚注。</h1>
<button onClick={removeFootnote}>
Generate
</button>
</div>
);
}
export default App;
移除脚注后,文档正文中原本显示上标标记的位置不再保留任何痕迹,页面底部的注释正文也一并清除,正文内容本身不会受到影响。

原因:脚注追加到段落后,未通过 Insert 方法将其插入到正确的子对象顺序中,导致脚注标记出现在段落末尾而非目标文本之后。
解决:通过 ChildObjects.IndexOf 获取目标文本的索引位置,再使用 Insert 方法将脚注插入到该位置之后:
let index = paragraph.ChildObjects.IndexOf(textRange);
paragraph.ChildObjects.Insert(index + 1, footnote);
原因:脚注编号格式设置在了错误的节上,或文档包含多个节但只修改了第一个节的设置。
解决:确认目标脚注所在的节,并为每个包含脚注的节分别设置编号格式:
for (let i = 0; i < document.Sections.Count; i++) {
let sec = document.Sections.get_Item(i);
sec.FootnoteOptions.NumberFormat = docModule.FootnoteNumberFormat.UpperCaseLetter;
}
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
超链接是 Word 文档中不可或缺的交互元素,广泛应用于指向网页、电子邮件地址、文档内部位置或外部文件。Spire.Doc for JavaScript 基于 WebAssembly 在浏览器端直接完成超链接的插入、查找、修改与移除操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
本文介绍三个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.Doc for JavaScript。以下示例默认已安装 Spire.Doc 并完成 WebAssembly 模块初始化。
插入超链接的核心流程分为三个阶段:首先通过 FetchFileToVFS 将字体文件载入 WASM 虚拟文件系统;然后实例化 Document,在段落中通过 AppendHyperlink 方法插入网页链接、邮件链接或图片链接;最后保存文档并从 VFS 读取生成的文件,封装为 Blob 后触发浏览器下载。
function App() {
const insertHyperlinks = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 检查模块是否就绪
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
const imageFileName = 'Spire.Doc.png';
await window.spire.FetchFileToVFS(imageFileName, '', `${process.env.PUBLIC_URL}/data/`);
// 创建文档对象
const doc = new docModule.Document();
const section = doc.AddSection();
// 插入网页链接
let paragraph = section.AddParagraph();
paragraph.AppendText("Home page");
paragraph.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
paragraph = section.AddParagraph();
paragraph.AppendHyperlink("www.e-iceblue.com", "www.e-iceblue.com", docModule.HyperlinkType.WebLink);
// 插入邮件链接
paragraph = section.AddParagraph();
paragraph.AppendText("Contact US");
paragraph.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
paragraph = section.AddParagraph();
paragraph.AppendHyperlink("mailto:support@ e-iceblue.com", "support@ e-iceblue.com", docModule.HyperlinkType.EMailLink);
// 插入图片链接
paragraph = section.AddParagraph();
paragraph.AppendText("Insert Link On Image");
paragraph.ApplyStyle({ builtinStyle: docModule.BuiltinStyle.Heading2 });
paragraph = section.AddParagraph();
const picture = paragraph.AppendPicture({ imgFile: imageFileName });
paragraph.AppendHyperlink("www.e-iceblue.com", picture, docModule.HyperlinkType.WebLink);
// 定义输出文件名
const outputFileName = "Hyperlink_output.docx";
// 保存文档
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// 从 VFS 读取生成的文件,触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>插入超链接。</h1>
<button onClick= {insertHyperlinks}>
Generate
</button>
</div>
);
}
export default App;
通过 AppendHyperlink 方法插入网页链接和邮件链接后,生成的 Word 文档中包含可点击的超链接文本与图片链接。

对于已包含超链接的 Word 文档,可以通过遍历文档对象模型找到所有超链接字段,并获取或修改其显示文本。
function App() {
const findAndModifyHyperlinks = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 检查模块是否就绪
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
const inputFileName = 'Hyperlinks.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/static/data/`);
// 加载文档
const doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// 遍历文档中所有超链接
let hyperlinks = [];
for (let i = 0; i < doc.Sections.Count; i++) {
let section = doc.Sections.get_Item(i);
for (let j = 0; j < section.Body.ChildObjects.Count; j++) {
let sec = section.Body.ChildObjects.get_Item(j);
if (sec.DocumentObjectType == docModule.DocumentObjectType.Paragraph) {
for (let k = 0; k < sec.ChildObjects.Count; k++) {
let para = sec.ChildObjects.get_Item(k);
if (para.DocumentObjectType == docModule.DocumentObjectType.Field) {
let field = para;
if (field.Type == docModule.FieldType.FieldHyperlink) {
hyperlinks.push(field);
}
}
}
}
}
}
// 修改第一个超链接的显示文本
hyperlinks[0].FieldText = "Spire.Doc component";
// 定义输出文件名
const outputFileName = "ModifyHyperlinkText_output.docx";
// 保存文档
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// 从 VFS 读取生成的文件,触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
// 释放资源
doc.Dispose();
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Find & Modify Hyperlinks</h1>
<button onClick={findAndModifyHyperlinks}>
Generate
</button>
</div>
);
}
export default App;
通过遍历文档对象模型找到所有超链接后,可以读取或修改每个超链接的显示文本与目标地址,满足批量更新需求。

当文档中的超链接不再需要时,可以将其移除,同时保留超链接对应的文本内容。核心思路是先找到所有超链接字段,然后通过扁平化(Flatten)操作将字段结构拆解,仅保留普通文本并去除超链接格式。
// 查找文档中所有超链接
function FindAllHyperlinks(document) {
let docModule = window.wasmModule.spiredoc;
let hyperlinks = [];
for (let i = 0; i < document.Sections.Count; i++) {
let section = document.Sections.get_Item(i);
for (let j = 0; j < section.Body.ChildObjects.Count; j++) {
let sec = section.Body.ChildObjects.get_Item(j);
if (sec.DocumentObjectType == docModule.DocumentObjectType.Paragraph) {
for (let k = 0; k < sec.ChildObjects.Count; k++) {
let para = sec.ChildObjects.get_Item(k);
if (para.DocumentObjectType == docModule.DocumentObjectType.Field) {
let field = para;
if (field.Type == docModule.FieldType.FieldHyperlink) {
hyperlinks.push(field);
}
}
}
}
}
}
return hyperlinks;
}
// 移除超链接格式,仅保留文本
function FormatFieldResultText(ownerBody, sepOwnerParaIndex, endOwnerParaIndex, sepIndex, endIndex) {
let docModule = window.wasmModule.spiredoc;
for (let i = sepOwnerParaIndex; i <= endOwnerParaIndex; i++) {
let para = ownerBody.ChildObjects.get_Item(i);
if (i == sepOwnerParaIndex && i == endOwnerParaIndex) {
for (let j = sepIndex + 1; j < endIndex; j++) {
let tr = para.ChildObjects.get_Item(j);
tr.CharacterFormat.TextColor = docModule.Color.get_Black();
tr.CharacterFormat.UnderlineStyle = docModule.UnderlineStyle.None;
}
} else if (i == sepOwnerParaIndex) {
for (let j = sepIndex + 1; j < para.ChildObjects.Count; j++) {
let tr = para.ChildObjects.get_Item(j);
tr.CharacterFormat.TextColor = docModule.Color.get_Black();
tr.CharacterFormat.UnderlineStyle = docModule.UnderlineStyle.None;
}
} else if (i == endOwnerParaIndex) {
for (let j = 0; j < endIndex; j++) {
let tr = para.ChildObjects.get_Item(j);
tr.CharacterFormat.TextColor = docModule.Color.get_Black();
tr.CharacterFormat.UnderlineStyle = docModule.UnderlineStyle.None;
}
} else {
for (let j = 0; j < para.ChildObjects.Count; j++) {
let tr = para.ChildObjects.get_Item(j);
tr.CharacterFormat.TextColor = docModule.Color.get_Black();
tr.CharacterFormat.UnderlineStyle = docModule.UnderlineStyle.None;
}
}
}
}
function FlattenHyperlinks(field) {
// 获取超链接字段各组成部分的位置索引
let ownerParaIndex = field.OwnerParagraph.OwnerTextBody.ChildObjects.IndexOf(field.OwnerParagraph);
let fieldIndex = field.OwnerParagraph.ChildObjects.IndexOf(field);
let sepOwnerPara = field.Separator.OwnerParagraph;
let sepOwnerParaIndex = field.Separator.OwnerParagraph.OwnerTextBody.ChildObjects.IndexOf(field.Separator.OwnerParagraph);
let sepIndex = field.Separator.OwnerParagraph.ChildObjects.IndexOf(field.Separator);
let endIndex = field.End.OwnerParagraph.ChildObjects.IndexOf(field.End);
let endOwnerParaIndex = field.End.OwnerParagraph.OwnerTextBody.ChildObjects.IndexOf(field.End.OwnerParagraph);
// 移除超链接格式(蓝色下划线)
FormatFieldResultText(field.Separator.OwnerParagraph.OwnerTextBody, sepOwnerParaIndex, endOwnerParaIndex, sepIndex, endIndex);
// 移除超链接字段结构
field.End.OwnerParagraph.ChildObjects.RemoveAt(endIndex);
for (let i = sepOwnerParaIndex; i >= ownerParaIndex; i--) {
if (i == sepOwnerParaIndex && i == ownerParaIndex) {
for (let j = sepIndex; j >= fieldIndex; j--) {
field.OwnerParagraph.ChildObjects.RemoveAt(j);
}
} else if (i == ownerParaIndex) {
for (let j = field.OwnerParagraph.ChildObjects.Count - 1; j >= fieldIndex; j--) {
field.OwnerParagraph.ChildObjects.RemoveAt(j);
}
} else if (i == sepOwnerParaIndex) {
for (let j = sepIndex; j >= 0; j--) {
sepOwnerPara.ChildObjects.RemoveAt(j);
}
} else {
field.OwnerParagraph.OwnerTextBody.ChildObjects.RemoveAt(i);
}
}
}
function App() {
const removeHyperlinks = async () => {
// 获取 Spire.Doc WASM 模块
const docModule = window.wasmModule?.spiredoc;
// 检查模块是否就绪
if (!docModule) {
alert('Spire.Doc is not ready yet');
return;
}
const inputFileName = 'Hyperlinks.docx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}/data/`);
// 加载文档
const doc = new docModule.Document();
doc.LoadFromFile(inputFileName);
// 查找所有超链接
let hyperlinks = FindAllHyperlinks(doc);
// 扁平化所有超链接(移除超链接格式,保留文本)
for (let i = hyperlinks.length - 1; i >= 0; i--) {
FlattenHyperlinks(hyperlinks[i]);
}
// 定义输出文件名
const outputFileName = "RemoveHyperlinks_output.docx";
// 保存文档
doc.SaveToFile({ fileName: outputFileName, fileFormat: docModule.FileFormat.Docx2013 });
// 从 VFS 读取生成的文件,触发下载
const modifiedFileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([modifiedFileArray], { type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
// 释放资源
doc.Dispose();
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>移除超链接。</h1>
<button onClick={removeHyperlinks}>
Generate
</button>
</div>
);
}
export default App;
移除超链接后,文档中的超链接文本保留为普通文本,原有的蓝色下划线样式被清除,内容保持不变。

原因:插入的链接目标地址格式不正确,例如缺少协议前缀(如 http:// 或 mailto:)导致 Word 无法识别为有效的可点击链接。
解决:确保网页链接使用完整的 URL,邮件链接添加 mailto: 前缀:
// 正确的网页链接
paragraph.AppendHyperlink("https://www.e-iceblue.com", "e-iceblue", docModule.HyperlinkType.WebLink);
// 正确的邮件链接
paragraph.AppendHyperlink("mailto:support@ e-iceblue.com", "support@ e-iceblue.com", docModule.HyperlinkType.EMailLink);
原因:仅移除了超链接字段结构,但未将文字颜色和下划线样式重置为普通文本样式。
解决:扁平化超链接时,同步将文字颜色设为黑色、下划线样式设为无:
tr.CharacterFormat.TextColor = docModule.Color.get_Black();
tr.CharacterFormat.UnderlineStyle = docModule.UnderlineStyle.None;
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。

在 Java 中将 Excel 转换为 JSON 是后端开发中的常见需求,尤其在构建 API、ETL 数据管道或数据集成工作流时。本指南将介绍如何使用 Spire.XLS for Java 将 Excel 转换为 JSON——该库同时支持 XLS 和 XLSX 格式,且所需代码量极少。
Excel 文件广泛用于数据存储和报表,而 JSON 已成为现代应用中数据交换的标准格式。然而,若在 Java 中手动实现 Excel 到 JSON 的转换并非易事——开发者需要处理文件解析、数据类型转换、空单元格以及多工作表结构等问题,这些工作很快就会变得复杂且容易出错。
借助 Spire.XLS for Java 与 Jackson,开发者可以用简洁、可维护的代码将 Excel 电子表格转换为结构化的 JSON 数据。本文将提供一套完整的分步教程,涵盖 Java Excel 转 JSON 的单工作表转换、多工作表处理以及嵌套 JSON 结构。
快速导航
Excel 和 JSON 在现代软件系统中被广泛使用,但它们的定位截然不同。Excel 专为结构化数据录入、分析和报表而设计,支持公式、格式化和多工作表工作簿。而 JSON(JavaScript 对象表示法)是一种轻量级数据格式,用于机器间通信、REST API、配置文件和 NoSQL 数据库。
正因如此,Java 开发者经常需要将 Excel 转换为 JSON,以便将基于电子表格的数据集成到后端系统中。
常见应用场景包括:
在 Java 应用中,将 Excel 转换为 JSON 远不止是读取行和映射列那么简单。实际文件往往包含不一致的数据类型、空单元格、日期格式问题以及多工作表结构,这使得手动解析既复杂又容易出错。
Spire.XLS for Java 简化了这一过程,它为 XLS 和 XLSX 格式提供了统一的 API。开发者可以直接访问单元格值、数据类型和格式化信息,从而编写出简洁可靠的 Excel 转 JSON 转换逻辑,无需处理底层文件解析。
在开始 Excel 转 JSON 之前,请在项目中配置以下依赖。
Spire.XLS for Java 可通过 e-iceblue Maven 仓库获取。在 pom.xml 中添加仓库和依赖:
<repositories>
<repository>
<id>com.e-iceblue</id>
<name>e-iceblue</name>
<url>https://repo.e-iceblue.cn/repository/maven-public/</url>
</repository>
</repositories>
<dependency>
<groupId>e-iceblue</groupId>
<artifactId>spire.xls</artifactId>
<version>16.6.5</version>
</dependency>
您也可以下载 Spire.XLS for Java 并手动添加到项目中。
Java 未内置 JSON 支持。本指南使用 Jackson——Java 生态中最广泛使用的 JSON 处理库:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.2</version>
</dependency>
在 Java 源文件中包含以下导入语句:
import com.spire.xls.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import java.io.File;
import java.io.IOException;
整个转换过程分为四个步骤:加载工作簿、读取标题行、遍历数据行以及组装 JSON 输出。本节将逐步讲解,最后给出完整代码。
使用 Workbook 类打开 Excel 文件,然后通过索引获取目标工作表:
Workbook workbook = new Workbook();
workbook.loadFromFile("EmployeeData.xlsx");
Worksheet worksheet = workbook.getWorksheets().get(0);
电子表格的第一行通常为列标题。这些标题将作为每条 JSON 记录的键。将其读取到 String 数组中:
int columnCount = worksheet.getLastColumn();
String[] headers = new String[columnCount];
for (int col = 1; col <= columnCount; col++) {
headers[col - 1] = worksheet.get(1, col).getValue();
}
从第 2 行开始,逐行遍历并为每条记录创建一个 ObjectNode。每个单元格的值映射到对应的标题键:
ObjectMapper mapper = new ObjectMapper();
ArrayNode arrayNode = mapper.createArrayNode();
for (int row = 2; row <= worksheet.getLastRow(); row++) {
ObjectNode record = mapper.createObjectNode();
for (int col = 1; col <= columnCount; col++) {
record.put(headers[col - 1], worksheet.get(row, col).getValue());
}
arrayNode.add(record);
}
使用 Jackson 的 ObjectMapper 将 ArrayNode 以格式化方式写入文件:
try {
mapper.writerWithDefaultPrettyPrinter().writeValue(new File("EmployeeData.json"), arrayNode);
System.out.println("JSON exported successfully.");
} catch (IOException e) {
System.err.println("Failed to write JSON file: " + e.getMessage());
}
workbook.dispose();
以下是读取 Excel 文件并将其转换为 JSON 的完整程序:
import com.spire.xls.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import java.io.File;
import java.io.IOException;
public class ExcelToJsonConverter {
public static void main(String[] args) {
// 加载 Excel 工作簿
Workbook workbook = new Workbook();
workbook.loadFromFile("EmployeeData.xlsx");
// 获取第一个工作表
Worksheet worksheet = workbook.getWorksheets().get(0);
// 从第一行读取列标题
int columnCount = worksheet.getLastColumn();
String[] headers = new String[columnCount];
for (int col = 1; col <= columnCount; col++) {
headers[col - 1] = worksheet.get(1, col).getValue();
}
// 创建 Jackson ObjectMapper 和 ArrayNode
ObjectMapper mapper = new ObjectMapper();
ArrayNode arrayNode = mapper.createArrayNode();
// 将每一行数据转换为 JSON 对象
for (int row = 2; row <= worksheet.getLastRow(); row++) {
ObjectNode record = mapper.createObjectNode();
for (int col = 1; col <= columnCount; col++) {
record.put(headers[col - 1], worksheet.get(row, col).getValue());
}
arrayNode.add(record);
}
// 将 JSON 以格式化方式写入文件
try {
mapper.writerWithDefaultPrettyPrinter().writeValue(new File("EmployeeData.json"), arrayNode);
System.out.println("Excel数据已成功转换为JSON。");
} catch (IOException e) {
System.err.println("JSON文件写入错误:" + e.getMessage());
}
// 释放工作簿资源
workbook.dispose();
}
}
预期 JSON 输出(以包含 Name、Department、Email 和 Salary 列的 Excel 文件为例):
[ {
"EmployeeID" : "E001",
"FirstName" : "John",
"LastName" : "Smith",
"Department" : "工程部",
"Position" : "软件工程师",
"Salary" : "85000",
"HireDate" : "2022/3/15 0:00:00"
} ]
下图展示了原始 Excel 数据与转换后 JSON 输出的对比。

get(int row, int column) —— 返回指定单元格的 CellRange 对象。行和列的索引从 1 开始。getValue() —— 返回单元格的显示值。与 getText() 不同,无论单元格的数据类型是文本、数字还是日期等,都能正确获取其值。getLastRow() / getLastColumn() —— 返回包含数据的最后一行和最后一列的编号。您还可以了解如何在 Java 中将 Excel 转换为 CSV,适用于需要轻量级表格格式进行数据交换和存储的场景。
Spire.XLS for Java 同时支持传统的 XLS 格式(Excel 97–2003)和现代的 XLSX 格式(Excel 2007 及更高版本)。该库在调用 loadFromFile() 时会自动检测文件格式,因此同一段 Java 代码无需任何修改即可将 XLS 和 XLSX 转换为 JSON。
// 将 XLSX 转换为 JSON(现代格式)
Workbook xlsxWorkbook = new Workbook();
xlsxWorkbook.loadFromFile("SalesReport.xlsx");
// 将 XLS 转换为 JSON(传统格式)
Workbook xlsWorkbook = new Workbook();
xlsWorkbook.loadFromFile("SalesReport.xls");
// 两个工作簿的处理方式完全相同
Worksheet sheet = xlsxWorkbook.getWorksheets().get(0);
int rowCount = sheet.getLastRow();
int colCount = sheet.getLastColumn();
// ... 与基础示例相同的转换逻辑
无需额外配置、格式标志或独立的代码分支。无论是来自传统系统的 .xls 文件还是来自现代应用的 .xlsx 文件,Spire.XLS 都能透明地完成解析。这在企业环境中尤为实用,因为 Excel 文件往往来源于不同系统,跨越多种格式版本。
您还可以了解如何在 Java 中实现 XLS 与 XLSX 格式互转,适用于文件格式迁移或传统格式升级的场景。
实际工作中的 Excel 工作簿通常包含多个工作表。将每个工作表转换为独立的 JSON 数组,可以生成保留工作簿结构的输出。在某些场景下,开发者还需要构建嵌套 JSON 对象来反映数据中的层级关系。
以下示例读取工作簿中的所有工作表,并创建一个 JSON 对象,其中每个键为工作表名称,每个值为该工作表的记录数组:
import com.spire.xls.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import java.io.File;
import java.io.IOException;
public class MultiSheetExcelToJson {
public static void main(String[] args) {
Workbook workbook = new Workbook();
workbook.loadFromFile("SalesReport.xlsx");
ObjectMapper mapper = new ObjectMapper();
ObjectNode fullReport = mapper.createObjectNode();
// 遍历工作簿中的每个工作表
for (int s = 0; s < workbook.getWorksheets().getCount(); s++) {
Worksheet worksheet = workbook.getWorksheets().get(s);
String sheetName = worksheet.getName();
// 从第一行读取标题
int columnCount = worksheet.getLastColumn();
String[] headers = new String[columnCount];
for (int col = 1; col <= columnCount; col++) {
headers[col - 1] = worksheet.get(1, col).getValue();
}
// 将数据行转换为 JSON 对象
ArrayNode sheetData = mapper.createArrayNode();
for (int row = 2; row <= worksheet.getLastRow(); row++) {
ObjectNode record = mapper.createObjectNode();
for (int col = 1; col <= columnCount; col++) {
record.put(headers[col - 1], worksheet.get(row, col).getValue());
}
sheetData.add(record);
}
// 将该工作表的数据添加到最终输出
fullReport.set(sheetName, sheetData);
}
// 将合并后的 JSON 以格式化方式写入文件
try {
mapper.writerWithDefaultPrettyPrinter().writeValue(new File("SalesReport.json"), fullReport);
System.out.println("多工作表工作簿已转换为JSON。");
} catch (IOException e) {
System.err.println("JSON文件写入错误:" + e.getMessage());
}
workbook.dispose();
}
}
输出(以包含"East Region"和"West Region"两个工作表的工作簿为例):
{
"East Region": [
{"Employee": "Alice", "Product": "Laptop", "Amount": "1200"},
{"Employee": "Bob", "Product": "Monitor", "Amount": "450"}
],
"West Region": [
{"Employee": "Carol", "Product": "Keyboard", "Amount": "150"},
{"Employee": "Dave", "Product": "Mouse", "Amount": "75"}
]
}
下图展示了多个 Excel 工作表如何映射为单个 JSON 对象结构。

某些场景需要嵌套的 JSON 结构而非扁平数组。例如,一个项目管理电子表格可能在相邻列中列出项目及其任务。以下代码将任务按所属项目进行分组:
import com.spire.xls.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import java.util.LinkedHashMap;
import java.util.Map;
import java.io.File;
import java.io.IOException;
public class NestedExcelToJson {
public static void main(String[] args) {
Workbook workbook = new Workbook();
workbook.loadFromFile("ProjectTasks.xlsx");
Worksheet worksheet = workbook.getWorksheets().get(0);
ObjectMapper mapper = new ObjectMapper();
// 使用 LinkedHashMap 保持项目的插入顺序
Map<String, ObjectNode> projectMap = new LinkedHashMap<>();
for (int row = 2; row <= worksheet.getLastRow(); row++) {
String projectName = worksheet.get(row, 1).getValue();
String taskName = worksheet.get(row, 2).getValue();
String assignee = worksheet.get(row, 3).getValue();
String status = worksheet.get(row, 4).getValue();
// 首次遇到该项目时创建条目
if (!projectMap.containsKey(projectName)) {
ObjectNode project = mapper.createObjectNode();
project.put("name", projectName);
project.set("tasks", mapper.createArrayNode());
projectMap.put(projectName, project);
}
// 构建任务对象并添加到项目的任务数组中
ObjectNode task = mapper.createObjectNode();
task.put("task", taskName);
task.put("assignee", assignee);
task.put("status", status);
((ArrayNode) projectMap.get(projectName).get("tasks")).add(task);
}
// 组装最终的 JSON 数组
ArrayNode projectsJson = mapper.createArrayNode();
for (ObjectNode project : projectMap.values()) {
projectsJson.add(project);
}
try {
mapper.writerWithDefaultPrettyPrinter()
.writeValue(new File("ProjectTasks.json"), projectsJson);
System.out.println("已成功生成嵌套JSON文件。");
} catch (IOException e) {
System.err.println("JSON文件写入错误:" + e.getMessage());
}
workbook.dispose();
}
}
输出(以项目-任务电子表格为例):
[
{
"name": "Website Redesign",
"tasks": [
{"task": "Design mockups", "assignee": "Alice", "status": "Complete"},
{"task": "Frontend implementation", "assignee": "Bob", "status": "In Progress"}
]
},
{
"name": "Mobile App",
"tasks": [
{"task": "API integration", "assignee": "Carol", "status": "Pending"},
{"task": "UI testing", "assignee": "Dave", "status": "Not Started"}
]
}
]
下图展示了扁平的 Excel 行数据如何转换为按项目分组的嵌套 JSON 结构。

这种模式适用于需要将 Excel 数据重构为层级格式的场景,例如匹配 API 接口规范或数据库文档模型。
您还可以了解如何在 Java 中解析 Excel 文件,适用于需要在转换前提取和处理原始电子表格数据的场景。
生产环境中的 Excel 文件很少包含完整、规范的数据。空单元格、混合数据类型和格式不一致的情况十分常见。一个健壮的 Java Excel 转 JSON 程序必须能够应对这些情况。
使用 CellRange.getType() 在读取值之前检查单元格是否为空。提供默认值以避免 JSON 输出中出现 null:
CellRange cell = worksheet.get(row, col);
String value;
if (cell.getType() == CellValueType.Empty) {
value = ""; // 或使用默认值,如 "N/A"
} else {
value = cell.getValue();
}
record.put(headers[col - 1], value);
注意: 在 Jackson 中,
ObjectNode.put(String, String)用于字符串值。对于其他类型,请使用put(String, double)、put(String, boolean)等方法。
getValue() 方法返回单元格的显示值(字符串类型)。对于数值数据,可以使用 getNumberValue() 在 JSON 输出中保留原始类型:
CellRange cell = worksheet.get(row, col);
if (cell.getType() == CellValueType.Number) {
record.put(headers[col - 1], cell.getNumberValue().doubleValue());
} else if (cell.getType() == CellValueType.Boolean) {
record.put(headers[col - 1], cell.getBooleanValue());
} else {
record.put(headers[col - 1], cell.getValue());
}
Excel 内部将日期存储为序列号。要在 JSON 中以 ISO 8601 字符串格式输出日期,需要检测日期格式并进行相应转换:
CellRange cell = worksheet.get(row, col);
if (cell.getType() == CellValueType.DateTime) {
java.util.Date date = cell.getDateTimeValue();
java.text.SimpleDateFormat iso = new java.text.SimpleDateFormat("yyyy-MM-dd");
record.put(headers[col - 1], iso.format(date));
} else {
record.put(headers[col - 1], cell.getValue());
}
这种方式确保日期以标准格式(如 "2026-07-02")输出,而非 Excel 内部的数值表示。
最常见的错误之一是从第 1 行而非第 2 行开始遍历数据。当第一行包含列标题时,将其包含在数据循环中会产生键和值重复的 JSON 对象。
解决方案: 始终先从第 1 行读取标题,然后从第 2 行开始数据循环。
硬编码列位置(如用 worksheet.get(row, 1) 表示"Name")会使代码变得脆弱。一旦 Excel 模板变更、列顺序调整,JSON 的键就无法正确匹配对应的数据。
解决方案: 从第一行动态读取标题,并使用标题数组来分配 JSON 键。这样即使列顺序变更也不会影响转换结果。
Excel 以双精度浮点数存储数字。使用 getValue() 返回的是单元格的显示内容(字符串形式)。如果 JSON 输出需要包含原始数值而非字符串,则需进行额外的类型转换。
解决方案: 通过 getType() 检查单元格类型,对数值型单元格使用 getNumberValue() 获取实际数值而非字符串表示。
Excel 将日期存储为序列号(如 45109 代表 2023 年 6 月 15 日)。虽然 getValue() 会返回日期单元格的显示内容,但具体格式取决于单元格的数字格式,不同工作簿之间可能不一致。
解决方案: 对日期格式的单元格使用 getDateTimeValue(),并将结果转换为标准 ISO 8601 字符串(yyyy-MM-dd 或 yyyy-MM-dd'T'HH:mm:ss),以确保 JSON 输出格式一致。
Spire.XLS 的工作簿对象持有非托管资源。如果在处理完成后未调用 dispose(),可能导致内存泄漏,尤其在批量转换多个文件时。
解决方案: 转换完成后务必调用 workbook.dispose()。使用 try-finally 块确保即使发生异常也能释放资源:
Workbook workbook = new Workbook();
try {
workbook.loadFromFile("EmployeeData.xlsx");
// ... 转换逻辑 ...
} finally {
workbook.dispose();
}
本文介绍了如何使用 Spire.XLS for Java 将 Excel 转换为 JSON。从基础的单工作表转换开始,逐步讲解了工作簿加载、基于标题的键映射以及 JSON 输出生成。随后扩展至 XLS 与 XLSX 格式处理、多工作表工作簿、嵌套 JSON 结构、空单元格处理以及数据类型保留。
Spire.XLS for Java 以简洁的 API 简化了整个转换流程,且无需安装 Microsoft Office。除了 Excel 转 JSON 之外,该库还提供了全面的电子表格功能,包括 PDF 导出、图表创建、公式计算和数据验证。您可以申请 30 天免费许可证,在项目中体验完整功能。
使用 Spire.XLS for Java 加载 Excel 文件,读取标题行确定 JSON 的键,从第 2 行开始遍历数据行,并将每个单元格的值映射到 Jackson ObjectNode 中对应的键。将所有对象收集到 ArrayNode 中,然后使用 ObjectMapper 将结果写入文件或作为字符串返回。完整代码示例请参见第 3 节。
Spire.XLS for Java 提供了全面的 API 来读取 Excel 数据,同时支持 XLS 和 XLSX 格式。它能原生处理单元格类型、公式和格式化,可以方便地提取结构化数据用于 JSON 转换,无需依赖 Microsoft Office 或其他外部组件。
可以。Spire.XLS for Java 会自动检测文件是传统 XLS 格式(Excel 97–2003)还是现代 XLSX 格式(Excel 2007 及更高版本)。同一段代码适用于两种格式,无需额外配置。详见第 4 节。
getValue() 返回单元格的显示值——适用于所有数据类型(文本、数字、日期、布尔值等),返回用户在单元格中看到的内容。getCellValue() 返回底层的原始值(Object 类型)。当 JSON 输出需要与用户在 Excel 中看到的一致时,使用 getValue();当需要带类型的数值或布尔值时,使用 getNumberValue() 或 getBooleanValue()。
在读取值之前,使用 CellRange.getType() 检查单元格类型。如果类型为 CellValueType.Empty,则分配一个默认值,如空字符串或 "N/A"。这可以避免 null 值,确保所有记录的 JSON 结构一致。代码示例请参见第 6 节。
Spire.XLS for Java 是一款商业库。提供免费版本 Free Spire.XLS for Java,但在工作表数量和部分功能上有限制。您也可以申请 30 天免费许可证,在购买前体验完整功能。
Spire.PDF for Java 12.7.0 现已发布。该版本修复了多个与数字签名、PDF 处理及转换相关的问题,并优化了替换文档内容时的内存占用。详情如下。
问题修复:
优化:
我们很高兴地宣布 Spire.Office 11.6.1 正式发布。在此版本中,Spire.Doc 增强了 Word 转 PDF 的转换效果;Spire.PDF 优化了 PDF 转固定布局 Word 的解析逻辑;Spire.XLS 调整了 Worksheet.SaveToPdf 方法;Spire.Presentation 新增支持将公式导出为 MathML 和 LaTeX 格式。此外,本版本还成功修复了大量已知问题,进一步提升了产品的稳定性和性能。更多更新内容如下。
本次发布集成了以下组件的最新版本:Spire.Doc、Spire.PDF、Spire.XLS、Spire.Presentation、Spire.Barcode、Spire.DocViewer、Spire.PDFViewer、Spire.Email、Spire.Spreadsheet 和 Spire.OfficeViewer。
https://www.e-iceblue.cn/Downloads/Spire-Office-NET.html
优化:
问题修复:
调整:
问题修复:
新功能:
// 设置默认字体
Presentation.SetDefaultLatinFontName("Arial");
Presentation.SetDefaultEastAsianFontName("微软雅黑");
// 重置/恢复默认的字体设置
Presentation.ResetDefaultEastAsianFontName();
Presentation.ResetDefaultLatinFontName();
IAutoShape shapeFormula = slide.Shapes.AppendShape(ShapeType.Rectangle,new RectangleF(40, currentTop, shapeWidth, formulaHeight));
// 插入 LaTeX 公式
TextParagraph formulaPara = shapeFormula.TextFrame.Paragraphs.AddParagraphFromLatexMathCode(latex);
// 导出 MathML
string mathML = formulaPara.ExportMathML();
// 导出 LaTex
string LaTex = formulaPara.ExportLaTex();
问题修复:
调整:
| 旧类名 | 新类名 | 旧属性名 | 新属性名 |
|---|---|---|---|
| PdfLaunchAction | PdfLaunchAction | IsNewWindow | NewWindow |
| PdfActionDestination | PdfPredefinedAction | ||
| PdfEmbeddedGoToAction | PdfEmbeddedGoToAction | IsNewWindow | NewWindow |
| PdfResetAction | PdfResetFormAction | ||
| PdfSubmitAction | PdfSubmitFormAction | ||
| PdfUriAction | PdfURIAction | Uri | URI |
| PdfFieldActions | PdfFormFieldAdditionalActions | ||
| PdfAnnotationActions | PdfAnnotationAdditionalActions | MouseEnter | OnEnter |
| MouseLeave | OnExit | ||
| MouseDown | OnMouseDown | ||
| MouseUp | OnMouseUp | ||
| GotFocus | OnReceiveFocus | ||
| LostFocus | OnLostFocus | ||
| Calculate | / | ||
| Validate | / | ||
| KeyPressed | / | ||
| Format | / | ||
| PdfDocumentActions | PdfDocumentAdditionalActions | Calculate | OnCalculate |
| Validate | OnValidate | ||
| KeyPressed | OnModifyCharacter | ||
| Format | OnFormat | ||
| MouseEnter | OnEnter | ||
| MouseLeave | OnExit | ||
| MouseDown | OnMouseDown | ||
| MouseUp | OnMouseUp | ||
| GotFocus | OnReceiveFocus | ||
| LostFocus | OnLostFocus |
| 旧类名 (Spire.Pdf.General命名空间) |
新类名 (Spire.Pdf.Destinations命名空间) |
旧属性名 | 新属性名 |
|---|---|---|---|
| PdfDestination | PdfDestination, PdfExplicitDestination |
||
| PdfDestination (Mode=Location) |
PdfXYZExplicitDestination | Location | Left, Top |
| Rectangle | / | ||
| Mode | Type | ||
| PdfDestination (Mode=FitToPage) |
PdfFitExplicitDestination | Location | / |
| Rectangle | / | ||
| Zoom | / | ||
| Mode | Type | ||
| PdfDestination (Mode=FitH) |
PdfFitHExplicitDestination | Location | Top |
| Rectangle | / | ||
| Zoom | / | ||
| Mode | Type | ||
| PdfDestination (Mode=FitR) |
PdfFitRExplicitDestination | Location | / |
| Rectangle | Left,Bottom, Right, Top | ||
| Zoom | / | ||
| Mode | Type | ||
| PdfDestination (Mode=FitV) |
PdfFitVExplicitDestination | Location | Left |
| Rectangle | / | ||
| Zoom | / | ||
| Mode | Type | ||
| PdfFitBExplicitDestination | |||
| PdfFitBVExplicitDestination | / | Left |
问题修复:
问题修复:
问题修复:
Spire.PDFViewer 8.3.0 现已正式发布。该版本修复了预览 PDF 文件报 “ArgumentNullException” 的问题。详情如下。
问题修复:
Spire.Doc for Java 14.7.0 现已正式发布。该版本新增了 CompareOptions 类的配置选项,并支持“双行合一”功能。此外,一些在转换 Word 到 PDF 和 HTM 时出现的问题也已经成功被修复。详情如下。
新功能:
Document doc1 = ConvertUtil.GetNewEngineDocument();
doc1.loadFromFile(inputFile_1);
String[] name1 = new String[]{"DJG_Black_Book", "DJG_Repairs_Expense", "DJG_Sale_Amount", "DJG_Dealer_Payment", "DJG_Auction_Fees"};
String[] value1 = new String[]{"20,000.00", "2,000.00", "13,500.00", "1,000.00", "500.00"};
doc1.getMailMerge().execute(name1, value1);
doc1.isUpdateFields(true);
Document doc2 = ConvertUtil.GetNewEngineDocument();
doc2.loadFromFile(inputFile_2);
String[] name2 = new String[]{"DJG_Black_Book", "DJG_Repairs_Expense", "DJG_Sale_Amount", "DJG_Dealer_Payment", "DJG_Auction_Fees"};
String[] value2 = new String[]{"50,000.00", "5,000.00", "3,500.00", "1,000.00", "200.00"};
doc2.getMailMerge().execute(name2, value2);
doc2.isUpdateFields(true);
CompareOptions options = new CompareOptions();
options.setCompareMoves(true);
options.setIgnoreCaseChanges(true);
options.setIgnoreComments(true);
options.setIgnoreFields(true);
options.setIgnoreFootnotes(true);
options.setIgnoreTables(true);
options.setIgnoreTextboxes(true);
doc1.compare(doc2, "user", new Date(), options);
doc1.saveToFile(outputFile, FileFormat.Docx_2013);
doc1.close();
doc2.close();
Document doc = ConvertUtil.GetNewEngineDocument();
Section section = doc.addSection();
section.setTextDirection(TextDirection.Right_To_Left);
Paragraph titlePara = section.addParagraph();
titlePara.appendText("===== FarEastLayout Two-Lines-in-One Full Scenario Test =====\n\n");
titlePara.appendText("\n\n1. Basic Two-Lines-in-One: ");
// Basic Two-Lines-in-One Combine=true, no brackets, no vertical layout
Paragraph p1 = section.addParagraph();
TextRange farEastLayout = p1.appendText("Basic Two-Lines-in-One: One Two Three Four");
farEastLayout.getCharacterFormat().setFontSize(12);
farEastLayout.getCharacterFormat().setFontNameFarEast("Songti");
FarEastLayout layout1 = new FarEastLayout();
layout1.setCombine(true); // Two-Lines-in-One
farEastLayout.getCharacterFormat().setFarEastLayout(layout1);
// Two-Lines-in-One + various bracket styles
Paragraph p1Title = section.addParagraph();
p1Title.appendText("\n\n2. Two-Lines-in-One + Different Brackets: ");
for (CombineBrackets bracket : CombineBrackets.values()) {
Paragraph pTmp = section.addParagraph();
TextRange rt = pTmp.appendText("Bracket Type " + bracket.name() + ": A B C D");
rt.getCharacterFormat().setFontSize(12);
rt.getCharacterFormat().setFontNameFarEast("Songti");
FarEastLayout layoutTmp = new FarEastLayout();
layoutTmp.setCombine(true);
layoutTmp.setCombineBrackets(bracket);
rt.getCharacterFormat().setFarEastLayout(layoutTmp);
}
doc.saveToFile(outputFile, FileFormat.Docx);
doc.close();
问题修复:
在日常 Excel 文档处理中,复制工作表是最常用且高效的操作之一——无论是基于模板快速创建相似报表,还是跨文档汇总数据,都离不开工作表复制功能。Spire.XLS for JavaScript 基于 WebAssembly 在浏览器端直接完成此操作,通过虚拟文件系统(VFS)管理输入输出文件,无需后端服务支持。
本文介绍三个核心功能点:
有关安装和项目配置,请参考 React 项目中集成 Spire.XLS for JavaScript。以下示例默认已安装 Spire.XLS 并完成 WebAssembly 模块初始化。
在同一个工作簿中复制工作表是日常开发中的高频需求,例如基于月度报表模板快速创建下个月的报表副本。Spire.XLS for JavaScript 通过 CopyFrom 方法实现工作表的复制,复制后的副本会保留源工作表的所有内容,包括数据、样式、字体、颜色、边框、列宽、行高等格式信息。
function App() {
const sheetToSVG = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和 Excel 文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'Sample.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 加载工作簿
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// 获取第一个工作表。
let sheet = workbook.Worksheets.get(0);
// 添加一个工作表
let sheet1 = workbook.Worksheets.Add("MySheet");
// 将第一个工作表复制到新添加的工作表中。
sheet1.CopyFrom(sheet);
const outputFileName = "CopySheetWithinWorkbook_output.xlsx";
workbook.SaveToFile({ fileName: outputFileName });
// 释放 workbook 对象以释放资源
workbook.Dispose();
// 从 VFS 读取转换后的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Copy Worksheet Within Workbook</h1>
<button onClick={sheetToSVG}>
Start
</button>
</div>
);
}
export default App;
通过 CopyFrom 方法,源工作表中的数据、样式、字体、颜色、边框以及列宽等格式信息会被完整复制到新工作表中。

在实际业务中,经常需要将多个 Excel 文件的数据汇总到同一个工作簿中。例如,从不同部门的报表文件中提取特定工作表合并到总表。通过 AddCopy 方法可以将源工作簿中的工作表完整复制到目标工作簿中。
function App() {
const sheetToSVG = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将字体和 Excel 文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const sourceFileName = 'ReadImages.xlsx';
const targetFileName = 'Sample.xlsx';
await window.spire.FetchFileToVFS(sourceFileName, '', `${process.env.PUBLIC_URL}data/`);
await window.spire.FetchFileToVFS(targetFileName, '', `${process.env.PUBLIC_URL}data/`);
// 加载源工作簿
const sourceWorkbook = new xlsModule.Workbook();
sourceWorkbook.LoadFromFile({ fileName: sourceFileName });
// 获取源工作簿的第一个工作表
const srcWorksheet = sourceWorkbook.Worksheets.get(0);
// 加载目标工作簿
const targetWorkbook = new xlsModule.Workbook();
targetWorkbook.LoadFromFile({ fileName: targetFileName });
// 在目标工作簿中添加新工作表,并将源工作表复制过来
targetWorkbook.Worksheets.AddCopy({ sheet: srcWorksheet });
// 保存目标工作簿
const outputFileName = "CopyAcrossWorkbooks_output.xlsx";
targetWorkbook.SaveToFile({ fileName: outputFileName });
// 释放资源
sourceWorkbook.Dispose();
targetWorkbook.Dispose();
// 从 VFS 读取转换后的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Copy Worksheet Across Workbooks</h1>
<button onClick={sheetToSVG}>
Start
</button>
</div>
);
}
export default App;
跨工作簿复制时,源工作表的全部数据和样式同样会被保留,AddCopy 会将完整的工作表内容复制到目标工作簿中。

有时候我们并不需要复制整张工作表,而只需要将某个单元格区域(例如特定数据表或汇总结果)复制到目标位置。Spire.XLS for JavaScript 提供了 Copy 方法,支持将源区域的数据、样式和格式一同复制到目标区域的起始位置。
function App() {
const sheetToSVG = async () => {
// 获取 Spire.XLS WASM 模块
const xlsModule = window.wasmModule?.spirexls;
// 检查模块是否就绪
if (!xlsModule) {
alert('Spire.Xls is not ready yet');
return;
}
// 将原 Excel 文件载入 VFS
await window.spire.FetchFileToVFS('simsun.ttc', '/Library/Fonts/', `${process.env.PUBLIC_URL}/font/`);
const inputFileName = 'Sample.xlsx';
await window.spire.FetchFileToVFS(inputFileName, '', `${process.env.PUBLIC_URL}data/`);
// 加载工作簿
const workbook = new xlsModule.Workbook();
workbook.LoadFromFile({ fileName: inputFileName });
// 获取第一个第一行作为源区域
const sheet = workbook.Worksheets.get(0);
const sourceRange = sheet.Range.get("A1:E1");
// 新添加工作表
let sheet1 = workbook.Worksheets.Add("AddSheet");
// 将源区域复制到目标工作表的起始位置
sheet.Copy(sourceRange, sheet1, sheet.FirstRow, sheet.FirstColumn, true);
sheet1.AllocatedRange.AutoFitColumns();
// 保存工作簿
const outputFileName = "CopyRange_output22.xlsx";
workbook.SaveToFile({ fileName: outputFileName });
// 释放资源
workbook.Dispose();
// 从 VFS 读取转换后的文件,触发下载
const fileArray = window.dotnetRuntime.Module.FS.readFile(outputFileName);
const blob = new Blob([fileArray], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = outputFileName;
a.click();
URL.revokeObjectURL(url);
};
return (
<div style={{ textAlign: 'center', height: '300px' }}>
<h1>Copy Range</h1>
<button onClick={sheetToSVG}>
Start
</button>
</div>
);
}
export default App;
Copy 方法将源区域的数据、样式和格式一同复制到目标区域。适合仅需提取部分数据的轻量场景。

原因:使用字体差异
解决:确保在跨工作簿复制前,所有需要的字体文件已加载到目标工作簿的 VFS 环境中,例如:
await window.spire.FetchFileToVFS(
'simsun.ttc', '/Library/Fonts/', '/'
);
原因:Copy 方法中指定的 destRow 和 destColumn 参数不正确,导致数据粘贴到预期之外的位置。
解决:确认目标行和列索引从 1 开始计数(非 0),并在复制前验证目标工作表的行列范围。
如果您希望删除结果文档中的评估消息,或者摆脱功能限制,请该Email地址已收到反垃圾邮件插件保护。要显示它您需要在浏览器中启用JavaScript。获取有效期 30 天的临时许可证。
Spire.Office for Java 11.6.0 已正式发布。本版本中,Spire.Doc for Java 增强了 Word 到 PDF 的转换功能;Spire.XLS for Java 增强了 Excel到PDF和图片的转换;Spire.PDF for Java 支持设置 PDF 阅读方向与语言。此外,本版本还成功修复了多个问题。更多详情如下。
https://www.e-iceblue.cn/Downloads/Spire-Office-JAVA.html
问题修复:
问题修复:
新功能:
public static void main(String[] args) throws IOException {
// Create pdf document
PdfDocument doc = new PdfDocument();
// Load file from disk
doc.loadFromFile("Sample.docx");
// Load the X509 certificate for signature
PdfPKCS7Formatter formatter = new PdfPKCS7Formatter(new PdfCertificate("gary.pfx", "e-iceblue"), false);
// Create an instance of PdfOrdinarySignatureMaker using the loaded document and certificate
PdfOrdinarySignatureMaker signatureMaker = new PdfOrdinarySignatureMaker(doc, formatter);
// Create an instance of PdfCustomSignatureAppearance as the appearance for the signature
IPdfSignatureAppearance signatureAppearance = new PdfCustomSignatureAppearance();
// Make the signature with a specified name and the custom appearance
signatureMaker.makeSignature("Signature", signatureAppearance);
// Iterate through all hash algorithm types
for (HashAlgorithmType hashAlg : HashAlgorithmType.values()) {
// Skip SM3 (case-insensitive)
if ("SM3".equalsIgnoreCase(hashAlg.name())) {
continue;
}
try {
// Set the current hash algorithm
formatter.getProperties().setHashAlgorithm(hashAlg);
String filePath = "AddImageSignature_" + hashAlg.name() + ".pdf";
doc.saveToFile(filePath, FileFormat.PDF);
System.out.println("Succeed:" + hashAlg.name());
} catch (Exception ex) {
System.out.println("Error HashAlgorithmType:" + hashAlg.name());
System.out.println("Error Info:" + ex.getMessage());
}
}
// Close the document
doc.close();
}
// Custom signature appearance implementation
public static class PdfCustomSignatureAppearance implements IPdfSignatureAppearance {
@Override
public void generate(PdfCanvas pdfCanvas) {
// Set font size
int fontSize = 10;
// Create Arial font
PdfTrueTypeFont font = new PdfTrueTypeFont(new Font("Arial", Font.PLAIN, fontSize), true);
// Set line height
float lineHeight = fontSize;
// Draw text string
pdfCanvas.drawString("AAAAAAAAAAA", font, PdfBrushes.getRed(), new Point.Float(0, 0));
// Draw image at specified position
pdfCanvas.drawImage(PdfImage.fromFile("E-iceblue logo.png"), new Point.Float(20, 0));
}
}
OfdConverter converter = new OfdConverter(ofdFile.getAbsolutePath());
converter.getOptions().setDefaultFontName(fontName);
converter.toPdf(PdfPath);
CustomProgressNotifier progressNotifier =new CustomProgressNotifier(output_txt);
ofdConverter ofdconverter=new Ofdconverter(inputPath);
ofdconverter.registerProgressNotifier(progressNotifier);
ofdConverter.toPdf(outputPath);
CustomProgressNotifier progressNotifier2=new CustomProgressNotifier(output_txt2);
PdfToWordConverter converter=new PdfTowordConverter(inputPath2);
converter.registerProgressNotifier(progressNotifier2);
converter.saveToDocx(outputPath2)
PdfDocument pdf = new PdfDocument();
pdf.loadFromFile(inputFile);
pdf.getViewerPreferences().setReadingDirection(PdfReadingDirection.LeftToRight);
pdf.setLanguage("zh-CN");
pdf.saveToFile(outputFile , FileFormat.PDF);
PdfDocument document = new PdfDocument();
PdfPageBase pdfPageBase = document.getPages().add();
pdfPageBase.getCanvas().drawString("Hello, World!",
new PdfFont(PdfFontFamily.Helvetica, 30f),
PdfBrushes.getBlack(), 10, 10);
PdfCertificate certificate = new PdfCertificate(path+"gary.pfx", "e-iceblue");
PdfPKCS7Formatter formatter = new PdfPKCS7Formatter(certificate, false);
String timeStampUrl = "https://rfc3161.ai.moda/adobe";
formatter.setTimestampService(new TSAHttpService(timeStampUrl));
formatter.setOCSPService(new OCSPHttpService(null));
PdfMDPSignatureMaker signatureMaker = new PdfMDPSignatureMaker (document, formatter, PdfMDPSignatureMaker.Level2Permissions);
signatureMaker.makeSignature("signName");
com.spire.pdf.interactive.digitalsignatures.PdfSignature signature = signatureMaker.getSignature();
signature.setName("Gary");
// signature.setReason("This is the final version.");
signature.setLocation("U.S.");
signature.setContactInfo("112554");
PdfSignatureAppearance appearance = new PdfSignatureAppearance(signature);
appearance.setNameLabel("Signer: ");
// appearance.setReasonLabel("Reason: ");
appearance.setLocationLabel("Location: ");
appearance.setContactInfoLabel("Phone: ");
PdfImage image = PdfImage.fromFile(path+"logo.png");
appearance.setSignatureImage(image);
appearance.setGraphicMode(GraphicMode.SignImageAndSignDetail);
Rectangle2D rect = new Rectangle2D.Float();
rect.setFrame(new Point2D.Float(90, 550), new Dimension(150, 80));
signatureMaker.makeSignature("Signature", pdfPageBase,
(float) rect.getMinX(), (float) rect.getMinY(),
(float) rect.getWidth(), (float) rect.getHeight(),
appearance);
String output = "signature.pdf";
document.saveToFile(path+output, FileFormat.PDF);
document.close();
问题修复:
Spire.Presentation 11.6.11 现已发布。该版本新增支持将公式导出为 MathML 与 LaTeX 代码,方便在不同平台和应用场景中使用数学公式。详情如下。
新功能:
IAutoShape shapeFormula = slide.Shapes.AppendShape(ShapeType.Rectangle,new RectangleF(40, currentTop, shapeWidth, formulaHeight));
// 插入 LaTeX 公式
TextParagraph formulaPara = shapeFormula.TextFrame.Paragraphs.AddParagraphFromLatexMathCode(latex);
// 导出 MathML
string mathML = formulaPara.ExportMathML();
// 导出 LaTex
string LaTex = formulaPara.ExportLaTex();
https://www.e-iceblue.cn/Downloads/Spire-Presentation-NET.html