How to use XPath and XSLT to process a set of sibling nodes and handle a specific subset of siblings based on two of the siblings

匿名 (未验证) 提交于 2019-12-03 09:06:55

问题:

I have a group of sibling elements in an xml document that need to be processed with XSLT into a table, actually I'm using Apache FOP to transform it into a pdf. Table rows need to be created whenever one of two types of elements are encountered. The cells in the row consist of the element that causes the row to be created in the first cell and then the following siblings in following cells until the next element that causes a row to be created. Here is some example xml to explain it better:

    <pre class="prettyprint"><code class="language-xml">         <reqpers>           <person man="Four"/>            <person man="A" id="pers_a"/>           <perscat category="Recovery Supervisor"/>            <person man="B"  id="pers_b"/>           <perscat category="Ground Personnel"/>            <asrequir/>           <perscat category="As Required Category"/>           <trade>Bill Collector</trade>            <person man="C"  id="pers_c"/>           <perscat category="Ground Personnel"/>           <perskill skill="sk01"/>           <trade>welder</trade>           <esttime>.5 hr</esttime>            <asrequir/>           <perscat category="2nd Required Category"/>           <esttime>4 days</esttime>            <person man="D"  id="pers_d"/>           <perscat category="Rides in Chase Vehicle"/>           <perskill skill="sk02"/>            <person man="E"/>           <perscat category="Jack of all Trades"/>           <trade>engine mechanic</trade>     </reqpers>    </code>    </pre> 

A row needs to be created for each person or asrequir element, then the cells for the row filled with the group of siblings between these elements. I have looked at a lot of examples including: How to select siblings

and this one:How to select group of siblings

as well as many other's. None of these deal with my specific problem set, which is primarily the fact that there are two following-siblings to deal with in the XPath query. Here is an example of what the table should look like:

    <pre class="prettyprint"><code class="language-xml">     <table>         <table-header>             <table-row>                 <table-cell> Person </table-cell>                 <table-cell> Category/Trade </table-cell>                 <table-cell> Skill level </table-cell>                 <table-cell> Trade code </table-cell>                 <table-cell> Estimated time </table-cell>             </table-row>         </table-header>         <table-body>             <table-row>                 <table-cell>Four</table-cell>                 <table-cell/>                 <table-cell/>                 <table-cell/>                 <table-cell/>             </table-row>             <table-row>                 <table-cell>A</table-cell>                 <table-cell>Recovery Supervisor</table-cell>                 <table-cell/>                 <table-cell/>                 <table-cell/>             </table-row>             <table-row>                 <table-cell>B</table-cell>                 <table-cell>Ground Personnel</table-cell>                 <table-cell/>                 <table-cell/>                 <table-cell/>             </table-row>             <table-row>                 <table-cell>As required</table-cell>                 <table-cell/>                 <table-cell/>                 <table-cell>Bill Collector</table-cell>                 <table-cell/>             </table-row>             <table-row>                 <table-cell>C</table-cell>                 <table-cell>Ground Personnel</table-cell>                 <table-cell>skill gets converted to string value</table-cell>                 <table-cell>welder</table-cell>                 <table-cell>.5 hr</table-cell>             </table-row>             <table-row>                 <table-cell>As required</table-cell>                 <table-cell>2nd Required Category</table-cell>                 <table-cell/>                 <table-cell/>                 <table-cell>4 days</table-cell>             </table-row>             <table-row>                 <table-cell>D</table-cell>                 <table-cell>Rides in Chase Vehicle</table-cell>                 <table-cell>skill level</table-cell>                 <table-cell/>                 <table-cell/>             </table-row>             <table-row>                 <table-cell>E</table-cell>                 <table-cell>Jack of all Trades</table-cell>                 <table-cell/>                 <table-cell>engine mechanic</table-cell>                 <table-cell/>             </table-row>         </table-body>     </table> </code>        </pre> 

