2015-02-25 3 views
1

Привет, У меня есть проблема, требующая довольно специализированной сортировки и хотелось бы получить обратную связь по наилучшему подходу. Вот некоторые XML:Пользовательская группировка в xsl

<root> 
    <locations> 
    <location> 
     <address>123 4 st Smallville</address> 
     <number>1</number> 
    </location> 
    <location> 
     <address>432 1 st Metropolis</address> 
     <number>2</number> 
    </location> 
    </locations> 
    <insuranceCoverages> 
    <coverage> 
     <name>General Coverage</name> 
     <locationSplits> 
     <coverage> 
      <name>Building Coverage</name> 
      <locationNumber>1</locationNumber> 
      <clauses> 
      <coverage> 
       <name>Earthquake Exclusion</name> 
      </coverage> 
      </clauses> 
     </coverage> 
     </locationSplits> 
    </coverage> 
    <coverage> 
     <name>General Liability</name> 
    </coverage> 
    </insuranceCoverages> 
</root> 

Что я хотел бы сделать, это групповые охваты от места в довольно плоскую иерархию. Что-то вроде:

┌Location 1 
├┬Coverage "Building Coverage" 
│└Coverage "Earthquake Exclusion" 
├Location 2 
├No Location 
├┬Coverage "General Coverage" 
│└Coverage "General Liability" 

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

O(nm) 
    where n = # locations + 1 
     m = # coverages 

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

O(n) 
    where n = # coverages 

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

+0

Я хотел бы добавить, что я использую версию xslt 2.0, но могу свободно использовать любую версию. – eisenpony

ответ

1

если есть более эффективные способы выражения этого с XSL,

Эффективный способ сделать это с помощью XSLT - это использовать key. Вот простой пример:

XSLT 1,0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/> 

<xsl:key name="coverage-by-location" match="coverage" use="locationNumber" /> 

<xsl:template match="/root"> 
    <xsl:copy> 
     <xsl:for-each select="locations/location"> 
      <location number="{number}"> 
       <xsl:for-each select="key('coverage-by-location', number)"> 
        <coverage name="{name}"/> 
       </xsl:for-each> 
      </location> 
     </xsl:for-each> 
    </xsl:copy> 
</xsl:template> 

</xsl:stylesheet> 

Это проходит только расположение/расположение ветви дерева, и использует индекс, созданный <key> инструкции для того, чтобы отобрать охваты, соответствующие значению Текущее местоположение.

Добавление другой ветви «Нет места» для вывода дерева немного сложнее, но может быть сделано путем изменения ключа к:

<xsl:key name="coverage-by-location" match="coverage" use="string(locationNumber)" /> 

На данный момент, это будет более удобно иметь один шаблон для обработки покрытий обоих видов, так:

XSLT 1,0

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> 
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/> 

<xsl:key name="coverage-by-location" match="coverage" use="string(locationNumber)" /> 

<xsl:template match="/root"> 
    <xsl:copy> 
     <xsl:apply-templates select="locations/location"/> 
     <no-location> 
      <xsl:apply-templates select="key('coverage-by-location', '')"/> 
     </no-location> 
    </xsl:copy> 
</xsl:template> 

<xsl:template match="location"> 
    <location number="{number}"> 
     <xsl:apply-templates select="key('coverage-by-location', number)"/> 
    </location> 
</xsl:template> 

<xsl:template match="coverage"> 
    <coverage name="{name}"/> 
</xsl:template> 

</xsl:stylesheet> 

Применяя это к вашей входной examp ле будет возвращаться:

<?xml version="1.0" encoding="UTF-8"?> 
<root> 
    <location number="1"> 
     <coverage name="Building Coverage"/> 
    </location> 
    <location number="2"/> 
    <no-location> 
     <coverage name="General Coverage"/> 
     <coverage name="Earthquake Exclusion"/> 
     <coverage name="General Liability"/> 
    </no-location> 
