冰蓝科技
|
028-81705109
|
|
微信扫一扫
|

Spire.Cloud 纯前端文档控件

脚注是 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 文档输出


设置脚注位置与编号格式

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)重新开始,避免跨页后编号持续增长。这些配置按节独立生效,同一文档中不同节可以拥有不同的脚注规则,非常适合多章节、多作者协作的复杂文档场景。

设置脚注位置与编号格式后生成的 Word 文档输出


移除脚注

当文档经过多轮审阅或内容更新后,某些脚注可能不再适用或需要删除。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;

移除脚注后,文档正文中原本显示上标标记的位置不再保留任何痕迹,页面底部的注释正文也一并清除,正文内容本身不会受到影响。

移除脚注后生成的 Word 文档输出


常见问题

插入的脚注未显示在预期位置

原因:脚注追加到段落后,未通过 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 文档输出


查找并修改超链接

对于已包含超链接的 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;

通过遍历文档对象模型找到所有超链接后,可以读取或修改每个超链接的显示文本与目标地址,满足批量更新需求。

查找并修改超链接后生成的 Word 文档输出


移除超链接

当文档中的超链接不再需要时,可以将其移除,同时保留超链接对应的文本内容。核心思路是先找到所有超链接字段,然后通过扁平化(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;

移除超链接后,文档中的超链接文本保留为普通文本,原有的蓝色下划线样式被清除,内容保持不变。

移除超链接后生成的 Word 文档输出


常见问题

插入的超链接在文档中无法点击

原因:插入的链接目标地址格式不正确,例如缺少协议前缀(如 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 完整指南

在 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 结构。

快速导航

  1. 为什么要在 Java 中将 Excel 转换为 JSON
  2. 前置准备
  3. Java 中将 Excel 转换为 JSON——分步详解
  4. 将 XLS 和 XLSX 文件转换为 JSON
  5. 处理多工作表工作簿与嵌套 JSON
  6. 处理空单元格与数据类型
  7. 常见陷阱
  8. 总结
  9. 常见问题

1. 为什么要在 Java 中将 Excel 转换为 JSON

Excel 和 JSON 在现代软件系统中被广泛使用,但它们的定位截然不同。Excel 专为结构化数据录入、分析和报表而设计,支持公式、格式化和多工作表工作簿。而 JSON(JavaScript 对象表示法)是一种轻量级数据格式,用于机器间通信、REST API、配置文件和 NoSQL 数据库。

正因如此,Java 开发者经常需要将 Excel 转换为 JSON,以便将基于电子表格的数据集成到后端系统中。

常见应用场景包括:

  • REST API 集成 —— 将用户上传的 Excel 数据转换为 JSON 用于 API 响应
  • ETL 工作流 —— 提取电子表格数据并转换为 JSON,导入数据库或数据湖
  • 配置迁移 —— 将基于 Excel 的传统配置迁移到基于 JSON 的微服务系统
  • 自动化报表 —— 将 Excel 模板转换为结构化 JSON,供下游系统处理

在 Java 应用中,将 Excel 转换为 JSON 远不止是读取行和映射列那么简单。实际文件往往包含不一致的数据类型、空单元格、日期格式问题以及多工作表结构,这使得手动解析既复杂又容易出错。

Spire.XLS for Java 简化了这一过程,它为 XLS 和 XLSX 格式提供了统一的 API。开发者可以直接访问单元格值、数据类型和格式化信息,从而编写出简洁可靠的 Excel 转 JSON 转换逻辑,无需处理底层文件解析。


2. 前置准备

在开始 Excel 转 JSON 之前,请在项目中配置以下依赖。

通过 Maven 安装 Spire.XLS for Java(推荐)

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 并手动添加到项目中。

添加 JSON 库

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;

3. Java 中将 Excel 转换为 JSON——分步详解

整个转换过程分为四个步骤:加载工作簿、读取标题行、遍历数据行以及组装 JSON 输出。本节将逐步讲解,最后给出完整代码。

步骤一:加载 Excel 文件

使用 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();
}

步骤三:遍历数据行并构建 JSON 对象

从第 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);
}

步骤四:导出 JSON 输出

使用 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 输出的对比。

将 Excel 工作表转换为 JSON

Spire.XLS 核心类与方法

  • Workbook —— 代表一个 Excel 文件,负责加载、保存以及管理工作表。
  • Worksheet —— 代表工作簿中的单个工作表,提供对行、列和单元格的访问。
  • get(int row, int column) —— 返回指定单元格的 CellRange 对象。行和列的索引从 1 开始。
  • getValue() —— 返回单元格的显示值。与 getText() 不同,无论单元格的数据类型是文本、数字还是日期等,都能正确获取其值。
  • getLastRow() / getLastColumn() —— 返回包含数据的最后一行和最后一列的编号。

您还可以了解如何在 Java 中将 Excel 转换为 CSV,适用于需要轻量级表格格式进行数据交换和存储的场景。


4. 将 XLS 和 XLSX 文件转换为 JSON

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 格式互转,适用于文件格式迁移或传统格式升级的场景。


5. 处理多工作表工作簿与嵌套 JSON

实际工作中的 Excel 工作簿通常包含多个工作表。将每个工作表转换为独立的 JSON 数组,可以生成保留工作簿结构的输出。在某些场景下,开发者还需要构建嵌套 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 对象结构。

将多个 Excel 工作表转换为 JSON

从 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 工作表转换为嵌套 JSON

这种模式适用于需要将 Excel 数据重构为层级格式的场景,例如匹配 API 接口规范或数据库文档模型。

