Categories
Contact Doug!
Learn About Doug!
View Doug Boude's online resume
updated 11/18/2009

View Doug Boude's profile on LinkedIn
Link to me!

Follow Doug Boude on Twitter
Follow me!

Be Doug's friend on Facebook
Befriend me!
(I promise not to follow you home)
OO Lexicon
Chat with Doug!
Recent Entries
You may also be interested in...
Web Hosting

<< March, 2010 >>
SMTWTFS
123456
78910111213
14151617181920
21222324252627
28293031
Search Blog

Recent Comments
Re: Disappearing IE Popup Window During Save/Open Dialog (by LZ at 4/20 7:58 AM)
Re: Create Dynamic WHERE Clauses in PHP (by pooja at 3/20 7:29 AM)
Re: Just What IS a 'Service Layer', Anyway? (by EugenK at 3/07 7:56 PM)
Re: Using Google as your CF Mail Server (by 5starwebteam.com at 2/25 1:27 AM)
Re: Why Provide for Service layer objects in CFWheels? (by Steven Benjamin at 1/25 11:43 AM)
Re: What is an 'Advanced' Coldfusion Developer? (by ColdFusion Developer at 12/24 5:14 AM)
Re: Equivalent of SQL "TOP X" in Oracle (by Ashenafi Desalegn at 12/06 5:29 AM)
Re: PHP Export to Excel Snippet (by serene at 12/05 1:44 AM)
Re: Just What Is 'Application Logic', Anyway? (by Arif at 11/13 8:06 AM)
Re: Hosts File Changes Not Acknowledged on Vista 64 (by Aaron at 10/22 2:31 PM)
Re: PHP Export to Excel Snippet (by Jafar Shah at 10/10 4:28 AM)
Re: Viewing Option Text (in IE7) that's Wider than the Select List (by Chenelle S at 10/04 12:53 PM)
Re: PHP Export to Excel Snippet (by Kilo at 9/26 5:20 PM)
Re: Porting Coldfusion Code to Mura (by tariq at 9/03 9:51 AM)
Re: Just What IS a 'Service Layer', Anyway? (by James at 8/27 4:06 PM)
Re: Calculating Business Hours (by helen at 8/14 2:54 AM)
Re: What IS 'Business Logic', Anyway? (by dougboude at 8/06 11:30 AM)
Re: What IS 'Business Logic', Anyway? (by Adrianne at 8/06 10:29 AM)
Re: Family Law: The Weapon of Choice for Woman Scorned (by dougboude at 8/04 4:39 PM)
Re: Family Law: The Weapon of Choice for Woman Scorned (by Lola LB at 8/04 7:43 AM)
Archives
Photo Albums
Funnies (5)
Family (3)
RSS

Powered by
BlogCFM v1.11

31 March 2010
Easy Way to Integrate ColdFusion into Non-ColdFusion Templates
Ajax to the Rescue!

Have you ever found yourself in a situation where you needed to implement some new feature using one scripting language within a template that is written in a different scripting language, or possibly no scripting language at all (pure HTML)?  Of course, you could just turn the pure HTML template into a CFML template, and at times that is indeed the best route to go, as long as breaking every link pointing back to this page is acceptable. If the target template is written in another scripting language, however, transforming it into a CFML template would probably not be a viable option. Ah, but there is at least one way to still be able to integrate your Coldfusion functionality into any template (HTML OR those written in other scripting languages): Leveraging the power of Ajax.

Typically the first route our minds go when asked to integrate two discrete templates is to look for a way to "include" one within the other. HTML does provide a type of inclusion ability (called 'Server Side Includes' (SSI)), but this approach is typically used to include other HTML documents and would be challenging to make work with a Coldfusion template. Other scripting languages have the ability to include other templates written in the same language, but getting them to play nicely including Coldfusion would also prove challenging. You could also go the route of using an IFRAME within your template, but this approach also presents limitations and hurdles that you may not wish to deal with. The simple solution, and the simplest approach to making it work, again, is Ajax.

The Scenario

