Konwerter: jedna linia w najprostszej postaci, dowolne środowisko w najbardziej elastycznej formie

Pakiet converter biblioteki Aspose.Cells for Go via C++ Toolkits redukuje każde zapotrzebowanie na “przekształcenie arkusza kalkulacyjnego w inny format” do jednej funkcji wejściowej:

converter.Convert(source, saveOption, sink) error

Ma tylko trzy parametry, a każdy odpowiada dokładnie na jedno pytanie:

ParametrTypPytanie, na które odpowiadaTypowe wartości
sourcedatasource.DataSourceSkąd pochodzi wejście?FilePathSource(path), BytesSource(data), NewReaderSource(r)
saveOptionsaveoptions.SaveOptionW jakim formacie wyjście i jak je dostroić?formats.Get("pdf") dla domyślnych po nazwie, lub pdf.New(pdf.WithOnePagePerSheet(true)) dla precyzyjnej kontroli
sinkdatasource.DataSinkDokąd trafia wyjście?FilePathSink(path), &BytesSink{}, NewWriterSink(w)

Dla najbardziej zwięzłego stylu użyj domyślnych ustawień na wszystkich trzech osiach; dla najbardziej elastycznego obróć każde pokrętło niezależnie. Ponieważ trzy osie są niezależne, to samo Convert naturalnie pasuje do środowisk plikowych, pamięciowych, HTTP i wsadowych. (Istnieje również starszy jednoliniowy kod, ConvertSpreadsheetToFile, dla prostego przypadku plik→plik — porównano w sekcji 1 poniżej.) Reszta tego artykułu przechodzi od najbardziej zwięzłego do najbardziej elastycznego, rozwijając poprzedni fragment kodu w każdym kroku zamiast przełączać się na inne API.

1. Najprostszy: jedno wywołanie dla jednego formatu

1.1 Wbudowany jednolinijkowy konwerter plik→plik: ConvertSpreadsheetToFile

Pakiet zawiera także starszy, jeszcze krótszy wpis dla klasycznego przypadku „convert this file into that file”. Wystarczy podać dwie ścieżki:

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

Przestarzałe. ConvertSpreadsheetToFile jest cienką nakładką zachowaną dla zachowania kompatybilności wstecznej; nowy kod jest skierowany do converter.Convert poniżej. W przypadku file→file oba są dokładnie równoważne — to wszystko, co robi 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 i forma Convert powyżej dają identyczne wyniki; różni je scenariusz, jak pokazano dalej.

1.2 Zalecany jednowierszowy: Convert z ustawieniami domyślnymi

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

Wszystkie trzy ustawienia przyjmują wartości domyślne: wejście = plik, format = pdf.New() bez żadnej konfiguracji, wyjście = plik. To jest najbardziej zwięzła forma — bez ręcznego os.Open/os.Create, bez alokacji obiektów, bez zamykania uchwytów; źródło/odbiornik zarządzają zasobami za Ciebie.

1.3 Porównanie dwóch kształtów plik→plik

