按ID排序,然后按同一节点内的时间戳排序

时间:2011-12-13 11:44:10

标签: xslt sorting timestamp xslt-1.0

关于使用XSL 1.0进行排序,我有一个非常特殊的问题(只有1.0 - 我正在使用.Net Parser)。

这是我的xml:

<Root>
....
<PatientsPN>
        <Patient>
            <ID>1</ID>
            <TimeStamp>20111208165819</TimeStamp>
            <NomPatient>Dudule</NomPatient>
            <PrenomPatient>Fanny</PrenomPatient>
            <Sexe>F</Sexe>
        </Patient>
        <Patient>
            <ID>4</ID>
            <TimeStamp>20111208165910</TimeStamp>
            <NomPatient>Dudule</NomPatient>
            <PrenomPatient>Fanny4</PrenomPatient>
            <Sexe>F</Sexe>
        </Patient>
        <Patient>
            <ID>4</ID>
            <TimeStamp>20111208165902</TimeStamp>
            <NomPatient>Dudule</NomPatient>
            <PrenomPatient>FannyMOI</PrenomPatient>
            <Sexe>M</Sexe>
        </Patient>
        <Patient>
            <ID>2</ID>
            <TimeStamp>20111208170000</TimeStamp>
            <NomPatient>Dudule</NomPatient>
            <PrenomPatient>FannyMOI</PrenomPatient>
            <Sexe>F</Sexe>
        </Patient>
        <Patient>
            <ID>2</ID>
            <TimeStamp>20111208165819</TimeStamp>
            <NomPatient>Dudule</NomPatient>
            <PrenomPatient>Fanny</PrenomPatient>
            <Sexe>F</Sexe>
        </Patient>
        <Patient>
            <ID>2</ID>
            <TimeStamp>20111208170050</TimeStamp>
            <NomPatient>Dudule</NomPatient>
            <PrenomPatient>Cmoi2</PrenomPatient>
            <Sexe>F</Sexe>
        </Patient>
        <Patient>
            <ID>3</ID>
            <TimeStamp>20111208165829</TimeStamp>
            <NomPatient>Dudule</NomPatient>
            <PrenomPatient>Jesuis3</PrenomPatient>
            <Sexe>F</Sexe>
        </Patient>
    </PatientsPN>
...
</Root>

我想首先通过ID对我的PatientsNP进行排序,然后获取每个ID的更高的TimeStamp。 我的输出:

<Root>
<PatientsPN>
 <Patient>
            <ID>1</ID>
            <TimeStamp>20111208165819</TimeStamp>
            <NomPatient>Dudule</NomPatient>
            <PrenomPatient>Fanny</PrenomPatient>
            <Sexe>F</Sexe>
        </Patient>
<Patient>
            <ID>2</ID>
            <TimeStamp>20111208170050</TimeStamp>
            <NomPatient>Dudule</NomPatient>
            <PrenomPatient>Cmoi2</PrenomPatient>
            <Sexe>F</Sexe>
        </Patient>
<Patient>
            <ID>3</ID>
            <TimeStamp>20111208165829</TimeStamp>
            <NomPatient>Dudule</NomPatient>
            <PrenomPatient>Jesuis3</PrenomPatient>
            <Sexe>F</Sexe>
        </Patient>
<Patient>
            <ID>4</ID>
            <TimeStamp>20111208165910</TimeStamp>
            <NomPatient>Dudule</NomPatient>
            <PrenomPatient>Fanny4</PrenomPatient>
            <Sexe>F</Sexe>
        </Patient>
</PatientsPN>
</Root>

首先,我尝试按ID对列表进行排序,然后解析每个节点并使用Xpath提取更高的时间戳,但这不起作用。它不断重复其他节点。

还尝试了Muench排序方法,但我无法使用更通用的东西使其正常工作。

我的XSL是:

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

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

    <xsl:template match="/Root/*">
        <xsl:for-each select=".">
            <xsl:choose>
                <xsl:when test="substring(name(), (string-length(name()) - string-length($mark)) + 1) = $mark">
                    <!-- Search for an ID tag -->
                    <xsl:copy>
                        <xsl:if test="node()/ID">
<xsl:for-each select="node()">
                                <xsl:sort select="ID" order="ascending" />
