وقتی یک متن عادی به دستور محاسبه تبدیل می‌شود

نام مخاطب، توضیح سفارش یا یادداشت کاربر ممکن است در خروجی یک سامانه به فایل صفحه‌گسترده برسد. اگر برنامه دادهٔ واردشده را بدون قرارداد نوع ذخیره کند، برخی رشته‌ها می‌توانند به‌عنوان فرمول تفسیر شوند. خطر فقط خراب‌شدن نمایش نیست؛ فرمول می‌تواند رفتاری خارج از انتظار گزارش ایجاد کند. این مقاله به‌جای اعتماد به ظاهر متن، نوع سلول خروجی را صریح تعیین و نتیجه را در خود فایل بررسی می‌کند.

برای دادهٔ نامطمئنِ صرفاً متنی، نمونه فایل اکسل می‌سازد و نوع هر سلول را رشته ثبت می‌کند. این انتخاب با حذف نخستین علامت یا تغییر محتوای اصلی تفاوت دارد؛ مقدار نمایشی ورودی حفظ می‌شود. در خروجی سی‌اس‌وی، رفتار واردسازی و ذخیرهٔ دوبارهٔ نرم‌افزارهای مختلف یکسان نیست. بنابراین روش نقل‌قول‌گذاری را به‌عنوان تضمین همگانی معرفی نمی‌کنیم و قرارداد نمونه را به فایل متنی‌نوع‌دار محدود نگه می‌داریم.

قرارداد دادهٔ متنی در فایل خروجی

دو ستون نام و یادداشت در این مثال همیشه متن هستند. اگر گزارش شما مبلغ، تاریخ یا شناسه دارد، نوع و اعتبارسنجی هر ستون را جدا تعریف کنید؛ تبدیل همه‌چیز به متن ممکن است محاسبه و مرتب‌سازی مورد انتظار را تغییر دهد. این نمونه رشته‌های طولانی‌تر از سقف را رد می‌کند و فایل موجود را بازنویسی نمی‌کند. پذیرش تعداد نامحدود ردیف از یک درخواست عمومی نیز باید با بودجهٔ مستقل کنترل شود.

چهار فایل را کنار هم ذخیره کنید. بستهٔ openpyxl را در محیط مجازی نصب و python -m unittest test_export.py را اجرا کنید؛ این مثال با نسخهٔ ۳٫۱٫۵ آزموده شده است. سپس python demo_export.py را اجرا کنید تا contacts.xlsx ساخته شود. خروجی بررسی ساختار باید شمار فرمول صفر را نشان دهد. این شمارش روی فایل تولیدشده انجام می‌شود و نیازی به اجرای برنامهٔ اکسل برای آزمون پایه ندارد.

آزمون، چند مقدار شبیه فرمول و متن فارسی را پس از ذخیره دوباره می‌خواند و هم مقدار و هم نوع رشته را بررسی می‌کند. همچنین برچسب فرمول در ساختار داخلی کاربرگ شمرده می‌شود. نبود آن برچسب، آزمون همهٔ قابلیت‌های اکسل یا سلامت هر فایل ناشناخته نیست. ابزار شمارش فقط برای خروجی مورد اعتماد همین سازنده نوشته شده و نباید بدون محدودیت حجم و بررسی بایگانی به‌عنوان تحلیلگر عمومی فایل‌های دریافتی عرضه شود.

فایل‌های کامل و قابل اجرا

چهار فایل زیر کامل‌اند؛ پیش‌نیاز، ترتیب اجرا و روش بررسی در متن آمده است. کدها خلاصه یا پنهان نشده‌اند.

1. فایل کامل export_text.py

این فایل را با همین نام و بدون حذف سطرها کنار بقیهٔ فایل‌ها ذخیره کنید.

from pathlib import Path
from openpyxl import Workbook
from openpyxl.cell import WriteOnlyCell


def export_rows(rows, destination):
    workbook = Workbook(write_only=True)
    sheet = workbook.create_sheet('Contacts')
    sheet.append(['Name', 'Note'])
    for row in rows:
        values = []
        for field in ('name', 'note'):
            value = row[field]
            if not isinstance(value, str) or len(value) > 10000:
                raise ValueError('Expected short text fields')
            cell = WriteOnlyCell(sheet, value=value)
            cell.data_type = 's'  # Never serialize untrusted text as a formula.
            values.append(cell)
        sheet.append(values)
    with Path(destination).open('xb') as output:
        workbook.save(output)

