#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