转换器:最简的一行,最灵活的任何环境

Aspose.Cells for Go via C++ Toolkits 的 converter 包将所有“将电子表格转换为另一种格式”的需求简化为 一个入口函数:

converter.Convert(source, saveOption, sink) error

它只有三个参数,每个参数恰好回答一个问题:

参数类型它回答的问题典型值
sourcedatasource.DataSource输入来自何处?FilePathSource(path), BytesSource(data), NewReaderSource(r)
saveOptionsaveoptions.SaveOption输出为何种格式,如何调优?formats.Get("pdf") 用于按名称的默认值,或 pdf.New(pdf.WithOnePagePerSheet(true)) 用于精细控制
sinkdatasource.DataSink输出到哪里?FilePathSink(path), &BytesSink{}, NewWriterSink(w)

对于 最简 风格,使用所有三个轴的默认设置;对于 最灵活,则独立调节每个旋钮。因为这三个轴相互独立,同一个 Convert 自然适用于文件、内存、HTTP 和批处理环境。(还有一个传统的一行代码 ConvertSpreadsheetToFile,用于纯文件→文件的情况——在下文第 1 节中进行比较。)本文的其余部分从最简到最灵活,逐步扩展前面的代码片段,而不是切换到不同的 API。


1. 最简单的:一次调用对应一种格式

1.1 内置的文件→文件单行命令:ConvertSpreadsheetToFile

该包还提供了一个更早的、更简短的入口,用于经典的“将此文件转换为该文件”情况。只需要两个路径:

package main

import (
	"log"
	"github.com/aspose-cells/aspose-cells-go-cpp-toolkits/v26/core"
	"github.com/aspose-cells/aspose-cells-go-cpp-toolkits/v26/converter"
	_ "github.com/aspose-cells/aspose-cells-go-cpp-toolkits/v26/register" // registers every output format
)

func main() {
	defer core.Cleanup()
	// Both ends are files; the output format is inferred from the extension.
	if err := converter.ConvertSpreadsheetToFile("input.xlsx", "output.pdf"); err != nil {
		log.Fatal(err)
	}
}

已弃用. ConvertSpreadsheetToFile 是一个为保持向后兼容性而保留的薄包装器;新代码应使用下面的 converter.Convert。对于 file→file 情况,两者完全等价——这就是 ConvertSpreadsheetToFile 所做的全部。

opt := formats.Get(strings.TrimPrefix(filepath.Ext("output.pdf"), ".")) // "pdf"
converter.Convert(
	datasource.FilePathSource("input.xlsx"), // input: a file
	opt,                                     // format: taken from the extension
	datasource.FilePathSink("output.pdf"),   // output: a file
)

ConvertSpreadsheetToFile 和上面的 Convert 形式产生相同的结果;不同之处在于场景,如下所示。

1.2 推荐的一行代码:Convert 使用默认设置

package main

import (
	"log"
	"github.com/aspose-cells/aspose-cells-go-cpp-toolkits/v26/core"
	"github.com/aspose-cells/aspose-cells-go-cpp-toolkits/v26/converter"
	"github.com/aspose-cells/aspose-cells-go-cpp-toolkits/v26/datasource"
	"github.com/aspose-cells/aspose-cells-go-cpp-toolkits/v26/saveoptions/pdf"
)

func main() {
	defer core.Cleanup()
	if err := converter.Convert(
		datasource.FilePathSource("input.xlsx"), // input: a file
		pdf.New(),                               // format: PDF, all defaults
		datasource.FilePathSink("output.pdf"),   // output: a file
	); err != nil {
		log.Fatal(err)
	}
}

所有三个参数都采用默认值:input = a file,format = pdf.New(),没有任何配置,output = a file。这就是“最简洁”的形式——无需手动 os.Open/os.Create,无对象分配,也无需关闭句柄;source/sink 为您管理资源。

1.3 两个 file→file 形状的比较

