转换器:最简的一行,最灵活的任何环境
Aspose.Cells for Go via C++ Toolkits 的 converter 包将所有“将电子表格转换为另一种格式”的需求简化为 一个入口函数:
converter.Convert(source, saveOption, sink) error
它只有三个参数,每个参数恰好回答一个问题:
| 参数 | 类型 | 它回答的问题 | 典型值 |
|---|---|---|---|
source | datasource.DataSource | 输入来自何处? | FilePathSource(path), BytesSource(data), NewReaderSource(r) |
saveOption | saveoptions.SaveOption | 输出为何种格式,如何调优? | formats.Get("pdf") 用于按名称的默认值,或 pdf.New(pdf.WithOnePagePerSheet(true)) 用于精细控制 |
sink | datasource.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 种)
| 系列 | 输出扩展名 | 调优包 |
|---|---|---|
| Excel | xlsx xlsm xltx xltm xls xlsb | saveoptions/ooxml, xls, xlsb |
| OpenDocument | ods xml dif dbf | saveoptions/ods, xml, dif, dbf |
| 文本 / 数据 | csv txt tsv json sql | saveoptions/csv, txt, json, sqlScript |
| 文档 / 布局 | pdf docx pptx xps pcl epub | saveoptions/pdf, docx, pptx, xps, pcl, ebook |
| Web | html md | saveoptions/html, markdown |
| 图像 | png jpg jpeg gif bmp svg emf tif tiff | saveoptions/image |
为了保持二进制文件精简,只导入你使用的子包(例如 pdf);为了方便或通用调度,可以空导入 …/register。导入子包会触发其注册;注册表查找不区分大小写;未注册的扩展返回 nil(而不是错误),将回退决定留给调用者。
6. 最佳实践和注意事项
单一入口点 — 不要重新发明它。 文件到文件使用
Convert(FilePathSource, formats.Get(ext)/constructor, FilePathSink);仅在需要字节或流时切换 sink。不要手动编写os.Open → ReadAll → … → os.WriteFile;source/sink 会管理资源并为您创建父目录。并发与授权。 底层引擎是原生的:在同一进程的多个 goroutine 中 不要 共享同一个
SaveOption/workbook 对象;批量处理时请顺序处理文件或跨进程并行。评估版构建对加载次数有限制(每个进程大约一百次NewWorkbook_Stream加载)——对于大型基准测试,重新启动进程以重置计数。一次读取输入,然后重复使用。 若要从同一输入生成多种格式,先使用
os.ReadFile将其读取到[]byte,然后使用BytesSource多次转换,避免重复磁盘读取和重新加载。流会被缓冲。
NewReaderSource在首次访问时进行缓冲,因此适用于一次性输入,如 HTTP 请求体,而不适用于对超大文件的真正流式处理(对于这些情况请使用直接指向磁盘的FilePathSource)。了解谁决定输出布局。
Convert是 “一个输入 → 一个输出文件”。如果需要 每个工作表一个文件 或 zip 包,应使用manipulator.Split搭配FolderSink/ZipSink—— 不要强行通过Convert实现。
