مبدل: یک خط در ساده‌ترین حالت، هر محیط در بیشترین انعطاف‌پذیری

بسته 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 یک خط‌کد توکار file→file: 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 یک wrapper نازک است که برای سازگاری با نسخه‌های قبلی حفظ شده است؛ کد جدید به 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)
	}
}

تمامی سه تنظیم مقدار پیش‌فرض خود را می‌گیرند: ورودی = یک فایل، قالب = pdf.New() بدون هیچ پیکربندی، خروجی = یک فایل. این «most concise» شکل است — بدون os.Open/os.Create دستی، بدون تخصیص شیء، بدون بستن دسته؛ منبع/مقصد منابع را برای شما مدیریت می‌کند.

1.3 مقایسه دو شکل فایل→فایل

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 برود. این‌ها دقیقاً سناریوهای بخش‌های ۳ و ۴ هستند — یک‌بار با Convert بنویسید و هر تغییر تنها یک ویرایش آرگومان باشد.

2. ساده‌ترین × N: یک تابع برای تمام ۳۲ فرمت

سطح بعدی خلاصه‌گویی استفاده از همان تابع است، بدون توجه به قالب هدف. چون saveOption می‌تواند از پسوند فایل خروجی محاسبه شود، استخراج این استنتاج منجر به یک “universal file converter” می‌شود:

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

این import خالی _ "…/register" چیست؟

دو روش برای به دست آوردن یک saveOption وجود دارد؛ این تنها مفهومی است که سبک “مختصرترین” باید درک کند:

  • از رجیستری بر اساس نام فرمت جستجو کنید (formats.Get): پسوند باید در رجیستری formats موجود باشد. هر زیرپکیج saveoptions/* خود را در init() مخصوص خود ثبت می‌کند، اما فقط زمانی که وارد شود. زیرپکیج‌های مورد نیاز خود را یکی‌یکی وارد کنید (مثلاً فقط pdf اگر به PDF تبدیل می‌کنید)، یا یکبار …/register را به صورت خالی وارد کنید، که تمام ۳۲ فرمت را یک‌باره ثبت می‌کند — «مبدل جامع» بالا به آن یک خط تکی وابسته است.
  • به‌صورت مستقیم سازنده‌ای را فراخوانی کنید (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 استفاده کنید، فقط بدانید که این یک «تبدیل در حین پخش» واقعی نیست.

۲. قالب (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. تبدیل دسته‌ای در یک پوشه

کار روزمره “این پوشه از فایل‌های اکسل را به 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. دسته‌بندی خطاها

خطاهای مبدل می‌توانند به‌عنوان خطاهای sentinel طبقه‌بندی شوند؛ برای مدیریت دقیق، آن‌ها را با 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 به‌طور پیش‌فرض ثبت شده)

خانوادهپسوندهای خروجیبسته تنظیمات
اکسلxlsx 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 برمی‌گرداند (خطا نیست)، و تصمیم‌گیری دربارهٔ بازگشت به حالت پیش‌فرض به عهدهٔ فراخوانی‌کننده می‌ماند.


6. بهترین روش‌ها و نکات مهم

  1. یک نقطه ورودی — دوباره اختراعش نکنید. در حالت فایل‑به‑فایل از Convert(FilePathSource, formats.Get(ext)/constructor, FilePathSink) استفاده می‌شود؛ فقط زمانی که به بایت‌ها یا یک جریان نیاز دارید، سینک‌ها را تغییر دهید. os.Open → ReadAll → … → os.WriteFile را به‌صورت دستی ننویسید؛ منبع/سینک منابع را مدیریت می‌کند و برای شما پوشه‌های والد را ایجاد می‌کند.

  2. همزمانی و مجوزدهی. موتور زیرین بومی است: نکنید یک شیء SaveOption/workbook را بین goroutine‌ها در همان فرآیند به‌اشتراک بگذارید؛ برای کارهای حجیم، فایل‌ها را به‌صورت متوالی پردازش کنید یا پردازش‌ها را بین فرآیندها موازی کنید. نسخه ارزیابی بارگذاری‌ها را محدود می‌کند (حدود صد بار NewWorkbook_Stream در هر فرآیند) — برای بنچمارک‌های بزرگ، فرآیند را مجدداً راه‌اندازی کنید تا شمارنده بازنشانی شود.

  3. ورودی را یک‌بار بخوانید، سپس دوباره استفاده کنید. برای تولید چندین فرمت از یک ورودی، ابتدا با os.ReadFile آن را به یک []byte تبدیل کنید و سپس به‌صورت مکرر با BytesSource تبدیل کنید، تا از خواندن مکرر دیسک و بارگذاری‌های دوباره جلوگیری شود.

  4. جریان‌ها بافر می‌شوند. NewReaderSource در اولین دسترسی بافر می‌کند، بنابراین برای ورودی‌های یک‌بار مصرف مانند بدنه درخواست HTTP مناسب است، نه جریان واقعی فایل‌های بسیار بزرگ (برای این موارد از FilePathSource که مستقیماً به دیسک اشاره می‌کند استفاده کنید).

  5. بدانید چه کسی چیدمان خروجی را تعیین می‌کند. Convert به معنای «یک ورودی → یک فایل خروجی» است. اگر به یک فایل برای هر برگه کاری یا یک zip نیاز دارید، از manipulator.Split همراه با FolderSink/ZipSink استفاده کنید — آن را از طریق Convert مجبور نکنید.