ConvertSpreadsheetToFile(in, out)converter.Convert(source, opt, sink)
输入仅文件路径任意 DataSource:路径、[]byte 或读取器
输出仅文件路径任意 DataSink:路径、字节或 io.Writer
输出格式始终从扩展名推断,仅使用默认值任意 saveOption:默认或特定格式的调优
额外导入需要注册格式(_ "…/register",或相应的 saveoptions/* 子包)无需额外导入,当传入类似 pdf.New() 的构造函数时
状态已弃用的轻量包装器(稳定,保留兼容性)新代码的文档化单入口点

相同文件→文件结果,两种不同的情形:

  • ConvertSpreadsheetToFile — 你实际上只有两个路径,并且对目标格式使用引擎的默认设置感到满意。适用于一次性转换脚本、迁移批处理和测试辅助工具。其代价是:输入、输出以及设置永远只能是“file path + defaults”形式——而且已被弃用。
  • converter.Convert — 你想要同样的一行代码,但三个参数中的任意一个都可以替换:明天输入可能是上传的 []byte,目标可能需要 pdf.WithOnePagePerSheet(true),或者输出需要直接写入 HTTP 响应。这正是第 3 节和第 4 节的场景——使用 Convert 写一次,每次更改只需修改一个参数。

2. 最简 × N:一个函数适用于所有 32 种格式

更进一步的简洁性是使用 相同的函数,无论目标格式是什么。因为可以根据输出文件的扩展名计算 saveOption,将此推断抽取出来就得到一个“通用文件转换器”:

package main

import (
	"errors"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"
	"github.com/aspose-cells/aspose-cells-go-cpp-toolkits/v26/core"
	"github.com/aspose-cells/aspose-cells-go-cpp-toolkits/v26/converter"
	"github.com/aspose-cells/aspose-cells-go-cpp-toolkits/v26/datasource"
	toolkiterrors "github.com/aspose-cells/aspose-cells-go-cpp-toolkits/v26/errors"
	"github.com/aspose-cells/aspose-cells-go-cpp-toolkits/v26/formats"
	_ "github.com/aspose-cells/aspose-cells-go-cpp-toolkits/v26/register" // registers every output format
)

// convert re-saves in as out; the output format is read from out's extension.
// The same code handles every built-in format (pdf/csv/html/md/xlsx/png/…).
// It is exactly what the deprecated ConvertSpreadsheetToFile does internally.
func convert(in, out string) error {
	opt := formats.Get(strings.TrimPrefix(filepath.Ext(out), ".")) // ".pdf" -> "pdf"
	if opt == nil {
		return fmt.Errorf("%w: %q", toolkiterrors.ErrUnsupportedFormat, filepath.Ext(out))
	}
	return converter.Convert(
		datasource.FilePathSource(in), // where the input comes from
		opt,                           // what to produce: decided by the extension
		datasource.FilePathSink(out),  // where the output goes
	)
}

func main() {
	defer core.Cleanup()
	if len(os.Args) != 3 {
		log.Fatalf("usage: %s <input.xlsx> <output.ext>", os.Args[0])
	}
	if err := convert(os.Args[1], os.Args[2]); err != nil {
		if errors.Is(err, toolkiterrors.ErrUnsupportedFormat) {
			log.Printf("no registered output format for %q", filepath.Ext(os.Args[2]))
		}
		log.Fatal(err)
	}
	fmt.Println("done")
}

用法保持不变 — 仅更改输出扩展名:

$ go run . input.xlsx report.pdf     # to PDF
$ go run . input.xlsx report.csv     # to CSV
$ go run . input.xlsx report.html    # to HTML
$ go run . input.xlsx notes.md       # to Markdown
$ go run . input.xlsx sheet.png      # to a PNG image
$ go run . input.xlsx legacy.xls     # to legacy Excel

那个空的 _ "…/register" 导入是什么?

有两种方法可以获取 saveOption;这是“最简洁”风格需要理解的唯一概念:

  • 通过格式名称从注册表中查找 (formats.Get): 必须在 formats 注册表中存在该扩展名。每个 saveoptions/* 子包会在其自己的 init() 中注册自身,但仅在被导入时才会注册。按需逐个导入所需的子包(例如如果要转换为 PDF,只导入 pdf),或 空导入 …/register 一次,它会一次性注册所有 32 种格式——上面的 “通用转换器” 正是依赖这一行。
  • 直接调用构造函数 (pdf.New(), markdown.New()): 绕过注册表,无需 register 导入;代价是每使用一种格式就需要一行导入语句。

简而言之:仅在通过 formats.Get 加上输出扩展名进行解析时才使用 _ "…/register";直接传递构造函数则不需要额外操作。空导入是无害的,并且在官方示例中始终存在。

3. 最灵活的:独立调节每个旋钮

当需求超出 “file to file with default settings” 时,您不需要更改 API——这三个 Convert 参数可以独立互换。

1. 输入(源):从文件、到内存、到任意流

datasource.FilePathSource("input.xlsx") // a file on disk
datasource.BytesSource(data)            // []byte already in memory (downloads, cached reuse)
datasource.NewReaderSource(r.Body)      // any io.Reader (HTTP body, compressed stream, …)

注意:底层引擎需要对整个内容进行随机访问,因此 ReaderSource 在首次访问时会完整缓冲流;之后的每次 Open 都会提供该缓冲的副本。可以放心地在 HTTP 场景中使用,只需了解它并非真正的 “convert while streaming”。

2. 格式 (saveOption):默认,或已调优

场景编写方式原因
使用默认设置的某种格式formats.Get("pdf")通过扩展名查找;不区分大小写;如果未注册则返回 nil
带有特定格式选项的格式pdf.New(pdf.WithOnePagePerSheet(true))函数式选项
默认设置,显式格式ooxml.New() / csv.New() / …每种格式都有一个零参数构造函数
自定义私有格式formats.Register("fmt", factory)插入注册表 — 参见 docs/register.md

功能选项仅出现在具有这些选项的格式中:PDF 中每个工作表一页的选项是 WithOnePagePerSheet;HTML 中内联图像的选项是 WithExportImagesAsBase64;TSV 使用 formats.Get("tsv")。选项采用指针语义:省略即使用引擎默认值;显式传递 false 也算作一次设置。

3. 输出(接收端):文件、字节或 io.Writer

datasource.FilePathSink("output.pdf") // to disk (parent dirs are created)
&out                                 // into memory; read it back with out.Bytes()
datasource.NewWriterSink(w)          // straight into any io.Writer (http.ResponseWriter, buffer, socket)

BytesSink 和 WriterSink 是实现 “最灵活” 的两个关键:它们让转换结果完全跳过磁盘,直接流入 HTTP 响应、对象存储、zip 等。所有这三种实现都是同一 sink 参数的不同实现,因此任何接受 []byte 或 io.Writer 的对象都可以插入到转换器中。


4. 真实环境示例:一个 Convert,多个形状

A. 在内存中转换;下载端点流式传输 PDF

常见的服务器端场景 — 用户上传 xlsx,端点返回 PDF,无临时文件:

func handleExcelToPDF(w http.ResponseWriter, r *http.Request) {
	var out datasource.BytesSink
	if err := converter.Convert(
		datasource.NewReaderSource(r.Body),    // input: the request body
		pdf.New(pdf.WithOnePagePerSheet(true)), // format: PDF, one page per sheet
		&out,                                   // output: memory
	); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	w.Header().Set("Content-Type", "application/pdf")
	w.Header().Set("Content-Disposition", `attachment; filename="report.pdf"`)
	_, _ = w.Write(out.Bytes())
}

要改为提供 CSV / Excel / HTML,只需更换 saveOption(例如 formats.Get("csv"));其他内容保持不变。

B. 一次读取,批量导出为多种格式

读取输入一次,然后通过 BytesSource 重用它以生成任意数量的输出——非常适合“导出每个报告”。

data, err := os.ReadFile("input.xlsx")
if err != nil {
	log.Fatal(err)
}

for _, ext := range []string{"pdf", "csv", "html", "json", "md"} {
	var out datasource.BytesSink
	if err := converter.Convert(datasource.BytesSource(data), formats.Get(ext), &out); err != nil {
		log.Fatalf("%s: %v", ext, err)
	}
	if err := os.WriteFile("output."+ext, out.Bytes(), 0o644); err != nil {
		log.Fatal(err)
	}
}

C. “另存为” 在电子表格系列中(迁移 / 兼容性)

在电子表格系列内部进行格式迁移,例如 xlsx → xls、xlsx → xlsb、xlsx → ods,以及模板 xltx/xltm — 仍然是一次 Convert,仅 saveOption 发生变化:

src := datasource.FilePathSource("input.xlsx")
converter.Convert(src, formats.Get("xls"),  datasource.FilePathSink("legacy.xls")) // legacy Excel
converter.Convert(src, formats.Get("xlsb"), datasource.FilePathSink("binary.xlsb")) // binary Excel
converter.Convert(src, formats.Get("ods"),  datasource.FilePathSink("open.ods"))    // OpenDocument
converter.Convert(src, formats.Get("xltx"), datasource.FilePathSink("template.xltx")) // template

D. 仅在需要时为每种格式微调选项

功能选项在需要精细控制时出现 — 请注意下面的每个调用都是真实且类型正确的:

// PDF: one page per worksheet + a compliance level
pdf.New(
	pdf.WithOnePagePerSheet(true),
	pdf.WithCompliance("pdf15"),
	pdf.WithProducer("my-report-app"),
)

// HTML: single file, images inlined as base64, CSS prefix (avoids style clashes)
html.New(
	html.WithSaveAsSingleFile(true),
	html.WithExportImagesAsBase64(true),
	html.WithCellCssPrefix("tbl_"),
)

// CSV: custom separator and encoding
// (the separator is a byte, and the encoding is a name — matched case- and
// punctuation-insensitively, so "UTF-8", "utf8" and "UTF8" are one name)
csv.New(
	csv.WithSeparator(';'),
	csv.WithEncoding("UTF-8"),
)

// Image: pick the type (the only switch the image package currently exposes)
image.New(image.WithImageType("png")) // or "jpg" / "svg" / "gif" / "tif" / …

提示:对于 CSV/HTML/图像 等单文件输出,默认情况下引擎仅导出它认为“活动”的工作表。要控制范围,请使用相应格式的选项,例如 html.WithExportActiveWorksheetOnly(true)(仅活动工作表?)、html.WithShowAllSheets(true) 或 csv.WithExportAllSheets(true)。像素级图像质量、精确选择要渲染的工作表以及其他深层控制仍然存在于引擎层,但当前的 image 包仅公开 WithImageType 开关——如果需要更多功能,请使用底层绑定;工具包会随时间不断加入更多选项。

E. 对目录进行批量转换

日常的“将这个文件夹中的 Excel 转换为 PDF”任务在使用扩展名推断时变得非常简短:

func convertAll(inputDir, outputDir string) error {
	if err := os.MkdirAll(outputDir, 0o755); err != nil {
		return err
	}
	return filepath.Walk(inputDir, func(path string, info os.FileInfo, err error) error {
		if err != nil || info.IsDir() {
			return err
		}
		ext := strings.ToLower(filepath.Ext(path))
		if ext != ".xlsx" && ext != ".xls" && ext != ".xlsm" && ext != ".csv" {
			return nil
		}
		out := filepath.Join(outputDir, strings.TrimSuffix(filepath.Base(path), ext)+".pdf")
		if err := convert(path, out); err != nil { // reuse the convert from section 2
			return fmt.Errorf("convert %s: %w", path, err)
		}
		fmt.Printf("done: %s -> %s\n", path, out)
		return nil
	})
}

F. 错误分类

转换器错误是可分类的哨兵错误;请使用 errors.Is 进行精确处理:

err := convert("in.xlsx", "out.xyz")
switch {
case errors.Is(err, toolkiterrors.ErrUnsupportedFormat):
	log.Println("this extension has no registered output format")
case errors.Is(err, os.ErrNotExist):
	log.Println("the input file does not exist")
default:
	log.Printf("conversion failed: %v", err)
}

formats.List() 返回当前已注册的所有扩展(已排序,线程安全)— 方便用于生成 “受支持的格式” 列表或错误提示。


5. 支持的格式概览(默认注册 32 种)

系列输出扩展名调优包
Excelxlsx xlsm xltx xltm xls xlsbsaveoptions/ooxml, xls, xlsb
OpenDocumentods xml dif dbfsaveoptions/ods, xml, dif, dbf
文本 / 数据csv txt tsv json sqlsaveoptions/csv, txt, json, sqlScript
文档 / 布局pdf docx pptx xps pcl epubsaveoptions/pdf, docx, pptx, xps, pcl, ebook
Webhtml mdsaveoptions/html, markdown
图像png jpg jpeg gif bmp svg emf tif tiffsaveoptions/image

为了保持二进制文件精简,只导入你使用的子包(例如 pdf);为了方便或通用调度,可以空导入 …/register。导入子包会触发其注册;注册表查找不区分大小写;未注册的扩展返回 nil(而不是错误),将回退决定留给调用者。


6. 最佳实践和注意事项

  1. 单一入口点 — 不要重新发明它。 文件到文件使用 Convert(FilePathSource, formats.Get(ext)/constructor, FilePathSink);仅在需要字节或流时切换 sink。不要手动编写 os.Open → ReadAll → … → os.WriteFile;source/sink 会管理资源并为您创建父目录。

  2. 并发与授权。 底层引擎是原生的:在同一进程的多个 goroutine 中 不要 共享同一个 SaveOption/workbook 对象;批量处理时请顺序处理文件或跨进程并行。评估版构建对加载次数有限制(每个进程大约一百次 NewWorkbook_Stream 加载)——对于大型基准测试,重新启动进程以重置计数。

  3. 一次读取输入,然后重复使用。 若要从同一输入生成多种格式,先使用 os.ReadFile 将其读取到 []byte,然后使用 BytesSource 多次转换,避免重复磁盘读取和重新加载。

  4. 流会被缓冲。 NewReaderSource 在首次访问时进行缓冲,因此适用于一次性输入,如 HTTP 请求体,而不适用于对超大文件的真正流式处理(对于这些情况请使用直接指向磁盘的 FilePathSource)。

  5. 了解谁决定输出布局。 Convert 是 “一个输入 → 一个输出文件”。如果需要 每个工作表一个文件 或 zip 包,应使用 manipulator.Split 搭配 FolderSink/ZipSink —— 不要强行通过 Convert 实现。