دستور اجرا، نتیجهٔ مورد انتظار و محدودیت این فایل در بخش اجرای مقاله توضیح داده شده است.

2. فایل کامل validate_xlsx.py

این فایل را با همین نام و بدون حذف سطرها کنار بقیهٔ فایل‌ها ذخیره کنید.

import zipfile
from xml.etree import ElementTree as ET


def formula_count(path):
    count = 0
    with zipfile.ZipFile(path) as archive:
        for name in archive.namelist():
            if name.startswith('xl/worksheets/') and name.endswith('.xml'):
                root = ET.fromstring(archive.read(name))
                count += len(root.findall('.//{http://schemas.openxmlformats.org/spreadsheetml/2006/main}f'))
    return count

دستور اجرا، نتیجهٔ مورد انتظار و محدودیت این فایل در بخش اجرای مقاله توضیح داده شده است.

3. فایل کامل demo_export.py

این فایل را با همین نام و بدون حذف سطرها کنار بقیهٔ فایل‌ها ذخیره کنید.

from export_text import export_rows
from validate_xlsx import formula_count

if __name__ == '__main__':
    export_rows([{'name': 'کاربر نمونه', 'note': '=1+1'}, {'name': 'Example', 'note': '@SUM(A1:A2)'}], 'contacts.xlsx')
    print({'formulas': formula_count('contacts.xlsx')})

دستور اجرا، نتیجهٔ مورد انتظار و محدودیت این فایل در بخش اجرای مقاله توضیح داده شده است.

4. فایل کامل test_export.py

این فایل را با همین نام و بدون حذف سطرها کنار بقیهٔ فایل‌ها ذخیره کنید.

from pathlib import Path
import tempfile
import unittest
from openpyxl import load_workbook
from export_text import export_rows
from validate_xlsx import formula_count


class ExportTests(unittest.TestCase):
    def test_formula_like_values_are_literal_strings(self):
        with tempfile.TemporaryDirectory() as directory:
            target = Path(directory) / 'contacts.xlsx'
            values = ['=1+1', '+SUM(A1:A2)', '-1+2', '@SUM(A1:A2)', 'متن فارسی']
            export_rows([{'name': 'Example', 'note': v} for v in values], target)
            self.assertEqual(formula_count(target), 0)
            workbook = load_workbook(target, data_only=False)
            try:
                cells = list(workbook.active.iter_rows(min_row=2))
                self.assertEqual([r[1].value for r in cells], values)
                self.assertTrue(all(r[1].data_type == 's' for r in cells))
            finally: workbook.close()


if __name__ == '__main__': unittest.main()

دستور اجرا، نتیجهٔ مورد انتظار و محدودیت این فایل در بخش اجرای مقاله توضیح داده شده است.

ساخت فایل و بررسی فرمول‌های ناخواسته

در سامانهٔ واقعی، دادهٔ ورودی را از فرمول‌های مجازِ نوشته‌شده توسط خود برنامه جدا نگه دارید و نمونهٔ خروجی را با نرم‌افزار مقصد بررسی کنید. کاربر نباید مجبور باشد برای دیدن گزارش، هشدار امنیتی را نادیده بگیرد. اصل داده و قرارداد ستون‌ها را مستند کنید تا تغییر بعدی قالب، حفاظت را حذف نکند. معیار موفقیت، حفظ دقیق متن و تولید ساختار مورد انتظار است، نه فقط بازشدن فایل بدون پیام خطا.

چک‌لیست اجرای عملی

  • ورودی متنی نوع رشته داشته باشد.
  • مقدار و نوع سلول پس از ذخیره آزمون شوند.
  • دادهٔ متنی از فرمول‌های مجاز برنامه جدا بماند.
  • محدودیت ردیف و تحلیل فایل ناشناخته جدا طراحی شود.

منابع

تدوین و راهنمای عملی: دانشنامه لیان.