In this scenario, the target template is a static HTML site and the code you want to integrate is written in Coldfusion. Our goal is to dynamically display the contents of an upcoming events database table in our Events section of the company's HTML home page. Here's how I approached it:

1. Create a target within the static HTML page where you want the event data to appear. In my case, I created this bit of HTML:

  <h3>Corporate Events</h3>
  <div id="eventTarg"></div>

 

2. Create a CFM template to act as your data provider. Mine is called eventAjax.cfm, and contains several different methods since it is used by other applications as well;

3. "Wire up" your HTML page with your favorite Javascript library. Mine (currently) is Prototype, so my code snippets will reflect Prototype's syntax, but you should be able to easily relate it to your favorite flavor

That's it!  Now, some details...

 

The Data Provider

Some may feel the need to write full blown web services to provide data to the consumer/caller, but personally I feel that most times this is major overkill when a RESTful approach works beautifully. Here is what my eventAjax.cfm looks like, showing only a couple of possible methods:

<!--- suppress any and all output except what we explicitly allow via cfoutput tags... --->
<cfsetting enablecfoutputonly="true" showdebugoutput="false" />


<!--- our switch expression value. this determines what action will take place --->
<cfparam name="meth" default="" />

<cfswitch expression="#meth#">
 <cfcase value="getEventLinks">
  <!--- retrieving all events that have not yet ended and are published --->
  <cfquery name="qryGetLiveEvents" datasource="#dsn#">
   select id,name,descrip,eventfromdate,eventtodate
   from registrationevents
   where <cfqueryparam value="#now()#" cfsqltype="cf_sql_date" /> <= eventToDate
   AND publish = <cfqueryparam value="1" cfsqltype="cf_sql_tinyint" />
   order by eventFromDate
  </cfquery>

  <!--- by default, create this return value... --->
  <cfset links = "no events currently scheduled" />

  <cfif qryGetLiveEvents.recordcount gt 0><!--- if we have records, loop over them and create the 'links' content --->
   <cfoutput>
   <cfsavecontent variable="links">
    <p id="eventlinks" class="eventlink">
     <cfloop query="qryGetLiveEvents">
      <a href="registration.cfm?eventid=#id#" target="_blank">#name#</a><span class="eventText"> <em>(from #dateformat(eventfromdate,"mm/dd/yyyy")# to #dateformat(eventtodate,"mm/dd/yyyy")#)</em><br>#descrip#</span><br><br>
     </cfloop>
    </p>
   </cfsavecontent>
   </cfoutput>
  </cfif>
  <cfoutput>#links#</cfoutput>
 </cfcase>

 <cfcase value="updatePublish">
  <cfparam name="eventid" default=""/>
  <cfif not isnumeric(eventid)>
   <cfoutput>INVALID EVENT ID</cfoutput>
   <cfabort>
  </cfif>
  <cfquery name="qryUpdatePublish" datasource="#dsn#">
   UPDATE registrationevents
   SET publish = <cfqueryparam value="#newval#" cfsqltype="cf_sql_tinyint" />
   WHERE id=<cfqueryparam value="#eventid#" cfsqltype="cf_sql_integer" />
  </cfquery>
 </cfcase>

 <cfdefaultcase>
  <cfoutput>UNAUTHORIZED ACCESS</cfoutput>
 </cfdefaultcase>
</cfswitch>
<!--- helper functions can reside below this line... --->

Reading through this code, it is pretty self-explanatory. Basically, when the ajax call is made, only values explicitly output using CFOUTPUT tags will be returned to the caller. My "getEventLinks" method is returning a pre-formed block of html that my client side javascript will simply dump into its target div.

Wiring up the HTML

Wiring up our HTML page consists of doing only two things:

1. include your javascript library;
2. create a function to be executed after the html has fully loaded;


My step 1 line looks like this:

<html>
...
<title>Company Name Here</title>
<script type="text/javascript" src="js/prototype.js"></script>
....
</html>

 

Step 2 is inline script written at the bottom of the template, just within the <body> tag. I don't believe it HAS to reside within the <body> tag, but that's where i chose to put it. It looks like this:

<script>
 document.observe("dom:loaded", function() {
  params = new Hash();
  params.set("meth","getEventLinks");
  new Ajax.Updater('eventTarg','eventAjax.cfm',{parameters:params,method:'post'});
 });
</script>

 

That's it! Now, everytime the page is loaded, Prototype will execute an ajax call to the template specified and will stuff the results into the targeted div 'eventTarg'. If no qualifying events are found, we'll simply get the phrase "no events currently scheduled".

Here is a screenshot of the html page loaded, using Firebug to see the ajax call that was made and the value returned:

firebug ajax call

Posted by dougboude at 3:52 PM | PRINT THIS POST! | Link | 5 comments



16 March 2010
Leveraging Response Headers in Ajax Calls

If you're doing any sort of Ajax development, then you undoubtedly already are quite familiar with the different ways you can use an Ajax call. For instance, you can have the call return a fragment of fully formed html (such as a table populated with data). For a more advanced user, you may have your Ajax calls returning raw data in the form of JSON or XML and parse it on the client side. In either case, though, your single Ajax call is returning a single value. But what if you want several different discrete pieces of "stuff" returned in a single call? You could get creative, and do something such as this in your backend code:

<cfsetting enablecfoutputonly="true" />
<cfscript>
  stReturnData = structnew();
  stReturnData.htmlBlob = "<table><tr><td>bla bla bla</td></tr></table>";
  stReturnData.statistics = structnew();
  stReturnData.statistics.recordcount = 212;
  stReturnData.statistics.redWidgets = 32;
  stReturnData.statistics.blueWidgets = 54;
  stReturnData.someQuery = qryIRanPriorToThis;
 
  returnVal = serializeJSON(stReturnData);
</cfscript>

<cfoutput>#returnVal#</cfoutput>

The above would work perfectly fine. It would, however, require that you be willing to write some serious Javascript to access the individual pieces of data, and compels you to have to visualize how the data sets are structured and nested as a whole.

Again, nothing wrong with doing something like the above. But, when I find myself wanting to return more than one value with a single Ajax call, more often than not I will approach it by leveraging response headers.

Response headers are like the saddle bags on a Harley. If your Ajax response is the Harley, then the burley tatted dude (or dudette) riding it is your primary response value. This Harley, however, comes equipped with a pre-defined set of saddle bags that contain what I'm calling "peripheral" information, or metadata about the response, AND (this is most critical), the ability for you to add your own custom saddle bags before the response comes riding home to its caller!

I have never had a need to even look at or care about those pre-canned response headers. They contain name value pairs such as "Transfer-encoding:chunked", "content-type:text-html", "Date: Tue, 16 Mar 2010 15:43:35 GMT", and a couple of others. But, in order to satisfy needs such as I described above in the code sample, I DO add my own response headers and stuff them with values my Javascript needs.

Doing this in Coldfusion is very simple and easy (as are most tasks). Re-doing the example above, then, I'm going to choose one of the values to be my burley tatted rider (typically the value consuming the most bytes), and designate the rest to response headers, like so:

<cfsetting enablecfoutputonly="true" />

<cfset ReturnData = "<table><tr><td>bla bla bla</td></tr></table>" />

<cfheader name="recordcount" value="212" />

<cfheader name="redWidgets" value="32" />

<cfheader name="blueWidgets" value="54" />

<cfheader name="someQuery" value="#serializeJSON(qryIRanPriorToThis,true)#" />

<cfoutput>#ReturnData#</cfoutput>

So now my Harley has several new, custom saddle bags attached, each containing the value I set in the "value" attribute of the cfheader tag, and my main rider, or the actual response value is my ReturnData (the blob of html).

Let's look at the Javascript needed to utilize this response. In this example we are going to use the Prototype library to create a new Ajax Updater. Updaters simplify the task of making an Ajax call and stuffing the response value directly into a target element. So let's say we have a div with an ID 'dataDiv' that will hold a table of data. The Ajax updater line will look like this:

