المحول: سطر واحد في أبسط صورة، أي بيئة بأقصى مرونة لها

حزمة converter من Aspose.Cells for Go via C++ Toolkits تقلل كل حاجة “turn a spreadsheet into another format” إلى دالة إدخال واحدة:

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 أدناه.) بقية هذه المقالة تنتقل من الأكثر اختصارًا إلى الأكثر مرونة، مع توسيع المقتطف السابق في كل خطوة بدلاً من الانتقال إلى واجهة برمجة تطبيقات مختلفة.

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

جميع الثلاث مقابض تأخذ القيم الافتراضية: input = ملف، format = pdf.New() بدون أي تكوين، output = ملف. هذا هو الشكل “الأكثر اختصارًا” — لا يوجد os.Open/os.Create يدوي، ولا تخصيص كائن، ولا إغلاق مقبض؛ المصدر/المستقبل يديران الموارد نيابةً عنك.

1.3 مقارنة الشكلين file→file

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. الأكثر مرونة: ضبط كل مقبض على حدة

عندما يتجاوز الحاجة إلى “ملف إلى ملف بالإعدادات الافتراضية”، لا تقوم بتغيير 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، العديد من الأشكال

أ. التحويل في الذاكرة؛ نقطة النهاية للتحميل تبث 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 مسجلة افتراضيًا)

العائلةامتدادات الإخراجحزمة الضبط
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 (ليس خطأ)، وتترك قرار الرجوع إلى الخلف للمستدعي.

6. أفضل الممارسات والاحتياطات

  1. نقطة دخول واحدة — لا تعيد اختراعها. يستخدم التحويل من ملف إلى ملف الدالة Convert(FilePathSource, formats.Get(ext)/constructor, FilePathSink)؛ قم بتغيير الـ sinks فقط عندما تحتاج إلى بايتات أو تدفق. لا تقم بإنشاء سلسلة يدوية os.Open → ReadAll → … → os.WriteFile؛ فالمصدر/المستقبل يديران الموارد وينشئان الأدلة الأم للملف تلقائيًا.
  2. التزامن والترخيص. المحرك الأساسي أصلي: لا تشارك كائن SaveOption/workbook واحد عبر goroutines في نفس العملية؛ للعمليات الضخمة عالج الملفات تسلسليًا أو وزع العمل عبر عمليات متعددة. نسخة التقييم تحدّ من عدد التحميلات (حوالي مئة تحميل NewWorkbook_Stream لكل عملية) — للمعايير الكبيرة، أعد تشغيل العملية لإعادة تعيين العداد.
  3. اقرأ الإدخال مرة واحدة، ثم أعد استخدامه. لإنتاج عدة صيغ من إدخال واحد، استخدم os.ReadFile لقراءة الملف إلى []byte أولاً ثم قم بالتحويل المتكرر باستخدام BytesSource، لتجنب قراءات القرص المتكررة وإعادة التحميل.
  4. التدفقات تُخزن مؤقتًا. NewReaderSource يخزن مؤقتًا عند الوصول الأول، لذا فهو مناسب للمدخلات ذات الاستخدام الواحد مثل جسم طلب HTTP، وليس للبث الحقيقي للملفات الكبيرة جدًا (استخدم FilePathSource الذي يشير مباشرة إلى القرص لهذه الحالة).
  5. اعرف من يحدد تخطيط الإخراج. Convert هو “إدخال واحد → ملف إخراج واحد”. إذا كنت بحاجة إلى ملف واحد لكل ورقة عمل أو ملف zip، فذلك يتم عبر manipulator.Split مع FolderSink/ZipSink — لا تجبره على المرور عبر Convert.