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?
XML

Avatar of undefined
Last Comment
Gertone (Geert Bormans)

8/22/2022 - Mon
zc2

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

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?
zc2

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

I started with Experts Exchange in 2004 and it's been a mainstay of my professional computing life since. It helped me launch a career as a programmer / Oracle data analyst
William Peck
Ioannis Paraskevopoulos

<?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

Gertone (Geert Bormans)

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
FelineConspiracy

ASKER
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>
Get an unlimited membership to EE for less than $4 a week.
Unlimited question asking, solutions, articles and more.
ASKER CERTIFIED SOLUTION
Gertone (Geert Bormans)

Log in or sign up to see answer
Become an EE member today7-DAY FREE TRIAL
Members can start a 7-Day Free trial then enjoy unlimited access to the platform
Sign up - Free for 7 days
or
Learn why we charge membership fees
We get it - no one likes a content blocker. Take one extra minute and find out why we block content.
Not exactly the question you had in mind?
Sign up for an EE membership and get your own personalized solution. With an EE membership, you can ask unlimited troubleshooting, research, or opinion questions.
ask a question
FelineConspiracy

ASKER
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.
Gertone (Geert Bormans)

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
Gertone (Geert Bormans)

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
All of life is about relationships, and EE has made a viirtual community a real community. It lifts everyone's boat
William Peck