Link to home
Start Free TrialLog in
Avatar of FelineConspiracy
FelineConspiracy

asked on

copy xml elements with matching names from another doc with slightly different structure

My requirement is to pre-populate an xml document A by copying matching xml elements from an existing document B.

I cannot introduce any new elements into A; I just want to bring in elements from B if there is already an element in A of the same name.

B's structure will be slightly different from A's. Both will be exactly three elements deep, with the same root, and one child whose name will vary. It's the common elements under that child that I want to copy. (There is an oddball document that doesn't match this three-layer pattern but I'm ignoring that for now.)


Document A

<enroll>
      <orp>
            <email></email>
            <zip></zip>
            <npi></npi>
      </orp>
</enroll>

Document B - I don't want <monkey> copied to A because A doesn't already have one:

<enroll>
      <phys>
            <email>p@z.g</email>
            <zip>11111</zip>
            <npi>0101010101</npi>
            <monkey></monkey>
      </phys>

</enroll>

The result should be

<enroll>
      <orp>
            <email>p@z.g</email>
            <zip>11111</zip>
            <npi>0101010101</npi>
      </orp>
</enroll>




I have a pretty good idea that xsl:copy is the tool, but... no idea how to do this. An xsl 1.0 solution would be best, we're on a pretty old platform and I'm not sure 2.0 is supported. Can anyone assist?
Avatar of zc2
zc2
Flag of United States of America image

How about  this:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
	<xsl:template match="/">
		<xsl:apply-templates select="*"/>
	</xsl:template>
	
	<xsl:template match="*">
		<xsl:copy>
			<xsl:apply-templates select="*|text()"/>
		</xsl:copy>
 	</xsl:template>

<!-- replace phys to orp  -->
	<xsl:template match="phys">
		<orp>
			<xsl:apply-templates select="*|text()"/>
		</orp>
 	</xsl:template>

<!-- exclude monkey -->
	<xsl:template match="monkey">
 	</xsl:template> 	
 	
 	<xsl:template match="text()">
 		<xsl:copy/>
    </xsl:template>
</xsl:stylesheet>

Open in new window

Avatar of FelineConspiracy
FelineConspiracy

ASKER

Is there a way to pass in "orp" as a param and have the sheet replace the immediate child of <enroll>, whatever it is, with an element of that name?
I'm not sure where you going to pass that param from, but it could be done, like:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
	<xsl:template match="/">
		<xsl:apply-templates select="*"/>
	</xsl:template>
	
	<xsl:template match="*">
		<xsl:copy>
			<xsl:apply-templates select="*|text()"/>
		</xsl:copy>
 	</xsl:template>

	<xsl:template match="enroll">
		<xsl:copy>
			<xsl:apply-templates select="*|text()">
				<!-- pass the tag name as a param  -->
				<xsl:with-param name="tag" select="'orp'"/>
			</xsl:apply-templates>
		</xsl:copy>
 	</xsl:template>

	<!-- the immediate child of <enroll>, whatever it is -->
	<xsl:template match="enroll/*">
		<xsl:param name="tag"/>
		<xsl:element name="{$tag}">
			<xsl:apply-templates select="*|text()"/>
		</xsl:element>
 	</xsl:template>

	<xsl:template match="monkey">
 	</xsl:template> 	
 	
 	<xsl:template match="text()">
 		<xsl:copy/>
    </xsl:template>
</xsl:stylesheet>

Open in new window

<?xml version="1.0"?>

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="xml" version="1.0" encoding="iso-8859-7" indent="yes"/>
  <xsl:template match="/">
    <enroll>
      <xsl:apply-templates/>
    </enroll>
  </xsl:template>
  <xsl:template match="*">
    <orp>
      <email>
        <xsl:value-of select="*/email"/>
      </email>
      <zip>
        <xsl:value-of select="*/zip"/>
      </zip>
      <npi>
        <xsl:value-of select="*/npi"/>
      </npi>
    </orp>
  </xsl:template>
</xsl:stylesheet>

Open in new window

mmh, I had the impression you were looking for something somewhat more dynamic:
given the skeleton in A and the XML in B, populate A with the data in B

If that is the case, try this

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    version="1.0">
    <xsl:variable name="B-doc" select="document('B.xml')"/>
    
    <xsl:template match="/">
        <xsl:apply-templates select="*">
            <xsl:with-param name="B-child" select="$B-doc/*"/>
        </xsl:apply-templates>
    </xsl:template>
    
    <xsl:template match="*">
        <xsl:param name="B-child"/>
        <xsl:variable name="this-name" select="name()"></xsl:variable>
            <xsl:choose>
                <xsl:when test="*">
                    <xsl:element name="{$this-name}">
                        <xsl:apply-templates select="node()">
                        <xsl:with-param name="B-child" select="$B-child/*"/>
                    </xsl:apply-templates>
                    </xsl:element>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:copy-of select="$B-child[name() = $this-name]"/>
                </xsl:otherwise>
            </xsl:choose>
    </xsl:template>
    
</xsl:stylesheet>

Open in new window


A.xml is the skeleton and should serve as the source document
B.xml is where the data is, it is loaded from a variable through the document function
make sure you enable the document() function in your transform (some architectures have it by default switched off
To refine the requirement a little: I have to make sure that Document A is complete. Suppose that nothing can be copied in from B. The result must still be:

<enroll>
      <orp>
            <email></email>
            <zip></zip>
            <npi></npi>
      </orp>
</enroll>

And, the various document types have certain blocks of elements that pop up in many forms. These I want to copy whole. For example, for document A, I want addr1 and addr2 and service1 and service2 if they exist. So if this document A:

<enroll>
      <orp>
      <email></email>
      <zip></zip>
      <npi></npi>

      <ssn/>
      
      <addr1/>
      <addr2/>

      <service1/>
      <service2/>
                  
      </orp>
</enroll>

and this is document B:

<enroll>
      <phys>
      <email>sfsf</email>
      <zip>1111</zip>
      <npi>23222</npi>
      <monkey>roar</monkey>
      
      <addr1>123 Main</addr1>
      <addr2>around the corner<addr2>

      <service1>radiation</service1>
      <service2>aroma</service2>
                  
      </phys>
</enroll>

The result should be:

<enroll>
      <orp>
      <email>sfsf</email>
      <zip>1111</zip>
      <npi>23222</npi>

      <ssn/>
      
      <addr1>123 Main</addr1>
      <addr2>around the corner<addr2>

      <service1>radiation</service1>
      <service2>aroma</service2>
                  
      </orp>
</enroll>
ASKER CERTIFIED SOLUTION
Avatar of Gertone (Geert Bormans)
Gertone (Geert Bormans)
Flag of Belgium image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
That's... amazing. Is there a way to load that B variable from a String? The existing data ("b") is being fetched from a relational database column, in Java.
That then largely depend from your XSLT processor.
Id this is Java, I assume you are using Xalan.
I have no experience passing a DOM object as a parameter, I will check some literature.
You don't need to pass it as a string, I would recommend to pass it as a XML object (most likely DOM)
just get rid of the variable B-doc
and make that a parameter
<xsl:param name="B-doc"/>
and figure out how to pass from Java a XML object as a parameter to the XST
I have done this using Saxon, but never Xalan
worth trying
http://apache-xml.6118.n7.nabble.com/How-can-I-pass-a-node-as-parameter-to-translets-for-XSLTC-Processor-td21139.html

don't look at the problem mentioned there, look at the very first example
it seems as if the parameter passed as an XML document, maintains its type inside the XSLT
(I know it does with POJO's)... it is worth trying I believe... it could really be that simple