Question

[Note: I have searched Stack Overflow for an answer, but none of the existing answers quite matches my requirements]

Hi, I have two complex XML files, one is a template and the other is client data, and I am looking for a way to merge the two structures into a single output XML.

The template looks similar to this

<Template>
    <Inputs>
        <Client>
            <People>
            </People>
        </Client>
        <RuleCategories>
            <RuleCategory>
                <Name>Basic Rules</Name>
                <Rules>
                    <Rule>
                        <Name>Standard Basic Rule 1</Name>
                        <Logic>if foo then bar</Logic>
                    </Rule>
                    <Rule>
                        <Name>Standard Basic Rule 2</Name>
                        <Logic>if bar then foo</Logic>
                    </Rule>
                </Rules>
            </RuleCategory>
            <RuleCategory>
                <Name>Intermediate Rules</Name>
                <Rules>
                    <Rule>
                        <Name>Standard Intermediate Rule 1</Name>
                        <Logic>if fooz then barz</Logic>
                    </Rule>
                    <Rule>
                        <Name>Standard Intermediate Rule 2</Name>
                        <Logic>if barz then fooz</Logic>
                    </Rule>
                </Rules>
            </RuleCategory>
        </RuleCategories>
    </Inputs>
    <Outputs>
        <Queries>
            <Query>
                <Name>All the foos</Name>
                <Logic>Select all foos</Logic>
            </Query>
            <Query>
                <Name>All the bars</Name>
                <Logic>Select all the bars</Logic>
            </Query>
        </Queries>
    </Outputs>
</Template>

And the client data will look something like this

<ClientData>
    <Inputs>
        <Client>
            <People>
                <Person>
                    <Name>Fred Flinstone</Name>
                    <DateOfBirth>1 Jan 00</DateOfBirth>
                </Person>
            </People>
        </Client>
        <RuleCategories>
            <RuleCategory>
                <Name>Basic Rules</Name>
                <Rules>
                    <Rule>
                        <Name>Client Basic Rule 1</Name>
                        <Logic>if clientfoo then clientbar</Logic>
                    </Rule>
                </Rules>
            </RuleCategory>
            <RuleCategory>
                <Name>Intermediate Rules</Name>
                <Rules>
                    <Rule>
                        <Name>Client Intermediate Rule 1</Name>
                        <Logic>if fred then wilma</Logic>
                    </Rule>
                    <Rule>
                        <Name>Client Intermediate Rule 2</Name>
                        <Logic>if barney then betty</Logic>
                    </Rule>
                </Rules>
            </RuleCategory>
        </RuleCategories>
    </Inputs>
    <Outputs>
        <Queries>
            <Query>
                <Name>Freds stuff</Name>
                <Logic>Select all stuff belonging to Fred</Logic>
            </Query>
        </Queries>
    </Outputs>
</ClientData>

And the final output should look like this

<Request>
    <Inputs>
        <Client>
            <People>
                <Person>
                    <Name>Fred Flinstone</Name>
                    <DateOfBirth>1 Jan 00</DateOfBirth>
                </Person>
            </People>
        </Client>
        <RuleCategories>
            <RuleCategory>
                <Name>Basic Rules</Name>
                <Rules>
                    <Rule>
                        <Name>Standard Basic Rule 1</Name>
                        <Logic>if foo then bar</Logic>
                    </Rule>
                    <Rule>
                        <Name>Standard Basic Rule 2</Name>
                        <Logic>if bar then foo</Logic>
                    </Rule>
                    <Rule>
                        <Name>Client Basic Rule 1</Name>
                        <Logic>if clientfoo then clientbar</Logic>
                    </Rule>
                </Rules>
            </RuleCategory>
            <RuleCategory>
                <Name>Intermediate Rules</Name>
                <Rules>
                    <Rule>
                        <Name>Standard Intermediate Rule 1</Name>
                        <Logic>if fooz then barz</Logic>
                    </Rule>
                    <Rule>
                        <Name>Standard Intermediate Rule 2</Name>
                        <Logic>if barz then fooz</Logic>
                    </Rule>
                    <Rule>
                        <Name>Client Intermediate Rule 1</Name>
                        <Logic>if fred then wilma</Logic>
                    </Rule>
                    <Rule>
                        <Name>Client Intermediate Rule 2</Name>
                        <Logic>if barney then betty</Logic>
                    </Rule>
                </Rules>
            </RuleCategory>
        </RuleCategories>
    </Inputs>
    <Outputs>
        <Queries>
            <Query>
                <Name>All the foos</Name>
                <Logic>Select all foos</Logic>
            </Query>
            <Query>
                <Name>All the bars</Name>
                <Logic>Select all the bars</Logic>
            </Query>
            <Query>
                <Name>Freds stuff</Name>
                <Logic>Select all stuff belonging to Fred</Logic>
            </Query>
        </Queries>
    </Outputs>
