Почему мой байтовый массив, возвращаемый из fread, меньше, чем количество байтов в самом файле, когда он включает специальные символы Юникода?

#matlab #unicode #character-encoding #xml-parsing

#matlab #юникод #кодировка символов #xml-синтаксический анализ

Вопрос:

У меня возникли некоторые проблемы с кодированием специальных символов и синтаксическим анализом XML в большом сложном проекте в Matlab. Я выделил проблему, и хотя я до сих пор не знаю, как ее решить, я думаю, что это связано с этим вопросом.

Рассмотрим следующий XML-файл:

 <?xml version="1.0"?>
<info>
    <desc>
        <channels>
            <channel>
                <label>accZ</label>
                <unit>mg₀</unit>
                <type>ACC</type>
            </channel>
        </channels>
    </desc>
</info>
 

Предполагая окончания строк в стиле Unix, этот файл содержит 175 байт данных. Действительно, это то, что он говорит, когда я открываю его в Notepad . Теперь у меня есть функция синтаксического анализа XML, которую я почти точно скопировал из объяснения Mathworks о том, как анализировать XML в Matlab (https://au.mathworks.com/help/matlab/ref/xmlread.html ). Эта функция работает очень хорошо и не является проблемой, она включена только для полноты картины:

 % parse a simplified (attribute-free) subset of XML into a MATLAB struct
function result = parse_xml_struct(str)
import org.xml.sax.InputSource
import javax.xml.parsers.*
import java.io.*
tmp = InputSource();
tmp.setCharacterStream(StringReader(str));
result = parseChildNodes(xmlread(tmp));

% this is part of xml2struct (slightly simplified)
    function [children,ptext] = parseChildNodes(theNode)
        % Recurse over node children.
        children = struct;
        ptext = [];
        if theNode.hasChildNodes
            childNodes = theNode.getChildNodes;
            numChildNodes = childNodes.getLength;
            for count = 1:numChildNodes
                theChild = childNodes.item(count-1);
                [text,name,childs] = getNodeData(theChild);
                if (~strcmp(name,'#text') amp;amp; ~strcmp(name,'#comment'))
                    if (isfield(children,name))
                        if (~iscell(children.(name)))
                            children.(name) = {children.(name)}; end
                        index = length(children.(name)) 1;
                        children.(name){index} = childs;
                        if(~isempty(text))
                            children.(name){index} = text; end
                    else
                        children.(name) = childs;
                        if(~isempty(text))
                            children.(name) = text; end
                    end
                elseif (strcmp(name,'#text'))
                    if (~isempty(regexprep(text,'[s]*','')))
                        if (isempty(ptext))
                            ptext = text;
                        else
                            ptext = [ptext text];
                        end
                    end
                end
            end
        end
    end

% this is part of xml2struct (slightly simplified)
    function [text,name,childs] = getNodeData(theNode)
        % Create structure of node info.
        name = char(theNode.getNodeName);
        if ~isvarname(name)
            name = regexprep(name,'[-]','_dash_');
            name = regexprep(name,'[:]','_colon_');
            name = regexprep(name,'[.]','_dot_');
        end
        [childs,text] = parseChildNodes(theNode);
        if (isempty(fieldnames(childs)))
            try
                text = char(theNode.getData);
            catch
            end
        end
    end
end
 

Теперь, чтобы проверить это:

 finfo = dir('xml_example');
sz = finfo.bytes
fid = fopen('xml_example', 'r', 'ieee-le.l64');
data = fread(fid, sz, '*char');
data_size = size(data)
h = parse_xml_struct(data);
unit = h.info.desc.channels.channel.unit
 

и вывод:

 sz =

   175


data_size =

   173     1


unit =

    'mg₀'
 

Итак, каким-то образом я получаю правильный вывод, но потерял 2 байта по пути. Я не понимаю, почему это происходит.

И просто чтобы доказать себе, что именно маленький нижний индекс «o» вызывает несоответствие между размером файла и количеством байтов в моем data массиве, я удаляю его из XML-файла и получаю следующее:

 sz =

   172


data_size =

   172     1


unit =

    'mg'
 

Все еще правильный вывод для метки xml, и теперь размер файла и размер массива байтов совпадают. В чем дело?

Обновить

Кроме того, если я выполняю тот же тест для символа длиной 2 байта, я все равно получаю явление сжатия.

 <?xml version="1.0"?>
<info>
    <desc>
        <channels>
            <channel>
                <label>ωX</label>
                <unit>mrad/s</unit>
                <type>AUX</type>
            </channel>
        </channels>
    </desc>
</info>
 

Вывод:

 sz =

   175


data_size =

   174     1


unit =

'ωX'
 

Ответ №1:

Символ имеет длину три байта с кодировкой UTF-8 ( 0xE2 0x82 0x80 ) . И внутренний для MATLAB, на самом деле это два байта из-за кодировки UTF-16 ( 0x80 0x20 с малым порядком окончания).

Однако, поскольку a precision of *char был передан fread , возвращаемые данные возвращаются в виде char массива 1. И для char массива, независимо от базовой кодировки, это просто один символ при рассмотрении его size :

Если A это символьный вектор типа char , то size возвращает вектор строки [1 M] , где M количество символов.

1 Я предполагаю, что строгое утверждение о соответствии размера матрицы из fread if sizeA задано (согласно документации), справедливо только для числовых данных, поскольку, как может быть очевидно выше, количество байтов и количество символов не обязательно взаимно однозначны.

Комментарии:

1. О, я вижу, массив теперь состоит из символов, а не байтов.

2. Однако я не понимаю, как Matlab узнает, что ‘o’ имеет длину три байта, когда char равен (по определению) 2. Это проблема при чтении из двоичного файла, что я и хочу сделать. Откуда мне знать, как разделить байты заранее? Очевидно, что Matlab имеет некоторое представление о том, как это сделать.

3. @dmedine Поскольку encodingIn не был указан, » fopen использует автоматическое определение набора символов для определения кодировки» . И какой бы алгоритм ни использовал MATLAB, кодировка была UTF-8. Кодировка UTF-8 определяет, сколько байтов нужно прочитать для символа. В этом случае, поскольку первые четыре бита 0xE2 байта равны «1110», кодировка указывает на чтение трех байтов (потому что три «1»).

4. Это XML-файл — почему вы хотите прочитать его как двоичный, а не как текст? Текст — это правильный способ его обработки. Здесь нет ничего смешного: вы читаете текстовые данные в формате UTF-8, который представляет собой кодировку переменной длины (разные символы представлены разным количеством байтов), а Matlab преобразует их в UTF-16, кодировку с одной шириной, где любой символ (в BMP)представлен одним 2-байтовым элементом кода (элементом Matlab char ). size() возвращает количество элементов массива в массиве Matlab, а не количество байтов.

5. @AndrewJanke Это уменьшенная версия из более крупного проекта. В реальной жизни фрагменты xml перемежаются двоичными данными, и мне приходится читать их раздел за разделом. Там я получал странные результаты для многобайтовых символов. XML-файл — это тривиальный пример, предназначенный для изоляции моей проблемы. Кстати, я исправил проблему в своей нетривиальной программе, используя именно то, что вы в итоге предложили : fread(..., '*uint8') . В случае, если вам интересно: github.com/xdf-modules/xdf-Matlab/pull/11