Делает ли XML извлечения с помощью XSLT, не считывая все дерево DOM в память? - PullRequest
4 голосов
/ 17 декабря 2009

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

К сожалению, те реализации XSLT, о которых я знаю (за исключением самой дорогой версии Saxon), поддерживают не только чтение необходимой части DOM, но чтение всего дерева. Это приводит к тому, что компьютер переходит на смерть.

XPath, о котором идет речь,

//m/e[contains(.,'foobar')

так что по сути это просто grep.

Есть ли реализация XSLT, которая может это сделать? Или реализация XSLT, которая дала подходящий «совет», может сделать трюк с удалением частей в памяти, которые больше не понадобятся?

Я бы предпочел реализацию Java, но Windows и Linux являются жизнеспособными нативными платформами.


РЕДАКТИРОВАТЬ: входной XML выглядит следующим образом:

<log>
<!-- Fri Jun 26 12:09:27 CEST 2009 -->
<e h='12:09:27,284' l='org.apache.catalina.session.ManagerBase' z='1246010967284' t='ContainerBackgroundProcessor[StandardEngine[Catalina]]' v='10000'>
<m>Registering Catalina:type=Manager,path=/axsWHSweb-20090626,host=localhost</m></e>
<e h='12:09:27,284' l='org.apache.catalina.session.ManagerBase' z='1246010967284' t='ContainerBackgroundProcessor[StandardEngine[Catalina]]' v='10000'>
<m>Force random number initialization starting</m></e>
<e h='12:09:27,284' l='org.apache.catalina.session.ManagerBase' z='1246010967284' t='ContainerBackgroundProcessor[StandardEngine[Catalina]]' v='10000'>
<m>Getting message digest component for algorithm MD5</m></e>
<e h='12:09:27,284' l='org.apache.catalina.session.ManagerBase' z='1246010967284' t='ContainerBackgroundProcessor[StandardEngine[Catalina]]' v='10000'>
<m>Completed getting message digest component</m></e>
<e h='12:09:27,284' l='org.apache.catalina.session.ManagerBase' z='1246010967284' t='ContainerBackgroundProcessor[StandardEngine[Catalina]]' v='10000'>
<m>getDigest() 0</m></e>
......
</log>

По сути, я хочу выбрать несколько m-узлов (и я знаю, что XPath не подходит для этого, это был просто быстрый взлом), но поддерживаю компоновку XML.


РЕДАКТИРОВАТЬ: Похоже, что STX может быть то, что я ищу (я могу жить с другим языком преобразования), и что Joost является реализацией этого. Есть опыт?


РЕДАКТИРОВАТЬ: Я обнаружил, что Saxon 6.5.4 с -Xmx1500m может загружать мой XML, так что это позволило мне использовать мои XPath прямо сейчас. Это просто удачный ход, поэтому я все же хотел бы решить это в общем - это означает возможность написания сценариев, что, в свою очередь, означает, что сначала Java-фильтрация не производится вручную.


РЕДАКТИРОВАТЬ: О, кстати. Это файл журнала, очень похожий на тот, который генерируется log4j XMLLayout. Причина для XML заключается в том, чтобы иметь возможность делать именно это, а именно делать запросы в журнале. Это начальная попытка, отсюда и простой вопрос. Позже я хотел бы иметь возможность задавать более сложные вопросы - поэтому я бы хотел, чтобы язык запросов мог обрабатывать входной файл.

Ответы [ 10 ]

6 голосов
/ 17 декабря 2009

Рассмотрим VTD-XML . Это намного более эффективно памяти. Вы можете найти API здесь и тесты здесь .

alt text

Обратите внимание, что на последнем графике указано, что DOM использует как минимум в 5 раз больше памяти, чем большой файл XML. Это ведь действительно удивительно, не правда ли?

В качестве бонуса, он также быстрее в разборе и Xpath, в отличие от DOM и JDK:

alt text

alt text
(источник: sourceforge.net )

2 голосов
/ 17 декабря 2009

Вы должны быть в состоянии реализовать это без полного сканирования таблицы. Оператор «//» означает найти элемент в дереве на любом уровне. Это довольно дорого, особенно если вы работаете с документом вашего размера. Если вы оптимизируете свой запрос XPath или рассматриваете возможность настройки шаблонов совпадений, преобразователю XSLT может не потребоваться загрузка всего документа в память.

Исходя из вашего примера XML, вы ищете соответствие / log / e / m [... предикат ...]. Это должно быть в состоянии оптимизировать некоторые процессоры XSLT, чтобы не сканировать полный документ там, где // его не будет.

Поскольку ваш XML-документ довольно прост, может быть проще вообще не использовать XSLT. STaX - отличный API для потоковой передачи больших XML-документов. Dom4j также имеет хорошую поддержку XPath-запроса к большим документам. Информация об использовании dom4j для больших документов находится здесь: http://dom4j.sourceforge.net/dom4j-1.6.1/faq.html#large-doc

Образец из вышеуказанного источника:

SAXReader reader = new SAXReader();
reader.addHandler( "/ROWSET/ROW", 
    new ElementHandler() {
        public void onStart(ElementPath path) {
            // do nothing here...    
        }
        public void onEnd(ElementPath path) {
            // process a ROW element
            Element row = path.getCurrent();
            Element rowSet = row.getParent();
            Document document = row.getDocument();
            ...
            // prune the tree
            row.detach();
        }
    }
);

Document document = reader.read(url);

// The document will now be complete but all the ROW elements
// will have been pruned.
// We may want to do some final processing now
...
1 голос
/ 14 января 2013

У меня была такая же проблема, и я не хотел писать код Java. Мне удалось решить это с STX через Joost.