</Request>

Of course, I can do this easily in code using the .NET XmlDocument and XmlNode classes, however I have been asked to provide a way to do this merge non-programatically. From my research so far, I believe it could be done with either XQuery or XSLT, but I am not proficient enough in either to determine which to use, or where to begin.

The key requirements are (1) to merge the client rules into the correct rule category by matching RuleCategory/Name and (2) merge all sub object (Persons, Rules, Queries) in a single pass.

Any and all assistance would be greatly appreciated. Please ask any question if anything is unclear.

Was it helpful?

Solution

You have pretty much described what XSLT does (assuming the "template" is unchanging). You just need to rewrite the template as an XSLT stylesheet, for example like this:

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

<xsl:template match="/">
    <Request>
        <Inputs>
            <xsl:copy-of select="ClientData/Inputs/Client"/>
            <RuleCategories>
                <RuleCategory>
                    <Name>Basic Rules</Name>
                    <Rules>
                        <Rule>
                            <Name>Standard Basic Rule 1</Name>
                            <Logic>if foo then bar</Logic>
                        </Rule>
                        <Rule>
                            <Name>Standard Basic Rule 2</Name>
                            <Logic>if bar then foo</Logic>
                        </Rule>
                        <xsl:copy-of select="ClientData/Inputs/RuleCategories/RuleCategory[Name='Basic Rules']/Rules/Rule"/>
                    </Rules>
                </RuleCategory>
                <RuleCategory>
                    <Name>Intermediate Rules</Name>
                    <Rules>
                        <Rule>
                            <Name>Standard Intermediate Rule 1</Name>
                            <Logic>if fooz then barz</Logic>
                        </Rule>
                        <Rule>
                            <Name>Standard Intermediate Rule 2</Name>
                            <Logic>if barz then fooz</Logic>
                        </Rule>
                        <xsl:copy-of select="ClientData/Inputs/RuleCategories/RuleCategory[Name='Intermediate Rules']/Rules/Rule"/>
                    </Rules>
                </RuleCategory>
            </RuleCategories>
        </Inputs>
        <Outputs>
            <Queries>
                <Query>
                    <Name>All the foos</Name>
                    <Logic>Select all foos</Logic>
                </Query>
                <Query>
                    <Name>All the bars</Name>
                    <Logic>Select all the bars</Logic>
                </Query>
                <xsl:copy-of select="ClientData/Outputs/Queries/Query"/>
            </Queries>
        </Outputs>
    </Request>
</xsl:template>

</xsl:stylesheet>

Note that the result of the transformation is not much different from the source client data document - so the more experienced programmer would likely start by copying everything as is using the identity transform template, then add a few more templates to handle the exceptions e.g.:

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

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

<!-- exceptions -->

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

<xsl:template match="Queries">
    <xsl:copy>
        <Query>
            <Name>All the foos</Name>
            <Logic>Select all foos</Logic>
        </Query>
        <Query>
            <Name>All the bars</Name>
            <Logic>Select all the bars</Logic>
        </Query>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template> 

This needs more work to handle boiler-plate category rules in a manner similar to the boiler-plate queries.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top