Как удалить пустые листовые элементы из файла XML, не сохраняя весь XML в памяти? - PullRequest
0 голосов
/ 14 июня 2019

Нам необходимо исключить элементы из XML-файла, элементы, которые удовлетворяют одному из следующих условий: C1.Это конечные элементы (не имеющие других элементов в качестве дочерних), а их обрезанный текст (связанный с неэлементными дочерними узлами) является пустым (только пробел).или C2.У них есть только дети, уважающие С1 или С2.Другими словами, C2.У них нет дочерних элементов, которые не относятся к C1 или C2.

Так что это алгоритм рекурсивной очистки.Проблема с подходом DOM заключается в том, что для хранения дерева в памяти требуется множитель размера XML.Мы ищем альтернативы для подхода с постоянной памятью, даже если нам нужно несколько циклов чтения-записи с диска, например, запись нескольких файлов XML до тех пор, пока не будет получен нужный XML.

У нас есть реализация dom4j, но она требуетпримерно в 5 раз больше памяти, чем размер XML (он, очевидно, сохраняет все дерево в памяти, хотя в конкретном тесте на самом деле не выполняется никаких изменений - в конкретном тестовом случае не исключается ни один элемент).

Мы рассматриваем просто выполнить C1 для полного XML за одну итерацию (если это можно сделать таким образом, чтобы потреблять меньше памяти, например, сопоставить пропуски с XPath и удалить их, не загружая всю структуру впамять - есть ли способ уникальной идентификации таких элементов? Всегда ли XPath однозначно идентифицирует узлы?), выводить в файл и делать это итеративно до тех пор, пока не будут сопоставлены никакие листья, и тогда XML не будет очищен.

Преобразование за один или несколько этапов, включающее обработку JVM с использованием Java, XSLT или чего-либо еще, которое принимает случайный XML (с использованием нескольких XML-схем) и выводит очищающий XML (в виде файла или потока вывода / ввода).

1 Ответ

1 голос
/ 14 июня 2019

Это сложно, потому что это включает в себя взгляд вперед.Рассмотрим

<a>
  <b/>
  <c/>
  <d/>
  <z>23</z>
</a>

Вы не знаете, следует ли удалять элемент <a>, пока не увидите элемент <z/>.Так что это определенно не чисто трансформируемое преобразование.

То, что вы могли бы сделать за один проход, это составить список всех элементов, которые должны быть удалены.

Это было бы полезнознать, ожидаете ли вы устранить очень много или очень мало элементов;в первом случае первый проход должен собирать ссылки на элементы, которые должны быть сохранены, во втором случае он должен собирать ссылки на элементы, которые должны быть отброшены.

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

В преобразовании XSLT 3.0 с достаточно легко собирать пути всех предков непробельных текстовых узлов:

//text()[normalize-space()] ! ancestor::* ! path(.)

Единственная проблема заключается в том, что без каких-либо объемов я не знаю, является ли этот список невероятно большим.Вы можете удалять дубликаты по ходу дела, помещая это в выражение карты:

map:merge(//text()[normalize-space()] ! ancestor::* ! path(.) ! map{.:1},
            map{'duplicates':'use-first'})

После построения этого списка достаточно просто выполнить потоковое преобразование, исключающее элементы, которых нет в списке:

<xsl:mode streamable="yes" on-no-match="shallow-copy"/>
<xsl:template match="*[not(map:contains($retained-path, path(.))]"/>

Как я уже сказал, проблема в том, что список сохраняемых узлов может стать очень большим.

Другой подход состоит в том, чтобы попытаться создать список путей элементов, которые должны быть отброшены.Алгоритм для этого может быть следующим: при обнаружении начального тега элемента добавьте элемент в список кандидатов на исключение;когда вы встретите текстовый узел без пробелов, удалите всех его предков из списка.Проблема заключается в том, что, как выражено здесь, для списка требуется изменяемая структура данных.Это делает его кандидатом на аккумуляторы XSLT 3.0:

<xsl:accumulator name="dropped-elements" as="map(xs:string, xs:integer)">
 <xsl:accumulator-rule match="*" select="map:merge($value, map{path(.), 1}"/>
 <xsl:accumulator-rule match="text()[normalize-space()]
    select="map:remove($value, ancestor::*!path(.))"/>
</xsl:accumulator>

, а затем в конце обработки map:keys(accumulator-after('dropped-elements')) дает вам пути элементов, которые должны быть отброшены.

Все непроверенные: я надеюсьэто дает вам некоторые идеи.

...