-1

Really hard to fit the explanation into an accurate title but I tried.

Anyhow the question is simple.

Given the following xml:

<Parent> 
  <Child/> 
  <Child/> 
  <Child/> 
  <Child/> 
  <!-- Trying to insert an element here -->
  <OtherChild/>
  <OtherChild/>
  <OtherChild/>
</Parent>

And assuming the following:

  • We know the schema and have access to it.
  • All elements are optional according to the schema but must appear in order.
  • The element we are inserting may be optional as well.
  • There are no guarantees that any of the parents or other child nodes are there or how many there will be.
  • The depth of nesting is not necessarily 1 it could be multiple nested elements.

My solution:

  1. Check to see if there are any of the child we are inserting.
  2. Recursive method to check for previous siblings (requires knowledge of the sibling that precede the element we are inserting) and then check the parent.

Results:

A. If an element that we are trying to insert exists we insert after.
B. If a preceding sibling exists we insert after.
C. If no preceding sibling exists but the parent does we prepend to the parent.
D. Walking up the tree from there we would keep going to the next parent or preceding sibling of the current parent until we find one that exists then insert the entire tree down to the element we are trying to insert.

I am currently doing this in C# code by first using the schema to discover the preceding siblings then iteratively trying to select one until I either find one or reach the end of the list. Then I try to discover the parent then repeat with the parent as the element to be inserted.

The Question:

Is it possible to do this in another, faster, more efficient, neater, better way?

I am just learning about XSLT and if it can be done with a transform that would be perfect (note must be XSLT 1.0).

Alternatively clever ways of minimising the work seeing as at present it will basically have to walk the entire tree until it reaches an existing node which could be a lot of work.

Note: For reference this is actually for use in InfoPath where I will be working on the main data source. In particular I am referring to repeating tables, it is possible for the user to delete all rows from the table at which point any code attempting to add rows to that table may have a very difficult time.

InfoPath itself is able to add new rows to the tables as easy as pie and if I could figure out how they manage it that would be great. In lieu of that I would be happy just to have a simple method for doing so.

I already have a solution to the problem, as I said I am just looking for some folk to bounce ideas off of and see if I can get some improvements. I appreciate any suggestions that you may have.

Thank you :)

Dave Williams
  • 2,166
  • 19
  • 25
  • IMHO, XSLT is ill-suited to the described task. – michael.hor257k Jan 23 '14 at 14:52
  • @michael.hor257k I could be inclined to agree with you. But I'm not sure my current method is any better... have a suggestion? – Dave Williams Jan 24 '14 at 08:30
  • If your current method works, then it's already better :-) I'll try and come back to this later, perhaps something will come to mind. However, it seems to me that this requires the full power of a general programming language - which XSLT is definitely not (and XSLT 1.0 even less). – michael.hor257k Jan 24 '14 at 09:31

2 Answers2

0

This is not a complete answer, but if you give me some clarifications I might be able to work something out.

The following given an xml like :

<root>
    <parent>
        <child>child1</child>
        <child>child2</child>
        <otherChild>other1</otherChild>
    </parent>
</root>

Will transform it into:

<?xml version="1.0" encoding="UTF-8"?> 
<root>
    <parent>
        <child>child1</child>    
        <child>child2</child>
        <insertedChild>inserted1</insertedChild>
        <otherChild>other2</otherChild>
    </parent>
</root>

This is the xsl:

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  version="1.0"> 
  <xsl:template match="/root"> 
    <xsl:for-each select=".">
      <xsl:element name="root">
          <xsl:for-each select="parent">
              <xsl:element name="parent">
                  <xsl:copy-of select="child" />
                  <xsl:element name="insertedChild">
                    inserted1
                  </xsl:element>
                 <xsl:copy-of select="otherChild" />            
              </xsl:element>
           </xsl:for-each>      
      </xsl:element>
    </xsl:for-each>
  </xsl:template> 
</xsl:stylesheet>

This can be altered for any number of distinct elements before and after the element we wish to add.

What I did not get from the problem description is whether the element to be added might be in any place; that is, we do not know beforehand the exact schema we wish to end with.

Is this a transformation you will need to run for multiple elements? If that is the case you may need to create multiple xsl files or include them in this one. Either way it will be a bit more complex.

XSL is however open to parametrization, as you can choose for example both the inserted element's name and value, like so :

<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  version="1.0"> 
<xsl:param name="insertedChild"></xsl:param>
<xsl:param name="valueOfInsertedChild"></xsl:param>
  <xsl:template match="/root"> 
    <xsl:for-each select=".">
        <xsl:element name="root">
            <xsl:for-each select="parent">
                <xsl:element name="parent">
                    <xsl:copy-of select="child" />
                    <xsl:element name="{$insertedChild}">
                        <xsl:value-of select="$valueOfInsertedChild" />
                    </xsl:element>
                    <xsl:copy-of select="otherChild" />         
                </xsl:element>
            </xsl:for-each>     
        </xsl:element>
    </xsl:for-each>
  </xsl:template> 
