تصور کنید برای پیادهسازی کامل استاندارد یونیکد ۱۷.۰، بهجای درگیر شدن با هزارتوی وابستگیهای نرمافزاری، تنها دو فایل به پروژه خود اضافه کنید. این وعدهٔ Mojibake است؛ کتابخانهای مستقل که با زبان C11 نوشته شده و با C++17 سازگاری کامل دارد. این کتابخانه تحت لایسنس MIT منتشر شده و هدف آن حذف کامل بار اضافی زمان اجرا (Runtime baggage) است که معمولاً با پردازش متون استاندارد و مطابق با آخرین نسخههای یونیکد همراه است.
مدیریت متون جهانی در زبان C معمولاً نیازمند کتابخانههای عظیمی مثل ICU است که ادغام آنها در پروژهها بهدلیل حجم زیاد و پیچیدگیهای پیکربندی، بسیار دشوار و سنگین است. این موضوع همیشه یک نقطهٔ ضعف و چالش شناخته شده برای برنامهنویسان سیستمهایی بوده است که به کنترل دقیق، سبک و در عین حال دقیق روی کاراکترها نیاز دارند. Mojibake این مشکل را با ارائه یک «آمالگامات» (Amalgamation) — یعنی تنها دو فایل mojibake.c و mojibake.h — حل کرده است که به هیچ وابستگی خارجی نیاز ندارد.
بر اساس بررسی منابع فنی، این ابزار روی تمامی سیستمعاملهای مدرن از جمله لینوکس، macOS، FreeBSD، OpenBSD، NetBSD و ویندوز ۱۰ و ۱۱ بدون نیاز به هیچ پیشنیازی اجرا میشود. همانطور که در تحلیلهای پیشین ما دربارهی بهینهسازی لایههای زیرین نرمافزار اشاره کردیم، حذف وابستگیها (Dependencies) کلید افزایش پایداری در سیستمهای حساس است. Mojibake دقیقاً همین مسیر را طی کرده است تا توسعهدهندگان را از «جهنم وابستگیها» در پردازش متن نجات دهد.
تحول متن و نرمالسازی
این کتابخانه تمامی الگوریتمهای اصلی استاندارد یونیکد را پیاده کرده و تمام الزامات تطبیق (Unicode Conformance Requirements) را برآورده میکند. تابع mjb_normalize عملیات نرمالسازی را در چهار حالت NFC، NFD، NFKC و NFKD مدیریت میکند. برای مثال در حالت NFC، توالی e + ◌́ (U+0301 COMBINING ACUTE ACCENT) بهطور خودکار به کاراکتر واحد é (U+00E9) تبدیل میشود.
برای برنامهنویسانی که سرعت اولویت دارند، سازوکار «بررسی سریع» (mjb_string_is_normalized) اجازه میدهد اگر رشته از قبل نرمال شده است، از مراحل پردازش کامل و هزینهبر عبور کنند. خروجی این بررسی یک مقدار از نوع mjb_quick_check_result است که وضعیت را به سه حالت تقسیم میکند: MJB_QC_YES به معنای نرمال بودن، MJB_QC_NO به معنای عدم نرمال بودن، و MJB_QC_MAYBE که سیگنال میدهد برای تصمیمگیری نهایی، یک گذر کامل (Full Pass) از متن همچنان مورد نیاز است. این فرآیند کاملاً مطابق با استانداردهای UAX #15 برای یونیکد ۱۷.۰.۰ است.
تغییر وضعیت حروف (Case conversion) توسط تابع mjb_case انجام میشود که تبدیلها را به حروف بزرگ (Uppercase)، کوچک (Lowercase) و وضعیت Titlecase مدیریت میکند. این فرآیند شامل نگاشتهای شرطی و موارد خاص (Special-casing) است. در پیادهسازی Titlecase، از مرزبندیهای کلمات مطابق با استاندارد UAX #29 استفاده شده تا تضمین شود اولین کاراکتر دارای حالت (Cased) در هر بخش از کلمه به صورت Titlecase تبدیل شده و کاراکترهای بعدی کوچک شوند.
تنظیمات حروف بر اساس محل یا همان Locale است که با تابع mjb_locale_set در سطح سراسریِ فرآیند تعیین میشود. پیشفرض این تنظیم روی MJB_LOCALE_EN (انگلیسی) است. با این حال، پشتیبانی تخصصی برای MJB_LOCALE_TR و MJB_LOCALE_AZ برای مدیریت حروف I نقطهدار در زبانهای ترکی و آذربایجانی و همچنین Case-folding مخصوص اقوام ترک فراهم شده است. همچنین MJB_LOCALE_LT قواعد خاص نقطهگذاری در زبان لیتوانیایی را اعمال میکند. این دقت باعث میشود تبدیلهای خاص، مانند تبدیل سیگمای یونانی (U+0041) به سیگمای کوچک (U+03C3)، بهدرستی انجام شود.
در سطح کدپوینت (Codepoint)، توسعهدهندگان میتوانند از توابع هدفمندتری استفاده کنند: mjb_codepoint_to_lowercase برای تبدیل به حروف کوچک، mjb_codepoint_to_uppercase برای حروف بزرگ و mjb_codepoint_to_titlecase برای وضعیت Titlecase. برای مثال، فراخوانی mjb_codepoint_to_uppercase(0x00E9) برای کاراکتر 'é' مقدار U+00C9 را برمیگرداند و فراخوانی mjb_codepoint_to_titlecase(0x01F3) برای 'dz' مقدار U+01F2 را بازمیگرداند.
برای کارهای مربوط به شناسهها (Identifiers)، تابع mjb_nfkc_casefold نگاشتهای استاندارد NFKC_Casefold را اعمال کرده و نتیجه را به فرم NFC نرمال میکند. این عملیات باعث حذف کدپوینتهای «پیشفرض قابل چشمپوشی» (Default-ignorable) شده و Folding سازگاری را انجام میدهد که برای مقایسه دقیق شناسهها حیاتی است. برای نمونه، این تابع واژه آلمانی "Straße" را به "strasse" تبدیل میکند.
فیلترگذاری و تجزیه متن
علاوه بر تغییرات ساختاری، Mojibake تابع mjb_string_filter را ارائه میدهد. این تابع به توسعهدهندگان اجازه میدهد تا همزمان با نرمالسازی متن، کاراکترهای کنترلی، فاصلهها یا اعداد را حذف کنند. گزینههای فیلتر شامل MJB_FILTER_COLLAPSE_SPACES است که چندین فضای خالی متوالی (مانند \t\t\n\n) را به یک تک-فاصله تبدیل میکند. همچنین فیلتر MJB_FILTER_CONTROLS برای حذف کاراکترهای غیرقابل چاپ به کار میرود. برای مثال، رشتهای حاوی \x1\x2\t\n\v\f\r\x1f اگر با این فیلتر پردازش شود، تنها کاراکترهای فضای سفید استاندارد مثل \t\n\v\f\r را حفظ میکند.
یک فیلتر تخصصی به نام MJB_FILTER_LIMIT_COMBINING برای مقابله با متنهای سبک «Zalgo» طراحی شده است. این فیلتر علامتهای ترکیبی را پس از رسیدن به حد مجاز MJB_FILTER_MAX_COMBINING_MARKS در یک اجرای متوالی حذف میکند. این کار باعث میشود اکسنتهای معمولی حفظ شوند اما از انباشت بیش از حد علامتها که باعث بههمریختگی بصری میشود، جلوگیری گردد.
تحلیل فنی و امنیت
Mojibake فراتر از تبدیلهای ساده، قابلیتهای تحلیل عمیق متن را فراهم کرده است:
- پایگاه داده کاراکترها: دسترسی کامل به تمام ویژگیهای پایگاه داده کاراکترهای یونیکد (UCD) از طریق تابع
mjb_codepoint_characterفراهم است. این تابع یک رکوردmjb_characterشامل دسته (Category)، اسکریپت، بلوک، صفحه (Plane)، مقدار عددی و نام کاراکتر را پر میکند. ویژگیها را میتوان به صورت باینری (مانندmjb_codepoint_property_binaryبرای ویژگیMJB_PR_ALPHABETIC) یا به صورت عدد صحیح (مانندmjb_codepoint_property_intبرایMJB_PR_SCRIPT) استعلام کرد. - مقادیر عددی: تابع
mjb_codepoint_numeric_valueمقادیر اعشاری، ارقام و رشتههای عددی را استخراج میکند. برای مثال، U+0031 (رقم 1) مقدار اعشاری 1 را برمیگرداند، در حالی که U+00BD (کسر ۱/۲) رشته عددی "1/2" را بازمیگرداند. اگر کدپoyneای مقدار عددی نداشته باشد، فیلدهای اعشاری و رقمی آن رویMJB_NUMBER_NOT_VALID(عدد -1) تنظیم میشود. - اعتبارسنجی: توسعهدهندگان میتوانند با
mjb_codepoint_is_validصحت یک کدپوینت (مثلاً U+10FFFD) را بررسی کنند. همچنین توابعی برای تشخیص اینکه آیا یک کاراکتر گرافیکی است (mjb_codepoint_is_graphic) یا یک ایدئوگراف CJK است (mjb_codepoint_is_cjk_ideograph) یا ایدئوگراف توسعهیافته CJK است (mjb_codepoint_is_cjk_ext) وجود دارد. - تحلیل صفحه (Plane): توابعی مانند
mjb_codepoint_planeصفحه یونیکد را شناسایی میکنند (مثلاً U+1F600 در صفحه SMP قرار دارد). توسعهدهندگان میتوانند صفحات را باmjb_plane_is_validاعتبارسنجی کرده و نام آنها را باmjb_plane_name(مانند "Basic Multilingual Plane") دریافت کنند. - قطعهبندی (Segmentation): پیادهسازی کامل UAX #29 و UAX #14 برای شناسایی خوشههای گرافم (Grapheme Clusters)، کلمات، جملات و فرصتهای شکست خط. توابعی مانند
mjb_break_grapheme_clusterاجازه میدهند توسعهدهنده روی کاراکترهایی که کاربر بهصورت بصری ادراک میکند پیمایش کند. برای مثال، رشته «e + اکسنت ترکیبشونده» (U+0065 U+0301) از دو کدپوینت تشکیل شده اما تنها یک خوشه گرافم محسوب میشود. - جزئیات شکست متن: توابع
mjb_break_wordوmjb_break_sentenceامکان کشف مرزهای کلمات و جملات را میدهند. برای ورودی "Hello. Goodbye."، تابعmjb_break_sentenceحدود ۱۵ بایت مرز را شناسایی میکند. - برش متون (Truncation): کتابخانه تابع
mjb_truncateرا برای برش متون با آگاهی از گرافم (مثلاً برش "A🇮🇹Z" به ۲ گرافم که ۹ بایت اشغال میکند)،mjb_truncate_wordبرای برش با آگاهی از قطعهبندی کلمات وmjb_truncate_word_widthبرای محدود کردن متن بر اساس عرض ستونی فراهم کرده است. - متون دوجهتی (Bidi): پشتیبانی کامل از الگوریتم یونیکد برای متون دوجهتی (UAX #9). این شامل پرداخت پاراگرافها از طریق
mjb_bidi_resolve(مثلاً تبدیل "abc אבג" به ۷ کدپوینت)، مرتبسازی مجدد خطوط باmjb_bidi_reorder_lineو استخراج تکههای متن از طریقmjb_bidi_line_runsبرای مدیریت صحیح متون ترکیبی راستبه-چپ و چپبه-راست است. - امنیت: شناسایی کاراکترهای مشابه یا Confusable از طریق
mjb_confusable_skeletonکه اسکلتهای متنی را بر اساس UTS #39 تولید میکند. این فرآیند شامل اعمال الگوریتم Bidi L4، نرمالسازی NFD، حذف موارد پیشفرض قابل چشمپوشی و جایگزینی پروتوتایپها از فایلconfusables.txtاست. تابعmjb_string_is_confusableتشخیص میدهد آیا دو رشته از نظر بصری شبیه هستند یا خیر؛ مثلاً تشخیص تفاوت بین "good" انگلیسی و "gооd" که در آن از حرف 'о' سیریلیک استفاده شده است. - پشتیبانی از ایموجی: پیادهسازی UTS #51 برای تشخیص ایموجیهای RGI از طریق
mjb_string_is_rgi_emoji(مثلاً برای پرچم ایتالیا 🇮🇹)، تحلیل توالیها باmjb_string_emoji_sequenceو بررسی ویژگیهای خاص مانند اصلاحکنندههای پوستی (mjb_codepoint_is_emoji_modifierبرای U+1F3FD)، نحوه نمایش (mjb_codepoint_is_emoji_presentationبرای 0x1F600) یا اجزای تشکیلدهنده مانند ZWJ (U+200D) از طریقmjb_codepoint_is_emoji_component. - عرض نمایش: پشتیبانی از UAX #11 (East Asian Width). تابع
mjb_display_widthستونهای ترمینال را با در نظر گرفتن کاراکترهای عریض (مانند U+754C 界 کهMJB_EAW_WIDEاست) و علامتهای ترکیبی محاسبه میکند. همچنینmjb_truncate_widthاجازه برش متن را با آگاهی از عرض در زمینههایی مانندMJB_WIDTH_CONTEXT_WESTERNمیدهد.
هوشمندی محلی و زبان کرهای
Mojibake منطق بسیار خاصی برای خط کرهای پیاده کرده است. توابعی برای شناسایی اجزای Jamo فراهم شده است: mjb_codepoint_is_hangul_l برای Jamoهای پیشرو (مانند U+1100)، mjb_codepoint_is_hangul_v برای مصوتها (مانند U+1161) و mjb_codepoint_is_hangul_t برای Jamoهای پایانی (مانند U+11A8).
توسعهدهندگان میتوانند تجزیه سیلابهای کرهای را با mjb_hangul_syllable_decomposition (مثلاً U+AC01 که با U+1100 تجزیه میشود) یا ترکیب آنها را با mjb_hangul_syllable_composition انجام دهند. همچنین تابع mjb_hangul_syllable_name نام رسمی یک سیلاب را بازمیگرداند، مانند "HANGUL SYLLABLE GAG" برای کدپوینت U+AC01.
برای نیازهای گستردهتر زبانی، تابع mjb_locale_parse تگهای زبانی BCP 47 (مانند "sr-Latn-RS") را بهطور سختگیرانه تجزیه میکند و آنها را به بخشهای زبان، اسکریپت، منطقه، گونه و پسوندها تقسیم میکند. تگهای نادرست با یک خطای دقیق در شیء mjb_error رد میشوند.
ادغام و عملکرد
این کتابخانه از طیف وسیعی از رمزگذاریها شامل UTF-8، UTF-16 (LE/BE) و UTF-32 (LE/BE) پشتیبانی میکند. تابع mjb_string_convert_encoding تبدیلها را مدیریت میکند. برای مثال، تبدیل "café" از UTF-8 منجر به تولید ۸ بایت در فرمت UTF-16LE میشود. در ورودیهای عمومی UTF-16/UTF-32، کتابخانه از BOM (Byte Order Mark) به عنوان امضا برای تشخیص ترتیب بایتها استفاده میکند و در صورت نبود آن، ورودی را رد میکند.
تشخیص رمزگذاری توسط mjb_string_encoding انجام میشود که طرحهای مشتق شده از BOM را گزارش میکند. در مواقعی که فلگها همپوشانی دارند — مثلاً یک BOM مربوط به UTF-32LE که پیشوندی شبیه UTF-16LE دارد — کتابخانه اولویت را به UTF-32 میدهد. یک BOM مربوط به UTF-16LE یعنی \xFF\xFE به صورت MJB_ENC_UTF_16 | MJB_ENC_UTF_16LE شناسایی میشود.
برای کوچک نگه داشتن حجم باینری نهایی، توسعهدهندگان میتوانند از فلگهای ویژگی در زمان ساخت (Build-time) استفاده کنند. برای مثال، غیرفعال کردن MJB_FEATURE_CHARACTER_NAMES حجم خروجی را حدود ۳۰٪ کاهش میدهد، زیرا جداول نام کاراکترها کامپایل نمیشوند و مقدار mjb_character.name به صورت ساده "Codepoint U+XXXX" گزارش میشود. این تنظیم از طریق CMake با پارامتر -DMJB_FEATURE_CHARACTER_NAMES=OFF یا از طریق Makefile قابل پیکربندی است.
تستها در این پروژه یک رکن اساسی هستند. Mojibake از مجموعه تست Attractor استفاده کرده و بیش از ۱.۵ میلیون Assertion (تأییدیه) را با موفقیت پشت سر گذاشته است. این شامل مجموعههای تطبیق رسمی یونیکد برای تمام الگوریتمهای پشتیبانی شده است. برای اطمینان از پایداری در برابر ورودیهای بایتی غیرقابلاعتماد، پروژه با libFuzzer آزمایش شده و توسط AddressSanitizer و UBSan تأیید شده است.
برای توسعهدهندگان C++، یک Wrapper در مسیر src/cpp/mojibake.hpp فراهم شده است. همچنین یک API برای WASM و TypeScript در src/api برای ابزارهای مبتنی بر مرورگر و یک ابزار CLI اختصاصی در src/shell برای تستهای محیط شل وجود دارد. ابزار CLI اجازه بررسیهای سریع را میدهد؛ مثلاً اجرای mojibake nfc $'Cafe\u0301' برای مشاهده خروجی NFC یا mojibake emoji "☺️" برای تحلیل توالی ایموجیها به صورت [1] Basic, [2] Fully-qualified.
از آنجا که Mojibake از تخصیصکنندههای حافظه سفارشی (Custom Allocators) از طریق mjb_set_memory_functions پشتیبانی میکند، برای سیستمهای جاسازیشده (Embeddable) که در آنها تکه تکه شدن حافظه (Fragmentation) یک نگرانی است، کاملاً مناسب است. کاربران میتوانند توابع پیشفرض را با alloc_fn، realloc_fn و free_fn خود جایگزین کنند. همچنین وضعیت کتابخانه را میتوان با mjb_shutdown بازنشانی کرد.
مدیریت حافظه برای نتایج از طریق mjb_result_free انجام میشود که بافرهای خروجی تخصیصیافته را بهصورت ایمن آزاد میکند. برای مثال، اشیاء mjb_result حاصل از mjb_normalize یا mjb_collation_key باید آزاد شوند تا از نشت حافظه (Memory Leak) جلوگیری شود. ساختار mjb_result ردیابی میکند که آیا خروجی واقعاً تغییر یافته (transformed) است یا خیر تا از تخصیصهای غیرضروری جلوگیری شود.
توابع کمکی اضافی مانند mjb_string_length به توسعهدهندگان اجازه میدهد تعداد واقعی کدپوینتهای یونیکد را به جای تعداد بایتها بشمارند. برای مثال، رشته "文字化け" در UTF-8 دارای ۱۲ بایت است اما بهدرستی به عنوان ۴ کاراکتر شناسایی میشود. بررسیهای سادهای مانند mjb_string_is_ascii و mjb_string_is_utf8 اعتبارسنجی سریع بافرهای ورودی را فراهم میکنند.
این تغییر رویکرد به سمت آمالگامات تک-فایلی و بهشدت بهینهشده، نشاندهنده فاصله گرفتن از رویکرد «همه چیز در یک جا» (Everything-and-the-kitchen-sink) در کتابخانههای قدیمی است. با ارائه یک ابزار کاملاً مطابق با استاندارد اما با کمترین اثر (Footprint)، Mojibake مانع ورود توسعهدهندگان برای تبدیل یونیکد به یک شهروند درجه یک در اپلیکیشنهای سطح پایین (Low-level) را از بین برده است.
اگر در حال طراحی یک کامپایلر یا پارسر هستید، اکنون میتوانید از تابع mjb_string_is_identifier برای اعتبارسنجی XID/ID مطابق با UAX #31 بدون وارد کردن یک فریمورک عظیم استفاده کنید. این امکان را فراهم میکند تا بهطور دقیق بررسی کنید که آیا یک رشته با یک کاراکتر شناسایی معتبر شروع میشود (mjb_codepoint_is_id_start یا mjb_codepoint_is_xid_start) و با کاراکترهای ادامه معتبر، چه در پروفایل DEFAULT و چه در NFKC، ادامه مییابد.
گام بعدی شما
- اگر در حال طراحی کامپایلر یا پارسر هستید، از تابع
mjb_string_is_identifierبرای اعتبارسنجی شناسهها مطابق UAX #31 استفاده کنید. - برای کاهش حجم نهایی اپلیکیشن، فلگ
MJB_FEATURE_CHARACTER_NAMESرا در CMake غیرفعال کنید. - ابزار CLI را برای بررسی سریع خروجیهای NFC یا تحلیل توالی ایموجیها تست کنید.
این کتابخانه تنها بخشی از زنجیره بهینهسازی است؛ برای درک چالشهای پردازش متن در مقیاس بزرگ، به تحلیل ما دربارهی توکنسازی در مدلهای زبانی مراجعه کنید.




گفتگو