علامة BOM في UTF-8: إصلاح أخطاء تحليل JSON وCSV
خطأ تحليل JSON بسبب علامة BOM في UTF-8 هو ثلاثة بايتات لا تراها. يفتح الملف نظيفاً في محرّرك، ويطبع cat ما تتوقّعه بالضبط، ولا يشكو المدقّق (linter) من شيء، ومع ذلك يُلقي JSON.parse خطأً عند أول محرف بالذات.
وبالقياس على node v25.8.2، يبدو الخطأ هكذا:
SyntaxError: Unexpected token '', "{"a":1}" is not valid JSON
ومهما رسمت طرفيّتك داخل تلك العلامتين، فهو محرف واحد: U+FEFF، مخزَّن على هيئة البايتات EF BB BF. وJSON الصارم لا يترك له موضعاً. فالمحلّل عند الموضع 0 يتوقّع { أو [ أو رقماً أو علامة اقتباس أو مسافة بيضاء، وU+FEFF ليس أياً من هذه.
فإن كنت تعرف أصلاً أنها علامة BOM، فاختر الطرف الذي تملك تغييره:
| أين يمكنك أن تغيّر شيئاً | الإصلاح |
|---|---|
| Node، عند قراءة ملف | JSON.parse(raw.replace(/^/, '')) |
| Python، عند قراءة ملف | open(path, encoding='utf-8-sig') |
| الملف على القرص | tail -c +4 data.json > clean.json |
وبقيّة هذه الصفحة لِما لا ينفع فيه ذلك: الخطأ الذي يبدو علامة BOM وليس كذلك، والمصدر الذي يعيد وضعها كل مرّة، والصيغة الوحيدة التي يكون فيها حذفها هو العطل لا الإصلاح. أما ما هي علامة BOM وهل ينبغي لملف جديد أن يحملها، فذاك موضوع دليل UTF-8 مقابل UTF-16 وUnicode. وهذه الصفحة تفترض أن نسختك قد كسرت شيئاً بالفعل.
وكل ما قيس أدناه نُفِّذ على node v25.8.2 وPython 3.14.5.
1. ما يستبعده خطؤك قبل أن تتّهم علامة BOM
معظم من يبحثون عن خطأ JSON عند الموضع 0 ليست لديهم علامة BOM أصلاً. أربع مشكلات مختلفة تُنتج رسالة بالشكل نفسه، ونظرة واحدة إلى المحرف الموضوع بين علامتَي الاقتباس تفصل بينها. وهذه هي السلاسل الحرفية التي يُصدرها V8:
| نصّ الخطأ | ما هو فعلاً | الخطوة التالية |
|---|---|---|
Unexpected token '', "{"a":1}" is not valid JSON | علامة BOM في UTF-8 عند البايت 0 | القسم 2 |
Unexpected token '<', "<!DOCTYPE "... is not valid JSON | كان الردّ HTML: صفحة خطأ، أو إعادة توجيه إلى تسجيل الدخول، أو إشعار من وسيط | سجّل جسم الردّ الخام ورمز الحالة |
Unexpected end of JSON input | كان جسم الردّ فارغاً | افحص رمز الحالة وContent-Length |
"undefined" is not valid JSON | أعطيت JSON.parse متغيّراً لم يُسنَد إليه شيء قطّ | أصلح المُستدعي |
والقاعدة قصيرة بما يكفي لحفظها. اقرأ المحرف الواقع بين علامتَي الاقتباس المفردتين. فـ< تعني أنك استقبلت HTML. ومربّع أو فراغ أو علامة استفهام لا تستطيع تحديدها بالمؤشّر يعني U+FEFF. وألّا يكون بين العلامتين شيء أصلاً يعني أنه لم يكن ثمّة مُدخَل من البداية.
الصياغة القديمة والصياغة الجديدة
معظم نتائج البحث عن json parse unexpected token position 0 مكتوبة بناءً على رسالة V8 أقدم:
SyntaxError: Unexpected token in JSON at position 0
كانت تلك الصياغة تسمّي الإزاحة وتُخفي المحرف. والصياغة الحالية تفعل العكس: تعرض المحرف ومقتطفاً من المُدخل، وهو أنفع بكثير، لكنه يعني أن الصفحة التي تصل إليها قد تصف بيئة تشغيل غير التي تستخدمها. فإن كان خطؤك ما زال يسمّي موضعاً بدل محرف، فأنت على محرّك أقدم، والتشخيص التالي لا يتغيّر.
2. تأكّد أنها علامة BOM في عشر ثوانٍ
أربعة فحوص، مرتّبة تقريباً بحسب سرعتها. وأيٌّ منها وحده يحسم الأمر.
انظر إلى البايتات الثلاثة الأولى.
$ hexdump -C data.json | head -1
00000000 ef bb bf 7b 22 61 22 3a 31 7d |...{"a":1}|
فـef bb bf قبل 7b (أي {) هي علامة BOM. أما ... في عمود ASCII فهي اعتراف من hexdump بأنه لا يملك شيئاً قابلاً للطباعة يعرضه عليك.
اسأل file. يقولها لك مباشرة، بل يغيّر رأيه في نوع الملف كلّياً:
$ file data.json
data.json: Unicode text, UTF-8 (with BOM) text, with no line terminators
$ file clean.json
clean.json: JSON data
افحص أول نقطة ترميز في Node.
const fs = require('fs');
const raw = fs.readFileSync('data.json', 'utf8');
console.log(raw.charCodeAt(0) === 0xFEFF); // true
اقرأ شريط الحالة في المحرّر. يعرض VS Code العبارة UTF-8 with BOM في ركن شريط الحالة السفلي، والنقر عليها يتيح Save with encoding. وهذه اللافتة وحدها تفسّر لماذا بدا الملف سليماً: محرّرك كان يعرف، ولم يرفع صوته بأكثر من ذلك.
وللاطّلاع على مستوى البايتات لشيء لا تستطيع تفريغه محلياً، الصقه في أداة ترميز وفكّ ترميز Base64. فعلامة BOM في UTF-8 في مقدّمة أي حمولة تُرمَّز دائماً إلى سلسلة تبدأ بـ77u/، ويستحقّ هذا المقطع أن تعرفه حين يمرّ بك في سطر سجلّ.
3. من أين جاءت علامة BOM لديك
حذف علامة BOM من ملف تعيد خطوة بناءٍ توليده كل ساعة هو إصلاح عمره ساعة واحدة. وهذه هي المصادر المعتادة:
- خيار Save As → CSV UTF-8 في Excel. وهو سلوك مقصود، يشرح القسم 7 سببه.
- Notepad وغيره من محرّرات Windows التي تعرض UTF-8 with BOM خياراً منفصلاً للحفظ، وأحياناً بوصفه الخيار الافتراضي.
- VS Code، حين تكون
files.encodingمضبوطة علىutf8bom، سواء في إعداداتك الشخصية أو مودَعة في.vscode/settings.jsonحيث لا ينظر أحد. - إعادة التوجيه في PowerShell. يكتب
>وOut-Fileعلامة BOM افتراضياً في بعض إصدارات PowerShell، والافتراضي يختلف بين خطّ 5.x المقتصر على Windows وخطّ 6/7 العابر للمنصّات. ولا تعتمد على ذاكرتك هنا: اكتب ملفاً واحداً وافحص بايتاته الثلاثة الأولى بالأوامر الواردة في القسم 2. - كود تصدير مكتوب يدوياً. أي كاتب يُنشئ مُرمِّز UTF-8 دون أن ينصّ على إصدار توقيع من عدمه يرث ما اختاره إطار العمل افتراضياً، والأطر لم تختر جميعها الشيء نفسه. ومسارات التصدير القديمة في .NET وJava هي المتّهم المعتاد.
- أدوات تصدير قواعد البيانات وذكاء الأعمال (BI)، وهي كثيراً ما تُصدر علامة BOM لأن مستهلكها الأول جدول بيانات.
فإن كان الملف يصلك من شريك أو مورّد ولا تستطيع تغيير المنتِج، فانتقل إلى القسم 4 واحذفها عند القراءة. وإن كان يأتي من مستودعك أنت، فالقسم 9 هو الجواب الدائم.
4. إصلاحها في JavaScript وNode
هنا يتركّز الالتباس، لأن منظومة JavaScript ليست لديها سياسة واحدة تجاه علامة BOM. لديها عدّة سياسات، وهي متعارضة. الملف نفسه، وبيئة التشغيل نفسها، بالقياس على node v25.8.2:
| الواجهة | سلوكها تجاه علامة BOM | JSON.parse بعدها |
|---|---|---|
fetch → res.json() | تُحذَف | ينجح |
fs.readFileSync(f, 'utf8') | تبقى | يفشل |
new TextDecoder() (الافتراضي) | تُحذَف | ينجح |
new TextDecoder('utf-8', { ignoreBOM: true }) | تبقى | يفشل |
require('./data.json') | تُحذَف | لا ينطبق، محلَّل أصلاً |
import(..., { with: { type: 'json' } }) | تُحذَف | لا ينطبق، محلَّل أصلاً |
وينتج عن هذا الجدول أمران، وكلاهما كلّف الناس أمسيات كاملة.
ignoreBOM يفعل عكس ما يقوله اسمه
ignoreBOM: true لا تعني «تجاهل علامة BOM». بل تعني «تجاهل المعنى الخاص لعلامة BOM وأبقِها محرفاً عادياً». والقيمة الافتراضية، false، هي التي تحذفها. فالاسم يصف ما يتجاهله المفكِّك، لا ما تحصل عليه أنت، وقراءته على وجهها الطبيعي تعطيك مفكِّكاً يحفظ بالضبط البايت الذي كنت تحاول حذفه.
لماذا تعمل في المتصفّح وتنكسر في Node
هذه أكثر صور المشكلة تكراراً في البلاغات: عنوان JSON نفسه يُحلَّل بلا مشكلة في كود الواجهة الأمامية، ثم يُلقي خطأً في اللحظة التي يقرأ فيها سكربت Node الملف من القرص. ولم يتغيّر شيء في الملف. فـres.json() يفكّ الترميز عبر الآلية نفسها التي يستخدمها TextDecoder ويُسقط علامة BOM في الطريق؛ أما fs.readFileSync(path, 'utf8') فهو فكّ ترميز أمين يسلّمك كل محرف يحتويه الملف، بما فيه U+FEFF.
والتفاوت نفسه يفسّر لماذا يعمل require('./config.json') بينما لا يعمل JSON.parse(fs.readFileSync('./config.json', 'utf8')). فمُحمِّل وحدات JSON في Node يحذف علامة BOM؛ والمسار اليدوي لا يفعل.
حذفها
const fs = require('fs');
const raw = fs.readFileSync('data.json', 'utf8');
const data = JSON.parse(raw.replace(/^/, ''));
وثبّت النمط بـ^. فالاستبدال الشامل غير المثبَّت سيحذف أيضاً محارف U+FEFF المشروعة من داخل قيم السلاسل، وهذا إتلاف للبيانات.
وثمّة بديل أهدأ يمرّ بالمصادفة: JSON.parse(raw.trim()) ينجح هو أيضاً، لأن ECMAScript يصنّف U+FEFF مسافةً بيضاء، وString.prototype.trim يحذفها. وهذا سلوك حقيقي جرى التحقّق منه، لكنه مصادفة في مواصفة JavaScript ولا ينتقل إلى لغات أخرى. فـstr.strip() في Python يترك U+FEFF في المكان الذي وجده فيه بالضبط.
وإن أردت أن تتأكّد من أن الناتج بعد الحذف صالح فعلاً لا مجرّد خالٍ من الأخطاء، فالصقه في منسّق JSON والمدقّق. وبعد زوال علامة BOM، لا يبقى من مرشّحي الموضع 0 إلا مشكلات التهريب العادية التي يغطّيها دليل تهريب سلاسل JSON.
5. إصلاحها في Python: utf-8-sig
Python هي بيئة التشغيل الوحيدة التي تسمّي المشكلة في رسالة الخطأ. افتح ملفاً مسبوقاً بعلامة BOM على أنه UTF-8 عادي، وسيخبرك json بالجواب والإصلاح في نفَس واحد:
JSONDecodeError: Unexpected UTF-8 BOM (decode using utf-8-sig): line 1 column 1 (char 0)
فإن كنت قد بحثت عن unexpected utf-8 bom ووصلت إلى هنا، فمن هذه السلسلة جاءت عبارتك. والمرمِّز الذي تشير إليه يقرأ علامة BOM بوصفها توقيعاً ثم يتخلّص منها:
import json
with open('data.json', encoding='utf-8-sig') as f:
data = json.load(f)
وutf-8-sig آمن على الملفات التي لا تحمل علامة BOM. يحذف واحدة إن وُجدت، ويتصرّف كـUTF-8 عادي فيما عدا ذلك، وهو ما يجعله الافتراضي الصحيح لأي ملف لم تُنتجه بنفسك.
البايتات والنصّ يتصرّفان تصرّفين مختلفين
الفرق بين تسليم بايتات وتسليم نصّ يجعل العطل يبدو متقطّعاً:
import json
json.loads(open('data.json', 'rb').read()) # {'a': 1} يعمل
json.loads(open('data.json', encoding='utf-8').read()) # يُلقي الخطأ أعلاه
فـjson.loads حين يتلقّى bytes يشغّل خطوة كشف ترميز أولاً، فيلمح علامة BOM، ويفكّ الترميز بـutf-8-sig نيابةً عنك. أما إن أعطيته str مفكوك الترميز أصلاً، فلم يبقَ شيء ليُكشَف، فيصل U+FEFF إلى المحلّل. مساران يبدوان متكافئين، وأحدهما يعالج الحالة بصمت.
كتابة علامة BOM عن قصد
والمرمِّز نفسه يعمل في الاتجاه المعاكس، وهكذا تُنتج ملفاً مخصّصاً لـExcel:
with open('report.csv', 'w', encoding='utf-8-sig', newline='') as f:
f.write('name\n')
وهذا الملف يبدأ بـef bb bf. والقسم 7 يتناول متى تريده أن يبدأ هكذا.
فخّ CSV
csv.DictReader على نصّ مسبوق بعلامة BOM يفعل بالضبط ما ينبغي لمحلّل CSV صحيح أن يفعله، فيُنتج مفتاحاً لا يستطيع أحد مطابقته:
import csv, io
data = 'name,age\nAlice,30\n'
print(list(next(csv.DictReader(io.StringIO(data))).keys()))
# ['name', 'age']
عمودك الأول ليس name. إنه U+FEFF متبوعاً بـname، وكل بحث بـrow['name'] يُلقي KeyError بينما يُطبَع العنوان صحيحاً في كل مُنقّح تملكه. وفتح الملف بـencoding='utf-8-sig' يحذفها قبل أن يراها القارئ أصلاً.
6. إزالة علامة BOM في Java وGo وPHP والصدفة
كل إصلاح هو الإصلاح نفسه على مستوى مختلف: احذف ثلاثة بايتات (EF BB BF) أو احذف محرفاً واحداً (U+FEFF)، بحسب ما إن كنت تمسك بايتات أم نصاً. وإن لم يكن في لغتك مرمِّز يعي علامة BOM، فافعلها يدوياً.
Java تفكّ ترميز علامة BOM إلى محرف في المقدّمة:
String text = Files.readString(path, StandardCharsets.UTF_8);
if (!text.isEmpty() && text.charAt(0) == '') {
text = text.substring(1);
}
Go، بالعمل على مستوى البايتات قبل فكّ التسلسل:
raw, err := os.ReadFile("data.json")
if err != nil {
return err
}
raw = bytes.TrimPrefix(raw, []byte{0xEF, 0xBB, 0xBF})
var v map[string]any
err = json.Unmarshal(raw, &v)
PHP، بنمط مثبَّت على البايتات:
$raw = file_get_contents('data.json');
$raw = preg_replace('/^\xEF\xBB\xBF/', '', $raw);
$data = json_decode($raw, true);
ولإزالة علامة BOM من ملف بدل متغيّر، أربعة أوامر جرى التحقّق منها جميعاً على ملف يبدأ بـef bb bf:
# في الموضع، مع sed من GNU (لينكس). الصدفة هي التي توسّع التهريبات، لا sed.
sed -i $'1s/^\xEF\xBB\xBF//' data.json
# في الموضع، مع sed من BSD (ماك)
sed -i '' $'1s/^\xEF\xBB\xBF//' data.json
# في الموضع، أينما وُجد Perl. السطر الأول فقط.
perl -i -pe 's/^\x{ef}\x{bb}\x{bf}// if $. == 1' data.json
# نسخ بلا البايتات الثلاثة الأولى. آمن فقط إن كنت تعلم أن العلامة موجودة.
tail -c +4 data.json > clean.json
وأمر tail هو الأخشن بينها: يحذف ثلاثة بايتات سواء أكانت تلك البايتات علامة BOM أم لا. فتأكّد أولاً بالقسم 2.
7. استثناء CSV: حين يحتاج Excel إلى بقاء علامة BOM
كل ما سبق يعامل علامة BOM على أنها ضرر. وفي موضع واحد تؤدّي وظيفة حقيقية، وحذفها يكسر ملفاً يعمل.
وتنقسم عمليات البحث عن csv bom excel إلى شكويين متعاكستين، وهذه إشارة جيّدة إلى أن قاعدة واحدة تُطبَّق في الاتجاه الخاطئ:
- «ملف CSV عندي يُفتح في Excel فأرى
éوæ¥æ¬èªبدل المحارف الحقيقية.» العلامة مفقودة. - «عمودي الأول اسمه
nameوسكربتي لا يعثر عليه.» العلامة موجودة.
لماذا يريدها Excel
لا يملك Excel على Windows طريقة موثوقة ليعرف أن ملف CSV مرمَّز بـUTF-8. لا ترويسة، ولا تصريح، ولا بيانات وصفية: ملف .csv بايتات وحسب. وبلا إشارة يرتدّ إلى محليّة النظام، أي Windows-1252 في الولايات المتحدة وأوروبا الغربية، وWindows-1251 في روسيا، فيخرج كل محرف خارج ASCII خاطئاً. وعلامة BOM هي تلك الإشارة. ثلاثة بايتات في المقدّمة، فيقرأ Excel ترميز UTF-8 قراءةً صحيحة.
فعلامة BOM في CSV ميزة، والقرار المترتّب عليها يسع سطراً واحداً:
إن كُتب الملف ليحلّله حاسوب، فاحذف علامة BOM. وإن كُتب ليفتحه إنسان بنقرة مزدوجة في Excel، فأبقِها.
العطل على الطرف الآخر
أطعِم المحلّل الملف نفسه، فتندمج علامة BOM في خليّة العنوان الأولى. في Node:
const header = 'name,age'.split(',');
console.log(JSON.stringify(header)); // ["name","age"]
const row = { 'name': 'Alice', age: 30 };
console.log(row.name); // undefined
فـrow.name قيمته undefined بينما يُطبَع المفتاح name في سجلّاتك ومُنقّحك وconsole.table لديك. وهو العطل ذاته الذي ظهر بصيغة KeyError في Python في القسم 5، ولهذا تستحقّ عبارة «اسم الحقل مطابق لكن القيمة مفقودة» أن تُعامَل عرضاً لعلامة BOM من أول نظرة.
ومحوّلاتنا تعالج الطرفين عن قصد. فـمحوّل CSV إلى JSON يحذف علامة BOM من مقدّمة المُدخل قبل التحليل، فيعطيك الملفُ الخارج من Excel مباشرةً المفتاح name لا name. وفي الاتجاه المعاكس، يجعل محوّل JSON إلى CSV العلامة مفتاح تشغيل صريحاً، وضبطه المسبق لـExcel يشغّلها إلى جانب الفاصلة المنقوطة ونهايات أسطر CRLF، وهي التوليفة التي تحتاجها محليّات Excel الأوروبية فعلاً. وللمجموعة الأوسع من قرارات التحويل حول الفواصل وعلامات الاقتباس واستنتاج الأنواع، يقدّم دليل التحويل بين CSV وJSON الشرح الكامل.
8. أبعد من JSON: أين تظهر علامة BOM أيضاً
JSON يصرخ بها. وغيره من الصيغ لا يفعل.
سكربتات الصدفة. تجلس علامة BOM بين بداية الملف و#!، فلا ترى النواة سطر shebang ولا تشغّل مفسّرك. وعلى macOS كانت النتيجة المقاسة أن الصدفة ارتدّت إلى sh وأبلغت عن سطر shebang بوصفه ملفاً مفقوداً:
./bom.sh: line 1: #!/bin/sh: No such file or directory
ثم شُغِّل السكربت رغم ذلك تحت المفسّر الخاطئ، وهذا أسوأ من الفشل. وأنظمة أخرى تصوغها صياغةً مختلفة، أشهرها خطأ bad interpreter. فإن أصرّ سكربت يبدأ بسطر #!/usr/bin/env python3 صحيح تماماً على أن هذا المسار غير موجود، فافحص البايتات.
PHP. كل ما هو خارج <?php ... ?> مخرجات، وعلامة BOM قبل الوسم الافتتاحي هي ثلاثة بايتات من المخرجات تُرسَل قبل أن يعمل كودك. وعندئذ يفشل أول نداء لـheader() أو session_start() أو setcookie() بتحذير headers already sent الكلاسيكي، مشيراً إلى السطر 1 من ملف يبدو سطره الأول فارغاً.
ملفات .env وأي صيغة مفتاح-قيمة. الآلية مطابقة لحالة CSV: متغيّرك الأول ليس DATABASE_URL، بل U+FEFF متبوعاً بـDATABASE_URL، فيُخطئ البحث بينما يقرأ الإنسان الملف قراءةً صحيحة. وكل متغيّر بعده يعمل، وهذا يجعل الأمر يبدو مشكلةً في إعداد بعينه.
وXML هو الاستثناء في الاتجاه الآخر. تسمح مواصفة XML صراحةً بعلامة BOM في UTF-8 عند بداية المستند بوصفها جزءاً من الكشف التلقائي للترميز، والمحلّلات مُلزَمة بالتعامل معها. وقد قبِل xml.etree.ElementTree في Python مستنداً مسبوقاً بالعلامة دون اعتراض أثناء الاختبار. فإن كان XML يفشل، فالأرجح أن علامة BOM ليست السبب.
9. أوقفها عند المصدر
الإزالة اليدوية تصمد حتى يعيد محرّر مضبوط على الخطأ حفظ الملف، أو تعيد خطوة البناء توليده. وهذه الإعدادات تقطع الطريق على عودة علامة BOM.
ثبّت الترميز في .editorconfig. تقبل الخاصية charset القيمتين utf-8 وutf-8-bom قيمتين منفصلتين، فيكون النصّ على ما تريده بلا لبس:
[*]
charset = utf-8
افحص إعداد المحرّر الذي يتجاوزه. في VS Code هو "files.encoding": "utf8"، والقيمة التي تبحث عنها هي utf8bom. وافحص ملف .vscode/settings.json الخاص بمساحة العمل إلى جانب إعداداتك الشخصية، لأن إعداد مساحة عمل مودَعاً في المستودع ينطبق بصمت على كل من في الفريق.
افحص في CI أو في خطّاف ما قبل الإيداع. هذا محمول، وبلا اعتماديات، ويخرج بقيمة غير صفرية حين يجد شيئاً:
#!/bin/sh
# يفشل إن بدأ أي ملف متتبَّع بالبايتات EF BB BF
found=0
for f in $(git ls-files '*.json' '*.md' '*.sh'); do
if [ "$(head -c3 "$f" | od -An -tx1 | tr -d '[:space:]')" = "efbbbf" ]; then
echo "BOM: $f"
found=1
fi
done
exit $found
وقد جرى التحقّق منه في الاتجاهين: يسرد المسارات المخالفة ويخرج بالقيمة 1 حين يكون ملف مسبوق بالعلامة متتبَّعاً، ويخرج بالقيمة 0 متى نظفت الملفات.
دوّن الاستثناء الوحيد المسموح به. قاعدة «لا علامة BOM في أي مكان» تنكسر أول مرّة يحتاج فيها أحدهم إلى تصدير جدول بيانات، ثم يُهمَل العمل بها عموماً. فانصّ على الاستثناء بدل ذلك: العلامة مسموح بها في ملفات CSV المولَّدة لـExcel، ولا مكان سواه. استثنِ دليل التصدير من الفاحص، فتنجو القاعدة من الاحتكاك بالواقع.
10. مسار تنصيف في ستّين ثانية
نفّذها بالترتيب. فكل خطوة إما تُنهي التحقيق وإما تسلّم التالية مشكلةً أصغر.
- اقرأ المحرف، لا الموضع. القسم 1. فـ
<تعني HTML وقد انتهى شأنك هنا. وألّا يكون بين العلامتين شيء يعني جسم ردّ فارغاً. ومربّع غير مقروء يعني أن تُكمل. - أكّد البايتات.
hexdump -C file | head -1. فإن لم تكن البايتات الثلاثة الأولىef bb bf، فتوقّف: هذه ليست علامة BOM ولن ينفعك شيء مما يلي. - اعثر على نقطة الدخول. هل الملف مسبوق بالعلامة على القرص، أم أنه نظيف على القرص ومسبوق بها حين يصل إلى كودك؟ فملف نظيف على القرص يعني أن شيئاً في خطّ معالجتك يضيفها.
- اختر طرفاً واحداً لإصلاحه. احذفها عند القراءة حين يكون المنتِج مورّداً أو رفعاً من مستخدم أو خطوة بناء لا تملكها. وأصلح المنتِج حين يكون ملكك، لأن إصلاح جهة القراءة يجب أن يتكرّر عند كل قارئ.
- طبّق الإصلاح عند حدّ فكّ الترميز، لا أعمق.
encoding='utf-8-sig'عند نداءopen()، لا.lstrip()على سلسلة بعد ثلاث دوالّ. فإصلاحها في عمق المكدّس يعني أن المسار البرمجي التالي الذي يقرأ الملف سيعيد اكتشاف العطل من جديد. - تحقّق من أن البايتات تغيّرت. أعِد تنفيذ الخطوة 2. فالإصلاح الذي ينجح في مسار برمجي واحد ويترك الملف كما هو سيفشل في المسار التالي.
- أضف الفاحص. القسم 9. وإلا أعدت هذا كلّه في الربع القادم.
الأسئلة الشائعة
هل علامة BOM في UTF-8 مطلوبة؟
لا. فلـUTF-8 ترتيب بايتات واحد، فلا شيء تفصل فيه العلامة. ويسمح Unicode بها بوصفها توقيع ترميز لكنه لا يوصي بها، وJSON يحرّمها صراحةً: ينصّ RFC 8259 على أن التطبيقات يجب ألّا تضيف علامة ترتيب بايتات إلى نصّ JSON.
لماذا يبدو الملف سليماً في محرّري لكنه يفشل في التحليل؟
لأن U+FEFF لا يُرسَم شيئاً على الإطلاق. فالمحرّرات التي تتعرّف عليه تُخفي المحرف وتذكر UTF-8 with BOM في شريط الحالة بدلاً منه. والمحرّرات التي لا تتعرّف عليه ترسم صفر بكسل وحسب. وcat وless وفروق مراجعة الكود تبدو كلها متطابقة أيضاً. ولا يكشفه إلا عرض على مستوى البايتات.
هل يحذف JSON.parse علامة BOM تلقائياً في أي حال؟
أبداً. يأخذ JSON.parse سلسلةً ويعامل U+FEFF محرفاً غير متوقّع أينما ظهر. والذي يحذفها هي الطبقة الأعلى: res.json() بعد fetch، وrequire() في Node لملفات .json، وTextDecoder بإعداداته الافتراضية، كلها تزيلها قبل أن يرى المحلّل شيئاً.
هل ينبغي أن أزيل علامة BOM من ملفات CSV؟
يعتمد على من يفتح الملف. فأي محلّل سيطوي العلامة داخل اسم عمودك الأول، فيصير name هو name ويُخطئ كل بحث. احذفها هناك. أما Excel على Windows فيستخدم العلامة للكشف عن UTF-8 ويشوّه المحارف ذات علامات النطق ومحارف CJK بدونها، فأبقِها هناك.
هل علامة BOM هي نفسها المسافة صفرية العرض؟
نقطة الترميز نفسها، والوظيفة مختلفة. فـU+FEFF عند الإزاحة 0 علامة ترتيب بايتات. وفي أي موضع آخر من المستند هو ZERO WIDTH NO-BREAK SPACE، وهو استعمال أهمله Unicode لصالح U+2060 WORD JOINER. والنصوص القديمة ما زالت تحتويه، ولهذا يظهر U+FEFF في وسط الملفات.
هل تؤثّر علامة BOM في فروق git وحجم الملف؟
ثلاثة بايتات على القرص، وسطر مزعج واحد في كل فرق يمسّه. فـGit يقارن البايتات، لذا فإن إضافة العلامة أو إزالتها تعيد كتابة السطر 1 حتى حين يكون النصّ المعروض متطابقاً. وهذا مصدر ذلك التغيير من سطر واحد الذي لا يستطيع أحد في المراجعة تفسيره.