使用XSLT选择性复制和更新xml节点 [英] Selectively copy and update xml nodes using XSLT

查看:172
本文介绍了使用XSLT选择性复制和更新xml节点的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用一个xml,我需要复制和更新传递进一步处理。我遇到的问题是,我还没有想出一个有效的方法来做到这一点。基本上,我想更新一些数据,有条件地,然后复制所有未更新的节点。为什么这是具有挑战性的是由于要复制的节点的数量和名称的体积和方差。我也想不复制没有文本值的节点。以下是一个示例:



INPUT XML

 < root> 
< PersonProfile xmlns:'namespace'>
< ID> 0001< / ID>
< Name>
< FirstName> Jonathan< / FirstName>
< PreferredName> John< / PreferredName>
< MiddleName> A< / MiddleName>
< LastName> Doe< / LastName>
< / Name>
< Country> US< / Country>
< Biirthdate> 01-01-1980< / Birthdate>
< BirthPlace>
< City> Townsville< / City>
< State> OR< / State>
< Country> US< / Country>
< / Birthplace>
< Gender>男性< / Gender>
< HomeState> OR< / HomeState>
...
< nodeN> text< / nodeN>
< / PersonProfile>
< / root>

PersonProfile节点只是root元素中的几个节点集之一,它们自己的数据子集。例如邮寄地址,紧急联系信息等。我想要做的是更新节点,如果变量有一个新的值,然后复制所有未更新的节点。



以下是我目前的XSLT

 < xsl:stylesheet version =2.0xmlns:xs =http://www.w3.org/2001/XMLSchemaxmlns:xsl =http://www.w3.org/1999/XSL/Transform xmlns:xsi =http://www.w3.org/2001/XMLSchema-instance> 

< xsl:variable name ='updateData'select ='document(report)'/>

<! - Identity Transform - >
< xsl:template match ='@ * | node()'>
< xsl:if test'。 !='>
< xsl:copy>
< xsl:apply-templates select ='@ * | node()'/>
< / xsl:copy>
< / xsl:if>
< / xsl:template>

<! - 更新个人资料的模板 - >
< xsl:template match ='PersonProfile'>
< xsl:copy>
< xsl:apply-templates select ='*'/>
< xsl:element name ='Name'>
< xsl:if test ='exists($ updateData / Preferred)'>
< xsl:element name ='FirstName'>
< xsl:value -of select ='$ reportData / FirstName'/>
< / xls:element>
< / xsl:if>
< xsl:if test ='exists($ updateData / Preferred)'>
< xsl:element name ='PreferredName'>
< xsl:value-of select ='$ updateData / Preferred'/>
< / xsl:element>
< / xsl:if>
< xsl:if test ='exists($ updateData / Middle)'>
< xsl:element name ='MiddleName'>
< xsl:value-of select ='$ updateData / Middle'/>
< / xsl:element>
< / xsl:if>
< xsl:if test ='exists($ updateData / LastName)'>
< xsl:element name ='LastName'>
< xsl:value-of select ='$ updateData / wd:LastName'/>
< / xsl:element>
< / xsl:if>
< / xsl:element>
< xsl:if test ='exists($ updateData / Country)'>
< xsl:element name ='Country'>
< xsl:value-of select ='$ updateData / Country'/>
< / xsl:element>
< / xsl:if>
....
<! - 遵循相同的结构,直到模板结束 - >
< / xsl:copy>
< / xsl:template>

<! - 更多模板来更新其他节点集 - >

< / xsl:stylesheet>

现在发生了什么,是复制所有节点,然后添加更新值。使用Saxon-PE 9.3.0.5,我会得到类似下面的输出:



示例输出

 < root> 
< PersonProfile xmlns:'namespace'>
< ID> 0001< / ID>
< Name>
< FirstName> Jonathan< / FirstName>
< PreferredName> John< / PreferredName>
< MiddleName> A< / MiddleName>
< LastName> Doe< / LastName>
< / Name>
< Country> US< / Country>
< Biirthdate> 01-01-1980< / Birthdate>
< BirthPlace>
< City> Townsville< / City>
< State> OR< / State>
< Country> US< / Country>
< / Birthplace>
< Gender>男性< / Gender>
< HomeState> OR< / HomeState>
...
< nodeN> text< / nodeN>
< PreferredName> Jonathan< / PreferredName>
< HomeState> WA< / HomeState>
< / PersonProfile>
< / root>