<!-- So far everything I've done here failed -->
<xsl:for-each select=".[ID = '1']">
                                <xsl:copy>
                                  <xsl:copy-of select="node()[not(number(TimeStamp) &lt; (preceding-sibling::node()/TimeStamp | following-sibling::node()/TimeStamp))]"/>
                                  </xsl:copy>
                                </xsl:for-each>
<!-- This is just an example, I don't want to have ID = 1 and ID = 2 -->
</xsl:for-each>
                        </xsl:if>

                        <xsl:if test="not(node()/ID)">
                            <xsl:copy-of select="node()[not(number(TimeStamp) &lt; (preceding-sibling::node()/TimeStamp | following-sibling::node()/TimeStamp))]"/>
                        </xsl:if>
                    </xsl:copy>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:copy-of select="."/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

我希望自己清楚明白。 提前感谢您带来的所有帮助!

编辑:

我真的很抱歉我应该提到我想让它尽可能通用。在我的例子中,我说的是PatientsPN,但我真正想做的是匹配以PN结尾的每个PARENT节点(因此结束了XSL 1.0的copycat版本)。

无论如何,你真的很棒,我无法期待更多来自你。谢谢!

在重新构建Dimitre给出的解决方案之后,我想出了这个XSL:

<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kPatById" match="*['PN' = substring(name(), string-length(name()) -1)]/*" 
use="concat(generate-id(..), '|', ID)"/>

<xsl:template match="node()|@*">
 <xsl:copy>
  <xsl:apply-templates select="node()|@*"/>
 </xsl:copy>
</xsl:template>

<xsl:template match="*['PN' = substring(name(), string-length(name()) -1)]">
 <xsl:copy>
<xsl:apply-templates select="node()">
 <xsl:sort select="ID" data-type="number"/>
</xsl:apply-templates>
 </xsl:copy>
</xsl:template>

<xsl:template match="*['PN' = substring(name(), string-length(name()) -1)]/node()[TimeStamp &lt; key('kPatById', concat(generate-id(..), '|', ID))/TimeStamp]"/>
</xsl:stylesheet>

它完美地完成了这项工作,并且它允许我拥有多个将被处理和排序的父节点。

2 个答案:

答案 0 :(得分:2)

可以这么简单

<强>予。 XSLT 1.0解决方案:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kPatById" match=
 "*['PN' = substring(name(), string-length(name()) -1)]/Patient"
  use="concat(generate-id(..), '|', ID)"/>

 <xsl:template match="node()|@*">
     <xsl:copy>
       <xsl:apply-templates select="node()|@*"/>
     </xsl:copy>
 </xsl:template>

 <xsl:template match=
 "*['PN' = substring(name(), string-length(name()) -1)]">
  <xsl:apply-templates select="Patient">
    <xsl:sort select="ID" data-type="number"/>
  </xsl:apply-templates>
 </xsl:template>

 <xsl:template match=
 "*['PN' = substring(name(), string-length(name()) -1)]
     /Patient
       [TimeStamp &lt; key('kPatById', concat(generate-id(..), '|', ID))/TimeStamp]
  "/>
</xsl:stylesheet>

应用于提供的XML文档

<Root>
....
    <PatientsPN>
        <Patient>
            <ID>1</ID>
            <TimeStamp>20111208165819</TimeStamp>
            <NomPatient>Dudule</NomPatient>
            <PrenomPatient>Fanny</PrenomPatient>
            <Sexe>F</Sexe>
        </Patient>
        <Patient>
            <ID>4</ID>
            <TimeStamp>20111208165910</TimeStamp>
            <NomPatient>Dudule</NomPatient>
            <PrenomPatient>Fanny4</PrenomPatient>
            <Sexe>F</Sexe>
        </Patient>
        <Patient>
            <ID>4</ID>
            <TimeStamp>20111208165902</TimeStamp>
            <NomPatient>Dudule</NomPatient>
            <PrenomPatient>FannyMOI</PrenomPatient>
            <Sexe>M</Sexe>
        </Patient>
        <Patient>
            <ID>2</ID>
            <TimeStamp>20111208170000</TimeStamp>
            <NomPatient>Dudule</NomPatient>
            <PrenomPatient>FannyMOI</PrenomPatient>
            <Sexe>F</Sexe>
        </Patient>
        <Patient>
            <ID>2</ID>
            <TimeStamp>20111208165819</TimeStamp>
            <NomPatient>Dudule</NomPatient>
            <PrenomPatient>Fanny</PrenomPatient>
            <Sexe>F</Sexe>
        </Patient>
        <Patient>
            <ID>2</ID>
            <TimeStamp>20111208170050</TimeStamp>
            <NomPatient>Dudule</NomPatient>
            <PrenomPatient>Cmoi2</PrenomPatient>
            <Sexe>F</Sexe>
        </Patient>
        <Patient>
            <ID>3</ID>
            <TimeStamp>20111208165829</TimeStamp>
            <NomPatient>Dudule</NomPatient>
            <PrenomPatient>Jesuis3</PrenomPatient>
            <Sexe>F</Sexe>
        </Patient>
    </PatientsPN>
...
</Root>

产生了想要的正确结果

<Root>
....
    <Patient>
      <ID>1</ID>
      <TimeStamp>20111208165819</TimeStamp>
      <NomPatient>Dudule</NomPatient>
      <PrenomPatient>Fanny</PrenomPatient>
      <Sexe>F</Sexe>
   </Patient>
   <Patient>
      <ID>2</ID>
      <TimeStamp>20111208170050</TimeStamp>
      <NomPatient>Dudule</NomPatient>
      <PrenomPatient>Cmoi2</PrenomPatient>
      <Sexe>F</Sexe>
   </Patient>
   <Patient>
      <ID>3</ID>
      <TimeStamp>20111208165829</TimeStamp>
      <NomPatient>Dudule</NomPatient>
      <PrenomPatient>Jesuis3</PrenomPatient>
      <Sexe>F</Sexe>
   </Patient>
   <Patient>
      <ID>4</ID>
      <TimeStamp>20111208165910</TimeStamp>
      <NomPatient>Dudule</NomPatient>
      <PrenomPatient>Fanny4</PrenomPatient>
      <Sexe>F</Sexe>
   </Patient>
...
</Root>

<强>解释

  1. 使用"PN"substring()的组合匹配名称以string-length()结尾的任何元素。

  2. 覆盖 identity rule

  3. 排序,但不使用任何Muenchian分组

  4. 使用密钥在同一xxxPN父母下获取同一患者的所有记录。

  5. “简单”最大值(不排序)。

  6. 正确的模式匹配,以排除任何不需要的记录。


  7. <强> II。 XSLT 2.0解决方案:

    我发现最好的XSLT 2.0解决方案几乎与上面的XSLT 1.0解决方案相同,但可能更有效:

    <xsl:stylesheet version="2.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:strip-space elements="*"/>
    
     <xsl:key name="kPatById" match="*[ends-with(name(),'PN')]/Patient"
              use="concat(generate-id(..), '|', ID)"/>
    
     <xsl:template match="node()|@*">
         <xsl:copy>
           <xsl:apply-templates select="node()|@*"/>
         </xsl:copy>
     </xsl:template>
    
     <xsl:template match="*[ends-with(name(),'PN')]">
      <xsl:apply-templates select="Patient">
        <xsl:sort select="ID" data-type="number"/>
      </xsl:apply-templates>
     </xsl:template>
    
     <xsl:template match=
     "*[ends-with(name(),'PN')]
         /Patient
            [number(TimeStamp)
            lt
              max((key('kPatById', concat(generate-id(..), '|', ID))
                                                 /TimeStamp/xs:double(.)))
            ]"/>
    </xsl:stylesheet>
    

答案 1 :(得分:1)

第一个答案(没有检查)似乎是正确的,但我无法抗拒发布一个不使用'evil'for-each关键字的XSLT 1.0版本。

通过在排序之前合并ID和时间戳来完成排序。

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl"
>
    <xsl:output method="xml" indent="yes"/>

  <xsl:template match="PatientsPN">
    <xsl:copy>
      <xsl:apply-templates select="//Patient">
        <xsl:sort select="concat(ID,TimeStamp)"/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="Patient">
    <xsl:if test="not(ID=following-sibling::Patient/ID)">
      <xsl:copy>
        <xsl:apply-templates select="@* | node()"/>
      </xsl:copy>
    </xsl:if>
  </xsl:template>

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

希望这有帮助,