מניפסט
מטרת מסמך זה היא לקדם את השימוש והתמיכה בקידוד UTF-8, ולשכנע שזו צריכה להיות ברירת המחדל לקידוד מחרוזות בזיכרון או על דיסק, בתקשורת וכל שאר השימושים. אנו מאמינים כי גישה זו משפרת ביצועים, מפשטת את התוכנה ומונעת באגים הקשורים ל-Unicode. אנו טוענים כי קידודים אחרים של Unicode (או טקסט בכלל) שייכים למקרי קצה נדירים של אופטימיזציה ומשתמשים רגילים צריכים להמנע מהם.
בפרט אנו מאמינים כי לקידוד UTF-16 הפופולרי (שלעיתים קרובות מכונה בשגוי ’widechar‘ או סתם ’Unicode‘ בעולם ה-Windows) אין מקום בממשקי הספריות מלבד ספריות המתמחות בעיבוד טקסט, כדוגמת ה-ICU.
כמו כן, מסמך זה ממליץ לבחור ב-UTF-8 לייצוג פנימי של מחרוזות ביישומי Windows, על אף שתקן זה פחות נפוץ שם, בשל סיבות היסטוריות וגם בשל העדר תמיכה מובנית ב-UTF-8 במערכת. אנו מאמינים כי גם בפלטפורמה זו הטיעונים הבאים עולים על העדר תמיכה מובנית. כמו כן, אנו ממליצים לשכוח לעד מה הם ’קידודי ANSI‘ ולמה שימשו. זוהי זכותו המלאה של המשתמש לשלב כל מספר של שפות בכל מחרוזת טקסט.
בתעשיית התוכנה, על באגים רבים הקשורים בלוקליזציה, מאשימים את אי בקיאות המתכנתים ב-Unicode. לעומת זאת, לדעתנו, ליישום שלא מתמקד בטקסט, התשתיות יכולות וצריכות לאפשר לתוכנה להתעלם מסוגיות הקידוד. לדוגמה, כלי להעתקת קבצים לא צריך להיכתב שונה על מנת לתמוך בשמות קבצים שלא באנגלית. בנוסף, במניפסט זה נסביר מה המתכנת צריך לעשות כדי לא להתעמק בכל מורכבויות Unicode ואם לא באמת אכפת לו מה יש בתוך המחרוזת.
יתרה מזאת, אנו טוענים כי ברוב תרחישי עיבוד טקסט אין לראות בספירה או איטרציה על נקודות קוד של Unicode משימה חשובה במיוחד. באופן מוטעה, מפתחים רבים רואים בנקודות קוד הכללה לתווי ASCII. זה מוביל להחלטות עיצוב תוכנה סגנון גישה בזמן O(1) לנקודות קוד בפייתון. עם זאת, האמת היא ש-Unicode מטבעו מורכב יותר, ואין בו מושג כללי של תו Unicode. אנו לא רואים סיבה מיוחדת להעדיף נקודות קוד של Unicode על פני מקבצי גרפמות של Unicode, יחידות קוד, או אולי אפילו מילים בשפה לשם כך. מצד שני, לראות ביחידות קוד של UTF-8 (בתים) יחידות בסיסיות של טקסט, הינו שימושי במיוחד למשימות רבות, כגון ניתוח פורמטים טקסטואלים נפוצים. וזאת בזכות תכונות מסויימות של קידוד זה. גרפמות, יחידות קוד, נקודות קוד ומונחי Unicode רלוונטיים אחרים מוסברים בחלק 5. בנוגע לפעולות על מחרוזות טקסט מקודדות אנו דנים בחלק 7.
בשנת 1988, פרסם Joseph D. Becker את הטיוטה הראשונה של הצעת Unicode. התיכנון שלו התבסס על ההנחה הנאיבית ש-16 סיביות לתו תספיקנה. ב-1991 פורסמה הגרסה הראשונה של תקן ה-Unicode, עם נקודות קוד מוגבלות ל-16 סיביות. בשנים הבאות מערכות רבות הוסיפו תמיכה ב-Unicode ועברו לקידוד UCS-2. דרך זו נראתה מפתה במיוחד עבור טכנולוגיות חדשות, כמו Qt framework מ-1992, Windows NT 3.1 מ-1993, ו-Java מ-1995.
במהרה התגלה כי 16 סיביות לתו לא תספיקנה ל-Unicode. בשנת 1996 נוצר קידוד UTF-16, על מנת שמערכות קיימות יוכלו לעבוד עם תווים מעבר ל-16 סיביות. חידוש זה למעשה ביטל את הרציונל מאחורי בחירת קידוד 16 ביטים, כקידוד בגודל קבוע. כיום Unicode משתרע על פני 109,449 תווים, כאשר בערך 74,500 מהם אידיוגרפים של CJK.
לעיתים קרובות מייקרוסופט השתמשה באופן מוטעה, ב Unicode ו Widechar כמילים נרדפות ל 2 USC- ול- UTF-16. יתרה מכך, מכיוון ש UTF-8 לא יכול להיות מוגדר כקידוד של גרסאת Narrow WinAPI, חייבים לקמפל את הקוד הקיים עם הגדרות קבוע Unicode
. מלמדים את מתכנתי ה ++Windows C שה Unicode חייב להעשות עם Widechars (או גרוע מכך: TCHAR שמשמעותו תלויה בהגדרת קומפילציה - המאפשר למתכנת שלא לתמוך בכל התווך של Unicode). כתוצאה מכך, כיום קיים בלבול רב אצל מתכנתי Windows לגבי מהו הדבר הנכון לעשות לגביי טקסט.
יחד עם זאת, בעולמות ה Linux וה- Web, קיימת הסכמה שקטה כי UTF-8 הוא הקידוד המוצלח ביותר ל Unicode. אף על פי שהוא מספק ייצוג קצר יותר לאנגלית ובשל כך לשפות המחשב (כגון C++, HTML, XML, ועוד...) מעל לכל טקסט אחר, הוא לעיתים רחוקות פחות יעיל מאשר ה UTF-16 גם לשפות אחרות.
MAX_PATH
והנתיב ארוך במיוחד (3) יכולת "יצירת שם קצר" מבוטלת בהגדרות מערכת ההפעלה.std::exception::what()
מבלי להשתמש ב UTF-8. כמו כן, אין דרך לתמוך ב Unicode ל- localeconv
אחרת מאשר להשתמש ב- UTF-8.אם נסתכל על תוכנית שמעתיקה קבצים; בעולם ה- UNIX, מחרוזות צרות (Narrow string) נחשבות ל UTF-8 כברירת מחדל כמעט בכל מקום. בשל עובדה זאת, היוצר של שרותי העתקת קובץ לא יצטרך לדאוג לגביי Unicode. קוד שייבדק על מחרוזות ASCII לפרמטר שם קובץ, יעבוד באופן תקין לקבצי שם בכל שפה, הרי הוא מתייחס לפרמטרים כאל Cookies. הקוד של שרותי העתקה של הקובץ לא יצטרך להשתנות כלל על מנת לתמוך בשפות זרות. ()fopen
יקבל Unicode באופן שקוף, וכך גם argv
.
ניתן לעשות זאת במייקרוסופט, Windows שהיא ארכיטקטורה מבוססת על UTF-16, באופן הבא: כדי ליצור שרות העתקת קובץ שיכול לקבל שמות קבצים מתוך ערבוב של בלוקים (שפות) Unicode שונות, נשתמש בתכסיסים משוכללים. ראשית, האפליקציה חייבת להיות מקומפלת כ Unicode - aware. במקרה זה, לא יכולה להיות לה פונקציית ()main
עם פרמטרים סטנדרטיים של C. באופן זה היא תאפשר argv
מקודד ב UTF-16. על מנת להמיר תוכנות Windows הכתובות בהנחה של טקסט צר לתמוך ב Unicode, יש לעשות רפקטורינג עמוק, ולטפל במשתנה של כל מחרוזת בנפרד.
הסיפריות הסטנדרטיות שנשלחות עם MSVC מיושמות באופן גרוע בהתייחס לתמיכת Unicode. זאת בין היתר משום שהן מעבירות פרמטרים צרי- מחרוזת ישירות ל OS ANSI API ולא קיימת דרך לעקוף את זה. שינוי של std::locale
לא עובד. בלתי אפשרי לפתוח קובץ בעל שם Unicode על MSVC על ידי שימוש בכלים סטנדרטיים של ++C. הדרך הסטנדרטית לפתוח קובץ הוא:
std::fstream fout("abc.txt");
אך זה לא יעבוד נכון. הדרך המוצעת לעקוף זאת היא ע"י שימוש ב Hack תוצרת עצמית של מייקרוסופט, שמאפשר פרמטרים של מחרוזות רחבות. זאת לא הרחבה סטנדרטית.
ב Windows, מפתח רג'יסטרי HKLM\SYSTEM\CurrentControlSet\Control\Nls\CodePage\ACP
מאפשר קבלת תווים שאינם מסוג ASCII ב- Narrow API, אבל רק codepage ANSI יחיד. ערך לא ממומש של 65,001 ככל הנראה היה פותר את נושא ה Cookies, ב Windows. אם מייקרוסופט תתמוך בערך ACP זה, זה יעזור באימוץ רחב יותר של UTF-8 על בפלטפורמת Windows.
למתכנתי Windows וספקי סיפריות רב שכבתיות, אנו מעמיקים בדיון לגביי גישתנו בטיפול מחרוזות טקסט ושיפור תוכנות לתמיכת Unicode טובה יותר, בפרק "איך לעשות טקסט ב Windows" .
להלן חלק מההגדרות שמתייחסות לתווים, Code Points, יחידות קידוד ו Grapheme Clusters לפי הסטנדרט של ה Unicode, עם ההערות שלנו. אנו מעודדים אתכם לפנות לפרקים רלוונטיים בסטנדרט להרחבה נוספת.
f0 b2 90 bf
’ ב UTF-8, שתי יחידות קידוד ‘d889 dc3f
’ ב UTF-16 ויחידת קידוד אחת ‘0003243f
’ ב UTF-32. שימו לב, אלו רק רצפים של קבוצות של ביטים; איך הם מאוחסנים כרצף בתים תלוי בסדר הבתים של קידוד ספציפי. כאשר מאחסנים את יחידות הקידוד של UTF-16 שמוזכר לעיל, הם יומרו ל ‘d8 89 dc 3f
’ ב UTF-16BE ול ‘89 d8 3f dc
’ ב UTF-16LE.יחידת מידע המשמשת ארגון, בקרה, או ייצוג של נתוני טקסט. [§3.4, D7] בהמשך, הסטנדרט מציין ב§3.1.:
"בשביל התקן של ה Unicode, [...] הרפרטואר פתוח מטבעו. בגלל שה Unicode הוא קידוד אוניברסלי, כל תו מופשט שאי פעם היה יכול להיות מקודד, הוא מועמד פוטנציאלי להיות מקודד, ללא קשר להאם התו מוכר כרגע."
ההגדרה בהחלט מופשטת. כל מה שניתן לחשוב עליו כעל תו- הוא התו המופשט. לדוגמא: tengwar letter ungwe הוא תו מופשט למרות שהוא עדיין לא ניתן לייצוג ב Unicode.
מיפוי בין Code Points ותו מופשט. [§3.4, D11] לדוגמא, U+1F428 הוא תו מקודד אשר מייצג את התו המופשט: 🐨 koala.
מיפוי זה אינו מלא, חד-חד ערכי ולא "על":
מעבר לכך, לחלק מהתווים המופשטים, קיים ייצוג ע"י Code Points מרובים, בנוסף לייצוג של התו המקודד הבודד. התו המופשט ǵ יכול להיות מקודד ע"י נקודת קוד בודדה: U+01F5 Latin small letter g with acute או ע"י הרצף: <U+0067 latin small letter g, U+0301 combining acute accent>.
המילה "תו" יכולה להתייחס לכל אחד מהסעיפים לעיל. תקן ה Unicode משתמש ב"תו" כשם נרדף ל"תו מקודד". [§3.4] כאשר שפת התכנות או תיעוד של ספרייה מתייחסת ל"תו", היא בדרך כלל מתכוונת ליחידת קוד. כאשר המשתמש הסופי נשאל לגבי מספר התווים במחרוזת, הוא יספור את "תפיסת המשתמש" כתווים. מתכנת עלול לספור תווים כיחידות קידוד, Code Points, או Grapheme Clusters לפי מידת התמקצעותו ב Unicode. לדוגמא, ראה איך טוויטר סופר תווים. לדעתנו, פונקציית האורך של מחרוזת לא בהכרח אמורה להחזיר 1 בשביל המחרוזת ‘🐨’ על מנת שתחשב כתואמת Unicode.
רוב נקודות הקוד Unicode משתמשות באותו מספר של בתים ב UTF-8 וב UTF-16. זה כולל עברית, רוסית, יוונית וכל היחידות קידוד שהן לא ב BMP שמשתמשות ב2 או 4 בתים בשני הקידודים. אותיות לטיניות, יחד עם סימני פיסוק ושאר ה ASCII דורשים יותר מקום ב UTF-16, בעוד שתווים אסיאתיים מסויימים דורשים יותר מקום ב UTF-8. האם לא יכלו המתכנתים האסיאתיים, באופן תאורטי, להתנגד להשלכת UTF-16, אשר חוסך להם 50% זיכרון לתו?
המציאות היא כזאת: חיסכון של מחצית מהמקום בזיכרון מתקיים רק בדוגמאות המורכבות באופן מלאכותי המכילות רק תווים בטווח ה U+0800 עד U+FFFF . עם זאת, ממשקי טקסט מחשב-למחשב הם הדומיננטיים בהשוואה לכל שימושים האחרים של טקסט. זה כולל XML ,HTTP filesystem paths וקבצי קונפיגורציה – כל אלו, באופן כמעט מוחלט, משתמשים בתווי ASCII, ולמעשה UTF-8 מאוד פופולארי בארצות אסיאתיות רלוונטיות.
עבור אחסון ייעודי של ספרים סיניים, עדיין ניתן להשתמש ב UTF-16 כאופטימיזציה הגיונית. ברגע שהטקסט נשלף מאחסון כזה, הוא אמור להיות מומר לסטנדרט שעומד בקנה מידה אחד עם שאר העולם. בכל מקרה, אם האחסון חשוב כל כך, עדיף להשתמש בדחיסה. במקרים כאלו, UTF-8 ו- UTF-16 יצרכו בערך את אותה כמות של מקום. מעבר לכך: "בשפות אלה, ה Glyph מכיל יותר אינפורמציה מאשר תו לטיני כך שזה מוצדק שהוא ידרוש יותר מקום". ( UTF-16 נחשב כמזיק , Tronic).
מוצגות לפניכם תוצאות מניסוי פשוט. בעמודה הראשונה מוצג המקום שתופס מקור HTML של דף אינטרנט מסויים (מאמר "יפן" ששוחזר מוויקיפדיה יפנית ב 01-01-2012). העמודה השניה מראה את התוצאות של טקסט שה Markup שלו הוסר, כלומר "בחר הכל, העתק, הדבק לתוך קובץ טקסט ריק".
HTML Source (Δ UTF-8) | Dense text (Δ UTF-8) | |
---|---|---|
UTF-8 | 767 KB (0%) | 222 KB (0%) |
UTF-16 | 1 186 KB (+55%) | 176 KB (−21%) |
UTF-8 zipped | 179 KB (−77%) | 83 KB (−63%) |
UTF-16LE zipped | 192 KB (−75%) | 76 KB (−66%) |
UTF-16BE zipped | 194 KB (−75%) | 77 KB (−65%) |
ניתן לראות כי UTF-16 צורך בערך 50% יותר מקום מאשר UTF-8 בנתונים אמיתיים, חוסך רק 20% יותר לטקסט אסיאתי דחוס, ובקושי עומד בתחרות עם אלגוריתמי דחיסה למטרות כלליות. התרגום הסיני של מסמך זה שנתרם לנו ע"י קבוצת מתנדבים, תופס 58.8KB ב UTF-16 ורק 51.7KB ב UTF-8.
הפורמטים הפופולאריים מבוססי טקסט (כמו: CSV ,XML ,HTML ,JSON ,RTF וקוד מקור של תוכנות מחשב) לעתים קרובות מכילים תווי ASCII כאלמנטי בקרת מבנה, ויכולים להכיל גם מחרוזות מתווים של ASCII ותווים שאינם ASCII. עבודה עם קידודים באורך משתנה, היכן תווי ASCII קצרים יותר מ Code Points אחרות, עלולה להראות כמשימה קשה במיוחד, מכיוון שהגבולות של התווים המקודדים בתוך המחרוזת לא ידועים מייד. זה דחף את מהנדסי התוכנה לבחור בקידוד קבוע רוחב USC-4 (לדוגמא Python v3.3). למעשה, זה לא הכרחי ולא פותר אף בעיה אמיתית שאנחנו מכירים.
על פי הדיזיין של קידוד זה, UTF-8 מבטיח שערך תווים או תתי-מחרוזת ASCII לעולם לא יתאימו לחלק של תווי מקודד שתופס מספר בתים. אותו הדבר נכון לגביי ה UTF-16. בשני הקידודים, בכל יחידות הקוד של Code Points המקודדות למספר חלקים, הסיבית המשמעותית ביותר (MSB) תהיה שווה ל-1.
כדי למצוא, למשל, סימן "<" המגדיר את התחלתו של תג HTML, או גרש (') במשפט SQL מקודד UTF-8, כדי להגן מפני SQL injection עשו כפי שהייתם עושים לכל מחרוזת ASCII רגילה שכולה אנגלית. הקידוד מבטיח שזה יעבוד. כלומר, כל תו שהוא לא ASCII יקודד ב UTF-8 כרצף של בתים, שלכל אחד ערך גבוה מ 127. זה אינו משאיר מקום להתנגשות באלגוריתם נאיבי - פשוט, מהיר, אלגנטי ומבטל את הצורך לדאוג לגביי גבולות של תווים מקודדים.
כמו כן, הנך יכול לחפש תת מחרוזת שאינה ASCII מקודדת ב UTF-8 כאילו היא מערך ביתים פשוט – אין צורך להתייחס לגבולות Code Points. זה הודות לתכנון רכיב אחר של UTF-8 – בית מוביל של נקודת קוד מוצפנת לעולם לא יוכל להיות בעל ערך זהה לאחד מהבתים המסיימים של אף Code point אחרת.
כפי שהוזכר קודם לכן, קיימת מחשבה נפוצה על פיה ספירה, פיצול, אופרציות אינדקס או חזרה על Code Points במחרוזת Unicode צריכים להחשב כפעולה חשובה ותכופה. בחלק זה אנו נבחן זאת לעומק.
זוהי טעות נפוצה אצל אלו המאמינים כי UTF-16 היא קידוד קבועת רוחב. הוא לא. למעשה, UTF-16 הוא קידוד עם אורך משתנה. אם אתה מתכחש לקיום של תווים מחוץ ל BMP, ראה סעיף זה ב "שאלות נפוצות".
זה תלוי במשמעות המילה "תו", שלרוב טועים במשמעותה. זה נכון שאפשר לספור יחידות קידוד ו Code Points בזמן קבוע ב UTF-32. עם זאת, Code Points אינן קשורות לאיך שתו נתפס ע"י המשתמש. אפילו בהקשר של ה Unicode , חלק מנקודות הקוד קשורות לתו מקודד וחלק ל"לא –תווים" .
אנו מאמינים כי החשיבות שמייחסים ל Code Points, לעיתים קרובות מוגזמת. זה קורה הודות לחוסר הבנה נפוצה לגביי המורכבות של ה Unicode, שבסך הכל משקף את המורכבות של השפות האנושיות. אין זאת בעיה לציין כמה תווים יש במילה "Abracadabra", אך בואו נחזור למחרוזת הבאה: Приве́т नमस्ते שָׁלוֹם, היא מורכבת מ-22 (!) Code Points, אך רק 16 Grapheme Clusters. ניתן להקטין את מספר נקודות הקוד ל-20 אם תומר ל NFC. עם זאת, מספר נקודות הקוד בתוכה אינו רלוונטי לרוב משימות הנדסת תוכנה, עם יוצא הדופן היחיד: המרת המחרוזת ל UTF-32. לדוגמא:
לא, מכיוון שמספר התווים "איך שנתפסים ע"י המשתמש" הניתנים לייצוג ב Unicode הוא למעשה אינסופי. אפילו בפרקטיקה, לרוב התווים אין מבנה מורכב במלואו. לדוגמא, מחרוזת ה NFD מהדוגמא לעיל, אשר מורכבת משלוש מילים אמיתיות בשלוש שפות אמיתיות, תהיה בנויה מ 20 Code Points ב NFC. זה עדיין יותר מה 16 תווים שנתפסים ע"י המשתמש.
()length
של המחרוזת חייבת לספור את התווים הנתפסים ע"י המשתמש או תווים מקודדים. אם לא, היא לא תומכת ב Unicode כמו שצריך.תמיכת ה Unicode בסיפריות ובשפות תכנות נאמדת לעיתים קרובות ע"י הערך שמוחזר מפעולת "אורך המחרוזת". לפי אומדן זה לתמיכת ה Unicode, השפות הכי פופולאריות, כמו למשל: C# ,Java ואפילו ICU בעצמו, לא יתמכו ב Unicode. לדוגמא: אורך של המחרוזת בעלת תו יחיד ‘🐨’ לעיתים קרובות תדווח כשני תווים היכן שמשתמשים ב UTF-16 כייצוג פנימי של המחרוזת, וכארבעה תווים לשפות שמבוססות על ייצוג פנימי של UTF-8. המקור לטעות הוא שאפיון של שפות אלו משתמשות במילה "תו" כדי להגדיר יחידת קידוד בעוד שהמתכנת מצפה שזה יהיה משהו אחר.
עם זאת, ספירת יחידות הקוד המוחזר ע"י API אלו, בעל משמעות מעשית גבוהה. כאשר כותבים מחרוזת UTF-8 לתוך תיקייה, החשוב הוא האורך בבתים. ספירת כל סוג אחר של תווים, לעומת זאת, לא באמת שימושית.
UTF-16 הוא הגרוע בשני העולמות, מכיוון שהוא גם בעל אורך משתנה וגם רחב מידי. הוא קיים אך ורק מסיבות היסטוריות ויוצר בלבול רב, לכן אנו מקווים כי השימוש בו ימשיך לדעוך.
ניידות חוצה פלטפורמות ופשטות יותר חשובות מאשר תקלות העבודה עם API של פלטפורמות קיימות. אי לכן, הגישה הטובה ביותר היא שימוש בלעדי במחרוזות צרות UTF-8 והמרה שלהן, הלוך וחזור, כאשר משתמשים ב API של פלטפורמה שלא תומכת ב UTF-8 ומקבלים מחרוזות רחבות (כדוגמאת Windows API). לעיתים רחוקות הביצועים בהמרת מחרוזות הן שיקול כאשר מתמודדים עם מערכות API שמקבלים מחרוזות במערכות (כדוגמאת, API של ממשקי משתמש או מערכות קבצים), וקיים יתרון משמעותי לשימוש בקידוד זהה בכל מקום בתוכנה. לכן לעניות דעתנו, אין סיבה מספיק טובה לעשות זאת באופן שונה.
בהתייחס לביצועים, לעיתים קרובות מכונות עושות שימוש במחרוזות לצרכי תקשורת (כדוגמאת HTTP Headers ,XML ,SOAP). רבים רואים בכך טעות לכשעצמה, אך בהתעלם מכך, זה כמעט תמיד מתבצע באנגלית ותווי ASCII, מה שנותן יתרון נוסף ל UTF-8. שימוש בקידודים שונים למחרוזות מסוגים שונים מעלה באופן משמעותי את המורכבות והיווצרות באגים.
בפרט, אנו מאמינים כי זאת היתה טעות להוסיף wchar_t
לתקן של ++C, וכך גם תוספות הUnicode ל C++11. עם זאת יש לדרוש מהיישומים שכל קידוד תווים שמשתמשים בו יוכל לשמור כל נתון מה Unicode. כך כל std::string
או פרמטר מסוג *char
יוכל להיות תואם ל Unicode. "אם זה מקבל טקסט, זה צריך להיות תואם Unicode" – וזה קל ליישום עם UTF-8.
ב ++C קיימים פגמי דיזיין רבים ב facets של הסטנדרט. הם כוללים בין היתר את העובדה ש: std::numpunct
, std::moneypunct
ו- std::ctype
לא תומכים בתווים המקודדים באורכים משתנים (non-ASCII UTF-8 ו- non-BMP UTF-16), או שחסר להם מידע לביצוע של ההמרות הדרושות. מן ההכרח לתקן אותם:
()decimal_point
ו- ()thousands_sep
יחזירו מחרוזת ולא יחידת קוד בודדה. כך עושים זאת C locales דרך פונקציית ה localeconv
, אם כי לא ניתן להתאמה אישית.()toupper
ו- ()tolower
לא יהיו מנוסחים במונחים של יחידות קידוד, מאחר וזה לא עובד ב Unicode. לדוגמא, הליגטורה הלטינית ffl חייבת להיות מומרת ל FFL וה ß הגרמנית ל-SS. בנוסף, שפות מסויימות (כמו למשל יוונית) מקיימות צורות סופיות מיוחדות של חלק מהאותיות הקטנות, כך שפונקציות ההמרה, חייבות להיות מודעות למיקומן על מנת לבצע את ההמרה באופן נכון.חלק זה מוקדש לפיתוח ספריות פורטביליות (Class platform) ולתכנות Windows. הבעיה עם הפלטפורמה של Windows שהיא לא תומכת (עדיין) ב Unicode בגרסאת ה API של מחרוזות צרות. הדרך היחידה להעביר מחרוזות Unicode ל Windows API היא ע"י המרת UTF-16 (הידוע גם כמחרוזת רחבה).
יש לציין כי ההנחיות המוצגות כאן, שונות באופן משמעותי מהמדריך המקורי של מייקרוסופט לטיפול ב Unicode. הגישה שלנו מבוססת על ביצוע המרת המחרוזת לרחבות כמה שיותר קרוב לקריאות ל API , ולעולם לא להחזיק נתונים של המחרוזת הרחבה. בחלקים הקודמים הסברנו שהתוצאה של זה היא בדרך כלל ביצועים טובים יותר, יציבות, פשטות של הקוד, ותאימות לתוכנות אחרות.
wchar_t
או ב std::wstring
בשום מקום פרט לבסמיכות ל API המקבל UTF-16.("")T_
או ב ""L
בשום מקום פרט לפרמטרים ל API המקבלים UTF-16.Unicode
, כמו: ()LPTSTR
,CreateWindow
או המאקרו ()T_
. השתמשו במקום ב ()LPWSTR
,CreateWindowW
וב ""Literals L
מפורשים.Unicode
ו- Unicode_
תמיד מוגדרים כדי למנוע העברת מחרוזת צרה ב UTF-8 ל ANSI WinAPI שעלול להתקמפל באופן שקט. זה יכול להתבצע בהגדרות פרוייקט ב VS, תחת השם Use Unicode character set.*char
וב std::string
נחשבים כ UTF-8 בכל מקום בתוכנה.()narrow
()/widen
להלן יכולות להיות נוחות להמרות באותה שורה. כמובן, כל קוד המרת קידוד UTF-8/UTF-16 אחר יספיק.LPWSTR
, ולעולם לא את אלו המקבלות LPTSTR
או LPSTR
. העבר פרמטרים באופן הבא:
::SetWindowTextW(widen(someStdString or "string litteral").c_str())
מדיניות שעושה שימוש בפונקציות המרה מפורטת מטה. ראה גם, ביצועים של פונקציות המרה.
CString someoneElse; // something that arrived from MFC.
// Converted as soon as possible, before passing any further away from the API call:
std::string s = str(boost::format("Hello %s\n") % narrow(someoneElse));
AfxMessageBox(widen(s).c_str(), L"Error", MB_OK);
string[index]
עלול לחזור חלק מתו (כמו שזה היה קורה במערך בתים ב UTF-8). כאשר מייצאים מחרוזות לתוך קבצי פלט או מכשירי תקשורת, תזכרו תמיד לציין Encoding.UTF8. תהיו מוכנים לשלם את הקנסות הביצועים על ההמרה, כמו באפליקציות WEB ב ASP.NET , שבדרך כלל מייצרות פלט HTML המקודד ב UTF-8. ()fopen
מסיבות RAII/OOD עם זאת, אם הכרחי, השתמשו בהמרות ()wfopen_
וב WinAPI כמתואר לעיל.std::string
או *const char
אל משפחת ה MSVC CRT .fstream
לא תומך בפרמטרים ב UTF-8 , אבל יש לו הרחבה לא סטנדרטית שאמורים להשתמש בה כדלהלן:std::string
ל std::wstring
עם widen
:
std::ifstream ifs(widen("hello"), std::ios_base::binary);
נצטרך להסיר באופן ידני את ההמרה, כשהעמדה של MSVC כלפי ה fstream
תשתנה.
בהנחיות אלו אנו משתמשים בפונקציות המרה מה Boost.Nowide library (אין זה עדיין חלק מה BOOST):
std::string narrow(const wchar_t *s);
std::wstring widen(const char *s);
std::string narrow(const std::wstring &s);
std::wstring widen(const std::string &s);
הסיפריה גם מספקת עטיפות לפונקציות הספריה הסטנדרטיות של C ו ++C, של טיפול בקבצים, וכן אמצעים של קריאה וכתיבה של UTF-8 דרך Iostreams.
פונקציות ו-wrappers אלו קלים למימוש על ידי שימוש בפונקציות Windows ה MultiByteToWideChar
ו WideCharToMultiByte
. ניתן להשתמש בכל רוטינת המרה אחרת.
ת: לא, גדלתי על Windows, ואני בראש ובראשונה מפתח Windows. אנו מאמינים כי מייקרוסופט עשתה בחירת דיזיין מוטעית בתחום הטקסט, מכיוון שהם עשו זאת לפני כל השאר.
ת: לא, וארצנו לא דוברת ASCII. איננו חושבים ששימוש בפורמט המקודד תווי ASCII בבית יחיד הוא "אנגלו- צנטרי", או שיש לזה איזה שהוא קשר לתקשורת בין בני אנוש. אף על פי שאפשר לטעון כי קודי מקור של תוכנות, דפי אינטרנט וקבצי XML, שמות קבצים של מערכת הפעלה וממשקי טקסט מחשב-למחשב אחרים, לא היו צריכים להיות קיימים. אך כל עוד הם קיימים, הטקסט אינו מיועד רק לקורא אנושי.
ת: אין זה נכון. גם ב #C וגם ב Java יש טיפוס char
בעל 16 ביטים, שזה פחות מתו Unicode, מזל טוב. האינדקסר str[i]
של NET. עובד ביחידות של ייצוג פנימי, וזאת שוב "אבסטרקציה דולפת". פונקציות SUBSTRING יחזירו בשמחה מחרוזת לא חוקית, ויחתכו את התו הלא BMP לחלקים ב #C.
מעבר לכך, צריך לשים לב לקידוד כשאתה כותב את הטקסט שלך לקבצים על דיסק, תקשורת, מכשירים חיצוניים, או כל מקום אחר ממנו תוכנה יכולה לקרוא. במקרים אלו בבקשה תשתדל להשתמש ב (System.Text.Encoding.UTF8 (
.NET ולעולם לא ב Encoding.ASCII
,UTF-16 או ב cellphone PDU, ללא קשר להנחות לגביי התוכן.
מערכות WEB כמו ASP.NET אכן סובלות מהבחירה הלא מוצלחת של ייצוג מחרוזת פנימית בתשתית עליה בנויות: פלט המחרוזת הצפוי (והקלט) של אפליקציית אינטרנט כמעט תמיד UTF-8, מה שיוצר תקורת המרה משמעותית ביישומי אינטרנט ושרותי אינטרנט המיועדים לביצועים גבוהים.
ת: ללא קשר להאם UTF-8 נוצר במקור כ Hack תאימות, כיום זהו הקידוד הטוב והפופולארי ביותר של Unicode מכל קידוד אחר.
ת: האם אתה רציני לגביי לא לתמוך בכל ה Unicode בדיזיין של התוכנה שלך? ואם אתה כן מתכוון לתמוך בה בכל מקרה, איך העובדה שתווים שהם לא BMP נדירים משנה משהו, חוץ מלהקשות על בדיקות התוכנה? מה שכן משנה, לעומת זאת, הוא שמניפולציות טקסט הן נדירות יחסית באפליקציות אמיתיות- בהשוואה לסתם העברה של מחרוזות כמו שהן. המשמעות של זה של "רוחב כמעט קבוע" אין יתרון ביצועי גדול (ראה ביצועים), בעוד שלמחרוזות קצרות יותר יכול דווקא להיות יתרון משמעותי.
ת: אין לנו בעיה עם שימוש נכון בכל סוג של קידוד. עם זאת, יש בעיה כאשר לאותו הטיפוס, למשל std::string
, יש משמעויות שונות בהקשרים שונים. לחלק המשמעות היא ‘ANSI codepage’ בעוד שלאחרים המשמעות היא "הקוד הזה שבור ולא תומך בטקסט שלא באנגלית". בתוכנות שלנו המשמעות של זה היא מחרוזת תומכת Unicode בקידוד UTF-8. המגוון הזה הוא מקור לבאגים ותסכול. המורכבות הנוספת הזאת היא משהו שהעולם לא באמת צריך. התוצאה היא הרבה תוכנות שבורות Unicode, לכל רוחב התעשייה. JoelOnSoftware מציע כי הפתרון לכל התוכנות שבורות ה Unicode הוא לגרום לכל המתכנתים להיות מודעים לקידוד. אנו מאמינים שבהמצא קידוד אחד שיהפוך לברירת מחדל של ה API, כל אחד יוכל לכתוב תוכנה להעתקת קובץ בצורה נכונה מבלי להיות מומחה לענייני טקסט ושפות.
ת: זהו קיצור דרך תקף. בהחלט יכול להיות שזהו מצב סביר להשתמש במחרוזות רחבות. אבל אם אתה מתכנן להוסיף קובץ קונפיגורציה כלשהוא או כתיבה לקובץ לוג בעתיד, בבקשה תשקול להמיר את הכל למחרוזות לצרות. זה יחסוך צרות.
Unicode
Define, אם אתם לא מתכננים להשתמש במאקרואים LPTSTR
/TCHAR
/וכ"ו של Windows?ת: זהו אמצעי הגנה נוסף כנגד הכנסה של מחרוזת ה *UTF-8 char
כפרמטר לפונקציה של Windows API שמצפה ANSI. אנחנו רוצים שזה ייצר שגיאת קומפילציה. זהו באג שקשה למצוא מאותו סוג כמו העברה של מחרוזת []argv
ל ()fopen
על Windows: ההנחה היא שהמשתמש לעולם לא יעביר קבצים עם שמות שהם לא מה codepage הנוכחי. סביר להניח שלא תמצא באג מסוג זה בבדיקה ידנית, אלא אם הבודקים שלך אומנו לספק קבצי עם שמות סיניים מידי פעם, ועם זאת מדובר בלוגיקה שבורה של התוכנה. הודות ל Unicode
Define, תקבל שגיאת קומפיצליה במקרה זה.
ת: בואו נראה תחילה מתי הם יתחילו לתמוך ב CP_UTF8
כ codepage תקף. זה לא יהיה קשה לביצוע. לאחר מכן אנו לא רואים שום סיבה שבגינה ימשיכו מפתחי Windows להשתמש ב Widechar APIs. בנוסף, הוספת תמיכה ל CP_UTF8
יתקן חלק מהתוכנות והספריות השבורות בUnicode.
יש האומרים כי הוספת תמיכת CP_UTF8
תשבור אפליקציות קיימות המשתמשות ב ANSI API, ושזה לכאורה הסיבה לכך שמייקרוסופט נזקקה ליצירת API מחרוזת רחבה. זה לא נכון. אפילו חלק מקידודי ANSI פופולאריים הם בעלי אורכים משתנים (למשל Shift JIS), כך שאף קוד נכון לא יהפוך לתקול. הסיבה לכך שמייקרוסופט בחרה ב UCS-2 היסטורית גרידא. בזמנים ההם UTF-8 עוד לא היה קיים, Unicode נחשב רק כ "ASCII רחב יותר", וזה נחשב חשוב להשתמש בקידוד קבוע רוחב.
ת: לפי הסטנדרט של ה Unicode (v6.2, p.30): "השימוש ב BOM לא נדרש ולא מומלץ ל UTF-8". ענייני סדר הבתים הם סיבה נוספת להמנע מ UTF-16. ל- UTF-8 אין בעיות סדר הבתים (Endianness), וה UTF-8 BOM קיים רק כדי להצהיר כי זוהי מחרוזת UTF-8. אם UTF-8 יישאר הקידוד הפופולארי היחיד (כמו שהוא כבר בעולם האינטרנט), ה BOM הופך למיותר. למעשה, כיום רוב קבצי טקסט UTF-8 משמיטים את ה BOM.
שימוש ב BOMים ידרוש מכל קוד קיים כיום להיות מודעים אליהם, אפילו בתרחישים פשוטים כמו שרשור מחרוזות. זה לא מקובל.
ת: תמיד תשתמשו במעברי שורה n (0x0a)
line endings\ אפילו ב Windows. קבצים צריכים להכתב ולהקרא בBinary mode, אשר מבטיח פורטביליות- התוכנה תמיד תיתן את אותו הפלט בכל מערכת ההפעלה. מאחר והסטנדרטים של C ו ++C משתמשים במעביר שורה n\
למחרוזות השמורות בזיכרון, זה יגרום לכל הקבצים להכתב בקונבנציות POSIX. זה עלול לגרום לבעיות כשהקובץ נפתח בNotepad ב Windows; עם זאת כל עורך טקסטים ראוי מבין במעברי שורה אלו.
כמו כן, אנו מעדיפים יחידות SI, פורמט התאריך הבינלאומי ISO-8601 ונקודה צפה על פני פסיק צף .
ת: האם זה באמת יותר טוב עם UTF-16? אולי כן. ICU משתמש ב UTF-16 מסיבות היסטוריות, ולכן די קשה לאמוד את זה. עם זאת, רוב הזמן מחרוזות נחשבות ל Cookies, לא ממוינות או נהפכות בכל שימוש שני. לכן קידוד דחוס יותר מועדף בשביל ביצועים.
ת: לא באמת. אבל כן, בטיחות היא מאפיין מאד חשוב של כל דיזיין, וקידודי תווים אינם יוצאים מן הכלל.
std::string
משמעו UTF-8, האם לא יתבלבלו בין זה לבין קוד המאחסן "טקסט רגיל" ב std::string
?ת: אין דבר כזה "טקסט רגיל". אין שום סיבה לאחסן codepage-ANSI או טקסט מוגבל ל ASCII – בתוך אובייקט מסוג String.
ת: ראשית, אתה תעשה איזה שהיא המרה בכל מקרה. זה יהיה או בזמן הקריאות למערכת או בתקשורת עם שאר העולם, למשל כשנשלחת מחרוזת טקסט דרך TCP. כמו כן, אלו מה OS API שמקבלים מחרוזות, לעיתים קרובות מבצעים משימות איטיות מטבען, כמו ממשק משתמש או פעולות במערכת הקבצים. אם האינטראקציה שלך עם API של מערכת ההפעלה היא רוב האפליקציה שלך, הנה ניסוי קטן:
אחד השימושים האופייניים של ה OS API הוא לפתוח קבצים. הפונקציה הזאת רצה ב (184 ± 3)μs על המחשב שלי:
void f(const wchar_t* name)
{
HANDLE f = CreateFile(name, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
DWORD written;
WriteFile(f, "Hello world!\n", 13, &written, 0);
CloseHandle(f);
}
בעוד שזה רץ ב (186 ± 0.7)μs:
void f(const char* name)
{
HANDLE f = CreateFile(widen(name).c_str(), GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);
DWORD written;
WriteFile(f, "Hello world!\n", 13, &written, 0);
CloseHandle(f);
}
רץ בשני המקרים עם name="D:\\a\\test\\subdir\\subsubdir\\this is the sub dir\\a.txt"
ונעשה ממוצע לחמש הרצות.
זוהי תקורה של %(1 ± 2) בלבד. בנוסף, MultiByteToWideChar
אינה פונקציית ההמרה UTF-16 ↔ UTF-8 הכי מהירה.
אם עשית לוקליזציה לתוכנה שלך, אז כל המחרוזות שהן לא ASCII יועלו ממסד נתוני תרגום חיצוני, כך שאין זו בעיה.
אם אתה עדיין רוצה להכניס תו מיוחד אתה יכול לעשות את זה כדלקמן:
עם C++11:
u8"∃y ∀x ¬(x ≺ y)"
עם מקבצים שאינם תומכים ב ‘u8’ אתה יכול לבצע hard-code ליחידות קידוד של ה UTF-8:
"\xE2\x88\x83y \xE2\x88\x80x \xC2\xAC(x \xE2\x89\xBA y)"
עם זאת הדרך הפשוטה ביותר היא פשוט לכתוב את המחרוזת כפי שהיא ולשמור את קובץ המקור המקודד ב UTF-8:
"∃y ∀x ¬(x ≺ y)"
לצערנו, MSVC ממיר את זה לאיזה שהוא ANSI codepage, ומשחית את המחרוזת. כדי לעקוף בעיה זו,שמור את הקובץ ב UTF-8 בלי BOM .MSVC יניח שזה ה codepage הנכון ולא יגע במחרוזת שלך. עם זאת, לאחר הפעולה, לא יהיה ניתן להשתמש ב Identifiers ב Unicode וב wide string literals (שלא תשתמש בהם בכל אופן).
ת: תשאיר את ה CHARים. תגדיר Unicode
ו Unicode_
כדי לקבל שגיאות קומפילציה היכן שאמורים להשתמש ב narrow()
/()widen
(זה נעשה אוטומטית ע"י הגדרת Use Unicode Character Set בהגדרות פרוייקט של Visual Studio). מצא את כל שימושי ה fstream וה ()fopen
, ותשתמש ב wide overloads כמתואר לעיל. בזאת כמעט סיימת את העבודה.
אם אתה משתמש בסיפריות צד ג שלא תומכות ב Unicode, לדוגמא, שמעבירות מחרוזת שם קובץ כמו שהיא ל ()fopen
, אזי תצטרך לעקוף את זה עם כלים כמו ()GetShortPathName
, כמתואר לעיל.
ת: אולי עדיף שהיו עושים פחות כך שהתמיכה היתה טובה יותר. ביישום הסימוכין CPython v3.3, ייצוג המחרוזת הפנימית שונה. ה UTF-16 הוחלף ע"י אחד מהקידודים האפשריים: ISO-8859-1 ,UCS-2 או UCS, כתלות בתוכן המחרוזת. כדי להוסיף תו בודד שלא ASCII או BMP, כל המחרוזת תומר לקידוד אחר. הקידוד הפנימי שקוף לסקריפט. מטרת דיזיין זה היא לשפר את הביצוע אופרציות אינדקס (שליפה לפי מספר) של Unicode Code Points. עם זאת, אנו טוענים כי ספירה או אינדוקס דווקא של Code Points לא אמורות להיות פעולות חשובות למרבית השימושים - בהשוואה, לדוגמא, ל Grapheme Clusters, שאליו למיטב ידיעתנו, פייתון איננה מספקת תמיכה.
על כך, אנו מתנגדים לטיפול הבלתי תלוי בייצוג מחרוזות לטובת API שקוף לייצוגי עם ייצוגי פנימי ב UTF-8. פעולות אינדקס תספורנה יחידות קידוד במקום Code Points, כפי שאכן עשו לפני השינוי. זה גם יפשט את היישום וגם ישפר את הביצועים, כדוגמת scripts שמתעסקים עם אינטרנט, שכבר גם ככה נשלט ע"י טקסט מקודד UTF-8, ולכן הופכת את תכנות השפה של הפייתון ליותר אטרקטיבית בתכנות צד-שרת. ניתן להתווכח על בטיחות אופרציות חותכות מחרוזות ע"י מתכנתי סקריפטים, מצד שני אותו טיעון תקף גם לגביי חלוקת Grapheme Clusters. אפילו למרות שכיום ה Unicode נתמך לגמריי, אנחנו מאמינים שפייתון, ככלי מודרני ללא נטל היסטורי, חייב לעשות עבודה טובה יותר בתפעול טקסט.
מעבר לכך, JPython ו- IronPython ממשיכים להסתמך על הקידוד הפחות מוצלח בו משתמשת הפלטפורמה המארחת שלהם (NET. ו- Java בהתאמה) וצריך להקפיד לטפל ב Surrogate Pairs שם כהלכה.
std::string
? האם לא עדיף היה שתהיה מחלקה אמיתית תומכת UTF-8 כמקובל בתכנות מונחה עצמים?ת: לא כל חלק בקוד שמתמודד עם מחרוזות מעורב בעיבוד וולידציה של טקסט. התוכנית להעתקת קבצים ממקודם, שמקבלת קבצים עם שמות Unicode ומעבירה אותם לפוננקציות IO, תיהיה בסדר גמור עם byte buffer פשוט. אם אתה מתכנן סיפרייה שמקבלת מחרוזות כפרמטרים, ה std::string
הסטנדרטי, קל המשקל, והפשוט יספיק. עם זאת, זאת תהיה טעות להמציא מחדש מחלקה חדשה של מחרוזת ולהכריח את כולם לעבור עם הממשק המוזר שלך. כמובן שאם מישהו צריך יותר מאשר להעביר מחרוזות, הוא צריך להשתמש בכלים מתאימים לעיבוד טקסט. עם זאת, עדיף שכלים אלו יהיו בלתי תלויים בסוג האחסון בו משתמשים, ברוח הפרדת ה container/algorithm ב STL. למעשה, יש הרואים בממשק ה std::string
כיותר מידי נפוח, מאחר והיה עדיף להוציא את רובו מחוץ למחלקת ה std::string
.
ת: הפץ את הבשורה. תעבור על הקוד שלך ותבדוק איזו ספרייה הכי כואבת לשימוש בקוד פורטבילי תומך Unicode. דווח על באג לממציא הסיפרייה. אם אתה בונה סיפריית C או ++C, השתמש ב *char
וב std::string
עם הנחה של UTF-8, וסרב לתמוך בדפי קוד ANSI – מאחר והם שבורי Unicode באופן טבעי.
אם אתה עובד של מייקרוסופט, דחוף ליישום תמיכה של ה CP_UTF8
כאחד מדפי הקוד של narrow API.
רעיונות נוספים:
()fopen
) ב Windows, שיבצע את ההמרה לפרמטר הנכון. זה יכול להעשות גם ל ()main
ולמשתני הסביבה הגלובאליים.מסמך זה נכתב ע"י פבל רדזיוילובסקי, יעקב גלקה וסלבה נובגורודוב. מסמך זה הוא תוצאה של מחקר וניסיון בהתמודדות עם בעיות אמיתיות של נושאים הקשורים ל Unicode טעויות שנעשו ע"י מתכנתים. מטרתנו היא להגדיל את המודעות לבעיות של טקסט ולעורר שינויים בתעשייה כולה כדי לגרום לתכנות תומך Unicode להיות קל יותר, ובסופו של דבר לשפר את חווית המשתמש של תוכנות אלו שנכתבו ע"י בני אדם. אף אחד מאיתנו לא חלק מה Unicode consortium.
תודות מיוחדות ל: גלן לינדרמן שסיפק מידע לגביי פייתון. קון, ג'ל גירטס, לייזי רוי וג'אן רוג שדיווחו על באגים ושגיאות הדפסה במסמך זה. כמו כן, לקארינה שכטמן שעזרה בעריכת הטקסט ככלל ובעריכתו לעברית בפרט.
מסמך זה נכתב בעיקר בהשראת דיונים בtackOverflow , שפותחו ע"י ארתיום בייליס, הממציא של Boost.Locale. השראה נוספת הגיעה מקונבנציות הפיתוח ב VisionMap וה tauday.org של מייקל הרטל.
אתם מוזמנים להשאיר משוב/הערות בדף הפייסבוק שלנו UTF-8 Everywhere. אנו נעריך מאד את משובכם והערותיכם.
תרומת ביטקוין ל 1UTF8gQmvChQ4MwUHT6XmydjUt9TsuDRn
התרומות ישמשו להמשך מחקר וקידום.