</root> 

, который немного отличается от результата вы ожидали, но не объясняет, как именно покрытие «Earthquake исключения» будет ассоциироваться с позицией # 1.


EDIT:

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

в таком случае, я считаю, что было бы лучше, чтобы изменить ключ:

<xsl:key name="coverage-by-location" match="coverage" use="string(ancestor-or-self::coverage/locationNumber)" /> 
+0

Спасибо, Майкл! Мне интересно узнать о ключевой инструкции. Вы знаете, как это работает под капотом? Похоже, что он создает индекс. Это правда? Что касается исключения Землетрясения, важно, чтобы он оказался в элементе location 1, потому что он является дочерним лицом другого покрытия в местоположении 1. Может ли ключ быть сгенерирован для включения этого требования? Кроме того, я должен был упомянуть в вопросе: я использую серверную часть xsl, поэтому я свободен для использования версии 2.0 и выше. Есть ли какие-либо новые функции, которые могут помочь? – eisenpony

+0

Мне удалось изменить команду ключа, чтобы создать его индекс, проверив для дочернего элемента locationNumber или предка с дочерним элементом locationNumber '' – eisenpony

+0

** 1. ** Я не знаю точно, как это работает «под капотом». Я считаю, что он создает индекс, и на практике это намного быстрее - AFAIK, это справедливо для практически любого процессора. , Какой конкретный процессор вы используете? ** 2. ** Я отредактировал свой ответ, чтобы расширить ключ к местоположению покрытия предков (очень похоже на ваше собственное решение). ** 3. ** Я не вижу, что использование XSLT 2.0 было бы иным. –

0

Я не уверен, если этот подход достаточно эффективен - с помощью шаблона, который соответствует каждому location с number сопрягать locationNumber дочерний элемент locationSplits

<xsl:template match="location[number=//coverage//locationNumber]"> 

и второй шаблон, соответствующий любой другой location, то следующий XSLT

<?xml version="1.0" encoding="UTF-8"?> 
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> 
    <xsl:output method="xml" omit-xml-declaration="yes" encoding="UTF-8" indent="yes" /> 
    <xsl:strip-space elements="*" /> 
    <xsl:template match="insuranceCoverages"> 
     <xsl:apply-templates /> 
    </xsl:template> 
    <xsl:template match="@*|node()"> 
     <xsl:apply-templates select="@*|node()" /> 
    </xsl:template> 
    <xsl:template match="location[number=//coverage//locationNumber]"> 
     <xsl:variable name="number" select="number" /> 
     <xsl:text>Location</xsl:text> 
     <xsl:value-of select="$number" /> 
     <xsl:text>Coverage:</xsl:text> 
     <xsl:value-of select="//locationSplits//coverage[locationNumber=$number]/name" /> 
     <xsl:for-each select="//locationSplits//coverage[locationNumber=$number]/clauses/coverage"> 
     <xsl:text>Coverage:</xsl:text> 
     <xsl:value-of select="name" /> 
    </xsl:for-each> 
    </xsl:template> 
    <xsl:template match="location"> 
    <xsl:variable name="number" select="number" /> 
    <xsl:text>Location</xsl:text> 
    <xsl:value-of select="$number" /> 
    <xsl:text>No Location</xsl:text> 
    <xsl:for-each select="//insuranceCoverages//coverage[not(ancestor::locationSplits)]"> 
     <xsl:text>Coverage:</xsl:text> 
     <xsl:value-of select="name" /> 
    </xsl:for-each> 
    </xsl:template> 
</xsl:stylesheet> 

применительно к вашей входной XML производит вывод

Location 1 
Coverage: Building Coverage 
Coverage: Earthquake Exclusion 
Location 2 
No Location 
Coverage: General Coverage 
Coverage: General Liability 

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

<xsl:for-each select="//insuranceCoverages//coverage[not(ancestor::locationSplits)]"> 
+0

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

Смежные вопросы