new Ajax.Updater('dataDiv',getDataURL,{parameters:params,method:'get');

 

 

Now, the line above deals exclusively with the response value and ignores anything to do with the response headers. In order to access those headers, we have to tell our Updater to pass its response (Harley, rider, saddlebags, and all) to a function that we designate. This is called "using a callback function", and our line of code will now look like this:

new Ajax.Updater('dataDiv',getDataURL,{parameters:params,method:'get',onComplete:processSaddlebags);

 

 

The Updater will update its target div with the response value, then hand the Harley off to the processSaddlebags function, which will look something like this:

function processSaddlebags(ajaxResponse){
  //let's set each of our response header values to a local variable...
  var recordcount = ajaxResponse.getHeader('recordcount');
  var redWidgets = ajaxResponse.getHeader('redWidgets');
  var blueWidgets = ajaxResponse.getHeader('blueWidgets');
  var objQuery = ajaxResponse.getHeader('someQuery');

  //now we can do 'stuff' with these values!
  alert('total number of records in our query: ' + objQuery['ROWCOUNT']);
  //etc....
}

It should be noted at this point that a most MAHVELOUS way for you to get a clearer picture of what is happening when your code makes Ajax calls is to use the Firefox browser with the Firebug plugin. By doing this simple thing, Firebug will provide you with all the details of every Ajax call you make. Here's a screenshot of a sample Ajax call and the kinds of info you get back (notice the custom header 'total' that I added, and the fact that I'm outputting that value in the html as the "Total Records" value):

response headers

screenshot of firebug ajax call showing response headers

response value

screenshot of firebug ajax call showing the response value

Now, all that having been said, there are size limitations to the usage of response headers that you should keep in mind. I could not find a specific reference that talked about the size limit of response headers, but I did do some experimentation to try and approximate what that limit is. I stuffed as many characters as I could into a response header, and the value was truncated at 6,653 characters (including spaces). I created two custom headers with the same 6,653 characters in it, and both were returned, so I'm assuming the limitation is per header (though common sense tells me there is probably a cumulative maximum size as well). Knowing that there IS a limit, my recommendation is that you use headers for smaller pieces of data and never to convey something as large as a JSON data set unless you know that it will be limited in size.

That's it boys and girls! Now go out there and leverage those response headers!

Posted by dougboude at 12:47 PM | PRINT THIS POST! | Link | 5 comments
12 March 2010
Spreadsheet to XML Transformation Utility Overview

I am currently working on a project that has proven to be very challenging, architecturally speaking. I've been learning a lot along the way, and I have come up with some approaches to addressing the challenge that I feel are pretty doggone sweet, so I thought I'd share some of the details in case it helps spark ideas for other people.

Let me describe how this challenge came about, and the challenge itself.

I'm sitting in my office working on something when I hear several people in an informal meeting discussing how much in time and money it will cost them to pay several temps to enter data from a spreadsheet into a remote system. I wrestle with the idea of making work for myself for a few seconds, then inject myself into the conversation and ask them a few questions about the remote system and what ways it provides for inputting data. As it turns out, the remote system has an interface for receiving this same data in the form of an XML file. Simply drop the XML file into a special folder in their system and it is automatically imported. "So, can you turn our spreadsheets into xml files"?, they ask me. "Of course"!, I reply. And so it begins.

As I try to always do, I explored what potential future requests might entail surrounding this process, examined the possibility of this process' usefulness to other companies, and bore in mind the potential of using this process to accommodate OTHER spreadsheet sources and OTHER xml destination formats. The result was a full blown utility application that allows a user to define a spreadsheet layout, map that layout to a targeted xml layout, then be able to import spreadsheets and produce the xml files in the target format, fully populated and validated; upload those files to their FTP destination; and monitor the status of the uploaded files via feedback mechanisms built in to the target system.

Bear in mind this application is not finished, but I have a substantial amount of the architecture completed and code working up to the point of the FTP process, so its the ins and outs of some of those moving parts that I wanted to share with you.

System Overview: How it Works

1. User logs in and chooses 'Upload Claims'
2. selected spreadsheet is uploaded and validated
3. validated spreadsheet is imported via POI
4. xml node to spreadsheet column mappings are retrieved

for each row in the spreadsheet
  5. blank xml file is created by applying an XML stylesheet (xslt) to the xsd on file;
  6. node values are translated from their tokens to actual values
  7. nodes are located within the xml file via their id and their value replaced with translated value
  8. xml files are "cleaned up" (optional), removing all empty nodes and node trees by applying an XML stylesheet

9. completed xml files are FTP'd to their destination (based on client settings)


DETAILS

Maintaining XML Definitions in the System

So, the first aspect I tackled was the best way to maintain target XML definitions. Well, since I know I want to be able to validate the XML files I create, and since the existing methods of xml validation of necessity ARE a definition of the XML, it made perfect sense to me to start with either a DTD or an XML Schema that represents the targeted XML format. DTDs don't have near the depth of metadata that I would need for future use, so I opted to use XML Schemas (or, 'xsd' files). Every potential target XML format this system maintains will be represented by an existing XSD file stored in a subfolder of the app.

If you aren't familiar with XML Schemas, let me tell you they are invaluable if you want to validate your xml in any detail. You define elements, the data types they hold, any rules that should be applied to those values (such as "only one of the items in this list of values", "a date in the following format", etc.), what attributes an element has and the rules for the values THEY hold, what elements are required, how many instances of a particular element are legal, what order elements should appear in, whether an element is required or not, and pretty much ANYTHING else you can think of! If what you need to store about the XML is NOT available as a standard XSD item, then do what I did and create your OWN namespace within the XSD and add whatever other attributes you want!

For the system I am building, one vital step is to ensure that every node, or potential spot for storing a system value, had to have a unique id. Since there was nothing in the existing xs namespace that allowed for that, I created my own namespace named after the app and added an "ID" attribute to every element and attribute defined for the target XML format. Here's the header of my XSD (notice the 'xclaim' namespace defined): 

<xs:schema
   xmlns:xs="http://www.w3.org/2001/XMLSchema"
   elementFormDefault="qualified"
   attributeFormDefault="unqualified"
   xmlns:xclaim="http://www.somesite.com" >

 Having defined the xclaim namespace allows me to legally do the following: 

<xs:element name="XACTNET_INFO"  xclaim:id="id_1">

So, in my system, to add a new XML target, simply create an XSD schema file that 'describes' that format, complete with the xclaim namespace's 'id' attribute, and you have accomplished your goal! 

Maintaining Spreadsheet Definitions

I wanted to be able to "head off at the pass" as much confusion or needed validation code as possible, so I opted to 'validate' an uploaded spreadsheet before attempting to process it. What this means to me is that, when a spreadsheet is uploaded I will first check to see that it contains all of the columns (appropriately named) as the user said it should and that all of those columns appear in the correct order. If the spreadsheet passes these two checks, I call it good and we proceed. Otherwise, I reject it and give them a nice message so saying.

In my UI I provide them a way to create and maintain spreadsheet definitions. Essentially they tell the app what the name of the column is and what order it appears in.

phpmyadmin column definitions

Simple enough, eh? 

Maintaining Spreadsheet Column/XML Mappings

Ah, now THIS little bugger was much tougher than one may think at first glance. You see, properly populating your target XML poses many challenges, such as:

-not every column maps directly to only one xml node (element or attribute). This is a one to many relationship, for sure!;

-not every value needed in your xml file COMES from the incoming spreadsheet! Values such as today's date, the client's name, address, or other metadata, etc.;

-not every value needed in your xml file is a SINGLE value, but could very well be a CONCATENATED value! Such as in my case, where the values from three columns needed to be concated along with today's date and placed into a specific element's text, or where one attribute's value had to be the concatenation of a fixed string "COV" and an iterating value (COV1, COV2, etc.)

So you see, designing a storage mechanism to allow such fluidity and yet still be easily maintainable was no easy task to conquer! The solution I came up with accommodates all of the above, but still does have its limitations. Not a perfect solution, but close enough I'd say!

Here then is what the stored column-to-node mappings look like:

column to node mapping table

Some highlights of this approach:

-nodeID values correspond to the "xclaim:id" values spoken of earlier with regard to the XSD file;

-nodeID values with decimal counters indicate that there may be multiple occurrences of that node's branch in the final xml file. The decimal indicates which iteration of that branch the value belongs in;

-I created "tokens" as placeholders for those values that must be dynamically determined at run time. {column-x} refers to the value in column X, {column-x-name} is replaced with the actual column name, {iterator} is an incrementing value, {currentDateTime} is the current date and time, etc.;

-by including a targeted xsd for a node-value mapping (note the xsdID field), I can effectively map a single spreadsheet to multiple destination xml formats;

-the sortorder field is used in cases where a nodeid has multiple values indicated. This means that those multiple values should be concatenated in the order of 'sortorder'.

By using mysql's really awesome group_concat function, I can return a nice list of values, in their proper order. Here's the query I use to retrieve my mappings. Note the use of group_concat: 

   SELECT m.sheetID, m.xsdID, m.nodeID, GROUP_CONCAT( m.nodeValue ORDER BY m.sortorder SEPARATOR '|' ) AS nodeValue
   FROM mapping m
   WHERE m.sheetid = <cfqueryparam value="#arguments.sheetID#" cfsqltype="cf_sql_integer" />
   AND m.xsdID = <cfqueryparam value="#arguments.xsdID#" cfsqltype="cf_sql_integer" />
   GROUP BY m.sheetID, m.xsdID, m.nodeID
   ORDER BY m.nodeID

 The results looks like this:

node value query results

With the query above retrieved, I can now walk through each row of my imported spreadsheet and calculate the appropriate value for the targeted node IDs. :) 

