Страница Ивана Рощина > Статьи > Автоматическое определение кодировки текста — 2 >

Приложение 1

Здесь приведён исходный текст процедуры определения кодировки текста, написанной на ассемблере Z80. Процедура проходит по тексту, пока в каком-либо из вариантов не наберётся 128 сочетаний (если, конечно, текст не кончится раньше), игнорируя повторяющиеся сочетания. Определение кодировки происходит по табл. 10.

Первоначальный вариант процедуры

;---------------------------------------
;Процедура DEF_CODE определяет кодировку
;текста.
;
;Вход: HL - адрес начала текста,
;      BC - длина (>0).
;
;Выход: A=0 - ALT,
;         1 - WIN,
;         2 - KOI.

DEF_CODE   EXX

;Заполняем BUF_1-BUF_3:

           LD   HL,BUF_1
           LD   DE,BUF_1+1
           LD   BC,0+(128*3)-1
           LD   (HL),#FF
           LDIR

; В альтернативных регистрах хранятся:
; H - BAD_1', L - ALL_1
; D - BAD_2'
; B - BAD_3', C - ALL_3
; E - константа #FF, она пригодится в
;     дальнейшем, при коррекции данных.
;
;ALL_2 не подсчитывается, т.к.
;ALL_2=ALL_3.

;Сейчас BC=0 после LDIR!

           DEC  B     ;BC=#FF00
           LD   H,B
           LD   L,C   ;HL=#FF00
           LD   D,B   ;D=#FF
           LD   E,B   ;E=#FF

           EXX

;HL - адрес начала текста, BC - длина.

;Получаем в BC количество сочетаний
;символов - на 1 меньше длины текста:

           DEC  BC

;Если 0 - сразу выходим, считая, что
;кодировка - ALT:

           LD   A,B
           OR   C
           RET  Z         ;A=0

;Главный цикл:

DC_1       PUSH BC        ;Счётчик.

;Обрабатываем текущее сочетание для
;каждого из трёх вариантов:

           LD   DE,RET_
           LD   BC,BUF_1
           CALL WORK_2S
           EXX
           JR   Z,DC_2
           DEC  H
DC_2       ADD  A,L
           LD   L,A
           EXX

           LD   DE,WIN2ALT
           LD   BC,BUF_2
           CALL WORK_2S
           JR   Z,DC_3
           EXX
           DEC  D
           EXX

DC_3       LD   DE,KOI2ALT
           LD   BC,BUF_3
           CALL WORK_2S

           POP  BC

           EXX
           JR   Z,DC_4
           DEC  B
DC_4       ADD  A,C
           LD   C,A

;Проверка: может быть, в одном из
;вариантов уже набралось 128 сочетаний?

           OR   L  ;A=C!
           RLA
           JR   C,DC_9

;Иначе - проходим по тексту дальше, пока
;он не кончится:

           EXX
           INC  HL
           DEC  BC
           LD   A,B
           OR   C
           JR   NZ,DC_1

;Данные собраны. Теперь, если
;в каком-либо из вариантов
;недопустимых сочетаний не больше 1/32
;от общего их числа, то считаем, что
;в этом варианте их и не было:

           EXX

DC_9       LD   A,L
           RLCA
           RLCA
           RLCA
           AND  7    ;ALL/32
           INC  A    ;ALL/32+1
           CPL       ;255-ALL/32-1
           CP   H    ;255-ALL/32-1 CP 255-BAD
           JR   NC,DC_5
           LD   H,E  ;ALL/32+1 > BAD
                     ;(т.е. ALL/32 >= BAD)
DC_5       LD   A,C
           RLCA
           RLCA
           RLCA
           AND  7
           INC  A
           CPL
           CP   B
           JR   NC,DC_6
           LD   B,E

DC_6       CP   D
           JR   NC,DC_7
           LD   D,E

;По таблице определяем кодировку:

DC_7       LD   E,C    ;ALL_2=ALL_3.

           XOR  A      ;A:=0, флаг C сбрасывается.
           SBC  HL,DE
           RLA         ;Флаг C сбрасывается.
           ADD  HL,DE
           SBC  HL,BC
           RLA         ;Флаг C сбрасывается.
           EX   DE,HL
           SBC  HL,BC
           RLA

           LD   HL,CODE_TAB
           LD   D,0
           LD   E,A
           ADD  HL,DE
           LD   A,(HL)

           RET