我意识到这是发生,因为我将模板应用于PersonProfile中的所有节点,指定要排除哪些节点,但我觉得这是一个非常差的解决方案,因为节点的体积可能超过30或更多,并且需要为每个节点写一个值。我相信XML有一个比显式列出每个这些节点更优雅的解决方案。我想要有一个像这样:



希望的输出

 < root> 
< PersonProfile xmlns:'namespace'>
< ID> 0001< / ID>
< Name>
< FirstName> Jonathan< / FirstName>
< PreferredName> Jonathan< / PreferredName>
< MiddleName> A< / MiddleName>
< LastName> Doe< / LastName>
< / Name>
< Country> US< / Country>
< Biirthdate> 01-01-1980< / Birthdate>
< BirthPlace>
< City> Townsville< / City>
< State> OR< / State>
< Country> US< / Country>
< / Birthplace>
< Gender>男性< / Gender>
< HomeState> WA< / HomeState>
...
< nodeN> text< / nodeN>
< / PersonProfile>
< / root>

如果任何人可以帮助我创建一个适用于xml结构的模板结构,它。它将需要是可重用的,因为存在类似的人员简档的类似的节点结构将必须应用它,但具有不同的节点名称和元素数量等。

class =h2_lin>解决方案

这应该适用于您的原始问题:

 < xsl :stylesheet version =2.0
xmlns:xs =http://www.w3.org/2001/XMLSchema
xmlns:xsl =http://www.w3.org/1999 / XSL / Transform
xmlns:xsi =http://www.w3.org/2001/XMLSchema-instance>

< xsl:variable name ='updateData'select ='document(report)'/>

<! - Identity Transform - >
< xsl:template match ='@ * | node()'name ='copy'>
< xsl:copy>
< xsl:apply-templates select ='@ * | node()'/>
< / xsl:copy>
< / xsl:template>

< xsl:template match ='* [not(*)]'>
< xsl:variable name ='matchingValue'
select ='$ updateData / * [name()= name(current())]'/>
< xsl:choose>
< xsl:when test ='$ matchingValue'>
< xsl:copy>
< xsl:apply-templates select ='@ *'/>
< xsl:value-of select ='$ matchingValue'/>
< / xsl:copy>
< / xsl:when>
< xsl:when test ='normalize-space()'>
< xsl:call-template name ='copy'/>
< / xsl:when>
< / xsl:choose>
< / xsl:template>
< / xsl:stylesheet>

至于插入源XML中不存在的新元素,这很棘手。你能为此打开一个单独的问题吗?我可能会有一些想法如何处理。


I'm working with an xml that I need to copy and update to pass on for further processing. The issue I'm having is that I have not figured out an efficient method to do this. Essentially, I want to update some data, conditionally, then copy all the nodes that were not updated. Why this is challenging is due to the volume and variance in the number and name of nodes to be copied. I also want to NOT copy nodes that have no text value. Here is an example:

INPUT XML

  <root>
     <PersonProfile xmlns:'namespace'>
        <ID>0001</ID>
        <Name>
           <FirstName>Jonathan</FirstName>
           <PreferredName>John</PreferredName>
           <MiddleName>A</MiddleName>
           <LastName>Doe</LastName>
        </Name>
        <Country>US</Country>
        <Biirthdate>01-01-1980</Birthdate>
        <BirthPlace>
           <City>Townsville</City>
           <State>OR</State>
           <Country>US</Country>
        </Birthplace>
        <Gender>Male</Gender>
        <HomeState>OR</HomeState>
        ...
        <nodeN>text</nodeN>
     </PersonProfile>
 </root>

The "PersonProfile" node is just one of several node sets within the "root" element, each with their own subset of data. Such as mailing address, emergency contact info, etc. What I am attempting to do is update nodes if the variable has a new value for them then copy all the nodes that were not updated.

