Showing posts with label XSLT. Show all posts
Showing posts with label XSLT. Show all posts

Sunday, April 28, 2013

mdq.XmlTransform -- Part 2: Creating an MS Word Doc


Intro

mdq.xmlTransform can do amazing things. Today I will show you how to write a T-SQL query that returns a Microsoft Office Word document viewable in Word 2003 and later (and most programs that open .doc or .docx files). With mdq.xmlTransform it's very easy. If you don't yet have it, see Setting up mdq.XmlTransform for DDL and setup instructions; it takes less than a minute to setup.


Let's get right to it

Some may be intimidated at the idea of writing a query that returns an MS Word document. Don't be, it's simple. That's why I'll keep this real short and just show you.


(1) A Quick WordProcessingML (WordML) tutorial

1. Open notepad (Start > Run > notepad.exe)
2. Copy/paste the code below into your blank notepad file:

<?xml version="1.0" encoding="utf-8"?>
   
<?mso-application progid="Word.Document"?>
   
       
<w:wordDocument xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml">
           
<w:body>
                               
           
<w:p>
               
<w:r>
                   
<w:t>Hello Word!!!</w:t>
               
</w:r>
           
</w:p>
   
            
</w:body>
       
</w:wordDocument>

3. Go to file > save as and for "Save as Type" select All Files
4. For the file name type helloWord.xml
5. Click Save then close the file.

Now open the file with Microsoft Word version 2003 or later and you should see this:

Congrats! You just created your first word document using WordML.


(2) Using mdq.xmlTransform to produce a Word document

SQL Server supports the XML Data Type. WordML is XML. mdq.xmlTransform transforms XML. Now that you have seen how WordML works lets write a SQL query that returns a Word Document.

Using a database with mdq.xmlTranform, copy/paste and execute this code into SSMS:

DECLARE @xslt xml='

<xsl:stylesheet version="1.0"

    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

    xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml">

    <xsl:output method="xml"  xml:space="default" />

 

    <!--  This template does all the work -->

    <xsl:template match="/" xml:space="default">

        <xsl:processing-instruction name="mso-application" xml:space="default">

            <xsl:text>progid="Word.Document"</xsl:text>

        </xsl:processing-instruction>

  

        <w:wordDocument><!-- This creates the word doc -->

            <w:body>

            <w:p>

                <w:r>

                    <w:t>Created with mdq.xmlTransform (no loops)</w:t>

                </w:r>

            </w:p>

            </w:body>

        </w:wordDocument>

    </xsl:template>  

    <xsl:template match="DataSourceID"/>

</xsl:stylesheet>';

 