CODE_TAB   DB   0,0,#FF,2,1,#FF,1,2

;---------------------------------------
;Процедура WORK_2S - обработка одного
;сочетания.
;
;Вход: HL - адрес сочетания,
;      DE - адрес п/п перекодировки.
;      BC - адрес буфера для соотв.
;           варианта.
;
;Выход: A=0 - это не сочетание русских
;             букв, или это сочетание
;             уже встречалось раньше.
;       A=1 - это сочетание русских букв.
;
;Флаг Z сброшен, если сочетание является
;недопустимым, иначе установлен. (Если на
;выходе A=0, то флаг Z установлен.)
;
;HL не меняется.

WORK_2S    PUSH HL
           LD   (ADR_SUB),DE
           LD   (ADR_BUF),BC

           CALL GET_WORK ;Первый символ
           JR   C,NO_L   ;не буква!
           LD   B,A

           INC  HL

           CALL GET_WORK ;Второй символ
           JR   C,NO_L   ;не буква!
           LD   C,A

;Оба символа - буквы.

           LD   D,0
           LD   A,B
           ADD  A,A
           ADD  A,A
           LD   E,A
           LD   A,C
           RRA
           RRA
           RRA
           AND  3
           ADD  A,E
           LD   E,A

           LD   A,C
           CPL
           AND  7
           ADD  A,A
           ADD  A,A
           ADD  A,A
           OR   #46
           LD   (C_BIT_1),A
           LD   (C_BIT_2),A
           ADD  A,#40
           LD   (C_RES),A

;DE - смещение в таблице BUF.

;Проверяем: если соответствующий бит в
;таблице равен 0, значит, это сочетание
;уже встречалось раньше. Тогда выходим,
;игнорируя его (как если бы это были не
;буквы).

           LD   HL,0 ;Адрес буфера.
ADR_BUF    EQU  $-2
           ADD  HL,DE
           BIT  0,(HL)
C_BIT_1    EQU  $-1
           RES  0,(HL) ;Сразу обнуляем.
C_RES      EQU  $-1
           JR   Z,NO_L

;Такое сочетание ещё не встречалось.
;Проверяем, допустимо оно или нет:

           LD   HL,TABLE_2S
           ADD  HL,DE

           BIT  0,(HL)
C_BIT_2    EQU  $-1

           LD   A,1
           POP  HL
           RET

;Выход, если в сочетании - не буквы, или
;если такое сочетание уже встречалось.

NO_L       XOR  A ;A=0, Z=1.
           POP  HL
RET_       RET

;---------------------------------------
;Процедура GET_WORK берёт код символа,
;перекодирует его и, если это буква,
;преобразует к диапазону 0..31.
;
;Вход: HL - адрес символа.
;
;Выход: если это буква, в A - её поряд-
;       ковый номер (0..31), и флаг C
;       сброшен. Иначе флаг C=1.

GET_WORK   LD   A,(HL)
           CP   #80
           CALL 0      ;Перекодировали.
ADR_SUB    EQU  $-2
           RET  C      ;Не буква.

;Получаем порядковый номер, если буква:

           CP   #A0
           JR   NC,GET_W1

           SUB  #80
           RET

GET_W1     CP   #B0
           JR   NC,GET_W2

           SUB  #A0
           RET

GET_W2     CP   #E0
           RET  C    ;Не буква.

           CP   #F0
           CCF
           RET  C    ;Не буква.

           SUB  #D0
           RET

;---------------------------------------
;Процедура WIN2ALT перекодирует символ
;текста, код которого #80..#FF, из
;кодировки WIN в ALT, если этот символ
;является русской буквой. Тогда флаг C
;сброшен, иначе установлен.

WIN2ALT    CP   #C0
           RET  C    ;Не буква.

           SUB  #10
           CP   #E0
           RET  NC

           SUB  #30
           RET

;---------------------------------------
;Процедура KOI2ALT перекодирует символ
;текста, код которого #80..#FF, из
;кодировки KOI в ALT, если этот символ
;является русской буквой. Тогда флаг C
;сброшен, иначе установлен.

KOI2ALT    CP   #C0
           RET  C

           PUSH HL
           ADD  A,TAB_K_A-#C0\256
           LD   L,A
           LD   A,TAB_K_A-#C0/256
           ADC  A,0
           LD   H,A

