1

I tried to look for an answer on google, but the results i get back is either how to substitute a string or replacing a substring etc. But my question is slightly different.

Say I have an existing XSL template, say "hello-world", that processes "data/records/record", but I cannot modify hello-world, so I'm thinking of creating a wrapper template that will massage/modify the data inside each record before passing it to hello-world... is there a way to do that?

So far, I've managed to create a function that would filter out the duplicate records, and I was thinking of replacing all the records inside "data/records/*" with the new one...

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">


    <xsl:template match="/">
        <xsl:call-template name="get-unique-record">
            <xsl:with-param name="records" select="/data/records/record"/>
        </xsl:call-template>
    </xsl:template>

    <!-- This function will filter out the given records and return a unique set of records -->
    <xsl:key name="kField_ID" match="field[@name='ID']" use="."/>
    <xsl:template name="get-unique-record">
        <xsl:param name="records"/>
        <xsl:for-each select="$records">
            <xsl:variable name="record" select="."/>
            <xsl:if test="$record//field[generate-id() = generate-id(key('kField_ID', .))]">
            <xsl:copy-of select="$record"/>         
            </xsl:if>
        </xsl:for-each>
    </xsl:template>

</xsl:stylesheet>

Now... is it possible to do something like:

<xsl:variable name="/data/records/record">
    <xsl:call-template name="get-unique-record">
        <xsl:with-param name="records" select="/data/records/record"/>
    </xsl:call-template>
</xsl:variable>

EDIT: @LasrH, thanks for the quick reply. Is there a way to make a copy of the existing "/" and then replace all the /data/records/record with the filtered one?

EDIT2: @LasrH, I created couple of template to modify and rebuild the "data" node. Is it possible to use node-set to "replace" the existing input with my new data as input?

<xsl:variable name="data">
    <xsl:call-template name="rebuild-data-with-record">
        <xsl:with-param name="records">
                    <xsl:copy-of select="$unique-records"></xsl:copy-of>
                    </xsl:with-param>
    </xsl:call-template>
</xsl:variable>

Then further down I tried to use node-set like this:

<xsl:apply-templates select="exslt:node-set($data)/data"/>

But it doesn't look like is doing it... there is no error thrown either.

AstroCB
  • 12,337
  • 20
  • 57
  • 73
codenamezero
  • 2,724
  • 29
  • 64

2 Answers2

1

No, in XSL you cannot modify the source document in-place.

However, you can massage the source document upstream (using a separate XSL stylesheet), and pass the massaged document to the XSL stylesheet that calls "hello-world" template, instead of letting it process the original source document.

You can even do this within the same stylesheet that contains "hello-world", if you are able to modify that stylesheet. (But I guess you can't modify that stylesheet, or you would be able to modify "hello-world".)

LarsH
  • 27,481
  • 8
  • 94
  • 152
  • Thanks LarsH, is it possible to make a copy of / but replace the records with my modified ones? – codenamezero Jul 17 '12 at 21:39
  • @codenamezero: Yes it's possible, if you mean that the records *in the copy* are replaced. It's a bit harder if you must use XSLT 1.0, because the output of a template is a result tree fragment, and you would need the node-set() extension function to turn it back into a nodeset for use by the hello-world template. (See http://www.xml.com/pub/a/2003/07/16/nodeset.html) – LarsH Jul 18 '12 at 14:02
  • I will check that out. I finally got it to filter out the records, and i was able to replace /data with my filtered /data, however I am currently stuck at passing this new data to my template "hello-world". Been searching for an answer the whole morning, and yours arrived just in time! Is it possible to node-set the existing input with my modified one? (I've updated my original thread with new code that I experimented. – codenamezero Jul 18 '12 at 15:43
  • Actually, you can "modify" or even "substitute" data at XSL level. See my answer below. :D – codenamezero Aug 03 '12 at 14:35
1

Actually, after much research and trials, you CAN "replace/substitute" data at the XSL level!!! You just need to rebuild the root node yourself, and pass your "modified root" (RTF cast it back into node-set) to your template and have your template read it off your own data instead!!!

I asked another question here, which was part of my experiment to get this to work: Unable to cast from XRTreeFrag into XNodeSet

The idea is this, you have a template function that read/deal with the incoming data, and we pretty much always read the input off the root /blah/blah/blah... Rather than reading it off the root, you can do this in EVERY/ANY of your template:

<xsl:template name="helloworld">
    <xsl:param name="inputRoot" select="/"/>
    <xsl:variable name="root" select="$inputRoot"/>
    rest of your code goes here...

Now, replace all your root access with $root/blah/blah/blah and it will take in your modified XSL data!

Cool thing about this is that, if you don't pass any input data, it will just assume the input to be root! ;)

This was tested and work flawlessly. There is only one concern I have however, if the XSL input is HUGE, reconstructing the whole root might cause performance issue. But my input was only two dozen records, and there were zero performance hit for my case.

So you may want to double check if your input data is large.

This solution/method is XSL 1.0 friendly.

Community
  • 1
  • 1
codenamezero
  • 2,724
  • 29
  • 64
  • It sounds like you're doing what I said in my answer, massaging the source XML upstream and passing it to the helloworld template. Sorry I didn't make this clearer. Glad you figured it out! – LarsH Aug 03 '12 at 14:45