</xsl:stylesheet>

I hope this is even a bit close to what you're looking for. Clarify the points mentioned above for me and we might work something better out.

parakmiakos
  • 2,994
  • 9
  • 29
  • 43
  • Thanks for your answer, it isn't quite there but could be a good start. Reality is that I want this to be much more generic... We would have access to the schema, but it may well change from time to time. Any transform will have to be generated on the fly. The element being inserted could be named anything, could be at any nested depth, could be among any other elements. Additionally your transform will not insert an element if there is no parent. I need to insert the child no matter what, even if it has to build the entire tree above it. – Dave Williams Jan 22 '14 at 11:56
  • What is the exact condition to insert? Would it be : insert after node named "child"? insert before node "otherChild"? What will your point of reference be? – parakmiakos Jan 22 '14 at 12:18
  • I suppose that is the real question... Point of reference is the preceding sibling, but if that doesn't exist yet then it's the sibling before that and so on, or if there are no siblings at all then it's the parent, or the parents preceding sibling etc. It's possible to get a list of preceding siblings and parents and such from the schema and walk up the tree until you find one that exists, or reach the root. Then you can build back down to the child. I can do this with c# code by iterating over siblings in a recursive function. Question is can this be done in a more direct way with XSLT? – Dave Williams Jan 22 '14 at 13:14
0

Ok I believe I got this. Declared are 4 params, defining the inserted element's name, value, one param to define which shall be the preceding element, and one to define the parent element.

Two limitations :

  • It is required that the root (document) element is known (here "root").
  • You can not get mixed content (that is, insert a node into another that has character data instead of other nodes).

    <?xml version="1.0"?>
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  version="1.0"> 
    <xsl:param name="insertedChild">a</xsl:param>
    <xsl:param name="valueOfInsertedChild">a</xsl:param>
    <xsl:param name="appendAfterChild">child</xsl:param>
    <xsl:param name="appendIntoNode">parent</xsl:param>
    
     <xsl:template match="node()|@*">
         <xsl:copy>
           <xsl:apply-templates select="node()|@*" />
         </xsl:copy>
     </xsl:template>
    
    <xsl:template match="root">
        <xsl:choose>
        <xsl:when test="name() = $appendIntoNode">    
            <xsl:call-template name="insertNode" />
        </xsl:when>
        <xsl:otherwise>
            <xsl:call-template name="recurse" />
        </xsl:otherwise>
        </xsl:choose>
     </xsl:template>
    
    <xsl:template name="recurse">
        <xsl:copy>
        <xsl:for-each select="child::node()">
        <xsl:choose>
        <xsl:when test="name() = $appendIntoNode">    
            <xsl:call-template name="insertNode" />
        </xsl:when>
        <xsl:otherwise>
            <xsl:call-template name="recurse" />
        </xsl:otherwise>
        </xsl:choose>
        </xsl:for-each>
        </xsl:copy>
     </xsl:template>
    
      <xsl:template name="insertNode">    
            <xsl:element name="{name()}">
            <xsl:for-each select="*[(following-sibling::*[name()=$appendAfterChild] and name()!=$appendAfterChild) or name()=$appendAfterChild]">
                    <xsl:copy-of select="." />
            </xsl:for-each>
            <xsl:element name="{$insertedChild}">
                    <xsl:value-of select="$valueOfInsertedChild" />
            </xsl:element>
            <xsl:for-each select="*[not((following-sibling::*[name()=$appendAfterChild] and name()!=$appendAfterChild) or name()=$appendAfterChild)]">
                    <xsl:copy-of select="." />
            </xsl:for-each>
            </xsl:element> 
     </xsl:template>
    
    </xsl:stylesheet>
    

I am not sure how it works for performance reasons, but you can try it out. I think this is quite closer to what you asked. Let me know how this is. :)

parakmiakos
  • 2,994
  • 9
  • 29
  • 43
  • Well, it certainly helped me learn something new about XSLT which is always good :) Unfortunately it also doesn't work. If the parent doesn't exist the node is not inserted. If there is more than one child above it is inserted into the wrong place. If a child with the same name exists it is inserted into the wrong place. Michael may well be correct that XSLT is not a good solution. In the end, whatever the solution it will HAVE to use the schema or how will it know where to insert a node or what nodes should be above it? – Dave Williams Jan 24 '14 at 08:59
  • Well, thanks for the opportunity :P I learned most of this stuff trying to make this xsl. I had never used recursion in xsl before, that's something for me. Good luck to you :) – parakmiakos Jan 24 '14 at 09:44
  • Cool :p thanks for your help, it has given me a direction to look in at least, and it's always good to learn new things ;) – Dave Williams Jan 24 '14 at 09:57