;Если таблица TAB_K_A не пересекает
;границу сегментов, то предыдущие три
;команды можно заменить на
;LD H,TAB_K_A/256.

           LD   A,(HL)
           POP  HL
           RET

TAB_K_A    DB   "юабцдефгхийклмно"
           DB   "пярстужвьызшэщчъ"
           DB   "ЮАБЦДЕФГХИЙКЛМНО"
           DB   "ПЯРСТУЖВЬЫЗШЭЩЧЪ"

;---------------------------------------
;Таблица сочетаний хранится в
;инвертированном виде - это упрощает
;программу.

TABLE_2S

 db #00,#00,#00,#38,#01,#41,#08,#04
 db #02,#40,#08,#06,#03,#41,#0E,#7F
 db #00,#00,#08,#44,#00,#00,#00,#30
 db #21,#40,#2E,#F7,#00,#40,#0E,#40
 db #00,#00,#00,#38,#E2,#C0,#80,#7E
 db #58,#49,#0D,#7D,#00,#00,#8A,#24
 db #03,#40,#28,#62,#00,#51,#04,#20
 db #00,#00,#00,#38,#7B,#48,#0C,#60
 db #00,#00,#00,#24,#00,#40,#00,#00
 db #02,#40,#00,#00,#00,#00,#18,#38
 db #7B,#61,#0F,#ED,#43,#40,#0F,#7B
 db #5B,#45,#EF,#EF,#5B,#41,#47,#77
 db #53,#40,#08,#F5,#7B,#79,#6F,#F7
 db #FB,#FF,#FF,#FC,#80,#02,#08,#3E
 db #82,#51,#90,#34,#EA,#C2,#03,#FF
 db #80,#82,#18,#3D,#80,#02,#08,#3C

;---------------------------------------
;В BUF_1-BUF_3 хранится информация
;о том, какие сочетания букв уже
;встречались в каждом из трёх вариантов.

BUF_1      DS   128
BUF_2      DS   128
BUF_3      DS   128

Оптимизированный вариант процедуры

В январе 2004 года я написал оптимизированный вариант вышеприведённой процедуры. Если раньше эта процедура во время работы использовала три 128-байтных массива (BUF_1—BUF_3), то теперь — только два. Уменьшилась и длина самой процедуры, без учёта длины этих массивов: с 469 байтов до 393 — на 76 байтов (приведены длины для худшего случая, они могут быть немного уменьшены в случае расположения массивов, используемых в этих процедурах, по «удобным» адресам). К тому же новый вариант процедуры работает в несколько раз быстрее.

;---------------------------------------------------------------
;Процедура DEF_CODE определяет кодировку текста.
;
;Вход: HL - адрес начала текста,
;      BC - длина (>0).
;
;Выход: A - номер кодировки: 0 - ALT, 1 - WIN, 2 - KOI.

;TAB_KOI - таблица для получения порядкового номера буквы
;для кодировки KOI.

TAB_KOI    DB   30,0,1,22,4,5,20,3,21,8,9,10,11,12,13,14
           DB   15,31,16,17,18,19,6,2,28,27,7,24,29,25,23,26

DEF_CODE   EXX

;Заполняем BUF_1-BUF_2 значением #FF:

           LD   HL,BUF_1
           LD   DE,BUF_1+1
           LD   BC,#FF
           LD   (HL),C
           LDIR

;Если BUF_1 начинается с начала сегмента (т.е. младший байт
;адреса равен 0), то предыдущие пять команд и следующие две
;команды (DEC B и LD L,C) могут быть заменены на следующий
;фрагмент (экономия при этом составит 4 байта):
;
;           LD   HL,BUF_1
;           LD   B,#FF
;M1         LD   (HL),B
;           INC  L
;           JR   NZ,M1
;           LD   C,L      ;После этого BC=#FF00, L=0.

; В альтернативных регистрах хранятся:
; H - BAD_1', L - ALL_1,
; D - BAD_2',
; B - BAD_3', C - ALL_2 (=ALL_3),
; E - константа #FF, она пригодится в дальнейшем,
;     при коррекции данных.

;Сейчас BC=0 после LDIR!

           DEC  B
           LD   L,C