SELECT REPLACE(mdq.xmlTransform('',@xslt),

                      '<?mso-application progid="Word.Document"?>',

                      '<?xml version="1.0" encoding="ISO-8859-1"?>

                       <?mso-application progid="Word.Document"?>') AS xml_output;

 

You should already know what will happen if you: copy/paste the result set into a new notepad file, save it like we did earlier, and then open it with Word (but feel free to do it again if you thought it was cool).


...and now for something a little more impressive...

Hopefully some developers, DBAs and BI people see the potential here. Think of how many SQL objects that are (or can be) stored in XML format. Query plans, traces, SSRS reports (RDL) and SSIS packages (DTSX packages) to name a few. Word and Excel files, your whatever.config files, a multitude of SharePoint objects, RSS feeds, web service data, Extended Events, etc, etc... All XML. Thanks to the XML data type and mdq.xmlTransform, information from all these things can be stored, measured, analyzed and, as I will show you in a moment, stuffed into XML files or fragments.

(3) Create a Word doc with SSRS Report (RDL) data

The code below contains two XML documents. For the first (1) I grabbed an some data from an SSRS RDL file (just part of it to keep things simple). Using T-SQL REPLACE I removed the rd: namespace references. The second (2) is an XML transform that will extract the Data Provider and Connection String from an SSRS report and use it to create a Word Doc.

<!--  (1) Grab an RDL XML FRAGMENT from an SSRS report -->
<!--  (using T-SQL REPLACE to remove the rd: namespace references) -->

<DataSource Name="ReportingDemo">
   
<DataSourceID>f34d206b-ca72-4ca6-9d5c-4151cd7eaxxx</DataSourceID>
   
<ConnectionProperties>
       
<DataProvider>SQL</DataProvider>
       
<ConnectString>
            Data Source=ABC;EFG Catalog=XYZ
       
</ConnectString>
   
</ConnectionProperties>
</DataSource>

<!—(2) Create an XSLT function that puts this data into a MS Word Doc -->

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml">
   
<xsl:output method="xml"  xml:space="default" omit-xml-declaration="no" />


    <!--  This template does all the work -->
    <xsl:template match="/DataSource/ConnectionProperties" xml:space="preserve">
        <xsl:processing-instruction name="mso-application" xml:space="default">
            <xsl:text>progid="Word.Document"</xsl:text>
        </xsl:processing-instruction>
   
        <w:wordDocument><!-- This creates the word doc -->
            <w:body>

                <!-- These two templates collect the data -->
                <xsl:apply-templates select="DataProvider"/>
                <xsl:apply-templates select="ConnectString"/>
            </w:body>
        </w:wordDocument>
    </xsl:template>
   
    <xsl:template match="DataProvider" xml:space="preserve">
            <w:p>
                <w:r>
                    <w:t><xsl:apply-templates /></w:t>
                </w:r>
            </w:p>
    </xsl:template>

    <xsl:template match="ConnectString" xml:space="preserve">
            <w:p>
                <w:r>
                    <w:t><xsl:apply-templates /></w:t>
                </w:r>
            </w:p>
    </xsl:template>


    <!-- This disposes of DataSourceID-->
    <xsl:template match="DataSourceID"/>

</xsl:stylesheet>

If we feed that RDL data and transform to mdq.xmlTransform like this:

DECLARE @xml xml='

<DataSource Name="ReportingDemo">

    <DataSourceID>f34d206b-ca72-4ca6-9d5c-4151cd7eaxxx</DataSourceID>

    <ConnectionProperties>

        <DataProvider>SQL</DataProvider>

        <ConnectString>

            Data Source=ABC;EFG Catalog=XYZ

        </ConnectString>

    </ConnectionProperties>

</DataSource>',

 

@xslt xml='

<xsl:stylesheet version="1.0"

    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"

    xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml">

    <xsl:output method="xml"  xml:space="default" />

 

    <!--  This template does all the work -->

    <xsl:template match="/DataSource/ConnectionProperties" xml:space="default">

        <xsl:processing-instruction name="mso-application" xml:space="default">

            <xsl:text>progid="Word.Document"</xsl:text>

        </xsl:processing-instruction>

  

        <w:wordDocument><!-- This creates the word doc -->

            <w:body>

                <!-- These two templates collect the data -->

                <xsl:apply-templates select="DataProvider"/>

                <xsl:apply-templates select="ConnectString"/>

            </w:body>

        </w:wordDocument>

    </xsl:template>

  

    <xsl:template match="DataProvider" xml:space="preserve">

            <w:p>

                <w:r>

                    <w:t><xsl:apply-templates /></w:t>

                </w:r>

            </w:p>

    </xsl:template>

 

    <xsl:template match="ConnectString" xml:space="preserve">

            <w:p>

                <w:r>

                    <w:t><xsl:apply-templates /></w:t>

                </w:r>

            </w:p>

    </xsl:template>

 

    <!-- This disposes of DataSourceID-->

    <xsl:template match="DataSourceID"/>

</xsl:stylesheet>';

 

SELECT REPLACE(mdq.xmlTransform(@xml,@xslt),

                      '<?mso-application progid="Word.Document"?>',

                      '<?xml version="1.0" encoding="ISO-8859-1"?>

                       <?mso-application progid="Word.Document"?>') AS xml_output;

 

We get this:

<?xml version="1.0" encoding="utf-8"?>
   
<?mso-application progid="Word.Document"?>
   
       
<w:wordDocument xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml">
           
<w:body>
               
           
<w:p>
               
<w:r>
                   
<w:t>SQL</w:t>
               
</w:r>
           
</w:p>
               
           
<w:p>
               
<w:r>
                   
<w:t>Data Source=ABC;EFG Catalog=XYZ</w:t>
               
</w:r>
           
</w:p>
   
            
</w:body>
       
</w:wordDocument>

...which, if we copy/paste into notepad and save as wow.xml, then open with Microsoft Office Word, we get:

Conclusion

mdq.xmlTransform is a powerful tool. Period. Today I showed you how to create a basic Word document using just T-SQL and mdq.xmlTransform. Thanks for reading!


--ab

The new xmlsqlninja logo ('X-M-L'-'See-Quel' 'ninja')

Last Updated: 4/29/2013 11:35am (Posted, fixed code issues)

Saturday, April 27, 2013

XSLT 2.0 in SQL Server?


...not if you are relying on .NET or MSXML to process your XSLT 2.0 (or 3.0) transforms.

Many XSLT enthusiasts have been waiting years to show people what you can do in Microsoft Land (.NET, SQL Server, etc) with XSLT 2.0. MSXML still only supports XSLT 1.0. I went to MSDN's Frequently Asked Questions about XSLT page today for an update...

The end.


--ab

The new xmlsqlninja logo ('X-M-L'-'See-Quel' 'ninja')

Last Updated: 4/27/2013 (Posted)

Friday, April 26, 2013

mdq.XmlTransform -- Part 1: Basic XSLT in SQL Server


Intro

In my article, Missing CLR Part 2, I called mdq.XmlTransform the “baddest CLR ever” because it adds a ton of new functionality to your SQL Server instance. In this article I will demonstrate some of that functionality. For many, the concept of XML transforms is new so I’ll keep it basic. The purpose of this article is not to sell you on XML transforms but rather to inform you that this technology is available. Hopefully, as more people see the potential of mdq.XmlTransform, more developers will take some of these ideas and run with them.


XSLT is about transforming Data

The conventional wisdom is that XSLT is used to transform XML into something else (such as a new XML file or text). This is only part of the truth. XSLT is for transforming data into data; that’s what it does. This means that you are not limited to XML as input or as output. XSLT can receive text, numbers (of any kind), dates, XML files, XML fragments, delimited strings, XHTML, HTML, etc, then transform that data, and then return text, numbers, dates, XML, HTML, or pretty much anything else you need.

mdq.XmlTransform can do math, parse strings, perform date calculations and almost anything else SQL can do to data. XSLT solves many complex XML problems better than T-SQL's limited XPath & XQuery functionality. It can also solve many problems SQL cannot solve yet and, unlike any Microsoft language, you can create new XSLT functions without creating new CLRs or compiling more code (as would be the case with LINQ and F#).


A quick note about performance

For many this is a new topic so I am not going to discuss performance considerations yet. There is plenty I can do to make the test code below run much faster but, for the sake of simplicity, I will table that for a future post.


Transforming XML Into SQL

In Setting up mdq.XmlTransform I showed you how to use XSLT to calulate a factorial. I did this to demonstrate recursion and computability using XSLT. Today I will show you the type of task that XSLT does better than regex, padindex or anyting else: transforming XML into SQL.

To demonstrate, I grabbed the HTML code from this Onion article and structured it similar (kinda) to an RSS XML feed (namespaces and CDATA removed for simplicity). The resulting XML code looks like this:

<articles>

  <article name="Starbucks Interview">

    <p>

        BURLINGTON, VT—Citing the loud screeching sounds of milk being steamed in the background, the overly crowded setting, and the fact that a job applicant’s future is being

        discussed over a tiny Formica table at a national coffee shop chain, <b>sources can now confirm that <i>the most depressing job interview you could possibly imagine is currently

        being conducted within a <u>local Starbucks.</u></i></b></p>

    <p>

        The breathtakingly sad interview, which many speculate is for an entry-level position at a small nonprofit organization, was made even more depressing when reports indicated

        that the female job applicant in her early to mid-20s seemed <i style="width:95.5%; ">visibly eager</i> to impress her interviewer, a man who conducts his job interviews at a

        <b style="font-family: comic sans ms /*spelling_....?^?^?*/">Starbucks</b>.

        </p>

    <p>

      <span style="height:90.2%;[xxx=%]">Thanks for meeting me here,</span> said the middle-aged man, who went on to explain that the <i>fulfilling</i> job pays little money and doesn’t include

            health insurance for at least the first eight months of employment. <span style="fff%[%]">It says here you went to Notre Dame. What was that like?</span></p>

    <p>

      <span style="fff%[%]">Ultimately, we’re looking for motivated people who can think fast on their feet and are up to any challenge,</span> the man added while a Josh Groban song played over a

                storewide speaker and <b style="sfty5">seven people lined up three feet away to use the bathroom</b>.

        </p>

    <p>

        Following the interviewer’s description of the <span style="color:blue; address: http://www.x.xxx%50%20%%20%%%">rewarding work</span> and <span style="fff%[%]">character-building

                opportunities, </span> sources confirmed that the interviewee, who was the only individual in the establishment wearing professional attire, went on to describe <b>her</b> work

                experience and career aspirations in front of approximately two dozen café patrons and Starbucks <u>employees</u>.

        </p>

    <p>

        Reports indicate that the interview reached a <b><i>new level of despair when </i></b>a Starbucks patron interrupted the meeting and asked the job applicant if he could take

        the unused seat next to her back to his table.

        </p>

    <p>

      <span style="fff%[%]">Obviously, I’d be thrilled to be involved with an organization like this,</span> said the interviewee, who had been seen aimlessly milling about the outdoor

        commercial plaza that houses the coffee shop for approximately 45 minutes prior to the interview. <span style="width: 90%; f.x.^^">I’m sure you’re seeing a lot of candidates, but I’d like

        to think I can bring something to the table that you can’t get from anyone else.</span></p>

    <p>

      <span style="fff%[%]">I honestly feel that this could be a great chance for me to grow both <b>personally and professionally,</b></span> added the woman, whose earnestness

            juxtaposed with the Starbucks employee sweeping around her feet was, sources noted, absolutely heartbreaking.

        </p>

    <p>

        According to witnesses, while the interview itself was completely and utterly pathetic, the moments leading up to it were equally depressing, with the job applicant having

        difficulty identifying her interviewer when she first entered the Starbucks and asking another man if he was Jim Oswald. In addition, when the two ultimately found one another,

        the awkward small talk they engaged in while waiting in line to order coffee was reportedly a tragic spectacle, to say the least.

        </p>

    <p>

        Other unbearably bleak and humiliating features of the interview included the amount of muffin crumbs that needed to be wiped to the ground before the two sat down and the

        homeless people within earshot who were talking incoherently about a range of subjects.

        </p>

    <p>

        Official records also confirmed that job interviews don’t break your f[%removed%]ing heart when they are conducted in an office as opposed to what is essentially a fast-food restaurant.

        </p>

  </article>

</articles>

I also dirtied up the HTML code to look like the nonsense I often have to deal with (lot's stuff like brackets ([ ]), % signs and other things that make this tricky for T-SQL and Regular Expressions.)


The Requirement

Let's say you are asked to normalize the XML data posted above. In, other words, the requirement is to take that XML data, strip the tags and then insert the data into a couple tables like so:


How would you do that?

This would be extremely difficult if not impossible using anything else yet it's quite simple using XSLT. First we create two XSLT functions - one to strip the tags, another to clean up the white space like this:

<!-- XML to SQL Function -->

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

  <xsl:output method="text" indent="yes" xml:space="preserve" />

  <xsl:template match="/articles" xml:space="default">

    <!-- need: (1)par_id (2)title (3)par_nbr (4)content -->

    <xsl:apply-templates select="/articles/article" />

  </xsl:template>

  <xsl:template match="/articles/article" xml:space="default">

    <xsl:value-of select="position()" />||<xsl:apply-templates select="@name" />||

                <xsl:apply-templates select="p" /></xsl:template>

  <xsl:template match="p" xml:space="default">

    <xsl:apply-templates />

    <xsl:if test="position()!=last()">||</xsl:if>

  </xsl:template>

</xsl:stylesheet>

 

<!-- Normalize Space Function -->

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

        <xsl:output method="text" indent="yes" xml:space="preserve" />

        <xsl:template match="text()" xml:space="default">

                <xsl:value-of select="normalize-space()" />

        </xsl:template>

</xsl:stylesheet>

Once my XSLT functions were ready I would pass them to mdq.xmlTransform like this:

 SET NOCOUNT ON

GO


/*************************************************

(0) PREP: Create output tables:

       1. for the article (article_id, article)

       2. for the content (content_id, article_id, paragraph_nbr, paragraph)

*************************************************/

IF OBJECT_ID('tempdb..#articles') IS NOT NULL

       DROP TABLE #articles;

IF OBJECT_ID('tempdb..#article_content') IS NOT NULL

       DROP TABLE #article_content;

IF OBJECT_ID('tempdb..#tmp') IS NOT NULL

       DROP TABLE #tmp;

GO


CREATE TABLE #articles (article_id int identity primary key, article varchar(100) unique NOT NULL);

CREATE TABLE #article_content (content_id int identity primary key, article_id int NOT NULL, p_nbr int NOT NULL, paragraph varchar(1000) NOT NULL);

GO


/*************************************************

(1) XML DATA: Get the article in XML format

*************************************************/

DECLARE @xml xml='

<articles>

    <article name="Starbucks Interview">

        <p>

        BURLINGTON, VT—Citing the loud screeching sounds of milk being steamed in the background, the overly crowded setting, and the fact that a job applicant’s future is being

        discussed over a tiny Formica table at a national coffee shop chain, <b>sources can now confirm that <i>the most depressing job interview you could possibly imagine is currently

        being conducted within a <u>local Starbucks.</u></i></b>

        </p>

        <p>

        The breathtakingly sad interview, which many speculate is for an entry-level position at a small nonprofit organization, was made even more depressing when reports indicated

        that the female job applicant in her early to mid-20s seemed <i style="width:95.5%; ">visibly eager</i> to impress her interviewer, a man who conducts his job interviews at a

        <b style="font-family: comic sans ms /*spelling_....?^?^?*/">Starbucks</b>.

        </p>

        <p>

        <span style="height:90.2%;[xxx=%]">Thanks for meeting me here,</span> said the middle-aged man, who went on to explain that the <i>fulfilling</i> job pays little money and doesn’t include

            health insurance for at least the first eight months of employment. <span style="fff%[%]">It says here you went to Notre Dame. What was that like?</span>

        </p>

         <p>

        <span style="fff%[%]">Ultimately, we’re looking for motivated people who can think fast on their feet and are up to any challenge,</span> the man added while a Josh Groban song played over a

              speaker and <b style="sfty5">seven people lined up three feet away to use the bathroom</b>.

        </p>

         <p>

        Following the interviewer’s description of the <span style="color:blue; address: http://www.x.xxx%50%20%%20%%%">rewarding work</span> and <span style="fff%[%]">character-building

              opportunities, </span> sources confirmed that the interviewee, who was the only individual in the establishment wearing professional attire, went on to describe <b>her</b> work

              experience and career aspirations in front of approximately two dozen café patrons and Starbucks <u>employees</u>.

        </p>

       <p>

        Reports indicate that the interview reached a <b><i>new level of despair when </i></b>a Starbucks patron interrupted the meeting and asked the job applicant if he could take

        the unused seat next to her back to his table.

        </p>

        <p>

        <span style="fff%[%]" >Obviously, I’d be thrilled to be involved with an organization like this,</span> said the interviewee, who had been seen aimlessly milling about the outdoor

        commercial plaza that houses the coffee shop for approximately 45 minutes prior to the interview. <span style="width: 90%; f.x.^^">I’m sure you’re seeing a lot of candidates, but I’d like

        to think I can bring something to the table that you can’t get from anyone else.</span>

        </p>

        <p>

        <span style="fff%[%]">I honestly feel that this could be a great chance for me to grow both <b>personally and professionally,</b></span> added the woman, whose earnestness

            juxtaposed with the Starbucks employee sweeping around her feet was, sources noted, absolutely heartbreaking.

        </p>

        <p>

        According to witnesses, while the interview itself was completely and utterly pathetic, the moments leading up to it were equally depressing, with the job applicant having

        difficulty identifying her interviewer when she first entered the Starbucks and asking another man if he was Jim Oswald. In addition, when the two ultimately found one another,

        the awkward small talk they engaged in while waiting in line to order coffee was reportedly a tragic spectacle, to say the least.

        </p>

        <p>

        Other unbearably bleak and humiliating features of the interview included the amount of muffin crumbs that needed to be wiped to the ground before the two sat down and the

        homeless people within earshot who were talking incoherently about a range of subjects.

        </p>

        <p>

        Official records also confirmed that job interviews don’t break your f[%removed%]ing heart when they are conducted in an office as opposed to what is essentially a fast-food restaurant.

        </p>

    </article>

</articles>

';


/*************************************************

(2) TRANSFORMS: Create two transforms:

       1. To parse the XML

       2. Trims excess space

*************************************************/

DECLARE @xslt xml='

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

    <xsl:output method="text" indent="yes" xml:space="preserve" />

       <xsl:template match="/articles" xml:space="default">

              <!-- need: (1)par_id (2)title (3)par_nbr (4)content -->

              <xsl:apply-templates select="/articles/article" />

    </xsl:template>

       <xsl:template match="/articles/article" xml:space="default">

              <xsl:value-of select="position()" />||<xsl:apply-templates select="@name" />||

              <xsl:apply-templates select="p" />

       </xsl:template>

       <xsl:template match="p" xml:space="default">

              <xsl:apply-templates /><xsl:if test="position()!=last()">||</xsl:if>

       </xsl:template>

</xsl:stylesheet>

';

DECLARE @xslt_clean xml='

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">

    <xsl:output method="text" indent="yes" xml:space="preserve" />

       <xsl:template match="text()" xml:space="default">

              <xsl:value-of select="normalize-space()"/>

    </xsl:template>

</xsl:stylesheet>

';


/*************************************************

(3) EXECUTE TRANSFORMS & TRIM SPACE

       1. Transform the XML and pass the result tree to @val

       2. Trim excess space (free up memory)

*************************************************/

DECLARE @val varchar(7000)=(SELECT mdq.XmlTransform(@xml, @xslt));

SET @val=(SELECT mdq.XmlTransform(@val, @xslt_clean));

WITH article_info AS

(      SELECT sequence,

RTRIM(LTRIM(token)) AS value

       FROM mdq.split(@val, '||', 1, '', 0))

SELECT Sequence, ROW_NUMBER() OVER (ORDER BY sequence) ordinal, value

INTO #tmp FROM article_info;


/*************************************************

(4) PERFORM INSERTS

       1. articles

       2. content (with a_id as pk for a, fk for ac)

*************************************************/

INSERT INTO #articles

       SELECT value FROM #tmp WHERE Sequence=2;

INSERT INTO #article_content (article_id, p_nbr, paragraph)

       SELECT MAX(article_id) OVER (ORDER BY article_id), Sequence-2,

value

       FROM #tmp, #articles a

       WHERE value<>a.article AND value<>CAST(article_id AS varchar(10))

GO


/*************************************************

(5) TESTING: Ensure that data is correct.

*************************************************/

SELECT * FROM #articles;

SELECT * FROM #article_content;


/*************************************************

(6) CLEANUP

*************************************************/

DROP TABLE #tmp;

DROP TABLE #articles;

DROP TABLE #article_content;

... and that's it.

One thing to note is that I used a mdq.split, a CLR splitter function. You don't need a CLR splitter for this; that was the one I was using when I tested this code.


But what about portability?!?!

That's that best part. The XSLT functions I wrote were developed using XSLT 1.0. Not some "flavor" of XSLT, not T-XSLT, XSLT.NET-sharp, PL/XML or DBTransforms2 (I made those up). The XML format is version 1.0. People have been mastering XSLT 1.0 since last century (1999) because it works, it's proven and isn't going anywhere. Yeah, it's a little tricky but this opensource, non-proprietary functional coding goodness is available to you and your SQL instance at no cost and without compiling a new DLL or any new SQL objects. I have done transforms in MYSQL and Oracle using code I originally wrote for SQL Server and did not need to change a single line. That's what portability sounds like to me.


Conclusion

mdq.xmlTransform is a powerful tool; in this post I demonstrated how to use it to transform XML into relational data. As I said earlier, XSLT is about transforming data not just XML. If you don't like XML then think of mdq.xmlTransform as a way to turn your XML into SQL, CSV or anything else. XSLT 3.0 has great support for JSON. More about that in future posts. Thanks for reading!


--ab

The new xmlsqlninja logo ('X-M-L'-'See-Quel' 'ninja')

P.S. Two Declarative programming languages, 0 loops!



Last Updated: 4/27/2013 (Posted, re-posted with typo's fixed and extra code)