tags:

views:

519

answers:

6

Hi, everyone:

Here is a simplified version of my source xml document:

<eml> 
<additionalMetadata>
    <describes>sbclter.380</describes>
    <access authSystem="knb" order="denyFirst">
      <allow>
        <principal>public</principal>
        <permission>all</permission>
      </allow>
    </access>
  </additionalMetadata>

  <additionalMetadata>
    <describes>sbclter.415</describes>
    <describes>sbclter.380</describes>
      <access authSystem="knb" order="allowFirst">
        <allow>
          <principal>public</principal>
          <permission>all</permission>
        </allow>
      </access>
  </additionalMetadata>

  <additionalMetadata>
        <describes>sbclter.415</describes>
        <access authSystem="knb" order="allowFirst">
            <allow>
              <principal>public</principal>
              <permission>all</permission>
            </allow>
          </access>
    </additionalMetadata>
 <eml>

I want to select a node set of element "describes" - its value shows in more than one addtionalMetadata block and its "access" sibling has different order: "denyFirst" and "allowFirst".

In above example, the "describes" with value sbclter.380 is the case. Since it shows in two additionalMetadata block, the first one has "denyFirst" in access sibling, but the second one has "allowFirst" in access sibling. sbclter.415 would NOT be the case. Since it shows up in two additionalMetadata block, but both access siblings has the same value "allowFirst" at the attribute "order".

After transformation, i want get:

<eml> 
    <additionalMetadata>
        <describes>sbclter.380</describes>
        <access authSystem="knb" order="denyFirst">
          <allow>
            <principal>public</principal>
            <permission>all</permission>
          </allow>
        </access>
      </additionalMetadata>

      <additionalMetadata>
        <describes>sbclter.380</describes>
          <access authSystem="knb" order="allowFirst">
            <allow>
              <principal>public</principal>
              <permission>all</permission>
            </allow>
          </access>
      </additionalMetadata>

      <additionalMetadata>
            <access authSystem="knb" order="allowFirst">
                <allow>
                  <principal>public</principal>
                  <permission>all</permission>
                </allow>
              </access>
        </additionalMetadata>
     <eml>

Here is my node:

   <xsl:for-each select="/*/*">
        <xsl:choose>
           <xsl:when test="name()='additionalMetadata'">
            <xsl:call-template name="handle-describe-access-in-additional-metadata">
                <xsl:with-param name="describes-list" select="./describes[//additionalMetadata[describes = . and access[@order='allowFirst']] and //additionalMetadata[describes = . and access[@order='denyFirst']]]"
          ></xsl:with-param>
              ></xsl:with-param>
            </xsl:call-template>
          </xsl:when>
        </xsl:choose>
      </xsl:for-each>

However,it doesn't work. The xpath in "xsl:with-param" picked up nothing - no sbclter.415 either sbclter.380. Do you have any suggestion? Thank you so much!

Note: this is simplified version of code.

A: 

I would try making a variable of the current describes node and using it instead of referencing . in your XPath subexpressions.