;BC=#FF00, L=0.

           LD   H,B   ;HL=#FF00
           LD   D,B   ;D=#FF
           LD   E,B   ;E=#FF

;Итак, BAD_1'=#FF, BAD_2'=#FF, BAD_3'=#FF, ALL_1=0, ALL_2=0.

           EXX

;Главный цикл.

MAIN_LOOP  DEC  BC      ;Уменьшаем счётчик.
           LD   A,B
           OR   C       ;Весь текст обработан?
           JR   Z,CALC  ;Если да - к вычислению результата.

           LD   D,(HL)  ;Код первого символа.
           INC  HL
           LD   E,(HL)  ;Код второго символа.

;Если код хотя бы одного символа меньше #80 (т.е. старший разряд
;равен 0) - значит, это не русская буква ни в одной из трёх
;кодировок, тогда переходим к началу цикла.

           LD   A,D
           AND  E
           JP   P,MAIN_LOOP

           PUSH BC      ;Сохранили счётчик.
           PUSH HL      ;Сохранили адрес следующего сочетания.

           LD   B,0     ;Значение будет использовано далее.

;Если два старших разряда обоих кодов равны 1 (то, что старшие
;разряды обоих кодов равны 1, мы уже установили) - значит, коды
;находятся в диапазоне #C0-#FF и надо сначала проверить варианты
;WIN и KOI. Иначе сразу переходим к проверке варианта ALT.

           ADD  A,A
           JP   P,V_ALT

;Вариант WIN.
;Младшие 5 битов кодов букв - это и есть порядковые номера букв.

           LD   HL,BUF_2+#100 ;HL больше на #100, т.к. H должно
                              ;быть на 1 больше фактического.
           CALL WORK_2S

           EXX
           JR   Z,DC_3
           DEC  D          ;BAD_2'=BAD_2'-1.
DC_3       AND  A          ;Сочетание встречалось раньше?
           JR   Z,V_ALT_0  ;Если да, то оно встречалось и
                           ;в варианте KOI, так что сразу
                           ;переходим к варианту ALT.
           INC  C          ;ALL_2=ALL_2+1.
           EXX

;Вариант KOI.
;В DE сейчас коды символов сочетания.
;Уже известно, что сочетание раньше не встречалось.

           PUSH DE   ;Сохранили коды символов сочетания.

           LD   A,E  ;Код второй буквы.
           AND  31

 IF TAB_KOI/256-((TAB_KOI+31)/256) ;============================

;Таблица TAB_KOI пересекает границу сегментов.

           LD   C,A
           LD   HL,TAB_KOI
           ADD  HL,BC      ;Используется, что B=0.
           LD   E,(HL)     ;Получили номер второй буквы.

           LD   A,D        ;Код первой буквы.
           AND  31         ;После этого флаг C сброшен.
           SBC  HL,BC      ;После этого HL=TAB_KOI.
           LD   C,A
           ADD  HL,BC      ;Используется, что B=0.

 ELSE ;=========================================================

;Таблица TAB_KOI не пересекает границу сегментов.

           LD   H,TAB_KOI/256
           ADD  A,TAB_KOI\256
           LD   L,A
           LD   E,(HL)     ;Получили номер второй буквы.

           LD   A,D        ;Код первой буквы.
           AND  31
           ADD  A,TAB_KOI\256
           LD   L,A

 ENDIF ;========================================================

           LD   A,(HL)     ;Получили номер первой буквы.
           LD   H,1        ;Признак "без проверки", т.к. знаем,
                           ;что сочетание ещё не встречалось.
           CALL WORK_2S_2

           POP  DE ;Восстановили коды символов сочетания.

           JR   Z,V_ALT
           EXX
           DEC  B          ;BAD_3'=BAD_3'-1.
V_ALT_0    EXX

;Вариант ALT.
;Проверяем, являются ли коды символов текущего сочетания кодами
;русских букв в кодировке ALT, и если да, то получаем в младших
;пяти битах A и E порядковые номера соответственно первой и
;второй буквы.

V_ALT      LD   HL,#B030   ;Константы #B0 и #30.
           LD   A,E        ;Код второго символа.
           SUB  H
           JR   C,OK       ;Переход, если символ - буква.
           CP   L
           JR   C,NO_ALT   ;Переход, если символ - не буква.
           CP   #40
           JR   NC,NO_ALT  ;Переход, если символ - не буква.
           LD   E,A

