2015-01-26 2 views
1

У меня есть очень большой входной документ (тысячи записей), который имеет структуру что-то вроде этого (Data представляет множество дочерних элементов):Использование XSL: аккумулятор с XSL: попробуйте/XSL: поймать

<Input> 
    <Record id="1"> 
    <Data/> 
    </Record> 
    <Record id="2"> 
    <Data/> 
    </Record> 
    <Record id="3"> 
    <Data/> 
    </Record> 
    <Record id="4"> 
    <Data/> 
    </Record> 
    <Record id="5"> 
    <Data/> 
    </Record> 
    <Record id="6"> 
    <!-- This is bad data --> 
    <BadData/> 
    </Record> 
    <Record id="7"> 
    <Data/> 
    </Record> 
    <Record id="8"> 
    <Data/> 
    </Record> 
    <Record id="9"> 
    <!-- Also bad data --> 
    <BadData/> 
    </Record> 
</Input> 

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

<xsl:stylesheet 
    version="3.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
    xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    xmlns:err="http://www.w3.org/2005/xqt-errors" 
    exclude-result-prefixes="xs err"> 

    <xsl:output indent="yes"/> 

    <xsl:strip-space elements="*"/> 

    <xsl:template match="Input"> 
    <Output> 
     <xsl:apply-templates/> 
    </Output> 
    </xsl:template> 

    <xsl:template match="Record"> 
    <xsl:variable name="preprocessed" as="element(GoodData)?"> 
     <xsl:try> 
     <xsl:apply-templates mode="preprocess" select="."/> 
     <xsl:catch> 
      <xsl:message expand-text="yes">Couldn't create good data for {@id} Code: {$err:code} {$err:description}</xsl:message> 
     </xsl:catch> 
     </xsl:try> 
    </xsl:variable> 
    <!-- Do some more logic on the preprocessed record --> 
    <xsl:if test="$preprocessed"> 
     <NewRecord id="{@id}"> 
     <xsl:sequence select="$preprocessed"/> 
     </NewRecord> 
    </xsl:if> 
    </xsl:template> 



    <xsl:template mode="preprocess" match="Record"> 
    <!-- This represents a very complex transform with many potential dynamic errors --> 
    <xsl:variable name="source" as="element(Data)" select="*"/> 
    <xsl:if test="$source"> 
     <GoodData/> 
    </xsl:if> 
    </xsl:template> 

</xsl:stylesheet> 

Это прекрасно работает, но это боль, чтобы вырыть через большие входные документы, чтобы найти несколько записей, которые не удалось. Я бы хотел написать источник элементов Record, которые не могут получить новый входной документ с использованием xsl: result-document. Я пытаюсь добавить XSL: аккумуляторный что-то вроде этого:

<xsl:accumulator name="failed-source" initial-value="()" as="element(Record)*"> 
    <xsl:accumulator-rule match="Record" phase="end"> 
    <xsl:sequence select="$value, .[false()(:test for failure:)]"/> 
    </xsl:accumulator-rule> 
</xsl:accumulator> 

<xsl:template match="Input"> 
    <Output> 
    <xsl:apply-templates/> 
    </Output> 
    <xsl:if test="accumulator-after('failed-source')"> 
    <xsl:result-document href="failed.input.xml"> 
     <Input> 
     <xsl:sequence select="accumulator-after('failed-source')"/> 
     </Input> 
    </xsl:result-document> 
    </xsl:if> 
</xsl:template> 

Однако, я не могу понять, что предикат в XSL: Накопитель-правило должно быть, или, если это вообще возможно использовать этот шаблон. Можно создать единый результирующий документ без изменения таблицы стилей?

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

<xsl:template match="Input"> 
    <xsl:variable name="processed" as="document-node()"> 
    <xsl:document> 
     <xsl:apply-templates/> 
    </xsl:document> 
    </xsl:variable> 
    <xsl:if test="$processed/NewRecord"> 
    <Output> 
     <xsl:sequence select="$processed/NewRecord"/> 
    </Output> 
    </xsl:if> 
    <xsl:if test="$processed/Record"> 
    <xsl:result-document href="failed.input.xml"> 
     <Input> 
     <xsl:sequence select="$processed/Record"/> 
     </Input> 
    </xsl:result-document> 
    </xsl:if> 
</xsl:template> 

<xsl:template match="Record"> 
    <xsl:variable name="preprocessed" as="element(GoodData)?"> 
    <xsl:try> 
     <xsl:apply-templates mode="preprocess" select="."/> 
     <xsl:catch> 
     <xsl:message expand-text="yes">Couldn't create good data for {@id} Code: {$err:code} {$err:description}</xsl:message> 
     </xsl:catch> 
    </xsl:try> 
    </xsl:variable> 
    <!-- Do some more logic on the preprocessed record --> 
    <xsl:choose> 
    <xsl:when test="$preprocessed"> 
     <NewRecord id="{@id}"> 
     <xsl:sequence select="$preprocessed"/> 
     </NewRecord> 
    </xsl:when> 
    <xsl:otherwise> 
     <xsl:sequence select="."/> 
    </xsl:otherwise> 
    </xsl:choose> 
</xsl:template> 

ответ

2

Это интересный подход.

Значение аккумулятора всегда должно быть чистой функцией входного узла. Невозможно подавать информацию из других видов деятельности, например. произошла ли обработка узла. Мне непонятно, можете ли вы определить «плохие записи» независимо от обработки, которую вы выполняете по этим записям: если вы можете, то есть, если вы по существу выполняете выборочную проверку на входе, то этот шаблон может работать вполне Что ж. (Но в этом случае я не думаю, что вы будете пытаться/поймать. Скорее, ваша основная функция обработки сначала проверит аккумулятор, чтобы убедиться, что данные действительны.)

Обратите внимание, что спецификация для аккумуляторов позволяет вычисление одного аккумулятора для доступа к другим аккумуляторам, но в настоящее время это не выполняется в Саксоне.

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

+0

Я подумал о написании функции проверки, но это будет дублировать много работы. Исходная схема не удобна для пользователя, поэтому гораздо удобнее ловить ошибки во время обработки. Например. «Пустая последовательность не допускается, так как значение переменной $ year намного легче справляется, чем что-то вроде« существует (A21/@ att1) ». Правильно ли я полагаю, что последний подход, описанный выше, будет иметь более высокий профиль памяти, чем какой-то аккумулятор? – nine9ths

+0

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

+0

После измерения мы обнаружили, что сохранение результата в переменной и расщепление, как описано в последнем примере, привело лишь к примерно 10% -ному разрыву в использовании памяти в конце прогона, поэтому мы будем продолжать этот подход. Выполнение второго преобразования было бы абсолютно надежным способом решения этой проблемы, из-за деталей реализации это немного больше накладных расходов, чем мы хотим понести для более позднего удобства отладки. Большое спасибо за вашу помощь. – nine9ths