ממיר: שורה אחת בפשטותה, כל סביבה בגמישותה המרבית

החבילה converter של Aspose.Cells for Go via C++ Toolkits מצמצמת כל צורך של “להפוך גיליון אלקטרוני לפורמט אחר” לפונקציית כניסה אחת:

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

החבילה כוללת גם ערך ישן, אפילו קצר יותר למקרה הקלאסי “convert this file into that file”. כל מה שצריך זה שני נתיבים:

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 למטה. עבור המקרה של קובץ→קובץ, השניים זהים בדיוק — זה כל מה ש-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)
	}
}

כל שלושת ההגדרות מקבלות את ברירת המחדל שלהן: קלט = קובץ, פורמט = pdf.New() ללא שום תצורה, פלט = קובץ. זהו הצורה „הכי תמציתית” — ללא os.Open/os.Create ידניים, ללא הקצאת אובייקטים, ללא סגירת ידיות; המקור/היעד מנהלים את המשאבים עבורך.

השוואת שני צורות קובץ→קובץ

ConvertSpreadsheetToFile(in, out)converter.Convert(source, opt, sink)
קלטרק נתיב קובץכל DataSource: נתיב, []byte, או קורא
פלטרק נתיב קובץכל DataSink: נתיב, בתים, או io.Writer
פורמט פלטתמיד נגזר מההרחבה, ברירת מחדל בלבדכל saveOption: ברירת מחדל או התאמה ספציפית לפורמט
ייבוא נוסףדורש רישום של הפורמט (_ "…/register", או תת‑חבילה מתאימה saveoptions/*)אין, כאשר מעבירים בונה כמו pdf.New()
מצבעטיפה דקה מיושנת (יציבה, נשמרת לתאימות)נקודת הכניסה המתועדת היחידה לקוד חדש

קובץ זהה → תוצאה של קובץ, שני תרחישים שונים:

  • ConvertSpreadsheetToFile — אתה בעצם עובד עם שני נתיבים ושמח עם ההגדרות ברירת המחדל של המנוע עבור פורמט היעד. אידיאלי לסקריפטים של המרה חד‑פעמיים, קבוצות מיגרציה ועוזרי בדיקה. המחיר שלו: הקלט, הפלט וההגדרות לעולם לא יעזבו את הצורה “נתיב קובץ + ברירות מחדל” — והוא מיושן.
  • 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; זהו הרעיון היחיד שהסגנון “most concise” צריך להבין:

  • חפש ברשומה לפי שם פורמט (formats.Get): ההרחבה חייבת להיות קיימת ברשומת formats. כל תת‑חבילה saveoptions/* רושמת את עצמה ב‑init() שלה, אך רק כאשר היא מיובאת. ייבא את תת‑החבילות שאתה צריך אחת‑אחת (למשל רק pdf אם אתה ממיר ל‑PDF), או יבוא ריק …/register פעם אחת, שמרשום את כל 32 הפורמטים בבת אחת — ה"מתאם האוניברסלי" שלמעלה מסתמך על השורה היחידה הזאת.
  • קרא לבונה ישירות (pdf.New(), markdown.New()): מדלג על הרשומה, אין צורך בייבוא register; העלות היא שורת ייבוא אחת לכל פורמט שבו אתה משתמש.

בקיצור: השתמש רק ב-_ "…/register" כאשר אתה פותר דרך formats.Get + סיומת הפלט; העברת בנאי ישירות אינה דורשת דבר נוסף. הייבוא הריק אינו מזיק ותמיד קיים בדוגמאות הרשמיות.

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. פלט (sink): קובץ, בתים, או 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. המרה קבוצתית על ספרייה

המשימה היומיומית “convert this folder of Excels to 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
אינטרנטhtml mdsaveoptions/html, markdown
תמונותpng jpg jpeg gif bmp svg emf tif tiffsaveoptions/image

כדי לשמור על הבינארי רזה, ייבא רק את תתי‑החבילות שבהן אתה משתמש (כגון pdf); לנוחות או להפעלה גנרית, ייבא ריק …/register. ייבוא של תתי‑חבילה מפעיל את הרישום שלה; חיפושי רישום אינם רגישים לרישיות; הרחבה שלא נרשמה מחזירה nil (לא שגיאה), ומשאירה את החלטת fallback למתקשר.

6. שיטות מומלצות והזהרות

  1. נקודת כניסה אחת — אל תמציא מחדש. File-to-file משתמש ב‑Convert(FilePathSource, formats.Get(ext)/constructor, FilePathSink); החלף את ה‑sink רק כשאתה צריך בתים או זרם. אל תכתוב קוד ידני os.Open → ReadAll → … → os.WriteFile; המקור/ה‑sink מנהלים משאבים ויוצרים תיקיות הורה עבורך.
  2. קונקרנציה ורישוי. המנוע הבסיסי הוא מקורי: אל תשתף אובייקט SaveOption/workbook אחד בין goroutine‑ים באותו תהליך; עבור עבודה בכמות גדולה, עבד על קבצים ברצף או פרללליזציה בין תהליכים. גרסת ההערכה מגבילה טעינות (כ‑מאה טעינות NewWorkbook_Stream לכל תהליך) — עבור מדדים גדולים, הפעל מחדש את התהליך כדי לאפס את הסופר.
  3. קרא את הקלט פעם אחת, ואז השתמש בו מחדש. כדי לייצא כמה פורמטים מקלט אחד, תחילה os.ReadFile אותו ל‑[]byte והמר באופן חוזר עם BytesSource, מה שמונע קריאות דיסק חוזרות וטעינות מחדש.
  4. הזרמים מתווספים לבופר. NewReaderSource מאגר בזיכרון בגישה הראשונה, ולכן הוא מתאים לקלט חד‑פעמי כמו גוף בקשת HTTP, ולא לשידור רציף של קבצים גדולים מאוד (השתמש ב‑FilePathSource שמצביע ישירות על הדיסק עבור כאלה).
  5. דע מי מחליט על פריסת הפלט. Convert הוא “קלט אחד → קובץ פלט אחד”. אם אתה צריך קובץ אחד לכל גיליון או קובץ zip, זה manipulator.Split עם FolderSink/ZipSink — אל תכריח זאת דרך Convert.