Convertidor: una línea en su forma más simple, cualquier entorno en su máxima flexibilidad

El paquete converter de Aspose.Cells for Go via C++ Toolkits reduce cada necesidad de “convertir una hoja de cálculo a otro formato” a una única función de entrada:

converter.Convert(source, saveOption, sink) error

Solo tiene tres parámetros, y cada uno responde exactamente a una pregunta:

ParámetroTipoPregunta que respondeValores típicos
sourcedatasource.DataSource¿De dónde proviene la entrada?FilePathSource(path), BytesSource(data), NewReaderSource(r)
saveOptionsaveoptions.SaveOption¿En qué formato se genera la salida y cómo se ajusta?formats.Get("pdf") para los valores predeterminados por nombre, o pdf.New(pdf.WithOnePagePerSheet(true)) para un control fino
sinkdatasource.DataSink¿A dónde se envía la salida?FilePathSink(path), &BytesSink{}, NewWriterSink(w)

Para el estilo más conciso, tome la configuración predeterminada en los tres ejes; para el más flexible, gire cada perilla de forma independiente. Como los tres ejes son independientes, el mismo Convert se adapta de forma natural a entornos de archivo, memoria, HTTP y por lotes. (También existe una línea única heredada, ConvertSpreadsheetToFile, para el caso simple archivo→archivo — comparado en la sección 1 a continuación.) El resto de este artículo pasa de lo más conciso a lo más flexible, ampliando el fragmento anterior en cada paso en lugar de cambiar a una API diferente.


1. Lo más sencillo: una llamada para un formato

1.1 Una única línea incorporada de archivo→archivo: ConvertSpreadsheetToFile

El paquete también incluye una entrada más antigua y aún más corta para el caso clásico de “convertir este archivo en ese archivo”. Todo lo que se necesita son dos rutas:

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)
	}
}

Obsoleto. ConvertSpreadsheetToFile es un contenedor ligero mantenido por compatibilidad hacia atrás; el nuevo código se dirige a converter.Convert a continuación. Para el caso archivo→archivo los dos son exactamente equivalentes — esto es todo lo que hace 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 y la forma Convert anterior producen resultados idénticos; lo que difiere es el escenario, como se compara a continuación.

1.2 El one-liner recomendado: Convert con valores predeterminados

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)
	}
}

Los tres controles toman sus valores predeterminados: entrada = un archivo, formato = pdf.New() sin configuración, salida = un archivo. Esa es la forma “más concisa” — sin os.Open/os.Create manuales, sin asignación de objetos, sin cierre de manejadores; la fuente/absorción gestionan los recursos por ti.

1.3 Las dos formas archivo→archivo comparadas