Because, for example, in the statement [//additionalMetadata[describes = . , the . is referenceing the additionalMetadata node, not the describes node.

Welbog
A: 

Good point. However, I think select="./describes" move the current node to describes. I have some other node there (i didn't paste). It uses "." too. Obiviously, the "." points to describes in it. Here is my code:

<xsl:with-param name="describes-list"
                select="./describes[(not(//physical/distribution/@id =.) and not(//software/implementation/distribution/@id = .)]"
              ></xsl:with-param>

Thanks again for the help!

Do you have more suggestion?

In this example, there are no nested conditions, so the . refers to the outer describes node. In the question's example, the conditions are nested and I don't believe . refers to the topmost node, but rather the node being filtered by the condition.
Welbog
Thank you, Welbog! This is good a point. However, I want to get describes list which match my requirement. So using a variable of the current describes node doesn't work. The reason is my code will do different action if the list is empty or not. Do you have more suggestion? Thanks!
+1  A: 

Here is one transformation that produces the wanted result:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
 <xsl:output omit-xml-declaration="yes"/>
<!--                                                --> 
 <xsl:key name="kDescrByVal" match="describes"
  use="."/>
<!--                                                --> 
    <xsl:template match="node()|@*" name="identity">
      <xsl:copy>
         <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>
    </xsl:template>
<!--                                                --> 
    <xsl:template match="describes">
      <xsl:if test=
       "count(key('kDescrByVal', .)) > 1
       and
        following-sibling::access/@order
       !=
        key('kDescrByVal', .)/following-sibling::access/@order
       ">
       <xsl:call-template name="identity"/>
      </xsl:if>
    </xsl:template>
</xsl:stylesheet>

When applied to the provided XML document (corrected to be well-formed):

<eml>
    <additionalMetadata>
     <describes>sbclter.380</describes>
     <access authSystem="knb" order="denyFirst">
      <allow>
       <principal>public</principal>
       <permission>all</permission>
      </allow>
     </access>
    </additionalMetadata>
    <additionalMetadata>
     <describes>sbclter.415</describes>
     <describes>sbclter.380</describes>
     <access authSystem="knb" order="allowFirst">
      <allow>
       <principal>public</principal>
       <permission>all</permission>
      </allow>
     </access>
    </additionalMetadata>
    <additionalMetadata>
     <describes>sbclter.415</describes>
     <access authSystem="knb" order="allowFirst">
      <allow>
       <principal>public</principal>
       <permission>all</permission>
      </allow>
     </access>
    </additionalMetadata>
</eml>

the wanted result is produced:

<eml>
    <additionalMetadata>
     <describes>sbclter.380</describes>
     <access authSystem="knb" order="denyFirst">
      <allow>
       <principal>public</principal>
       <permission>all</permission>
      </allow>
     </access>
    </additionalMetadata>
    <additionalMetadata>

     <describes>sbclter.380</describes>
     <access authSystem="knb" order="allowFirst">
      <allow>
       <principal>public</principal>
       <permission>all</permission>
      </allow>
     </access>
    </additionalMetadata>
    <additionalMetadata>

     <access authSystem="knb" order="allowFirst">
      <allow>
       <principal>public</principal>
       <permission>all</permission>
      </allow>
     </access>
    </additionalMetadata>
</eml>

Do note the use of:

  1. Keys.

  2. The Xpath "!=" operator.

Dimitre Novatchev
+1 This answers the original question and if you add a template for additionalMetadata with a similar condition to the one in the describes template then this would answer the revised question.
mikemay
A: 

Elaborating on what I said in my comment to your answer, here's an example that demonstrates what I mean:

Using this XML file as a source,

<Header>
  <thing>
    <child>5</child>
  </thing>
  <thing>
    <child>6</child>
  </thing>
  <thing>
    <child>7</child>
  </thing>
  <thang>
    <children>5</children>
  </thang>
</Header>

With this XSLT file:

<xsl:template match="/Header">
    <xsl:value-of select="./thing[//thang[children = ./child]]"/>
</xsl:template>

If the . refered to thing in this example, the output would be 5, but since . refers to thang, there is no output. If there were a variable that contained thing, then it would work:

  <xsl:template match="/Header">
    <xsl:variable name="thing" select="./thing"/>
    <xsl:value-of select="./thing[//thang[children = $thing/child]]"/>
  </xsl:template>

You're in the same situation. Your . is referencing something other than what you think it is.

Welbog
A: 

hi, dear Dimitre Novatchev:

Thank you so much for you kind answer. Actually, the result I want is:

<eml>
    <additionalMetadata>
        <describes>sbclter.380</describes>
        <access authSystem="knb" order="denyFirst">
                <allow>
                        <principal>public</principal>
                        <permission>all</permission>
                </allow>
        </access>
    </additionalMetadata>
    <additionalMetadata>

        <describes>sbclter.380</describes>
        <access authSystem="knb" order="allowFirst">
                <allow>
                        <principal>public</principal>
                        <permission>all</permission>
                </allow>
        </access>
    </additionalMetadata>

</eml>

You can see: the last additionalMetadata was removed since there is no describes left.

This is the reason I want to get describes list. then call the template:

<xsl:template name="handle-describe-access-in-additional-metadata">
    <xsl:param name="describes-list"></xsl:param>
    <xsl:choose>
      <xsl:when test="count($describes-list)=0">
        <!--Scenario 2: do nothing since all desribes are reference to physical/distribtuion or software/implmentation/distribution-->
      </xsl:when>
      <xsl:otherwise>
        <!--Scenario 3: some desribes we need to keep them-->
        <additionalMetadata>
          <xsl:call-template name="recursive-describes">
            <xsl:with-param name="describes" select="$describes-list"></xsl:with-param>
          </xsl:call-template>
          <metadata>
            <xsl:apply-templates mode="copy-no-ns" select="./access | ./metadata/access"
            ></xsl:apply-templates>
          </metadata>
        </additionalMetadata>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
<xsl:template name="recursive-describes">
    <xsl:param name="describes"></xsl:param>
    <xsl:param name="index" select="1"></xsl:param>
    <xsl:choose>
      <!--finish-->
      <xsl:when test="$index > count($describes)">
        <!-- do nothing-->
      </xsl:when>
      <xsl:otherwise>
        <describes>
          <xsl:value-of select="$describes[$index]"></xsl:value-of>
        </describes>
        <xsl:call-template name="recursive-describes">
          <xsl:with-param name="describes" select="$describes"></xsl:with-param>
          <xsl:with-param name="index" select="$index + 1"></xsl:with-param>
        </xsl:call-template>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

<xsl:template mode="copy-no-ns" match="*">

    <xsl:element name="{name(.)}" namespace="{namespace-uri(.)}">
            <xsl:copy-of select="@*"></xsl:copy-of>
            <xsl:apply-templates mode="copy-no-ns"></xsl:apply-templates>
        </xsl:element>

</xsl:template>
It is not good to add additional info in an answer to your own question -- better comment the answer of the person you want to communicate with. Even better, edit your question -- right now my answer produces exactly the result you specified as correct. Think well before specifying the question.
Dimitre Novatchev
Continued: If you change the question many times it will automatically be tagged as "community-wiki" and not many people would wish to answer it.
Dimitre Novatchev
Dear Dimitre Novatchev: Thank you for the tip. The reason i didn't adding comment is the comment is too short and couldn't have a nice looking for code.
A: 

Dear Welbog:

Thank you for the suggestion. I changed the code to :

<xsl:variable name="thing" select="./describes"/>
<xsl:call-template name="handle-describe-access-in-additional-metadata">
     <xsl:with-param name="describes-list" select="./describes[//additionalMetadata[describes = $thing and access[@order='allowFirst']] and //additionalMetadata[describes = $thing and access[@order='denyFirst']]]"
              ></xsl:with-param>
</xsl:call-template>

However, the describes list will contain both sbclter.380 and sbclter.415 :(

Any suggestion? Thank you so much!