Комментарии 27
Почти по теме
Я тут недавно разбирался с типами парсеров XML на разных языках (но чаще в rust), насчитал три типа
1. DOM-based (Древовидные)
- Парсеры: DOM, XmlDocument, XDocument
- Суть: Загружают весь XML-документ в память и представляют его в виде дерева.
- Плюсы: Удобная работа с узлами, мощные инструменты навигации и модификации структуры.
- Минусы: Высокое потребление памяти, неэффективен для больших XML-файлов.
2. Streaming-based (Событийные, включая Pipe-based как подтип)
- Парсеры: XmlReader, roxmltree (чистый Streaming), serde, xml-rm (Pipe-based)
- Суть: Потоковая обработка XML по мере его поступления.
- Pipe-based (часть Streaming-подхода): Вместо работы с сырыми XML-данными, происходит автоматическое преобразование XML листьев в объектные структуры.
- Плюсы: Малое потребление памяти, высокая производительность, подходит для больших XML-файлов.
- Минусы: Сложность работы с произвольными узлами, в Pipe-based требуется строго заданная модель данных.
3. ORM-like (Объектная сериализация)
- Парсеры: XmlSerializer, serde, xml-rm
- Суть: XML автоматически сериализуется/десериализуется в объектные структуры классов.
- Плюсы: Упрощает работу с XML, минимизирует ручное парсирование, удобен для интеграции с объектными моделями.
- Минусы: Жёсткие требования к структуре XML, сложность в обработке неструктурированных данных.
Итоговая схема:
✅ DOM – для удобного доступа и манипуляции деревом.
✅ Streaming (включая Pipe-based) – для потоковой обработки XML, подходит для больших файлов.
✅ ORM-like – для работы с объектами и автоматического преобразования XML в классы.
Кстати, как .net у вас совмещается с импортозамещением? У нас из-за него все .net проекты на стоп.
А в чем проблема? Net Core он опенсорс, можно исходники с гитхаба скачать и самому рантайм собрать, если что
Министерских не убедить...
Лично я учу Раст
C# вещь хорошая, конечно...
Согласен с Вами, есть "особенности внедрения". У нас особенности проекта и продукта таковы, что министерских внедрений почти нет.
Зато убедить банковский сектор или крупные компании один раз установить .Net в закрытый контур, выдать выгрузку о зависимых пакетах и скрин проверки антивирусов - даст положительный результат при внедрении.
Единственное, что отсутствует, так это нормальные ide, которые были бы бесплатные или которые можно купить. Но на ide у нас закрыли глаза настолько, что можно поднять пиратский флаг (если никто не видит), а docker, nuget и npm мы развернули собственные.
Сами импортозамещаем всякое.
Примерно с осени 2024 года, почти сразу с "запретом на покупку из России", Rider "внезапно" стал доступен в режиме Community, что позволяет с удобством разрабатывать на C# под любой отечественной ОС.
Те, для кого он перегружен - вполне довольны VS Code, который так же везде очень хорошо работает.
Можете поделиться чем вас не устраивают данные IDE?
Сам пользуюсь Rider и бед не знаю, только вот Community версия не предполагает, что на ней будут вести коммерческую разработку, что примерно равно пиратству.
VsCode же ИМХО очень хорош, когда надо поправить какой-то конфиг или поковырять js, но в большом проекте ему очень далеко по удобству до студии или Rider. Лично мне в нем работать весьма неудобно.
Вот и получается, что легальную ide не особо купишь, vscode не имеет всех плюшек даже студии, а бесплатные альтернативы уже давно вымерли.
Прекрасно совмещается с импортозамещением. Не совмещается только устаревший .NET Framework, а вот нормальный .NET Core вполне себе внедряется во всю.
Прелесть .NET в том, что он с открытым исходным кодом и для нужного спокойствия можно даже собрать его из исходников, хотя по факту всех ИБ-шников с кем лично мне приходилось взаимодействовать это не беспокоило, брали все из наших поставок.
Единственное, что обычно беспокоит ИБшников - поддержка последних версий. То етсь устаревший .NET 5 уже правда не вкатить, да и .NET 7 уже вышел из поддержки. Поэтому требуют LTS-ные версии 6 или 8. Это немного усложняет жизнь, так как приходится постоянно обновлять версию фреймворка и тратить ресурсы на обеспечение совместимости с новыми пакетами, но это так, житейские проблемы.
Местами встречаются инциденты, когда не хотят ставить отдельно .NET Runtime, но в этом плане хорошо спасает возможность собрать автономную сборку, когда все нужные системные библиотеки встраиваются в релизную сборку. Тогда продуктами можно пользоваться на компьютерах пользователей как нативные без доустановки .NET.
.NET Core проходит сертификацию ФСТЭК https://fstec.ru/gniii-ptzi-fstek-rossii/deyatelnost-v-oblasti-it
А импортозамещение - соблюдайте требования к лицензиям ОС и используемых библиотек и т.д. требования на сайте, там же регистрируйте https://reestr.digital.gov.ru/
А почему вы не используете epplus?
Осень богатый пакет для Excel и в XML потроха лезть не надо.
А ещё раньше он был коммерческий и осень дорогой
Старая версия вроде бесплатная.
Короче, мы пользуемся
Рад, что есть определенные ходы и варианты внедрения, однако у нас есть особенности, например, важно уметь менять стили ячеек документов в разных пакетах, однако, старая версия не поддерживает этого.
А новая - платная и будут огромные проблемы при распространении и внедрении продукта, т к тип лицензии EPPLUS будет динамический по внедрениям. Что не есть хорошо в современном мире.
Если все помещается в память, то почему не https://github.com/dotnet/Open-XML-SDK?
в общем, я подумал, попробовал все типы парсеров
и решил остаться на pull parser
Pull parser — это подход к анализу XML-документов, основанный на событиях. В отличие от DOM-парсера, который загружает весь документ в память, или SAX-парсера, который отправляет события приложению, Pull parser позволяет приложению запрашивать (или «вытягивать») события анализа по необходимости.
пробовал DOM парсеры, с красивым синтаксисом
но уж очень легко ошибиться и не получить ничего
пример
так работает
/// Разбирает XML-содержимое листа Excel и возвращает структурированные данные таблицы
fn parse_worksheet(
xml_content: &str,
shared_strings: &[String], // Общие строки документа
merge_map: &HashMap<String, (i32, i32)>, // Карта объединенных ячеек (ссылка -> (colspan, rowspan))
styles_map: &HashMap<String, i32> // Карта стилей (id стиля -> значение поворота)
) -> TableData {
// Создаем XML парсер из входной строки
let parser = EventReader::from_str(xml_content);
// Инициализируем структуры для хранения данных
let mut table_data = TableData { table_rows: Vec::new() };
let mut current_row = RowData { row_cells: Vec::new() };
// Переменные для хранения текущего состояния ячейки
let mut current_cell_ref = String::new(); // Ссылка на ячейку (например, "A1")
let mut current_cell_style = String::new(); // ID стиля ячейки
let mut current_cell_type = String::new(); // Тип данных в ячейке
let mut current_cell_value = String::new(); // Значение ячейки
let mut in_value = false; // Флаг: находимся ли внутри тега значения
// Обрабатываем каждое событие XML
for event in parser {
match event {
// Обработка начальных тегов
Ok(XmlEvent::StartElement { name, attributes, .. }) => {
match name.local_name.as_str() {
// Начало новой строки
"row" => {
current_row = RowData { row_cells: Vec::new() };
},
// Начало новой ячейки
"c" => {
// Извлекаем атрибуты ячейки
current_cell_ref = attributes.iter()
.find(|attr| attr.name.local_name == "r")
.map_or(String::new(), |attr| attr.value.clone());
current_cell_style = attributes.iter()
.find(|attr| attr.name.local_name == "s")
.map_or(String::new(), |attr| attr.value.clone());
current_cell_type = attributes.iter()
.find(|attr| attr.name.local_name == "t")
.map_or(String::new(), |attr| attr.value.clone());
current_cell_value = String::new();
},
// Начало тега значения
"v" => {
in_value = true;
},
_ => {}
}
},
// Обработка закрывающих тегов
Ok(XmlEvent::EndElement { name }) => {
match name.local_name.as_str() {
// Конец строки
"row" => {
if !current_row.row_cells.is_empty() {
table_data.table_rows.push(current_row.clone());
}
},
// Конец ячейки
"c" => {
// Обработка значения в зависимости от типа ячейки
let value = if current_cell_type == "s" {
// Если тип "s", значение - это индекс в shared_strings
shared_strings.get(current_cell_value.parse::<usize>().unwrap_or(0))
.cloned()
.unwrap_or_default()
} else {
current_cell_value.clone()
};
// Получаем информацию об объединении ячеек
let (colspan, rowspan) = merge_map
.get(¤t_cell_ref)
.copied()
.unwrap_or((1, 1));
// Получаем значение поворота из стилей
let rotation = if !current_cell_style.is_empty() {
styles_map.get(¤t_cell_style).copied().unwrap_or(0)
} else {
0
};
// Создаем объект ячейки
let cell = CellData {
cell_id: current_cell_ref.clone(),
cell_value: value,
cell_colspan: colspan,
cell_rowspan: rowspan,
is_merged: colspan > 1 || rowspan > 1,
rotation,
};
// Добавляем ячейку только если она имеет допустимые размеры ???
if colspan > 0 && rowspan > 0 {
current_row.row_cells.push(cell);
}
},
// Конец тега значения
"v" => {
in_value = false;
},
_ => {}
}
},
// Обработка текстового содержимого внутри тега значения
Ok(XmlEvent::Characters(text)) if in_value => {
current_cell_value = text;
},
_ => {}
}
}
table_data
}
а так - нет
хотя вроде должен
// Структура для ячейки в XML.
#[derive(Debug, Deserialize, PartialEq)]
pub struct XmlCell {
#[serde(rename = "r")]
pub(crate) cell_id: String, // Атрибут "r" в теге <c> (например, "A1").
#[serde(rename = "s", default)]
pub(crate) style_id: String, // Атрибут "s" для стиля ячейки.
#[serde(rename = "t", default)]
pub(crate) cell_type: String, // Атрибут "t" для типа ячейки.
#[serde(rename = "hidden", default)]
hidden: bool, // Атрибут hidden для ячейки (по умолчанию false).
#[serde(rename = "v")]
pub(crate) value: Option<String>, // Тег <v> для значения ячейки.
}
// Структура для строки в XML.
#[derive(Debug, Deserialize, PartialEq)]
pub struct XmlRow {
#[serde(rename = "c", default)]
pub(crate) cells: Vec<XmlCell>, // Список ячеек в строке.
#[serde(rename = "hidden", default)]
pub(crate) hidden: bool, // Атрибут hidden для строки (по умолчанию false).
}
// Структура для столбца в XML.
#[derive(Debug, Deserialize, PartialEq)]
pub struct XmlCol {
#[serde(rename = "hidden", default)]
hidden: bool, // Атрибут hidden для столбца (по умолчанию false).
}
// Структура для всего листа в XML.
#[derive(Debug, Deserialize, PartialEq)]
pub struct XmlWorksheet {
#[serde(rename = "row", default)]
pub(crate) rows: Vec<XmlRow>, // Список строк.
#[serde(rename = "col", default)]
pub(crate) cols: Vec<XmlCol>, // Список столбцов.
}
pub fn parse_worksheet(
xml_content: &str,
shared_strings: &[String], // Общие строки документа
merge_map: &HashMap<String, (i32, i32)>, // Карта объединенных ячеек (ссылка -> (colspan, rowspan))
styles_map: &HashMap<String, i32> // Карта стилей (id стиля -> значение поворота)
) -> TableData {
let xml_worksheet: XmlWorksheet = from_reader(xml_content.as_bytes()).unwrap();
let mut table_data = TableData {
table_rows: Vec::new(),
table_cols: vec![ColData { hidden: false }; xml_worksheet.cols.len()],
};
// Обрабатываем строки и ячейки.
for xml_row in xml_worksheet.rows {
let mut row_data = RowData {
row_cells: Vec::new(),
hidden: xml_row.hidden,
};
for xml_cell in xml_row.cells {
let mut cell_value = xml_cell.value.clone().unwrap_or_default();
let cell_colspan = merge_map
.get(&xml_cell.cell_id)
.map_or(1, |(colspan, _)| *colspan);
let cell_rowspan = merge_map
.get(&xml_cell.cell_id)
.map_or(1, |(_, rowspan)| *rowspan);
// If the cell type is a shared string, resolve it
if xml_cell.cell_type == "s" {
if let Some(idx) = cell_value.parse::<usize>().ok() {
cell_value = shared_strings.get(idx).cloned().unwrap_or_default();
}
}
// Map XMLCell to CellData, с добавлением логики merge and rotation
let cell_data = CellData {
cell_id: xml_cell.cell_id,
cell_value,
cell_colspan,
cell_rowspan,
is_merged: cell_colspan > 1 || cell_rowspan > 1,
rotation: styles_map.get(&xml_cell.style_id).copied().unwrap_or(0),
};
row_data.row_cells.push(cell_data);
}
table_data.table_rows.push(row_data);
}
table_data
}
Изивните, а какое это имеет отношение к перечисленным импортозамезаемым, или же просто к опен соурсным офисным пакетам? То что вы пишите - прямая работа с документом и его структурами. И что с того? Как это позволит использовать конкретно ваш продукт PIX для взаимодействия с тем же Р7? Вопрос не праздный, так как мы пытались использовать ваш продукт для тестирования наших плагинов под Р7. И не сказал бы, что очень успешно.
А зачем вообще привязываться к конкретным офисным пакетам? Если библиотека позволяет сделать так, чтобы документ был отредактирован таким образом, что его корректное отображение будет в большинстве редакторов (в том числе в МойОфис, ОпенОфисе итд) - это же лучше.
Интересует как вы пытались использовать продукт для тестирования ваших плагинов, поделитесь, очень интересно!
Ну, например затем, что хотелось бы иметь конечный вид документа сразу таким, как видишь его в документе. За много лет, офисные работники просто уже не понимают, как можно работать с документами не в WYSIWYG (What You See Is What You Get, «что видишь, то и получишь») виде, который очень успешно сформировал офис от Майкрософт за 30 лет.
Все механизмы автоматизации на глубинном уровне, минуя средства пакета в котором будет потом происходить ознакомление с документом, и его последующее редактирование, изначально ущербны, ибо возникают проблемы форматирования в конечном представлении такого документа, сгенерированного или модифицированного сторонней библиотекой . Ваше решение это мегакостыль, который может где то для простых форм документов и сработает. Но он малопригоден для полноценной автоматизации. И кстати говоря, такие же решения просто бесят в МойОфис и Р7/OnlyOffice, где нет полноценной работы с документами извне, а есть такой же отдельный построитель документов, с такими же колосальными проблемами в случае малейших изменений шаблонов документов
Вы сами дали себе ответ:
>И кстати говоря, такие же решения просто бесят в МойОфис и Р7/OnlyOffice, где нет полноценной работы с документами извне
Это не "костыль", а решение проблемы за счет реализации того, чего в принципе нет в OpenOffice-пакетах. Microsoft потому все и любят, что они сделали чудо, на которое другие вовсе не способны.
Я то дал, но вы не ответили, как ваше решение отвечает на заявленное в заголовке "приручение табличных редакторов"? Вы их пытаетесь заменить, а вовсе не приручить!
Такие термины как "приручение табличных редакторов" и "импортозамещение" здесь отлично уместны. Сейчас попробую показать суть со стороны авторского видения проблемы.
Допустим, Вы фирма, у которой есть огромный пласт документации электронного вида, и все храниться в Excel + Word. Но вот дела - сверху поступил указ на переход на отечественные ОС, и приходится как-то искать решение по миграции данных на что-то не микрософтовское.
И тут на помощь приходит RPA - роботы, которые при правильной детальной настройке позволят мигрировать данные внутри документов. Отсюда и пошла идея для этой разработки.
А далее, большинство роботов RPA настроены на документацию и работу с ней, очень грустно если Вы уходите в другую ОС, а роботы не умеют работать с другой документацией другого офисного пакета (Р7 например :3), поэтому и появилось это решение. Чтобы помочь с миграцией и перенастройкой роботов на импортозамещаемые решения.
В данном случае, приручение табличных редакторов уже звучит более приемлимо, т к решение позволит обрабатывать различные данные внутри разных офисных пакетов ODF формата.
Замена была бы, когда мы бы разработали свой пакет и свою систему хранения.
Ещё раз, вы не табличные редакторы "приручаете", а в лучшем случае - тип документов пользователя (табличный). С таким же успехом, если я напищу универсальный составитель SQL запросов под разные типы популярных СУБД, я по вашему приручу все эти базы данных? Типично кликинбайтный заголовок. Хотя, спорить не стану, в целом для разработчиков информация у вас полезная.
Ограничились стандартом Microsoft Office XML formats, т.е. для Excel
Использование DOM модели (как и всевозможных библиотек с данной моделью), приводит к большому потреблению памяти, для больших файлов так и к резкому замедлению обработки.
Пока не появились библиотеки https://github.com/dotnet/Open-XML-SDK, использовали XmlReader/XmlWriter - дорого, объёмно в разработке, но самый скоростной по времени чтения/модификации/созданию Excel файла при минимальном потреблении памяти.
С использованием Open-XML-SDK механизм тот же, но писать быстрее и дешевле, и надежней т.к. есть готовые классы из xsd. Несколько медленнее обработка файла, но это проценты, ну и чуть выше потребление памяти, при таком подходе удобство и скорость разработки выше на порядок.
Кто сильнее: XDocument или XmlDocument, или как я приручал табличные редакторы