Here is my current XSLT

 <xsl:stylesheet version="2.0" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

 <xsl:variable name='updateData' select='document("report")'/>

 <!-- Identity Transform -->
 <xsl:template match='@* | node()'>
     <xsl:if test'. != ""'>
        <xsl:copy>
            <xsl:apply-templates select='@* | node()'/>
        </xsl:copy>
      </xsl:if>
  </xsl:template>

 <!-- Template to update Person Profile -->
  <xsl:template match='PersonProfile'>   
    <xsl:copy>
        <xsl:apply-templates select='*'/>    
        <xsl:element name='Name'>
            <xsl:if test='exists($updateData/Preferred)'>
               <xsl:element name='FirstName'>
                  <xsl:value-of select='$reportData/FirstName'/>
               </xsl:element>
            </xsl:if>            
            <xsl:if test='exists($updateData/Preferred)'>
               <xsl:element name='PreferredName'>
                   <xsl:value-of select='$updateData/Preferred'/>
               </xsl:element>
            </xsl:if>
            <xsl:if test='exists($updateData/Middle)'>
            <xsl:element name='MiddleName'>
                <xsl:value-of select='$updateData/Middle'/>
            </xsl:element>
            </xsl:if>
            <xsl:if test='exists($updateData/LastName)'>
               <xsl:element name='LastName'>
                   <xsl:value-of select='$updateData/wd:LastName'/>
               </xsl:element>
            </xsl:if> 
         </xsl:element>
         <xsl:if test='exists($updateData/Country)'>
            <xsl:element name='Country'>
               <xsl:value-of select='$updateData/Country'/>
            </xsl:element>
         </xsl:if> 
         ....
         <!-- follows same structure until end of template -->
    </xsl:copy>
  </xsl:template>

 <!-- More Templates to Update other Node sets -->

</xsl:stylesheet>

What's happening right now, is that it's copying ALL the nodes and then adding the updates values. Using Saxon-PE 9.3.0.5, I'll get an output similar to this:

Sample Output

  <root>
     <PersonProfile xmlns:'namespace'>
        <ID>0001</ID>
        <Name>
           <FirstName>Jonathan</FirstName>
           <PreferredName>John</PreferredName>
           <MiddleName>A</MiddleName>
           <LastName>Doe</LastName>
        </Name>
        <Country>US</Country>
        <Biirthdate>01-01-1980</Birthdate>
        <BirthPlace>
           <City>Townsville</City>
           <State>OR</State>
           <Country>US</Country>
        </Birthplace>
        <Gender>Male</Gender>
        <HomeState>OR</HomeState>
        ...
        <nodeN>text</nodeN>
        <PreferredName>Jonathan</PreferredName>
        <HomeState>WA</HomeState>
     </PersonProfile>
 </root>

I realize this is happening because I am applying the templates to all the nodes in PersonProfile and that I could specify which nodes to exclude, but I feel like this is a very poor solution as the volume of nodes could be upwards of 30 or more and that would require a written value for each one. I trust XML has a more elegant solution than to explicitly list each of these nodes. I would like to have an out like this:

Desired Output

  <root>
     <PersonProfile xmlns:'namespace'>
        <ID>0001</ID>
        <Name>
           <FirstName>Jonathan</FirstName>
           <PreferredName>Jonathan</PreferredName>
           <MiddleName>A</MiddleName>
           <LastName>Doe</LastName>
        </Name>
        <Country>US</Country>
        <Biirthdate>01-01-1980</Birthdate>
        <BirthPlace>
           <City>Townsville</City>
           <State>OR</State>
           <Country>US</Country>
        </Birthplace>
        <Gender>Male</Gender>
        <HomeState>WA</HomeState>
        ...
        <nodeN>text</nodeN>
     </PersonProfile>
 </root>

If anyone could help me create a template structure that would work for the xml structure, I would GREATLY appreciate it. It would need to be "reusable" as there are similar node structures like Person Profile I would have to apply it to, but with different node names and number of elements, etc.

Thanks in advance for any help!

  • J

解决方案

This should work for your original question:

<xsl:stylesheet version="2.0" 
                xmlns:xs="http://www.w3.org/2001/XMLSchema" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

  <xsl:variable name='updateData' select='document("report")'/>

  <!-- Identity Transform -->
  <xsl:template match='@* | node()' name='copy'>
      <xsl:copy>
        <xsl:apply-templates select='@* | node()'/>
      </xsl:copy>
  </xsl:template>

  <xsl:template match='*[not(*)]'>
    <xsl:variable name='matchingValue' 
                  select='$updateData/*[name() = name(current())]'/>
    <xsl:choose>
      <xsl:when test='$matchingValue'>
        <xsl:copy>
          <xsl:apply-templates select='@*' />
          <xsl:value-of select='$matchingValue'/>
        </xsl:copy>
      </xsl:when>
      <xsl:when test='normalize-space()'>
        <xsl:call-template name='copy' />
      </xsl:when>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

As far as inserting new elements that are not present in the source XML, that's trickier. Could you open a separate question for that? I may have some ideas for how to approach that.

这篇关于使用XSLT选择性复制和更新xml节点的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