special

This webpage has been robot translated, sorry for typos if any. To view the original content of the page, simply replace the translation subdomain with www in the address bar or use this link.

Как отобразить страницу в UTF-8, несмотря на windows-1251 в HTTP-заголовке

Недавно я закинул на сайт несколько статей — заливал в UTF-8. Кодировка была указана в теге meta, но, взглянув на страницы, я увидел крякозябры: «Р§С‚Рѕ-то случилось.» Оказывается, сайт шлёт HTTP-заголовок Content-Type: text/html; charset=windows-1251 и это на нём никак не отключается. Пользователь может получить читабельный текст — только если догадается вручную переключить кодировку в браузере.

Что делать? Переходить на другой хостинг? Само собой, но пока руки не дошли, хотелось добиться результата тут. Перекодировать тексты? Более достойным и интересным показалось поставить Javascript-«заплатку».

Способа переключить кодировку из Javascript я не нашёл. Остался вариант перекодировать текст скриптом, запускаемым по событию onready документа.

Итак, браузер получает текст в UTF-8, разбивает UTF-последовательности на группы по 8 бит и трактует их как коды символов в кодировке Windows-1251. Чтобы восстановить читаемость текста, нужно получить эти коды, объединить их в UTF-последовательности, а из них — восстановить Unicode-коды символов и вернуть последние посредством числовых ссылок HTML на символы. В этом деле обнаружились несколько закавык.

Во-первых, считывая текст из свойства innerHTML, мы обнаруживаем на месте неразрывного пробела (0xA0) HTML-сущность « ». Нужно её заменять обратно на 0xA0.

Во-вторых, функция charCodeAt возвращает код символа в Unicode, а не в Windows-1251, значит нужно преобразовывать первый во второй. В-третьих, символа с кодом 0x98 в Windows-1251 нет, так что эта функция возвращает для него undefined, это нужно предусмотреть.

В-четвёртых, Internet Explorer и Safari не позволяют поменять заголовок документа через DOM, только через соответствующее свойство документа — но туда нельзя писать числовые ссылки HTML. Для этого случая можно переводить Unicode-коды в шестнадцатеричную систему счисления, записывать их в виде «%код» и пропускать через функцию unescape.

Итоговый код получается таким:

bindReady( function(){ var Win1251 = { 0x0: 0x0, 0x1: 0x1, 0x2: 0x2, 0x3: 0x3, 0x4: 0x4, 0x5: 0x5, 0x6: 0x6, 0x7: 0x7, 0x8: 0x8, 0x9: 0x9, 0xA: 0xA, 0xB: 0xB, 0xC: 0xC, 0xD: 0xD, 0xE: 0xE, 0xF: 0xF, 0x10: 0x10, 0x11: 0x11, 0x12: 0x12, 0x13: 0x13, 0x14: 0x14, 0x15: 0x15, 0x16: 0x16, 0x17: 0x17, 0x18: 0x18, 0x19: 0x19, 0x1A: 0x1A, 0x1B: 0x1B, 0x1C: 0x1C, 0x1D: 0x1D, 0x1E: 0x1E, 0x1F: 0x1F, 0x20: 0x20, 0x21: 0x21, 0x22: 0x22, 0x23: 0x23, 0x24: 0x24, 0x25: 0x25, 0x26: 0x26, 0x27: 0x27, 0x28: 0x28, 0x29: 0x29, 0x2A: 0x2A, 0x2B: 0x2B, 0x2C: 0x2C, 0x2D: 0x2D, 0x2E: 0x2E, 0x2F: 0x2F, 0x30: 0x30, 0x31: 0x31, 0x32: 0x32, 0x33: 0x33, 0x34: 0x34, 0x35: 0x35, 0x36: 0x36, 0x37: 0x37, 0x38: 0x38, 0x39: 0x39, 0x3A: 0x3A, 0x3B: 0x3B, 0x3C: 0x3C, 0x3D: 0x3D, 0x3E: 0x3E, 0x3F: 0x3F, 0x40: 0x40, 0x41: 0x41, 0x42: 0x42, 0x43: 0x43, 0x44: 0x44, 0x45: 0x45, 0x46: 0x46, 0x47: 0x47, 0x48: 0x48, 0x49: 0x49, 0x4A: 0x4A, 0x4B: 0x4B, 0x4C: 0x4C, 0x4D: 0x4D, 0x4E: 0x4E, 0x4F: 0x4F, 0x50: 0x50, 0x51: 0x51, 0x52: 0x52, 0x53: 0x53, 0x54: 0x54, 0x55: 0x55, 0x56: 0x56, 0x57: 0x57, 0x58: 0x58, 0x59: 0x59, 0x5A: 0x5A, 0x5B: 0x5B, 0x5C: 0x5C, 0x5D: 0x5D, 0x5E: 0x5E, 0x5F: 0x5F, 0x60: 0x60, 0x61: 0x61, 0x62: 0x62, 0x63: 0x63, 0x64: 0x64, 0x65: 0x65, 0x66: 0x66, 0x67: 0x67, 0x68: 0x68, 0x69: 0x69, 0x6A: 0x6A, 0x6B: 0x6B, 0x6C: 0x6C, 0x6D: 0x6D, 0x6E: 0x6E, 0x6F: 0x6F, 0x70: 0x70, 0x71: 0x71, 0x72: 0x72, 0x73: 0x73, 0x74: 0x74, 0x75: 0x75, 0x76: 0x76, 0x77: 0x77, 0x78: 0x78, 0x79: 0x79, 0x7A: 0x7A, 0x7B: 0x7B, 0x7C: 0x7C, 0x7D: 0x7D, 0x7E: 0x7E, 0x7F: 0x7F, 0x402: 0x80, 0x403: 0x81, 0x201A: 0x82, 0x453: 0x83, 0x201E: 0x84, 0x2026: 0x85, 0x2020: 0x86, 0x2021: 0x87, 0x20AC: 0x88, 0x2030: 0x89, 0x409: 0x8A, 0x2039: 0x8B, 0x40A: 0x8C, 0x40C: 0x8D, 0x40B: 0x8E, 0x40F: 0x8F, 0x452: 0x90, 0x2018: 0x91, 0x2019: 0x92, 0x201C: 0x93, 0x201D: 0x94, 0x2022: 0x95, 0x2013: 0x96, 0x2014: 0x97, 0x2122: 0x99, 0x459: 0x9A, 0x203A: 0x9B, 0x45A: 0x9C, 0x45C: 0x9D, 0x45B: 0x9E, 0x45F: 0x9F, 0xA0: 0xA0, 0x40E: 0xA1, 0x45E: 0xA2, 0x408: 0xA3, 0xA4: 0xA4, 0x490: 0xA5, 0xA6: 0xA6, 0xA7: 0xA7, 0x401: 0xA8, 0xA9: 0xA9, 0x404: 0xAA, 0xAB: 0xAB, 0xAC: 0xAC, 0xAD: 0xAD, 0xAE: 0xAE, 0x407: 0xAF, 0xB0: 0xB0, 0xB1: 0xB1, 0x406: 0xB2, 0x456: 0xB3, 0x491: 0xB4, 0xB5: 0xB5, 0xB6: 0xB6, 0xB7: 0xB7, 0x451: 0xB8, 0x2116: 0xB9, 0x454: 0xBA, 0xBB: 0xBB, 0x458: 0xBC, 0x405: 0xBD, 0x455: 0xBE, 0x457: 0xBF, 0x410: 0xC0, 0x411: 0xC1, 0x412: 0xC2, 0x413: 0xC3, 0x414: 0xC4, 0x415: 0xC5, 0x416: 0xC6, 0x417: 0xC7, 0x418: 0xC8, 0x419: 0xC9, 0x41A: 0xCA, 0x41B: 0xCB, 0x41C: 0xCC, 0x41D: 0xCD, 0x41E: 0xCE, 0x41F: 0xCF, 0x420: 0xD0, 0x421: 0xD1, 0x422: 0xD2, 0x423: 0xD3, 0x424: 0xD4, 0x425: 0xD5, 0x426: 0xD6, 0x427: 0xD7, 0x428: 0xD8, 0x429: 0xD9, 0x42A: 0xDA, 0x42B: 0xDB, 0x42C: 0xDC, 0x42D: 0xDD, 0x42E: 0xDE, 0x42F: 0xDF, 0x430: 0xE0, 0x431: 0xE1, 0x432: 0xE2, 0x433: 0xE3, 0x434: 0xE4, 0x435: 0xE5, 0x436: 0xE6, 0x437: 0xE7, 0x438: 0xE8, 0x439: 0xE9, 0x43A: 0xEA, 0x43B: 0xEB, 0x43C: 0xEC, 0x43D: 0xED, 0x43E: 0xEE, 0x43F: 0xEF, 0x440: 0xF0, 0x441: 0xF1, 0x442: 0xF2, 0x443: 0xF3, 0x444: 0xF4, 0x445: 0xF5, 0x446: 0xF6, 0x447: 0xF7, 0x448: 0xF8, 0x449: 0xF9, 0x44A: 0xFA, 0x44B: 0xFB, 0x44C: 0xFC, 0x44D: 0xFD, 0x44E: 0xFE, 0x44F: 0xFF } String.prototype.Win1251_charCodeAt=function(char_num){ var char_code=this.charCodeAt(char_num); return (char_code===undefined)?0x98:Win1251[char_code]; } function utf8_decode(text){ text=text.replace(/ /g,"\u00A0"); var char_code, char_code2, char_code3, char_code4; var result_str=''; for(var char_num=0; char_num<text.length; char_num++) if((char_code=text.Win1251_charCodeAt(char_num))<0x80 || char_code===text.charCodeAt(char_num)) result_str+=text.charAt(char_num);//0zzzzzzz - 00000000 00000000 00000000 0zzzzzzz else if(char_code>=0xC0) if(char_code<0xE0){ if( (char_code2=text.Win1251_charCodeAt(++char_num))>=0x80 && char_code2<0xC0 )//110yyyyy 10zzzzzz - 00000000 00000000 00000yyy yyzzzzzz result_str+="&#"+((char_code-0xC0)*0x40+(char_code2-0x80))+";"; } else if(char_code<0xF0){ if( (char_code2=text.Win1251_charCodeAt(++char_num))>=0x80 && char_code2<0xC0 && (char_code3=text.Win1251_charCodeAt(++char_num))>=0x80 && char_code3<0xC0 )//1110xxxx 10yyyyyy 10zzzzzz - 00000000 00000000 xxxxyyyy yyzzzzzz result_str+="&#"+((char_code-0xE0)*0x1000+(char_code2-0x80)*0x40+(char_code3-0x80))+";"; } else if( char_code<0xF8 && (char_code2=text.Win1251_charCodeAt(++char_num))>=0x80 && char_code2<0xC0 && (char_code3=text.Win1251_charCodeAt(++char_num))>=0x80 && char_code3<0xC0 && (char_code4=text.Win1251_charCodeAt(++char_num))>=0x80 && char_code4<0xC0 )//11110www 10xxxxxx 10yyyyyy 10zzzzzz - 00000000 000wwwxx xxxxyyyy yyzzzzzz result_str+="&#"+((char_code-0xF0)*0x40000+(char_code2-0x80)*0x1000+(char_code3-0x80)*0x40+(char_code4-0x80))+";"; return result_str; } function unescapeTitle(title){ return unescape( utf8_decode(document.title).replace( /&#([0-9]+);/g, function(expression, value){ if(isNaN(value=parseInt(value, 10))) return NaN; var i=0, retval="", radix=16; while(i++<4 || value>0){ retval=["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"][value%radix]+retval; value=Math.floor(value/radix); } return "%u"+retval; } ) ); } document.body.innerHTML=utf8_decode(document.body.innerHTML); if("vendor" in navigator && navigator.vendor.indexOf("Apple")>-1){ document.title=unescapeTitle(document.title); return; } /*@cc_on document.title=unescapeTitle(document.title); return; @*/ document.getElementsByTagName("title")[0].innerHTML=utf8_decode(document.title); } );

Проверено в Firefox 3 и 4; Opera 9, 10 и 11; Internet Explorer 5.5, 6, 7, 8; Google Chrome и Safari последних релизных версий.

Конечно, это кунштюк; я на нём разобрался, что такое UTF-8. По-хорошему, сервер не должен вредить своими HTTP-заголовками. Но тут встаёт философский вопрос: а кому лучше знать кодировку документа — серверу (автору .htaccess) или самому HTML-документу (его автору)? Возможно, у браузеров есть веская причина верить серверу, а не meta-тегу в документе.


Created/Updated: 25.05.2018

';>