Also for completeness this is the schema snippet for the xml I'm processing:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" <xs:element name="reqpers" type="reqpersType"/> <xs:complexType name="reqpersType">      <xs:sequence>         <xs:element minOccurs="0" ref="applic"/>         <xs:sequence maxOccurs="unbounded">             <xs:choice>                 <xs:element ref="asrequir"/>                 <xs:element ref="person"/>             </xs:choice>             <xs:sequence minOccurs="0">                 <xs:element ref="perscat"/>                 <xs:element minOccurs="0" ref="perskill"/>                 <xs:element minOccurs="0" ref="trade"/>                 <xs:element minOccurs="0" ref="esttime"/>             </xs:sequence>         </xs:sequence>     </xs:sequence>     <xs:attribute ref="refapplic"/>     <xs:attributeGroup ref="bodyatt"/>     <xs:attributeGroup ref="cntlcontent"/> </xs:complexType> 

Here is an example of the xsl code that is the closest I've come, but it only works if there are person siblings, once the asrequir are added things fall apart. I basically repeat this code for each of the cells, replacing the perscat with the element I'm expecting.

    <xsl:for-each select="person | asrequir">             <fo:table-row>                 <fo:table-cell text-align="left" padding-before="1mm" padding-after="1mm" padding-left="1mm" padding-right="1mm">                     <fo:block font-size="10pt">                         <xsl:value-of select="self::person/@man"/>                     </fo:block>                 </fo:table-cell>     <fo:table-cell text-align="left" padding-before="1mm" padding-after="1mm" padding-left="1mm" padding-right="1mm">         <xsl:variable name="perscatSib" select="following-sibling::perscat"/>         <xsl:variable name="perscatPrec" select="following-sibling::person[1]/preceding-sibling::perscat"/>         <fo:block font-size="10pt">             <xsl:choose>                 <xsl:when test="$perscatSib[count(. | $perscatPrec) = count($perscatPrec)]">                     <xsl:value-of select="$perscatSib[count(. | $perscatPrec) = count($perscatPrec)]/@category"/>                 </xsl:when>                 <xsl:otherwise>                     <xsl:if test="preceding-sibling::person[1] and not(following-sibling::person[1])">                         <xsl:value-of select="following-sibling::perscat[1]/@category"/>                     </xsl:if>                 </xsl:otherwise>             </xsl:choose>         </fo:block>     </fo:table-cell> <continues with the rest of the cells../> 

This is the table I'm producing:

    <pre class="prettyprint"><code class="language-xml"> <table>     <table-header>         <table-row>             <table-cell> Person </table-cell>             <table-cell> Category/Trade </table-cell>             <table-cell> Skill level </table-cell>             <table-cell> Trade code </table-cell>             <table-cell> Estimated time </table-cell>         </table-row>     </table-header>     <table-body>         <table-row>             <table-cell>Four</table-cell>             <table-cell/>             <table-cell/>             <table-cell/>             <table-cell/>         </table-row>         <table-row>             <table-cell>A</table-cell>             <table-cell>Recovery Supervisor</table-cell>             <table-cell/>             <table-cell/>             <table-cell/>         </table-row>         <table-row>             <table-cell>B</table-cell>             <table-cell>Ground Personnel</table-cell>             <table-cell/>             **<table-cell>BillCollector</table-cell>**             <table-cell/>         </table-row>         <table-row>             <table-cell>As required</table-cell>             <table-cell/>             <table-cell/>             <table-cell>Bill Collector</table-cell>             <table-cell/>         </table-row>         <table-row>             <table-cell>C</table-cell>             <table-cell>Ground Personnel</table-cell>             <table-cell>skill gets converted to string value</table-cell>             <table-cell>welder</table-cell>             <table-cell>.5 hr</table-cell>         </table-row>         <table-row>             <table-cell>As required</table-cell>             <table-cell>2nd Required Category</table-cell>             <table-cell/>             <table-cell/>             <table-cell>4 days</table-cell>         </table-row>         <table-row>             <table-cell>D</table-cell>             <table-cell>Rides in Chase Vehicle</table-cell>             <table-cell>skill level</table-cell>             <table-cell/>             <table-cell/>         </table-row>         <table-row>             <table-cell>E</table-cell>             <table-cell>Jack of all Trades</table-cell>             <table-cell/>             <table-cell>engine mechanic</table-cell>             <table-cell/>         </table-row>     </table-body> </table> </code>        </pre> 

The table cell with ** around it should not have data in it.. It adds the value from the following asrequir which is not what I want..