OK         LD   A,D        ;Код первого символа.
           CP   H
           JR   C,OK2      ;Переход, если символ - буква.
           SUB  H
           CP   L
           JR   C,NO_ALT   ;Переход, если символ - не буква.
           CP   #40
           JR   NC,NO_ALT  ;Переход, если символ - не буква.

OK2        LD   HL,BUF_1+#100 ;HL больше на #100, т.к. H должно
                              ;быть на 1 больше фактического.
           CALL WORK_2S_1

           EXX
           JR   Z,DC_2
           DEC  H          ;BAD_1'=BAD_1'-1.
DC_2       ADD  A,L        ;Если надо, увеличиваем ALL_1 на 1.
           LD   L,A
           EXX

NO_ALT     POP  HL    ;Восстановили адрес следующего сочетания.
           POP  BC    ;Восстановили счётчик.

;Проверка: может быть, в одном из вариантов уже набралось 128
;сочетаний (тогда старший бит ALL_1 или ALL_2 равен 1).

           EXX
           LD   A,L
           OR   C     ;После этого A=(ALL_1 or ALL_2).
           EXX
           JP   P,MAIN_LOOP  ;Нет, не набралось.

;Данные собраны. Теперь, если в каком-либо из вариантов
;недопустимых сочетаний не больше 1/32 от общего их числа,
;то считаем, что в этом варианте их и не было.

CALC       EXX

           LD   A,L ;ALL_1
           RLCA
           RLCA
           RLCA
           AND  7   ;ALL_1/32
           INC  A   ;ALL_1/32+1
           CPL      ;255-ALL_1/32-1
           CP   H   ;255-ALL_1/32-1 CP BAD_1' (BAD_1'=255-BAD_1)
           JR   NC,DC_5

;255-ALL_1/32-1 < 255-BAD_1
;   -ALL_1/32-1 < -BAD_1
;    ALL_1/32+1 > BAD_1
;      ALL_1/32 >= BAD_1
;         BAD_1 <= ALL_1/32

           LD   H,E ;BAD_1'=255 -> BAD_1=0.

DC_5       LD   A,C ;ALL_2 (=ALL_3)
           RLCA
           RLCA
           RLCA
           AND  7   ;ALL_2/32
           INC  A   ;ALL_2/32+1
           CPL      ;255-ALL_2/32-1
           CP   B   ;255-ALL_2/32-1 CP BAD_3' (BAD_3'=255-BAD_3)
           JR   NC,DC_6

;BAD_3 <= ALL_2/32 (не забываем, что ALL_2=ALL_3).

           LD   B,E ;BAD_3'=255 -> BAD_3=0.

DC_6       CP   D   ;255-ALL_2/32-1 CP BAD_2' (BAD_2'=255-BAD_2)
           JR   NC,DC_7
           LD   D,E ;BAD_2'=255 -> BAD_2=0.

DC_7       LD   E,C

;Сейчас HL=a, DE=b, BC=c.
;Определяем кодировку (без таблицы).

           XOR  A      ;A:=0, флаг C сбрасывается.
           SBC  HL,DE
           RLA
           ADD  HL,DE
           SBC  HL,BC
           RLA

;Если сейчас A=0 - значит, a>=b и a>=c, следовательно,
;кодировка - ALT. Номер этой кодировки как раз равен 0,
;так что можно сразу выйти из процедуры.

           AND  A      ;Флаг C сбрасывается.
           RET  Z

;Теперь точно известно, что кодировка не ALT.

           EX   DE,HL
           SBC  HL,BC

;Если сейчас флаг C сброшен, то b>=c, следовательно,
;кодировка - WIN (её номер равен 1), иначе - KOI (2).

           LD   A,1
           ADC  A,0
           RET