Creating the Blank, or Skeleton XML File

In this system, the only thing we maintain regarding a tarteted xml format is its XSD schema file. Since the XSD schema file is itself an XML file, the logical most expeditious approach to creating our skeleton xml is to leverage XML Stylesheets, or XSLT. I searched high and low for an XSLT that would transform a valid schema file into the skeleton XML it represents and could not find a single example. I thought surely such a thing would have been done many times over! So, since I couldn't find one, I wrote one. It's not all that lengthy really, so here it is in all its glory:

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:str="http://exslt.org/strings"
 xmlns:xalan="http://xml.apache.org/xalan"
 xmlns:xs="http://www.w3.org/2001/XMLSchema"
 xmlns:xclaim="http://www.somesite.com"
 exclude-result-prefixes="#default xsl str xalan xs xclaim">
 <xsl:output method="xml" indent="no" encoding="ISO-8859-1" omit-xml-declaration="yes" />
 <xsl:template match="/">
  <xsl:element name="XACTDOC">
   <xsl:apply-templates />
  </xsl:element>
 </xsl:template>
 <!-- getting our elements, but excluding complexTypes with a name attribute (entity definitions, as i refer to them) -->
  <xsl:template match="xs:complexType[not(@name)]/xs:sequence/xs:element|xs:complexType[not(@name)]/xs:all/xs:element|xs:complexType[not(@name)]/xs:choice/xs:element">
   <xsl:call-template name="outputElement">
   <xsl:with-param name="isEntity" select="false()" />
   <xsl:with-param name="parentID" select="@xclaim:id" />
  </xsl:call-template> 
 </xsl:template>
 <xsl:template name="outputElement">
  <xsl:param name="isEntity" />
  <xsl:param name="parentID" />
  <xsl:choose>
   <!-- if the element we are looking at has a type specified, and that type is not a predefined xs: type (must be an entity!)... -->
   <xsl:when test="@type and substring(@type,1,3)!='xs:'">
    <xsl:element name="{@name}">
      <xsl:call-template name="outputEntity">
      <xsl:with-param name="thisname" select="@type" />
      <xsl:with-param name="parentID" select="@xclaim:id" />
     </xsl:call-template>
    </xsl:element>
   </xsl:when>
   <xsl:otherwise><!-- dealing with an element that is NOT pre-defined -->
    <!-- does this element have a type attribute that is enumerable? -->
    <xsl:variable name="hasEnum">
    <xsl:choose>
     <xsl:when test="count(xs:complexType/xs:attribute[@name='type']/*/xs:restriction/xs:enumeration) > 0">
      <xsl:value-of select="true()"/>
     </xsl:when>
     <xsl:otherwise>
      <xsl:value-of select="false()"/>
     </xsl:otherwise>
    </xsl:choose>  
    </xsl:variable>

    <xsl:variable name="thisnode">
     <xsl:element name="{@name}"><!-- output the new element tag -->
      <!-- if this element will contain text... -->
      <xsl:if test="@type or count(xs:simpleType/xs:restriction|xs:complexType/xs:restriction)>0">
       <xsl:value-of select="@xclaim:id" />
      </xsl:if>
      
      <!-- output all the attributes this element has. if it has a type attribute and the values are enumerable, put a marker in -->
      <xsl:for-each select="xs:complexType/xs:attribute">
       <xsl:attribute name="{@name}">
        <xsl:choose>
         <xsl:when test="$isEntity">
          <!-- if this attribute is from a pre-defined entity, create a value placeholder for it that is unique yet predictable -->
          <xsl:value-of select="concat($parentID,'-',substring-after(@xclaim:id,'_'))" />
         </xsl:when>
         <xsl:otherwise>
          <xsl:value-of select="@xclaim:id" />
         </xsl:otherwise>
        </xsl:choose>
       </xsl:attribute>
      </xsl:for-each>
      <!-- this makes it all recursive! -->
      <xsl:apply-templates />
     </xsl:element> 
    </xsl:variable>
    <xsl:copy-of select="$thisnode" /> 
   </xsl:otherwise>
  </xsl:choose>
 </xsl:template> 
 <xsl:template name="outputEntity">
  <xsl:param name="thisname" />
  <xsl:param name="parentID" />
  <xsl:for-each select="/xs:schema/xs:complexType">
   <xsl:if test="@name=$thisname">
    <xsl:for-each select="xs:sequence/xs:element|xs:choice/xs:element|xs:all/xs:element">
     <xsl:call-template name="outputElement">
      <xsl:with-param name="isEntity" select="true()" />
      <xsl:with-param name="parentID" select="$parentID" />
     </xsl:call-template>
    </xsl:for-each>
   </xsl:if>
  </xsl:for-each>
 </xsl:template>
 <xsl:template match="xs:annotation/xs:documentation">
 </xsl:template>