ConvertSpreadsheetToFile(in, out)converter.Convert(source, opt, sink)
Inputsolo una ruta de archivocualquier DataSource: ruta, []byte, o lector
Outputsolo una ruta de archivocualquier DataSink: ruta, bytes o io.Writer
Output formatsiempre inferido a partir de la extensión, solo valores predeterminadoscualquier saveOption: predeterminado o ajuste específico del formato
Extra importrequiere que el formato esté registrado (_ "…/register", o el subpaquete correspondiente saveoptions/*)ninguno, cuando pasas un constructor como pdf.New()
Statusenvoltorio delgado obsoleto (estable, mantenido por compatibilidad)el único punto de entrada documentado para código nuevo

Mismo archivo→resultado del archivo, dos escenarios diferentes:

  • ConvertSpreadsheetToFile — literalmente tienes dos rutas y estás satisfecho con la configuración predeterminada del motor para el formato de destino. Ideal para scripts de conversión puntuales, lotes de migración y utilidades de prueba. Su precio: la entrada, la salida y la configuración nunca pueden salir de la forma “file path + defaults” — y está obsoleto.
  • converter.Convert — deseas la misma línea única, pero cualquiera de los tres argumentos puede ser reemplazable: mañana la entrada será un []byte de una carga, el objetivo necesita pdf.WithOnePagePerSheet(true), o la salida debería ir directamente a una respuesta HTTP. Esos son exactamente los escenarios de las secciones 3 y 4 — escríbelo una vez con Convert, y cada cambio es una edición de un solo argumento.

2. Lo más simple × N: una función para los 32 formatos

El siguiente nivel de concisión es usar la misma función sin importar a qué formato apunte. Porque saveOption puede calcularse a partir de la extensión del archivo de salida, extraer esa inferencia produce un “convertidor de archivos universal”:

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")
}

El uso sigue siendo idéntico — solo cambia la extensión de salida:

$ 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

¿Qué es esa importación vacía _ "…/register"?

Hay dos formas de obtener un saveOption; este es el único concepto que el estilo “más conciso” necesita comprender:

  • Buscarlo en el registro por nombre de formato (formats.Get): la extensión debe estar presente en el registro formats. Cada subpaquete saveoptions/* se registra a sí mismo en su propio init(), pero solo cuando se importa. Importa los subpaquetes que necesites uno por uno (solo pdf, por ejemplo, si conviertes a PDF), o blank-import …/register once, lo que registra los 32 formatos de una sola vez — el “convertidor universal” anterior depende de esa única línea.
  • Llamar a un constructor directamente (pdf.New(), markdown.New()): omite el registro, no se necesita importar register; el costo es una línea de importación por cada formato que uses.

En resumen: solo use _ "…/register" cuando resuelva a través de formats.Get + la extensión de salida; pasar un constructor directamente no necesita nada extra. La importación en blanco es inofensiva y siempre está presente en los ejemplos oficiales.

3. Lo más flexible: gira cada perilla de forma independiente

Cuando la necesidad va más allá de “file to file with default settings”, no cambias la API — los tres parámetros de Convert son intercambiables de forma independiente.

1. Entrada (origen): desde un archivo, a la memoria, a cualquier flujo

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, …)

Nota: el motor subyacente necesita acceso aleatorio a todo el contenido, por lo que ReaderSource almacena en búfer la secuencia completamente en el primer acceso; cada Open posterior sirve una copia de ese búfer. Úselo con confianza para HTTP, solo tenga en cuenta que no es una verdadera “convertir mientras se transmite”.

2. Formato (saveOption): predeterminados, o ajustado

EscenarioEstilo de escrituraPor qué
Algún formato con la configuración predeterminadaformats.Get("pdf")buscado por extensión; sin distinción entre mayúsculas y minúsculas; nil si no está registrado
Un formato con opciones específicas del formatopdf.New(pdf.WithOnePagePerSheet(true))opciones funcionales
Configuración predeterminada, formato explícitoooxml.New() / csv.New() / …un constructor sin argumentos por formato
Un formato privado personalizadoformats.Register("fmt", factory)conectar al registro — ver docs/register.md

Las opciones funcionales aparecen solo en los formatos que las tienen: una página por hoja de cálculo en PDF es WithOnePagePerSheet; imágenes incrustadas en HTML es WithExportImagesAsBase64; TSV es formats.Get("tsv"). Las opciones son semántica de puntero: omitir = el valor predeterminado del motor; pasar false explícitamente también cuenta.

3. Salida (sink): un archivo, bytes o un 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 y WriterSink son las dos claves para “más flexible”: permiten que el resultado de la conversión omita el disco por completo y fluya directamente a una respuesta HTTP, almacenamiento de objetos, un zip, etc. Los tres son diferentes implementaciones del mismo parámetro sink, por lo que cualquier cosa que acepte []byte o un io.Writer puede conectarse al conversor.

4. Ejemplos en entorno real: un Convert, muchas formas

A. Convertir en memoria; el punto final de descarga transmite PDF

El escenario típico del lado del servidor — un usuario sube un xlsx, el endpoint devuelve un PDF, sin archivos temporales:

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())
}

Para entregar CSV / Excel / HTML en su lugar, cambie solo la saveOption (por ejemplo formats.Get("csv")); nada más cambia.

B. Leer una vez, exportar por lotes a muchos formatos

Lee la entrada una sola vez, luego reutilízala a través de BytesSource para cualquier número de salidas — ideal para “exportar cada informe”:

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. “Guardar como” dentro de la familia de hojas de cálculo (migración / compatibilidad)

Migración de formato dentro de la familia de hojas de cálculo, por ejemplo xlsx → xls, xlsx → xlsb, xlsx → ods, y las plantillas xltx/xltm — sigue siendo una sola Convert, solo cambia la 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. Ajustar opciones por formato, solo cuando sea necesario

Las opciones funcionales aparecen cuando necesitas un control fino — ten en cuenta que cada llamada a continuación es real y está tipada correctamente:

// 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" / …

Consejo: para salidas de un solo archivo como CSV/HTML/imagen, el motor exporta solo la hoja de cálculo que considera “activa” por defecto. Para controlar el alcance, use las propias opciones del formato — por ejemplo html.WithExportActiveWorksheetOnly(true) (¿solo la hoja activa?), html.WithShowAllSheets(true), o csv.WithExportAllSheets(true). La calidad de imagen a nivel de píxel, la selección exacta de qué hojas se renderizan y otros controles avanzados siguen existiendo en la capa del motor, pero el paquete image actual solo expone el interruptor WithImageType — recurra al enlace subyacente si necesita más; el conjunto de herramientas seguirá incorporando opciones con el tiempo.

E. Conversión por lotes sobre un directorio

La tarea cotidiana de “convertir esta carpeta de Excels a PDF” se vuelve muy corta con la inferencia de extensión:

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. Clasificando errores

Los errores del convertidor son errores centinela clasificables; combínalos con errors.Is para un manejo preciso:

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() devuelve cada extensión registrada actualmente (ordenada, segura para concurrencia) — útil para generar una lista de “formatos compatibles” o una pista de error.


5. Formatos compatibles de un vistazo (32 registrados por defecto)

FamiliaExtensiones de salidaPaquete de ajuste
Excelxlsx xlsm xltx xltm xls xlsbsaveoptions/ooxml, xls, xlsb
OpenDocumentods xml dif dbfsaveoptions/ods, xml, dif, dbf
Texto / datoscsv txt tsv json sqlsaveoptions/csv, txt, json, sqlScript
Documentos / diseñopdf docx pptx xps pcl epubsaveoptions/pdf, docx, pptx, xps, pcl, ebook
Webhtml mdsaveoptions/html, markdown
Imágenespng jpg jpeg gif bmp svg emf tif tiffsaveoptions/image

Para mantener el binario liviano, importe solo los subpaquetes que use (como pdf); para mayor comodidad o despacho genérico, importe en blanco …/register. Importar un subpaquete desencadena su registro; las búsquedas en el registro no distinguen entre mayúsculas y minúsculas; una extensión no registrada devuelve nil (no es un error), dejando la decisión de respaldo al llamador.


6. Mejores prácticas y advertencias

  1. Un único punto de entrada — no lo reinventes. File-to-file usa Convert(FilePathSource, formats.Get(ext)/constructor, FilePathSink); solo cambia los sinks cuando necesites bytes o un stream. No implementes manualmente os.Open → ReadAll → … → os.WriteFile; el source/sink gestiona los recursos y crea los directorios padre por ti.
  2. Concurrencia y licenciamiento. El motor subyacente es nativo: no compartas un objeto SaveOption/workbook entre goroutines en el mismo proceso; para trabajos masivos procesa los archivos secuencialmente o paraleliza entre procesos. La compilación de evaluación limita las cargas (aproximadamente cien cargas NewWorkbook_Stream por proceso) — para pruebas de gran escala, reinicia el proceso para restablecer el contador.
  3. Lee la entrada una vez y luego reutilízala. Para generar varios formatos a partir de una sola entrada, primero os.ReadFile en un []byte y conviértelo repetidamente con BytesSource, evitando lecturas de disco y recargas repetidas.
  4. Los streams se almacenan en búfer. NewReaderSource almacena en búfer en el primer acceso, por lo que es adecuado para entradas de un solo uso como el cuerpo de una solicitud HTTP, no para streaming real de archivos muy grandes (usa FilePathSource apuntando directamente al disco para esos casos).
  5. Sabe quién decide el diseño de salida. Convert es “one input → one output file”. Si necesitas un archivo por hoja de cálculo o un zip, eso es manipulator.Split con un FolderSink/ZipSink — no lo fuerces a pasar por Convert.