您还可以了解如何在 Java 中解析 Excel 文件,适用于需要在转换前提取和处理原始电子表格数据的场景。


6. 处理空单元格与数据类型

生产环境中的 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) 等方法。

在 JSON 输出中保留数据类型

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 内部的数值表示。


7. 常见陷阱

跳过标题行

最常见的错误之一是从第 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();
}

8. 总结

本文介绍了如何使用 Spire.XLS for Java 将 Excel 转换为 JSON。从基础的单工作表转换开始,逐步讲解了工作簿加载、基于标题的键映射以及 JSON 输出生成。随后扩展至 XLS 与 XLSX 格式处理、多工作表工作簿、嵌套 JSON 结构、空单元格处理以及数据类型保留。

Spire.XLS for Java 以简洁的 API 简化了整个转换流程,且无需安装 Microsoft Office。除了 Excel 转 JSON 之外,该库还提供了全面的电子表格功能,包括 PDF 导出、图表创建、公式计算和数据验证。您可以申请 30 天免费许可证,在项目中体验完整功能。


9. 常见问题

如何在 Java 中将 Excel 转换为 JSON?

使用 Spire.XLS for Java 加载 Excel 文件,读取标题行确定 JSON 的键,从第 2 行开始遍历数据行,并将每个单元格的值映射到 Jackson ObjectNode 中对应的键。将所有对象收集到 ArrayNode 中,然后使用 ObjectMapper 将结果写入文件或作为字符串返回。完整代码示例请参见第 3 节。

哪个 Java 库最适合 Excel 转 JSON?

Spire.XLS for Java 提供了全面的 API 来读取 Excel 数据,同时支持 XLS 和 XLSX 格式。它能原生处理单元格类型、公式和格式化,可以方便地提取结构化数据用于 JSON 转换,无需依赖 Microsoft Office 或其他外部组件。

Spire.XLS 能同时处理 XLS 和 XLSX 格式吗?

可以。Spire.XLS for Java 会自动检测文件是传统 XLS 格式(Excel 97–2003)还是现代 XLSX 格式(Excel 2007 及更高版本)。同一段代码适用于两种格式,无需额外配置。详见第 4 节。

getValue() 和 getCellValue() 在 Spire.XLS 中有什么区别?

getValue() 返回单元格的显示值——适用于所有数据类型(文本、数字、日期、布尔值等),返回用户在单元格中看到的内容。getCellValue() 返回底层的原始值(Object 类型)。当 JSON 输出需要与用户在 Excel 中看到的一致时,使用 getValue();当需要带类型的数值或布尔值时,使用 getNumberValue() 或 getBooleanValue()。

转换 Excel 到 JSON 时如何处理空单元格?

在读取值之前,使用 CellRange.getType() 检查单元格类型。如果类型为 CellValueType.Empty,则分配一个默认值,如空字符串或 "N/A"。这可以避免 null 值,确保所有记录的 JSON 结构一致。代码示例请参见第 6 节。

Spire.XLS for Java 是免费的吗?

Spire.XLS for Java 是一款商业库。提供免费版本 Free Spire.XLS for Java,但在工作表数量和部分功能上有限制。您也可以申请 30 天免费许可证,在购买前体验完整功能。

Spire.PDF for Java 12.7.0 现已发布。该版本修复了多个与数字签名、PDF 处理及转换相关的问题,并优化了替换文档内容时的内存占用。详情如下。

问题修复:

优化:


获取 Spire.PDF for Java 12.7.0 请点击:

https://www.e-iceblue.cn/Downloads/Spire-PDF-JAVA.html

我们很高兴地宣布 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。

版本信息如下:


获取Spire.Office 11.6.1,请点击:

https://www.e-iceblue.cn/Downloads/Spire-Office-NET.html

Spire.Doc

优化:

问题修复:

Spire.XLS

调整:

问题修复:

Spire.Presentation

新功能:

问题修复:

Spire.PDF

调整:

旧类名 新类名 旧属性名 新属性名
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.OCR

问题修复:

Spire.PdfViewer

问题修复:

Spire.PDFViewer 8.3.0 现已正式发布。该版本修复了预览 PDF 文件报 “ArgumentNullException” 的问题。详情如下。

问题修复:


获取 Spire.PDFViewer 8.3.0 请点击:

https://www.e-iceblue.cn/Downloads/Spire-PDFViewer-NET.html

Spire.Doc for Java 14.7.0 现已正式发布。该版本新增了 CompareOptions 类的配置选项,并支持“双行合一”功能。此外,一些在转换 Word 到 PDF 和 HTM 时出现的问题也已经成功被修复。详情如下。

新功能:

问题修复:


获取 Spire.Doc for Java 14.7.0 请点击:

https://www.e-iceblue.cn/Downloads/Spire-Doc-JAVA.html

在日常 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 阅读方向与语言。此外,本版本还成功修复了多个问题。更多详情如下。


获取 Spire.Office for Java 11.6.0,请点击:

https://www.e-iceblue.cn/Downloads/Spire-Office-JAVA.html

Spire.Doc for Java

问题修复:

Spire.XLS for Java

问题修复:

Spire.PDF for Java

新功能:

问题修复:

Spire.Presentation 11.6.11 现已发布。该版本新增支持将公式导出为 MathML 与 LaTeX 代码,方便在不同平台和应用场景中使用数学公式。详情如下。

新功能:


获取 Spire.Presentation 11.6.11 请点击:

https://www.e-iceblue.cn/Downloads/Spire-Presentation-NET.html