;---------------------------------------------------------------
;WORK_2S - обработка сочетания русских букв.
;
;Вход: B=0,
;      D - в младших пяти битах порядковый номер первой буквы,
;          старшие биты не определены,
;      E - в младших пяти битах порядковый номер второй буквы,
;          старшие биты не определены.
;      Если надо проверять, встречалось ли раньше данное
;      сочетание, то в HL указывается адрес 128-байтного массива
;      с информацией о том, какие сочетания в рассматриваемом
;      варианте уже встречались, причём H увеличено на 1.
;      Указанный массив не может располагаться по адресу,
;      старший байт которого равен 0 (на ZX Spectrum там всё
;      равно находится ПЗУ). Если не надо проверять, встречалось
;      ли раньше данное сочетание, то H=1.
;
;Выход: если сочетание уже встречалось, то A=0 и флаг Z
;       установлен. Иначе A=1, и при этом, если сочетание
;       является недопустимым, то флаг Z сброшен, иначе -
;       установлен.
;
;Точка входа WORK_2S_1 используется, когда в аккумуляторе уже
;содержится значение, содержащее в младших пяти битах номер
;буквы первого символа сочетания (содержимое регистра D тогда
;не используется), а точка входа WORK_2S_2 - когда, кроме того,
;значение аккумулятора уже находится в диапазоне 0-31.

WORK_2S    LD   A,D
WORK_2S_1  AND  31
WORK_2S_2  ADD  A,A
           ADD  A,A
           LD   C,A
           LD   A,E
           RRA
           RRA
           RRA
           AND  3
           ADD  A,C
           LD   C,A

           LD   A,E
           AND  7
           ADD  A,A
           ADD  A,A
           ADD  A,A
           OR   #46
           LD   (C_BIT_2),A

;BC - смещение в массиве (не забываем, B=0!).

;Надо проверять, встречалось ли раньше данное сочетание?

           DEC  H
           JR   Z,NO_CHECK

;Да, надо.

           LD   (C_BIT_1),A
           ADD  A,#40
           LD   (C_RES),A

;Проверяем: если соответствующий бит массива равен 0, то
;сочетание уже встречалось.

           ADD  HL,BC
           BIT  0,(HL)
C_BIT_1    EQU  $-1

;Если сочетание уже встречалось, выходим.

           LD   A,B ;A=0
           RET  Z   ;Z=1

;Сочетание ещё не встречалось. Помечаем, что оно встретилось.

           RES  0,(HL)
C_RES      EQU  $-1

;Проверяем, допустимо ли это сочетание.

NO_CHECK   LD   HL,TABLE_2S
           ADD  HL,BC

;Если TABLE_2S начинается с адреса, кратного 256, то предыдущие
;две команды можно заменить на LD H,TABLE_2S/256: LD L,C.

           BIT  0,(HL)
C_BIT_2    EQU  $-1

           LD   A,1
           RET

;---------------------------------------------------------------
;Таблица сочетаний (чтобы упростить программу, она хранится
;в инвертированном виде и с зеркально отражёнными значениями
;байтов).

TABLE_2S   DB #00,#00,#00,#1C,#80,#82,#10,#20
           DB #40,#02,#10,#60,#C0,#82,#70,#FE
           DB #00,#00,#10,#22,#00,#00,#00,#0C
           DB #84,#02,#74,#EF,#00,#02,#70,#02
           DB #00,#00,#00,#1C,#47,#03,#01,#7E
           DB #1A,#92,#B0,#BE,#00,#00,#51,#24
           DB #C0,#02,#14,#46,#00,#8A,#20,#04
           DB #00,#00,#00,#1C,#DE,#12,#30,#06
           DB #00,#00,#00,#24,#00,#02,#00,#00
           DB #40,#02,#00,#00,#00,#00,#18,#1C
           DB #DE,#86,#F0,#B7,#C2,#02,#F0,#DE
           DB #DA,#A2,#F7,#F7,#DA,#82,#E2,#EE
           DB #CA,#02,#10,#AF,#DE,#9E,#F6,#EF
           DB #DF,#FF,#FF,#3F,#01,#40,#10,#7C
           DB #41,#8A,#09,#2C,#57,#43,#C0,#FF
           DB #01,#41,#18,#BC,#01,#40,#10,#3C

;---------------------------------------------------------------
;В массивах BUF_1 и BUF_2 хранится информация о том, какие
;сочетания букв уже встречались соответственно в вариантах ALT
;и WIN. Эти массивы должны располагаться подряд и именно в таком
;порядке - это используется при их инициализации!

BUF_1      DS   128
BUF_2      DS   128
Скачать листинг оптимизированного варианта процедуры в текстовом виде (4 КБ ZIP)
Страница Ивана Рощина > Статьи > Автоматическое определение кодировки текста — 2 >