</xsl:stylesheet>

This XSLT is not quite generic enough to work with any XSD, but I hope to make it so before I'm finished (if anybody else wants to make that happen, please do!).  The XSD I'm applying this XSLT to is fairly complex, and includes some "named complexTypes" as well. For those who aren't aware (I know I wasn't), a named complexType is almost the equivalent of defining a variable, and then re-using that value throughout the rest of your document simply by referring to the variable's name. For instance, if I have a chunk of my schema that defines an address block, and there are several places within my xsd where this exact same xml is repeated, rather than actually write it each time i can write it one time, give it a name such as "addressblock", and then wherever I want to include that I simply refer to it by name.

Creating my skeleton XML then to fulfill step 5 in the system overview, I simply use the following line of code:
 

 <cfset skeletonXML = xmltransform(rtrim(ltrim(ToSTring(targXSD))),ltrim(rtrim(xslt))) />

 

CONCLUSION - FOR NOW

There are a LOT more details to all of this that I haven't touched on, like the xpath statements I use to find and populate the values of nodes, how I reproduce node trees that have multiple occurrences, etc. I'll save more of the nitty gritty details of that sort of thing for another post. In the meantime, if you have any questions or input (especially on how to make my xslt more efficient!), please do leave a comment for me! Most of this was fairly new territory for me, so there's likely lots of room for improvement and efficiency, and I'm all ears.

Posted by dougboude at 2:22 PM | PRINT THIS POST! | Link | 0 comments