ConvertSpreadsheetToFile(in, out)converter.Convert(source, opt, sink)
Wejścietylko ścieżka do plikudowolny DataSource: ścieżka, []byte, lub czytnik
Wyjścietylko ścieżka do plikudowolny DataSink: ścieżka, bajty lub io.Writer
Format wyjściazawsze wywnioskowany z rozszerzenia, tylko domyślnedowolny saveOption: domyślny lub dostosowany do formatu
Dodatkowy importwymaga zarejestrowania formatu (_ \"…/register\", lub pasującego podpakietu saveoptions/*)brak, gdy przekazujesz konstruktor taki jak pdf.New()
Statusprzestarzała cienka nakładka (stabilna, zachowana dla kompatybilności)udokumentowany jedyny punkt wejścia dla nowego kodu

Ten sam plik→wynik pliku, dwa różne scenariusze:

  • ConvertSpreadsheetToFile — dosłownie masz dwie ścieżki i jesteś zadowolony z domyślnych ustawień silnika dla formatu docelowego. Idealne dla jednorazowych skryptów konwersji, partii migracji i pomocników testowych. Jego koszt: wejście, wyjście i ustawienia nigdy nie mogą opuścić kształtu „file path + defaults” — i jest przestarzałe.
  • converter.Convert — chcesz tę samą jednowierszową metodę, ale którykolwiek z trzech argumentów może być wymienny: jutro wejście będzie []byte z przesyłki, docelowy wymaga pdf.WithOnePagePerSheet(true), lub wyjście powinno trafić bezpośrednio do odpowiedzi HTTP. To dokładnie scenariusze z sekcji 3 i 4 — napisz to raz przy użyciu Convert, a każda zmiana to edycja jednego argumentu.

2. Najprostszy × N: jedna funkcja dla wszystkich 32 formatów

Następny poziom zwięzłości polega na używaniu tej samej funkcji niezależnie od tego, jaki format jest celem. Ponieważ saveOption może być obliczone z rozszerzenia pliku wyjściowego, wyciągnięcie tej inferencji na zewnątrz daje “uniwersalny konwerter plików”:

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

Użycie pozostaje identyczne — zmienia się tylko rozszerzenie wyjściowe:

$ 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

Co to jest ten pusty import _ "…/register"?

Istnieją dwa sposoby uzyskania saveOption; jest to jedyny koncept, który styl „najbardziej zwięzły” musi zrozumieć:

  • Sprawdź w rejestrze po nazwie formatu (formats.Get): rozszerzenie musi być obecne w rejestrze formats. Każdy podpakiet saveoptions/* rejestruje się w własnej funkcji init(), ale tylko po zaimportowaniu. Importuj potrzebne podpakiety pojedynczo (np. tylko pdf, jeśli konwertujesz do PDF), lub pusty import …/register jednorazowo, który rejestruje wszystkie 32 formaty naraz — „uniwersalny konwerter” powyżej opiera się na tej jednej linii.
  • Wywołaj konstruktor bezpośrednio (pdf.New(), markdown.New()): omija rejestr, nie jest potrzebny import register; koszt to jedna linia importu na każdy używany format.

Krótko mówiąc: używaj _ "…/register" tylko wtedy, gdy rozwiązujesz przez formats.Get + rozszerzenie wyjściowe; przekazywanie konstruktora bezpośrednio nie wymaga nic dodatkowego. Pusty import jest nieszkodliwy i zawsze występuje w oficjalnych przykładach.

3. Najbardziej elastyczne: reguluj każde pokrętło niezależnie

Gdy potrzeba wykracza poza „plik do pliku z ustawieniami domyślnymi”, nie zmieniasz API — trzy parametry Convert można wymieniać niezależnie.

1. Wejście (źródło): z pliku, do pamięci, do dowolnego strumienia

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

Uwaga: podstawowy silnik wymaga losowego dostępu do całej zawartości, więc ReaderSource buforuje strumień w całości przy pierwszym dostępie; każde późniejsze Open zwraca kopię tego bufora. Używaj go śmiało w HTTP, ale pamiętaj, że nie jest to prawdziwe “konwertowanie w trakcie strumieniowania”.

2. Format (saveOption): domyślne, lub dostosowane

ScenariuszStyl kodowaniaDlaczego
Jakiś format z ustawieniami domyślnymiformats.Get("pdf")wyszukiwany po rozszerzeniu; nie uwzględnia wielkości liter; nil jeśli nie jest zarejestrowany
Format z opcjami specyficznymi dla formatupdf.New(pdf.WithOnePagePerSheet(true))opcje funkcyjne
Ustawienia domyślne, wyraźny formatooxml.New() / csv.New() / …pojedynczy konstruktor bezargumentowy dla każdego formatu
Niestandardowy prywatny formatformats.Register("fmt", factory)podłącz do rejestru — zobacz docs/register.md

Opcje funkcjonalne pojawiają się tylko w formatach, które je posiadają: jedna strona na arkusz w PDF to WithOnePagePerSheet; obrazy wbudowane w HTML to WithExportImagesAsBase64; TSV to formats.Get("tsv"). Opcje są semantyką wskaźnikową: pominięcie = domyślne ustawienie silnika; przekazanie false explicite również się liczy.

3. Wyjście (odbiornik): plik, bajty lub 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 i WriterSink są dwoma kluczami do „najbardziej elastycznego”: pozwalają, aby wynik konwersji ominął całkowicie dysk i przepłynął bezpośrednio do odpowiedzi HTTP, magazynu obiektowego, archiwum zip i tak dalej. Wszystkie trzy są różnymi implementacjami tego samego parametru sink, więc wszystko, co akceptuje []byte lub io.Writer, może być podłączone do konwertera.

4. Przykłady z rzeczywistego środowiska: jeden Convert, wiele kształtów

A. Konwertuj w pamięci; punkt końcowy pobierania strumieniuje PDF

Typowy scenariusz po stronie serwera — użytkownik przesyła plik xlsx, endpoint zwraca PDF, bez plików tymczasowych:

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

Aby zamiast tego wydać CSV / Excel / HTML, zamień tylko saveOption (na przykład formats.Get("csv")); nic innego się nie zmieni.

B. Odczytaj raz, eksportuj wsadowo do wielu formatów

Wczytaj wejście jednorazowo, a następnie użyj go ponownie poprzez BytesSource dla dowolnej liczby wyjść — idealne do “eksportu każdego raportu”:

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. “Zapisz jako” w rodzinie arkuszy kalkulacyjnych (migracja / kompatybilność)

Migracja formatu w obrębie rodziny arkuszy kalkulacyjnych, np. xlsx → xls, xlsx → xlsb, xlsx → ods, oraz szablony xltx/xltm — wciąż jedno Convert, zmienia się tylko 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. Dostosuj opcje w zależności od formatu, tylko w razie potrzeby

Opcje funkcyjne pojawiają się, gdy potrzebna jest precyzyjna kontrola — zauważ, że każde wywołanie poniżej jest rzeczywiste i poprawnie typowane:

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

Wskazówka: w przypadku wyjść jednoplikowych, takich jak CSV/HTML/obraz, silnik domyślnie eksportuje tylko arkusz, który uznaje za „aktywny”. Aby kontrolować zakres, użyj własnych opcji formatu — np. html.WithExportActiveWorksheetOnly(true) (tylko aktywny arkusz?), html.WithShowAllSheets(true), lub csv.WithExportAllSheets(true). Jakość obrazu na poziomie pikseli, dokładny wybór renderowanych arkuszy oraz inne zaawansowane kontrolki nadal istnieją na warstwie silnika, ale obecny pakiet image udostępnia jedynie przełącznik WithImageType — sięgnij po podstawowe powiązanie, jeśli potrzebujesz więcej; zestaw narzędzi będzie stopniowo dodawał kolejne opcje.

E. Konwersja wsadowa w katalogu

Codzienne zadanie “przekonwertuj ten folder Excela na PDF” staje się bardzo krótkie dzięki wnioskowaniu rozszerzenia:

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. Klasyfikowanie błędów

błędy konwertera są klasyfikowalnymi błędami sentinel; połącz je z errors.Is, aby precyzyjnie je obsługiwać:

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() zwraca wszystkie aktualnie zarejestrowane rozszerzenia (posortowane, bezpieczne w środowisku wielowątkowym) — przydatne przy generowaniu listy “obsługiwanych formatów” lub podpowiedzi o błędzie.


5. Obsługiwane formaty w skrócie (32 zarejestrowane domyślnie)

RodzinaRozszerzenia wyjściowePakiet dostrajania
Excelxlsx xlsm xltx xltm xls xlsbsaveoptions/ooxml, xls, xlsb
OpenDocumentods xml dif dbfsaveoptions/ods, xml, dif, dbf
Tekst / danecsv txt tsv json sqlsaveoptions/csv, txt, json, sqlScript
Dokumenty / układpdf docx pptx xps pcl epubsaveoptions/pdf, docx, pptx, xps, pcl, ebook
Webhtml mdsaveoptions/html, markdown
Obrazypng jpg jpeg gif bmp svg emf tif tiffsaveoptions/image

Aby utrzymać binarkę lekką, importuj tylko podpakiety, których używasz (np. pdf); dla wygody lub ogólnego dyspozycji, użyj pustego importu …/register. Importowanie podpakietu wywołuje jego rejestrację; wyszukiwania w rejestrze są nieczułe na wielkość liter; niezarejestrowane rozszerzenie zwraca nil (nie błąd), pozostawiając decyzję o fallbacku wywołującemu.


6. Najlepsze praktyki i uwagi

  1. Jedno wejście — nie wymyślaj tego od nowa. File-to-file używa Convert(FilePathSource, formats.Get(ext)/constructor, FilePathSink); zmieniaj sinki tylko wtedy, gdy potrzebujesz bajtów lub strumienia. Nie twórz ręcznie os.Open → ReadAll → … → os.WriteFile; źródło/sink zarządzają zasobami i tworzą katalogi nadrzędne za Ciebie.
  2. Współbieżność i licencjonowanie. Podstawowy silnik jest natywny: nie udostępniaj jednego obiektu SaveOption/workbook pomiędzy goroutine’ami w tym samym procesie; przy pracy masowej przetwarzaj pliki kolejno lub równolegle w osobnych procesach. Wersja ewaluacyjna ogranicza liczbę ładowań (około stu NewWorkbook_Stream na proces) — przy dużych benchmarkach uruchom ponownie proces, aby zresetować licznik.
  3. Wczytaj wejście raz, a potem używaj ponownie. Aby wygenerować kilka formatów z jednego wejścia, najpierw os.ReadFile je do []byte, a następnie konwertuj wielokrotnie przy użyciu BytesSource, unikając wielokrotnych odczytów z dysku i ponownych ładowań.
  4. Strumienie są buforowane. NewReaderSource buforuje przy pierwszym dostępie, więc nadaje się do jednorazowych wejść, takich jak ciało żądania HTTP, a nie do prawdziwego strumieniowania bardzo dużych plików (użyj FilePathSource wskazującego bezpośrednio na dysk w takich przypadkach).
  5. Wiedz, kto decyduje o układzie wyjścia. Convert to “jedno wejście → jeden plik wyjściowy”. Jeśli potrzebujesz jednego pliku na arkusz lub archiwum zip, użyj manipulator.Split z FolderSink/ZipSink — nie wymuszaj tego przez Convert.