I have tried adding following-sibling::person['1']|asrequir['1'] but the asrequir is always true so I get extra siblings in places I don't want them. Any insight or suggestions on how to solve this problem would be very much appreciated. I suspect I need to use a grouping of some sort or a key set, but am not sure how to implement those. While not a newbie to XSLT and XPath, I'm not an expert.

回答1:

A key may help which group all elements from reqpers which are not person or asrequir to the preceding-sibling person or asrequir.

<xsl:key name="kperson" match="reqpers/*[not(self::person or self::asrequir)]"         use="generate-id(preceding-sibling::*[self::person or self::asrequir][1]) 

The group stats with:

<xsl:for-each select="*[self::person or self::asrequir]"> 

The group members are then:

  <xsl:variable name="this" select="." />   <xsl:variable name="group" select=". | key('kperson',generate-id($this))" /> 

Get a value from the group:

<xsl:value-of select="$group[self::perscat]/@category"/> 

You may try this:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">   <xsl:output method="xml" indent="yes"/>    <xsl:key name="kperson" match="reqpers/*[not(self::person or self::asrequir)]"             use="generate-id(preceding-sibling::*[self::person or self::asrequir][1])"/>   <xsl:template match="reqpers">     <table>     <table-header>         <table-row>             <table-cell> Person </table-cell>             <table-cell> Category/Trade </table-cell>             <table-cell> Skill level </table-cell>             <table-cell> Trade code </table-cell>             <table-cell> Estimated time </table-cell>         </table-row>     </table-header>     <xsl:for-each select="*[self::person or self::asrequir]">        <xsl:variable name="this" select="." />       <xsl:variable name="group" select=". | key('kperson',generate-id($this))" />         <table-row>             <table-cell>               <xsl:value-of select="$group[self::person]/@man"/>               <xsl:if test="$group[self::asrequir]" >As required</xsl:if>           </table-cell>             <table-cell><xsl:value-of select="$group[self::perscat]/@category"/></table-cell>             <table-cell><xsl:value-of select="$group[self::perskill]/@skill"/></table-cell>             <table-cell><xsl:value-of select="$group[self::trade]"/></table-cell>             <table-cell><xsl:value-of select="$group[self::esttime]"/></table-cell>          </table-row>     </xsl:for-each>     </table>   </xsl:template>  </xsl:stylesheet> 

With following output:

<table>   <table-header>     <table-row>       <table-cell> Person </table-cell>       <table-cell> Category/Trade </table-cell>       <table-cell> Skill level </table-cell>       <table-cell> Trade code </table-cell>       <table-cell> Estimated time </table-cell>     </table-row>   </table-header>   <table-row>     <table-cell>Four</table-cell>     <table-cell/>     <table-cell/>     <table-cell/>     <table-cell/>   </table-row>   <table-row>     <table-cell>A</table-cell>     <table-cell>Recovery Supervisor</table-cell>     <table-cell/>     <table-cell/>     <table-cell/>   </table-row>   <table-row>     <table-cell>B</table-cell>     <table-cell>Ground Personnel</table-cell>     <table-cell/>     <table-cell/>     <table-cell/>   </table-row>   <table-row>     <table-cell>As required</table-cell>     <table-cell>As Required Category</table-cell>     <table-cell/>     <table-cell>Bill Collector</table-cell>     <table-cell/>   </table-row>   <table-row>     <table-cell>C</table-cell>     <table-cell>Ground Personnel</table-cell>     <table-cell>sk01</table-cell>     <table-cell>welder</table-cell>     <table-cell>.5 hr</table-cell>   </table-row>   <table-row>     <table-cell>As required</table-cell>     <table-cell>2nd Required Category</table-cell>     <table-cell/>     <table-cell/>     <table-cell>4 days</table-cell>   </table-row>   <table-row>     <table-cell>D</table-cell>     <table-cell>Rides in Chase Vehicle</table-cell>     <table-cell>sk02</table-cell>     <table-cell/>     <table-cell/>   </table-row>   <table-row>     <table-cell>E</table-cell>     <table-cell>Jack of all Trades</table-cell>     <table-cell/>     <table-cell>engine mechanic</table-cell>     <table-cell/>   </table-row> </table> 


易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!