Согласно спецификации :

процесс STX может разбить большой документ XML на более мелкие фрагменты, передать каждый из этих фрагментов во внешний фильтр (например, Процессор XSLT) и объединить результаты в большой результат XML документ.

Это именно то, что мне было нужно. Самый большой пример XML-файла у меня - 1,5 ГБ, и у меня был шаблон XSLT для его обработки. При использовании бесплатной версии Saxon он занимал более 3 ГБ памяти при обработке. С Joost это заняло меньше, чем 90MB .

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

Вот детали кода, надеюсь, это кому-нибудь пригодится.

Оригинальный XSLT-файл (это был первый реализованный XSLT, извините за неправильное использование операторов for-each):

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
  <xsl:template match="/">
    <xsl:for-each select="Products/Product">
      <!-- Some XSL statements relative to "Product" element -->
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

Я преобразовал его в следующий STX:

<?xml version="1.0" encoding="UTF-8"?>

<stx:transform version="1.0"
    output-method="text"
    output-encoding="UTF-8"
    xmlns:stx="http://stx.sourceforge.net/2002/ns">

  <stx:buffer name="xslt-product">

    <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
      <xsl:template match="Product">
        <!-- The same XSL statements relative to "Product" element -->
      </xsl:template>
    </xsl:stylesheet>

  </stx:buffer>

  <stx:template match="/">
    <stx:process-children />
  </stx:template>

  <stx:template match="Product">
    <stx:process-self filter-method="http://www.w3.org/1999/XSL/Transform"
                      filter-src="buffer(xslt-product)" />
  </stx:template>

</stx:transform>

При запуске Joost мне все еще приходилось добавлять библиотеки Saxon, так как я использую функции в своем XSLT, поэтому мне нужна была поддержка XSLT 2.0. В конце команда для запуска преобразования была такой:

java -Djavax.xml.transform.TransformerFactory=net.sf.saxon.TransformerFactoryImpl -cp joost.jar:commons-discovery-0.5.jar:commons-logging-1.1.1.jar:saxon9he.jar net.sf.joost.Main my-source.xml my-convert.stx

Суть в том, что теперь я могу запускать преобразование на серверах с малой памятью, не реализовав ни Java-кода, ни повторно применив оригинальные правила XSLT!

1 голос
/ 17 декабря 2009

Enterprise Edition Saxon XSLT Processor поддерживает потоковую передачу больших документов именно для этого типа проблемы.

0 голосов
/ 08 марта 2019

Напишите xslt для возврата значений в предпочитаемом макете xml, содержащем только те значения, которые вам нужны из largeXmls.

Однако, если вы хотите дополнительно обработать значения в Java, то:

  1. преобразовать этот простой xml в POJO и прочитать значения (предпочтительный вариант)
  2. используйте Regex для извлечения значений

Пример использования StreamSource для анализа xml через xslt:

Пакет используется:

import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.File;
import java.io.StringReader;
import java.io.StringWriter;

Код:

        String xmlStr = "<A><b>value</b><c>value</c></A>";
        File xslt = new ClassPathResource("xslt/Transformer.xslt").getFile();
        Source xsltSource = new StreamSource(xslt);
        Source xmlSource = new StreamSource(new StringReader(xmlStr));
        TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer(xsltSource);
        StringWriter stringWriter = new StringWriter();
        transformer.transform(xmlSource, new StreamResult(stringWriter));
        String response = stringWriter.toString();
0 голосов
/ 15 июня 2010

Вы можете сделать это через STX / Joost, как уже предлагалось, но обратите внимание, что многие реализации XSLT имеют режим потоковой передачи SAX и не должны хранить все в памяти. Вам просто нужно убедиться, что ваш XSLT-файл не находится ни на одной из осей.

Однако, если бы я был тобой и действительно хотел выступить, я бы сделал это в STaX. Это просто, стандартно и быстро. Он поставляется из коробки в java 6, хотя вы можете также использовать Woodstox для немного лучшей реализации.

Для указанного вами xpath реализация тривиальна. Недостатком является то, что вам нужно поддерживать больше кода, и он не такой выразительный и высокоуровневый, как XPath, как в Joost или XSLT.

0 голосов
/ 18 декабря 2009

STX содержит потоковое подмножество XPath, которое, я считаю, называется STXPath; Я должен помнить, потому что я соавтор спецификации: -)

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

0 голосов
/ 18 декабря 2009

Я не парень по Java, и я не знаю, имеют ли инструменты, которые я использовал бы для этого в .NET, аналоги в мире Java.

Чтобы решить эту проблему в .NET, я бы вывел класс из XmlReader и дал бы ему только те элементы, которые мне интересны. Затем я могу использовать XmlReader в качестве входных данных для любого XML объект, как XmlDocument или XslCompiledTransform. Подкласс XmlReader в основном предварительно обрабатывает входной поток, делая его похожим на гораздо меньший XML-документ, чем любой класс, использующий его для чтения.

Кажется, что описанная здесь техника аналогична. Но я, как я говорю, не парень по Java.

0 голосов
/ 17 декабря 2009

Попробуйте парсер CAX из xponentsoftware. Это быстрый xml-парсер, построенный на xmlreader от Microsoft. Он дает полный путь при разборе каждого элемента, поэтому вы можете проверить, если путь = "m / e", а затем проверить, содержит ли текстовый узел "foo"

0 голосов
/ 17 декабря 2009

Это удар в темноте, и, может быть, вы будете смеяться мне из дома.

Ничто не мешает вам подключить источник SAX к входу вашего XSLT; и, по крайней мере, теоретически достаточно просто сделать ваш grep из потока SAX без DOM. Итак ... хочешь попробовать?

...