На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
Дорогие друзья! Поздравляем вас с днём Победы!
msm.ru
Модераторы: ANDLL
  
> Интересная задачка по XSL , хитро отформатировать xml-ку
    Добрый день, форумчане!
    Только зарегистрировался, встречайте новичка! :thanks:

    Собственно, интересный, на мой взгляд, сабж.
    Есть xml-файл с примерно следующей разметкой:
    ExpandedWrap disabled
      <?xml version="1.0" encoding="UTF-8"?>
      <intermediate name="Simple" package="simple">
         <type wireName="point"  name="Point"
               package="simple.data"
               isAbstract="no"
               base="Element">
            <field name="lat" type="double" isOptional="yes" isArray="no" wireName="lat"
                    isSimple="yes"/>
            <field name="lon" type="double" isOptional="yes" isArray="no" wireName="lon"
                    isSimple="yes"/>
            <field name="lur" type="double" isOptional="yes" isArray="no" wireName="lur"
                    isSimple="yes"/>
            <field name="lit" type="double" isOptional="no" isArray="no" wireName="lit"
                    isSimple="yes"/>
         </type>
      </intermediate>

    Необходимо из него вывести текстовый файл с примерно таким содержанием:
    ExpandedWrap disabled
       - point: [lat, lon, lur, lit]

    , НО!!!
    Не совсем так. Строчек point должно быть столько, сколько есть всевозможных комбинаций опциональных параметров (isOptional="yes") + обязательных (isOptional="no"). То есть точно вывод должен быть такой:
    ExpandedWrap disabled
       - point: [lat, lon, lur, lit]
       - point: [lon, lur, lit]
       - point: [lat, lur, lit]
       - point: [lat, lon, lit]
       - point: [lat, lit]
       - point: [lon, lit]
       - point: [lur, lit]

    или в другом порядке, это неважно. Строки потом можно отформатировать.
    Последнее, что пришло в голову - это следующее:
    ExpandedWrap disabled
      <?xml version="1.0" encoding="UTF-8"?>
      <xsl:stylesheet version="2.0"
          xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format"
          xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions"
          xmlns:navbuilder="http://www.navbuilder.com">
          <xsl:output method="text" omit-xml-declaration="yes" indent="yes" />
          <xsl:strip-space elements="*" />
       
          <xsl:template match="/">
              <xsl:apply-templates />
              <xsl:call-template name="recursive" />
          </xsl:template>
       
          <xsl:template name = "recursive" match="package">
              <xsl:apply-templates />
          </xsl:template>
       
          <xsl:template match="type">
              <xsl:text> - </xsl:text>
              <xsl:value-of select="@wireName" />
              <xsl:text>: [</xsl:text>
              <xsl:for-each select="field">
                  <xsl:sort select="@wireName" data-type="text" order="ascending"/>
                  <xsl:if test="@isSimple = 'yes'">
                  <xsl:choose>
                      <xsl:when test="@isOptional = 'yes'">
                          <xsl:value-of select="@wireName" />
                          <xsl:text>]</xsl:text>
                          <xsl:text>&#xa;</xsl:text>
                          <xsl:call-template name="recursive" />
                      </xsl:when>
                      <xsl:otherwise>
                          <xsl:value-of select="@wireName" />
                          <xsl:if test="position() != last()">
                              <xsl:text>, </xsl:text>
                          </xsl:if>
                      </xsl:otherwise>
                  </xsl:choose>
                  </xsl:if>
              </xsl:for-each>
              <xsl:text>]</xsl:text>
              <xsl:text>&#xa;</xsl:text>
          </xsl:template>
      </xsl:stylesheet>

    В этом шаблоне я заодно сортирую атрибуты в строчке.
    Но это все равно должным образом не работает. Кто-нибудь может помочь? Вообще, обход всевозможных вариантов средством xsl возможен? Бьюсь с этим уже неделю. Боюсь, что скоро мысли иссякнут.
    П.С. Сильно не пинайте, если шаблон кривой. Я в этом новичок, но к сожалению, приходится уже решать нетривиальные задачи.
      ExpandedWrap disabled
        <?xml version="1.0" encoding="UTF-8"?>
        <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:template match="/">
                <xsl:apply-templates select="intermediate/type"/>
            </xsl:template>
         
            <xsl:template match="type">
                <xsl:apply-templates select="field[1]" mode="recursive-combine">
                    <xsl:with-param name="wire-name" select="@wireName"/>
                </xsl:apply-templates>
            </xsl:template>
            
            <xsl:template match="field" mode="recursive-combine">
                <xsl:param name="accumulated-string"/>
                <xsl:param name="wire-name"/>
                <xsl:param name="hidden" select="false()"/>  <!-- в первый раз никто не скрыт, то есть опциональный field будет выведен -->
         
                <xsl:variable name="is-last" select="not(following-sibling::field)"/>
                <xsl:variable name="comma" select="substring(', ', 1 div (not($hidden) and not($is-last)) )"/> <!-- это - сильное колдунство -->
                <xsl:variable name="new-accumulated" select="concat($accumulated-string, @name[not($hidden)], $comma)"/>
         
                <xsl:if test="not($is-last)">
                    <xsl:apply-templates select="following-sibling::field[1]" mode="recursive-combine">
                        <xsl:with-param name="accumulated-string" select="$new-accumulated"/>
                        <xsl:with-param name="wire-name" select="$wire-name"/>
                    </xsl:apply-templates>
                </xsl:if>
         
                <xsl:apply-templates select="current()[$is-last]" mode="output">
                    <xsl:with-param name="accumulated-string" select="$new-accumulated"/>
                    <xsl:with-param name="wire-name" select="$wire-name"/>
                </xsl:apply-templates>
         
                <xsl:apply-templates select="current()[@isOptional='yes'][not($hidden)]" mode="recursive-combine">
                    <xsl:with-param name="hidden" select="true()"/>
                    <xsl:with-param name="accumulated-string" select="$accumulated-string"/>
                    <xsl:with-param name="wire-name" select="$wire-name"/>
                </xsl:apply-templates>
            </xsl:template>
         
            <xsl:template match="*" mode="recursive-combine"/>
         
            <!-- жрёт всё подряд, потому что я люблю пихать условия в виде current()[$is-last]. можно заменить на именованый шаблон и xsl:if -->
            <xsl:template match="*" mode="output">
                <xsl:param name="accumulated-string"/>
                <xsl:param name="wire-name"/>
         
                <!-- вывод в таком виде, потому что на выходе всё-таки XML. переделывай как хочешь. -->
                <entry value="{concat('- ', $wire-name, ': [', $accumulated-string, ']')}"/>
            </xsl:template>
        </xsl:stylesheet>

      По-хорошему надо оперировать не строками, а node-set'ами, запятые тыкать в процессе вывода, а не обхода. Это тебе будет домашняя работа - переделать недолго.
        ss, большое спасибо! Прям огромное. То, что нужно.
        Переделал вывод в текстовый формат - теперь совсем хорошо. Для этого заменил последний темплейт на:
        ExpandedWrap disabled
            <xsl:template match="*" mode="output">
               <xsl:param name="accumulated-string"/>
               <xsl:param name="wire-name"/>
           
               <xsl:value-of select="concat('- ', $wire-name, ': [', $accumulated-string, ']', '&#xa;')" />
               </xsl:template>

        Ну и вначале поставил тип вывода на текстовый. В общем, это было несложно :).
        Еще вопрос: а как теперь выводить только те элементы, у которых isSimple = "yes"? Куда надо добавить это условие?
        Всего третий день изучаю xsl. Как же это сложно. Мне кажется, что я никогда это не пойму :no-sad:.

        И еще: может дадите какие-нибудь рекомендации по изучению? Чувствую, что андроид-программисту по текущей ситуации еще долго придется разбираться в xsl :). Книжки, задания, справочники? Кто по чему изучал?
        Спасибо!

        Добавлено
        ss, скажи пожалуйста, а нужна зачем строчка
        ExpandedWrap disabled
            <xsl:template match="*" mode="recursive-combine"/>

        Как я понимаю, этот шаблон ничего не делает. Зачем тогда он?
          Цитата tomboy1 @
          как теперь выводить только те элементы, у которых isSimple = "yes"

          ExpandedWrap disabled
             <xsl:template match="field" mode="recursive-combine">
          заменить на
          ExpandedWrap disabled
             <xsl:template match="field[@isOptional='yes']" mode="recursive-combine">

          Цитата tomboy1 @
          Всего третий день изучаю xsl. Как же это сложно. Мне кажется, что я никогда это не пойму
          Это называется "высокий порог вхождения". Входи - понравится :lol:

          Добавлено
          Цитата tomboy1 @
          Как я понимаю, этот шаблон ничего не делает. Зачем тогда он?
          Он отлавливает остатки.
          Даже если ты такой шаблон не напишешь, "всё остальное", что ты явно не match'ишь своими шаблонами, будет поймано built-in шаблоном (одним из), а они только и делают, что в итоге выводят всё (что могут) в output. Как правило, такой вывод не нужен.
            Тогда скорее на
            ExpandedWrap disabled
              <xsl:template match="field[@isSimple='yes']" mode="recursive-combine">
            ,
            а не на
            ExpandedWrap disabled
              <xsl:template match="field[@isOptional='yes']" mode="recursive-combine">

            Идею понял. Спасибо еще раз!
              Ну да. Невнимательно прочитал.
                Ан нет, не совсем так.
                Если элемент isSimple = 'yes', то он выводится. Если isSimple = 'no', то он не выводится, но вместо него выводятся пустые скобки.
                Т.е. для xml:
                ExpandedWrap disabled
                  <?xml version="1.0" encoding="UTF-8"?>
                  <intermediate name="simple" package="protocol">
                     <type wireName="point" name="Point">
                        <field name="lat" type="double" isOptional="yes" isArray="no" wireName="lat" isSimple="yes"/>
                        <field name="lon" type="double" isOptional="yes" isArray="no" wireName="lon" isSimple="yes"/>
                        <field name="lur" type="double" isOptional="yes" isArray="no" wireName="lur" isSimple="yes"/>
                        <field name="lit" type="double" isOptional="no"  isArray="no" wireName="lit" isSimple="yes"/>
                     </type>
                     <type wireName="box" name="Box" >
                        <field name="rightCornerPoint" type="Point" isOptional="no" wireName="point" isSimple="no"/>
                        <field name="leftCornerPoint"  type="Point" isOptional="no" wireName="point" isSimple="no"/>
                     </type>
                  </intermediate>

                вывод должен быть таким:
                ExpandedWrap disabled
                  - point: [lat, lon, lur, lit]
                  - point: [lat, lon, lit]
                  - point: [lat, lur, lit]
                  - point: [lat, lit]
                  - point: [lon, lur, lit]
                  - point: [lon, lit]
                  - point: [lur, lit]
                  - point: [lit]
                  - box: []

                а не таким:
                ExpandedWrap disabled
                  - point: [lat, lon, lur, lit]
                  - point: [lat, lon, lit]
                  - point: [lat, lur, lit]
                  - point: [lat, lit]
                  - point: [lon, lur, lit]
                  - point: [lon, lit]
                  - point: [lur, lit]
                  - point: [lit]

                Если добавить условие [@isOptional='yes']в строчку
                ExpandedWrap disabled
                  <xsl:template match="field" mode="recursive-combine">

                то эти поля просто будут пропускаться. Нужно, чтобы эти поля не пропускались, но выводились пустые скобки.
                  ExpandedWrap disabled
                    <xsl:variable name="comma" select="substring(', ', 1 div (not($hidden) and not($is-last)) )"/>
                    <xsl:variable name="new-accumulated" select="concat($accumulated-string, @name[not($hidden)], $comma)"/>

                  ExpandedWrap disabled
                    <xsl:variable name="is-simple" select="@isSimple='yes'"/>
                    <xsl:variable name="comma" select="substring(', ', 1 div (not($hidden) and not($is-last) and $is-simple) )"/>
                    <xsl:variable name="new-accumulated" select="concat($accumulated-string, @name[not($hidden) and $is-simple], $comma)"/>

                  Не проверял.
                    Сделал так (последний шаблон):
                    ExpandedWrap disabled
                          <!-- output template -->
                          <xsl:template match="field" mode="output">
                              <xsl:param name="accumulated-string"/>
                              <xsl:param name="wire-name"/>
                              <xsl:choose>
                                  <xsl:when test="@isSimple = 'yes'">
                              <!-- output string -->
                                      <xsl:value-of select="concat('- ', $wire-name, ': [', $accumulated-string, ']', '&#xa;')" />
                                  </xsl:when>
                                  <xsl:otherwise>
                                      <xsl:value-of select="concat('- ', $wire-name, ': [', ']', '&#xa;')" />
                                  </xsl:otherwise>
                              </xsl:choose>
                          </xsl:template>

                    Вроде работает :yes:
                      Цитата tomboy1 @
                      Вроде работает
                      Неправильно.
                        Цитата ss @
                        Неправильно.

                        Да, и правда неправильно.

                        Цитата ss @
                        Не проверял.

                        В этом случае выводятся несколько элементов, у которых атрибуты isOptional="yes" и "isSimple=no".
                        Т.е. для xml:
                        ExpandedWrap disabled
                             <type wireName="content-path" name="ContentPath">
                                <field name="startPoint"        type="Point"      isOptional="yes" wireName="start-point"         isSimple="no"/>
                                <field name="endPoint"          type="Point"      isOptional="yes" wireName="end-point"           isSimple="no"/>
                                <field name="pathFileID"        type="String"     isOptional="no"  wireName="id"                  isSimple="yes"/>
                                <field name="routeSplinePacked" type="BinaryData" isOptional="no"  wireName="route-spline-packed" isSimple="yes"/>
                             </type>

                        вывод будет таким:
                        ExpandedWrap disabled
                          - content-path: [pathFileID, routeSplinePacked]
                          - content-path: [pathFileID, routeSplinePacked]
                          - content-path: [pathFileID, routeSplinePacked]
                          - content-path: [pathFileID, routeSplinePacked]
                        ,
                        а должен быть таким:
                        ExpandedWrap disabled
                          - content-path: [pathFileID, routeSplinePacked]
                          ExpandedWrap disabled
                            <xsl:apply-templates select="current()[@isOptional='yes'][not($hidden)]" mode="recursive-combine">

                          ExpandedWrap disabled
                            <xsl:apply-templates select="current()[@isOptional='yes'][not($hidden)][$is-simple]" mode="recursive-combine">


                          Добавлено
                          та ещё каша
                            Цитата ss @
                            та ещё каша

                            Да уж.

                            ss, большое спасибо! Теперь, вроде, все правильно.
                              Ан нет, и сейчас немного неправильно.
                              Смысл вот в чем. Если последний атрибут сделать опциональным, то будет лишняя запятая в конце. Т.е. вот для такой xml:
                              ExpandedWrap disabled
                                <type wireName="point">
                                    <field wireName="lat" isOptional="yes" isSimple="yes"/>
                                    <field wireName="lit" isOptional="no" isSimple="yes"/>
                                    <field wireName="lon" isOptional="yes" isSimple="yes"/>
                                    <field wireName="lur" isOptional="yes" isSimple="yes" name="lur"/>
                                </type>
                              Вывод будет таким:
                              ExpandedWrap disabled
                                 - point: [lat, lit, lon, lur]
                                 - point: [lat, lit, lon, ]
                                 - point: [lat, lit, lur]
                                 - point: [lat, lit, ]
                                 - point: [lit, lon, lur]
                                 - point: [lit, lon, ]
                                 - point: [lit, lur]
                                 - point: [lit, ]

                              Есть запятая, например, во второй строчке перед закрывающейся скобкой. Она лишняя. Чтобы ее убрать, написал такую переменную:
                              После строчки
                              ExpandedWrap disabled
                                <xsl:variable name="is-last" select="not(following-sibling::field)"/>
                              добавил
                              ExpandedWrap disabled
                                <xsl:variable name="is-last-optional" select="(not($is-last) and following-sibling::field[$is-last][@isOptional='yes'][not($hidden)])"/>
                              и использовал ее здесь:
                              ExpandedWrap disabled
                                <xsl:variable name="comma" select="substring(', ', 1 div (not($hidden) and not($is-last) and $is-simple and is-last-optional))"/>

                              Не работает. Он вообще не ставит запятые.
                              По логике, если мы имеем предпоследний элемент, а последний у нас опциональный и скрытый, то мы не должны ставить запятую. Вроде так и написал, но не работает.
                              ss, добрый человек, помоги :-?
                                Помогу послезавтра.
                                  ExpandedWrap disabled
                                    <?xml version="1.0" encoding="UTF-8"?>
                                    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
                                      <xsl:output method="text" version="1.0" encoding="UTF-8" indent="yes"/>
                                      
                                      <xsl:template match="/">
                                         <xsl:apply-templates select="intermediate/type"/>
                                      </xsl:template>
                                     
                                      <xsl:template match="type">
                                         <xsl:apply-templates select="field[1]" mode="recursive-combine">
                                            <xsl:with-param name="wire-name" select="@wireName"/>
                                         </xsl:apply-templates>
                                      </xsl:template>
                                      
                                      <xsl:template match="field[@isSimple='yes']" mode="recursive-combine">
                                        <xsl:param name="accumulated" select="node()"/>
                                        <xsl:param name="wire-name"/>
                                        <xsl:param name="hidden" select="false()"/>  <!-- в первый раз никто не скрыт, то есть опциональный field будет выведен -->
                                     
                                        <xsl:variable name="is-last" select="not(following-sibling::field)"/>
                                        <xsl:variable name="is-simple" select="@isSimple='yes'"/>
                                        <xsl:variable name="new-accumulated" select="$accumulated | current()[not($hidden) and $is-simple]"/>
                                     
                                         <xsl:if test="not($is-last)">
                                            <xsl:apply-templates select="following-sibling::field[1]" mode="recursive-combine">
                                               <xsl:with-param name="accumulated" select="$new-accumulated"/>
                                               <xsl:with-param name="wire-name" select="$wire-name"/>
                                            </xsl:apply-templates>
                                         </xsl:if>
                                     
                                         <xsl:apply-templates select="current()[$is-last]" mode="output">
                                            <xsl:with-param name="accumulated" select="$new-accumulated"/>
                                            <xsl:with-param name="wire-name" select="$wire-name"/>
                                         </xsl:apply-templates>
                                     
                                        <xsl:apply-templates select="current()[@isOptional='yes'][not($hidden)][$is-simple]" mode="recursive-combine">
                                            <xsl:with-param name="hidden" select="true()"/>
                                            <xsl:with-param name="accumulated" select="$accumulated"/>
                                            <xsl:with-param name="wire-name" select="$wire-name"/>
                                        </xsl:apply-templates>
                                      </xsl:template>
                                     
                                      <xsl:template match="*" mode="recursive-combine"/>
                                     
                                      <xsl:template match="field" mode="output">
                                           <xsl:param name="accumulated"/>
                                           <xsl:param name="wire-name"/>
                                     
                                           <xsl:value-of select="concat('- ', $wire-name, ': [')"/>
                                           <xsl:for-each select="$accumulated">
                                               <xsl:value-of select="concat( substring(', ', 1 div (not(position()=1)) ), @name )" />
                                           </xsl:for-each>
                                           <xsl:value-of select="concat(']', '&#xa;')" />
                                       </xsl:template>
                                    </xsl:stylesheet>

                                  1. нафиг чойз в выводе, если до вывода доходят только простые поля?
                                  2. переделал на ноде-сеты (смотри внимательно на парметр accumulated), потому нужно допилить под твой парсер - каждый по-своему с сетами работает.
                                    В строке 15:
                                    ExpandedWrap disabled
                                       <xsl:template match="field[@isSimple='yes']" mode="recursive-combine">

                                    убрал [@isSimple='yes'], чтобы были выведены пустые элементы. Т.е. для
                                    ExpandedWrap disabled
                                        <type wireName="box" name="Box" >
                                           <field name="rightCornerPoint" type="Point" isOptional="no" wireName="point" isSimple="no"/>
                                           <field name="leftCornerPoint"  type="Point" isOptional="no" wireName="point" isSimple="no"/>
                                        </type>
                                    был такой вывод:
                                    ExpandedWrap disabled
                                      - box: []

                                    Все остальное, вроде, правильно. Еще раз большое спасибо!!!
                                    0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                    0 пользователей:


                                    Рейтинг@Mail.ru
                                    [ Script execution time: 0,0600 ]   [ 15 queries used ]   [ Generated: 9.05.24, 19:40 GMT ]