BizTalk Utilities CV ,   Jobs ,   Code library
 
Home Page


Add/Edit your code items
Search the code library
Browse for the code library


XSLT
Test if a bit is on
Web Methodology (Layout Management)
The pow(a, b) function
Grouping an XML Node by First Letter
Dependent ComboBox in HTML build with XSLT
Re: How to use XSL function sum() to sum 2 XML fields
Re: How to use XSL function sum() to sum 2 XML fields
How to use XSL function sum() to sum 2 XML fields
A simple template to display singular and plural counts
Helpful XSLT ""break"" template
Support automatic transformation using XSLT in IE in side by side mode
Displaying XML data in Pages
Repeated search and replace in XSLT
Combinations of k out of n numbers
Permutations of the values of N nodes
DVC Algorithms in XSLT
Another way of avoiding XSLT processor crash
Re: DVC Algorithms in XSLT
trig functions in xslt
XSLT string Replace function


 
 

<< XQuery.NET and XML >>


By Brandon Driesen
First Posted 05/18/2001
Times viewed 1537

JScript Super Class To Handle XML Transformations


Summary The super class that will meet most if not all of your development needs

1. DESIGN CONSIDERATIONS

In the course of designing a re-usable generic library for XML Transforms, we have to come up with a wish-list and thereby put down what we want out of the generic library that we are about to create. The library has to take into account of the needs of most web projects. At the same time, it cannot cater to every developer's needs. The core purpose of the generic library are ease of implementation, debugging functionality, re-usability and most importantly ease of maintenance. Having determined what the generic library should provide as its core functionality, we should now divert our attention to how XML-driven web sites are developed, maintained and debugged. We cannot develop a useful library if we do not understand how web masters create their web sites.

2. PERFORMANCE, SCALABILITY AND FLEXIBILITY

In most cases, web sites cache the stylesheets that are regularly being called upon. This not only saves time because the stylesheet is in memory, it also helps prevents I/O bottlenecks for web servers. However, cached stylesheets are not as flexible as uncached stylesheets.

They can add document nodes, objects (rarely used) and text nodes (most common); but nothing more than that. Uncached stylesheets suffer from required I/O processing but they allow for manipulation of any and every element. Because of the constraints of the current XSLT implementation, you may be unable to use variables or parameters in specific attributes: notedly the select attribute of the xsl:sort element; and yet you may be required to change that value. Here, un-cached stylesheets have the flexibility which allows you to directly change XPath expressions on elements other than parameters. Based on this, we strongly recommend the following approach:

  1. Cached StyleSheets Hold Presentation Rules.
  2. Uncached StyleSheets Hold Data Manipulation Rules.
  3. Two Transforms Are Performed On A Web Page.
  4. An Agreed Schema (implicit or explicit) On The Resulting XML Transform (In XML Format) When Uncached StyleSheets Are Applied.
  5. Final XML Transform Happens With The IXSLProcessor Object.

3. CODING ARCHITECTURE

In all but the simplest web application, we regularly need to effect conditionality in our XML Transforms and yet have to maintain the overall look and feel of the web site. Simply using cached stylesheets will give the best performance at the expense of flexiblity. Using uncached stylesheets provide flexibility at the expense of performance since each and every stylesheet needs to have the same presentation rules: this can be a maintenance nightmare if you write the rules into each stylesheet; it can also be a performance headache if you include or import a global stylesheet that holds the presentation rules because the imported/included global stylesheet is NOT loaded when the stylesheet is loaded but rather when the XML transform is being carried out.

This turns out to be an expensive and slow process. If you are doing exactly this, it would be wiser to carry out two transforms that are more efficient because the import and include directives instantiate another IXMLDOMDocument in the background and loads the specified documents. In the same vein, loading XML Documents via the XSLT document function turns out to be equally slower than explicitly loading the XML Document and appending it to the main XML Document before a Transform is effected. As far as the XSLT document function is concerned, don't go there if there is an alternative way to achieve your goals.

4. WISH LIST

  1. Caters to transforms using cached and/or uncached stylesheets. In particular, the adding of element nodes and text nodes to parameters.
  2. Centralised ProgID so that when we want to upgrade to a newer parser, we change only one value.
  3. All methods should return a boolean value indicating success or failure with the operation carried out as far as practicable.
  4. Error messages should be generated for failed operations. We however would like the ability to suppress the error messages or enable them.
  5. For debugging purposes, we want to see the documentElement xml property for the uncached stylesheet and the xml document for debugging purposes. In particular, the stylesheet xml property will give us an idea of its current state after the changes have been made: the addParameter method invoked. We would also like a snapshot of the IXSLProcessor's stylesheet; in this case, changes will not be reflected in the documentElement's xml property.
  6. Able to set the properties of the stylesheet: SelectionNamespaces and SelectionLanguage. If we want to use XPath instead of XSLPattern when we search for nodes or attributes in an uncached stylesheet, we can use XPath Queries in this library.
  7. Able to add attributes to any element in an uncached stylesheet.
  8. Able to amend any node in an uncached stylesheet.
  9. Able to return XML transforms: applicable to both cached and uncached stylesheets and also applies to dual transformations.
  10. Able to load XML and Uncached Stylesheets dynamically: the library determines whether to use the load or loadXML methods.
  11. Able to create IXSLProcessors; in other words, make use of cached stylesheets. We make the assumption that the cached stylesheets has its template owner existing in the application scope, where it should be.
  12. Able to append secondary XML documents into the main XML document.
  13. Able to add text nodes and document nodes into parameters that do not pre-exist for uncached stylesheets.
  14. Able to perform a straight transformation by applying uncached stylesheets.
  15. Able to perform transformation by applying the cached stylesheet or the IXSLProcessor Object.
  16. Able to provide two transformations: the secondary transformation between the XML document and the uncached stylesheet which manipulates the XML Data; and then a primary transformation between the returned XML document and the cached stylesheet.
  17. Able to remove the objects created internally.
  18. Able to immediately remove objects created internally as soon as they are no longer needed without any external calls required. This is good programming practice.
  19. Implement good error handling to ensure that the web application is not affected by unforeseen behaviours.

5. ANONYMOUS FUNCTIONS UNDER JSCRIPT
 (The backbone of the generic library)

Using the new keyword, we can create JScript Objects. Typically, we create objects which have properties like the following:

function myObject(szName,szValue) {
this.name = szName;
this.value = szValue;
this.hidden = ;
this.list = new Array();
}
var user = new myObject(Driesen,Analyst Programmer);
with (user) {
hidden = new;
/*
since JScript Arrays start from 0
can use the current length
and create new items in the array
without explicitly assigning the index
*/

list[list.length] = Paris;
list[list.length] = London;
value = Programmer;
}
Accessing these properties is relatively easy:

with (Response) {
Write(<div> + user.name + </div>);
Write(<div> + user.value + </div>);
Write(<div> + user.hidden + </div>);
//the list property is assigned to an array
Write(<div> + user.list.join(</div><div>) + </div>);
}
This results in the following output:

<div>Driesen</div>
<div>Programmer</div>
<div>new</div>
<div>Paris</div>
<div>London</div>
Notice that you can change the properties even after you have set the values initially. Creation of JScript Objects using the new keyword should be clearly understood before we proceed any further.

Now, we proceed to the anonymous functions under JScript. We can assign properties of an object to an anonymous function. By doing so, we can hide the implementation of the code. Suppose we want to provide return strings in upper, lower cases or in its original state. We can implement this in the myObject method.

function myObject() {
var _name = null;
var _value = null
var _hidden = ;
var _list = null;
this.setProperty = function(name,value) {
switch (String(name).toLowerCase()) {
case name:
  _name = value;
break;
case value:
  _value = value;
break;
case hidden:
  _hidden = value;
break;
case list:
  if (_list == null) _list = new Array();
  _list[_list.length] = value;
break;
}
};
this.getProperty = function(name,upper) {
var _upper = upper == true || upper == false ? upper : null;
var _output = null;
switch (String(name).toLowerCase()) {
case name:
  if (_name != null) _output = _name;
break;
case value:
  if (_value != null) _output = _value;
break;
case hidden:
  if (_hidden != null) _output = _hidden;
break;
case list:
  if (_list != null && _list.length != 0) _output = _list;
break;
}
if (_output == null) return null;
if (typeof(_output) == string) {
if (_output == null) return _output;
if (_output == true) return _output.toUpperCase();
return _output.toLowerCase();
}
if (typeof(_output) == object && _output.length != 0) {
if (_output == null) return _output;
for (var i = 0;i < _output.length;i++) {
if (_upper == true) {
  _output[i] = String(_output[i]).toUpperCase();
} else {
  _output[i] = String(_output[i]).toLowerCase();
}
}
return _output;
}
return null;
};
}
To use these new anonymous functions, you call them as you would any method. In fact, because these properties are assigned to the relevant functions, they became de facto methods. Now to populate the object as we did before, we have to use a slightly changed syntax:
var user = new myObject();
with (user) {
setProperty(name,Driesen);
setProperty(value,Programmer);
setProperty(hidden,new);
setProperty(list,Paris);
setProperty(list,London);
}
Accessing these values is also relatively simple. Note the different case argument (null equates to leaving the values as is; true means upper case and false means lower case).
with (Response) {
Write(<div> + user.getProperty(name,null) + </div>);
Write(<div> + user.getProperty(value) + </div>);
Write(<div> + user.getProperty(hidden,true) + </div>);
var output = user.getProperty(list,false);
if (output != null) Write(<div> + output.join(</div><div>) + </div>);
}
This is the result of the above operation:
<div>Driesen</div>
<div>Programmer</div>
<div>NEW</div>
<div>paris</div>
<div>london</div>

6. ERROR HANDLING
Although this is not the time nor place to expound on JScript 5.0+ Error Handling, it suffices to say that the basic syntax of JScript 5.0+ Error Handling has the following structure:

try {
  ...statement(s) follow...
} catch (e) {
  ...statement(s) that will execute in the event of an error occurring...
} finally {
  ...statement(s) that will execute regardless of any error...
  ...this block is optional...
}
It should be noted that the try... block can contain nested try...catch...finally blocks and that each nested block must have a different variable (like e1) as the variable that will catch the [error Object].

7. CREATING THE HELPER FUNCTION THAT INSTANTIATES THE MSXML2.DOMDOCUMENT OBJECTS
 (A re-usable function as we shall see later)
 private variant (IXMLDOMDocument30|null) Processor::SetXMLObject(string source, string progid, boolean suppress)

Without much adieu, the code is presented as is:

function SetXMLObject(source,progid,suppress) {
try {
var _IXMLDomExt = Server.CreateObject(progid);
with (_IXMLDomExt) {
async = resolveExternals = false;
if (String(source).indexOf(>) != -1) {
  loadXML(source);
} else {
  load(Server.MapPath(source));
}
with (_IXMLDomExt.parseError) {
  if (errorCode != 0) throw <div>Parser Error: + errorCode + <br>Reason: + reason + <br>Line: + line + </div>;
}
}
return _IXMLDomExt;
} catch (e) {
if (suppress == false) {
with (Response) {
if (e == [object Error]) {
  Write(<div>Runtime Error ( + e.number + ) Encountered.</div>);
  Write(<div>Description: + e.description + </div>);
} else {
  Write(e);
}
}
}
if (typeof(_IXMLDomExt) == object && _IXMLDomExt != null) {
  delete _IXMLDomExt; _IXMLDomExt = null;
}
CollectGarbage();
return null;
}
}
Part of the wish list includes the ability to suppress error messages and to have a boolean value returned where possible to indicate whether the operation undertaken was successful. Thus, as expected, the suppress argument accepts boolean values. If it is anything other than false, no error messages will be written. Since this is a helper function and not the main JScript library, we return a IXMLDOMDocument30 object if the creation is successful or null if creation and loading fails. The anonymous function within the main JScript library will return a boolean value indicating whether the instantiation is successful. One more thing, because the symbol > is illegal in URLs, we check for that to determine whether the source argument contains XML syntax or the path details of an XML file. In this way, the function dynamically determines which load method to invoke and saves you time writing down the code manually.

Because we want the main library to control the progid, it is not defined internally in this helper function. Thus, there is the progid argument.

8. CREATING THE HELPER FUNCTION THAT INSTANTIATES THE IXSLPROCESSOR OBJECT
 (Another re-usable function as we shall see later)
 private variant (IXSLProcessor|null) Processor::SetProcessor(string name)

We make the assumption that all IXSLTemplate objects exist in the application scope, where it should in the first place because it caches stylesheets and make them available to every user.

//generic function returning IXSLProcessor
function SetProcessor(name,suppress) {
try {
var _IXSLProcessorExt = Application.StaticObjects(name).createProcessor();
if (_IXSLProcessorExt == null) throw <div>Processor Not Created</div>;
return _IXSLProcessorExt;
} catch (e) {
if (suppress == false) {
with (Response) {
if(e == [object Error]) {
  Write(<div>Runtime Error ( + e.number + ) Encountered.</div>);
  Write(<div>Description: + e.description + </div>);
} else {
  Write(e);
}
}
}
if (typeof(_IXSLProcessorExt) == object && _IXSLProcessorExt != null) {
  delete _IXSLProcessorExt; _IXSLProcessorExt = null;
}
CollectGarbage();
return null;
}
}
As like the helper function in Section 7, this function returns an object if the operation is successful; the object being the IXSLProcessor. If the operation fails, null will be returned. The main JScript Library will return a boolean value indicating success or failure. Please note that we are referring to the StaticObjects collection which only contains references to all COM objects instantiated via the OBJECT tag. Microsoft has strongly recommended that all objects created in the Application and Session Scope use the OBJECT tag as these objects will only be instantiated if they are referenced for the first time; all objects created via the Server.CreateObject get immediately created whether they are referenced in the code or not. Furthermore, it is arguably good coding practice to use the OBJECT tag (yes I know there is the ongoing argument as to whether instantiating a COM object via the Server.CreateObject or OBJECT tag is better but that argument is reserved primarily for the page scope and not session or application scope). Also, my personal preference is to explicitly call objects. I could have used the abbreviated Application(name) syntax but in a complex web site, it would be confusing: Is this a variable or an object? The moral of the story is DO NOT BE LAZY.

This function is exactly the same with regard to the suppress argument.

9. CLASSES AND SUPERCLASSES

A class is an object that has properties and methods, exposing all or a number of methods. A superclass is an object that contains a collection of classes. Here, the JScript Library will contain objects that have their own properties and methods. Hence for all intents and purposes, it is a super class. We have decided to call this super class Processor as it was borne out of an attempt to emulate the IXSLProcessor object.

function Processor() {
...Statements...
}

10. INTERNAL VARIABLES OF THE SUPER CLASS PROCESSOR
 The Super Class Internal (Private) Variables

var _IProgID = Msxml2.DOMDocument.3.0;
var _INamespace = http://www.w3.org/1999/XSL/Transform;
var _Suppress = true;
var _IXMLDom = null;
var _IXSLTDom = null;
var _IXSLProcessor = null;
The _IProgID variable holds the progid that will be used in every instantiating of the XML COM Object. If you wish to use version 4, you just change the variable's value. The _INamespace variable holds the namespace that will be used if you add parameters to an uncached stylesheet which do not pre-exist. The _Suppress variable holds a boolean value which determines whether error messages will be outputted or suppressed. The default value is true which means that no error messages will be outputted unless you explicitly specify it to be otherwise. This is by design since you can obtain a boolean value indicating success or failure of most methods and act accordingly. The error messages would be useful in a debug mode. The _IXMLDom variable holds the XML Document. The _IXSLTDom variable holds the uncached stylesheet. The _IXSLProcessor variable holds the cached stylesheet. Initially all these three variables holding XML Objects are null to conserve resources.

11. cleanup METHOD
 Super Class Method
 public void Processor::cleanup()

this.cleanup = function() {
if (_IXMLDom != null) {
  delete _IXMLDom; _IXMLDom = null;
}
if (_IXSLTDom != null) {
  delete _IXSLTDom; _IXSLTDom = null;
}
if (_IXSLProcessor != null) {
  delete _IXSLProcessor; _IXSLProcessor = null;
}
CollectGarbage();
};
This cleanup method takes no arguments and basically de-references any of the internal variables that are not null. This method is provided to allow programmers to explicitly de-reference the objects in order to save resources. Note that there is one method that automatically does this and the reasons will be explained when we reach that method.

12. stylesheet METHOD
 Super Class Core Method
 public boolean Processor::stylesheet(string source)

this.stylesheet = function(source) {
_IXSLTDom = SetXMLObject(source,_IProgID,_Suppress);
if (_IXSLTDom != null) return true;
return false;
};
This stylesheet method causes the _IXSLTDom to hold the uncached stylesheet if the operation is successful. There is only one argument that is passed through to the anynomous function. Examples of acceptable syntax: <root><users>10</users></root>; xml/data.xml. Anything that returns a valid XML text or a virtual path to the file on the web server is valid. The SetXMLObject will resolve the virtual path to an absolute path. Note that in the anonymous function, access to the variables in the main class is possible. Thus, the anonymous function does not require additional arguments although the SetXMLObject method requires 3 arguments. (SetXMLObject cf. Section 7). The stylesheet method returns a boolean value indicating success or failure in instantiating the stylesheet's IXMLDOMDocument30 object and loading the document specified.

13. input METHOD
 Super Class Core Method
 public boolean Processor::input(string source)

this.input = function(source) {
_IXMLDom = SetXMLObject(source,_IProgID,_Suppress);
if (_IXMLDom != null) return true;
return false;
};
This input method causes the _IXMLDom variable to hold the XML document. Behaviour is exactly the same as in Section 12. (SetXMLObject cf. Section 7)

14. createProcessor METHOD
 Super Class Core Method
 public boolean Processor::createProcessor(string name)

this.createProcessor = function(name) {
_IXSLProcessor = SetProcessor(name,_Suppress);
if (_IXSLProcessor != null) return true;
return false;
};
This createProcessor method instantiates a IXSLProcessor object. A boolean value indicating success or failure of the operation is returned. (createProcessor cf. Section 8)

15. suppressErrors METHOD
 Super Class Method
 public void Processor::suppressErrors(boolean value)

this.suppressErrors = function(value) {
_Suppress = value == false ? false : true;
}
This sets the internal _Suppress variable. It is this variable's value that determines whether error messages will be suppressed or not. This functionality is provided in the event that the developer wants to manage errors by himself. By this, I mean that the developer will check for the boolean value of the relevant methods and act accordingly to the returned boolean value. The suppression of error messages is by design global. This method in itself returns no value. (Internal Variables cf. Section 10)

16. debug METHOD
 Super Class Method
 public void Processor::debug(string source)

this.debug = function(source) {
try {
switch (String(source).toLowerCase()) {
case processor:
if (_IXSLProcessor == null) throw <div>Processor Not Found</div>;
Response.Write(<xmp> + _IXSLProcessor.stylesheet.documentElement.xml + </xmp>);
break;
case stylesheet:
if (_IXSLTDom == null) throw <div>Stylesheet Not Found</div>; Response.Write(<xmp> + _IXSLTDom.documentElement.xml + </xmp>);
break;
case domdocument:
if (_IXMLDom == null) throw <div>DOMDocument Not Found</div>; Response.Write(<xmp> + _IXMLDom.xml + </xmp>);
break;
default:
Response.Write(<div>Invalid Source Parameter. Valid Values (Case Insensitive) are: processor, stylesheet, domdocument</div>);
}
} catch (e) {
if (_Suppress == false) {
with (Response) {
if (e == [object Error]) {
  Write(<div>Runtime Error ( + e.number + ) Encountered.</div>);
  Write(<div>Description: + e.description + </div>);
} else {
  Write(e);
}
}
}
}
};
This debug method causes the xml property of the relevant object to be written to the browser. There are three valid (case insensitive) values: processor, stylesheet and domdocument. The values are self-evident. Useful for debugging especially for the XML and uncached Stylesheet documents. The IXSLProcessor's stylesheet is actually a snapshot (copy) of the stylesheet loaded in its parent (IXSLTemplate);even if you have added values to the parameters in the cached stylesheet, you will not be able to see the changes reflected. Contrasted to this, uncached stylesheets allow for good debugging as the changes are reflected in the xml property. We take the xml property of the documentElement as there is no need to get any xml declaration. We put the xmp element as the root element so that in html view, you get to see the source without any errors particularly if there are script sections. In xml view, there is no problem in viewing the documents as the xmp is the root element.

17. setProperty METHOD
 Super Class Method
 public boolean Processor::setProperty(string name, string value)

this.setProperty = function(name,value) {
try {
var _bolReturn = false;
if (_IXSLTDom == null) throw <div>Non-Cached Stylesheet Not Instantiated. Setting Properties Not Possible.</div>;
switch (String(name).toLowerCase()) {
case selectionlanguage:
case selectionnamespaces:
  _IXSLTDom.setProperty(name,value);
  _bolReturn = true;
break;
default:
  throw <div>Invalid Property Name</div>;
}
} catch (e) {
if (_Suppress == false) {
with (Response) {
if (e == [object Error]) {
  Write(<div>Runtime Error ( + e.number + ) Encountered.</div>);
  Write(<div>Description: + e.description + </div>);
} else {
  Write(e);
}
}
}
_bolReturn = false;
} finally {
return _bolReturn;
}
};
This setProperty method emulates the setProperty of the IXMLDOMDocument30. In short, it acts as a proxy to pass the values to the uncached stylesheet. This allows developers to use XPath queries instead of XSLPattern queries when selecting nodes in a stylesheet. For further details as to the setProperty of the IXMLDOMDocument30, refer to the MSXML3 SDK which can be downloaded from msdn.microsoft.com. This method returns a boolean value indicating success or failure of the operation.

18. addParameter METHOD
 Super Class Method
 public boolean Processor::addParameter(string name, string value)

this.addParameter = function(name,value) {
try {
var _bolReturn = false;
if (_IXSLProcessor == null && _IXSLTDom == null) throw <div>No Valid XML Object Found For Adding Text Nodes To Parameters</div>;
if (_IXSLProcessor != null) {
_IXSLProcessor.addParameter(name,value);
_bolReturn = true;
}else if (_IXSLTDom != null ) {
var _Param = _IXSLTDom.selectSingleNode(//xsl:param[@name=' + name + ']);
if (_Param == null) {
_Param = _IXSLTDom.createNode(1,param,_INamespace);
_Param.setAttribute(name) = name;
_IXSLTDom.documentElement.insertBefore(_Param,_IXSLTDom.documentElement.childNodes(2));
}
_Param.text = value;
delete _Param; _Param = null; CollectGarbage();
_bolReturn = true;
}
} catch (e) {
if (_Suppress == false) {
with (Response) {
if (e == [object Error]) {
  Write(<div>Runtime Error ( + e.number + ) Encountered.</div>);
  Write(<div>Description: + e.description + </div>);
} else {
  Write(e);
}
}
}
_bolReturn = false;
} finally {
if ( typeof(_Param) == object && _Param != null) {
  delete _Param; _Param = null;
}
CollectGarbage();
return _bolReturn;
}
};
This addParameter method requires some careful understanding. It adds only text nodes to cached and uncached stylesheets whereas in the actual addParameter of the IXSLProcessor object, we can pass both text nodes and document nodes through to xsl:param elements. Because we want to maintain similarity in the addition of nodes to cached and uncached stylesheets, we have provided the addParameterNode method (addParameterNode cf. Section 19) to cater to both cached and uncached stylesheets. In the event of a two-transform technique (Wish List cf. Section 4 Item 16), you should instantiate the stylesheet first as the processor is given precedence and in the event that both the processor and the stylesheet have been instantiated, you will not be able to add parameters to the uncached stylesheet, as the default action will pass to the processor. This is by design.

There is no difference between passing parameters to cached and uncached stylesheets. You can pass values to parameters that do not pre-exist in the cached and uncached stylesheets. Be aware that changing the property SelectionLanguage of the uncached stylesheet through the setProperty (setProperty cf. Section 17) method will impact all searches on uncached stylesheets. The method returns a boolen value indicating the success or failure of the operation.

19. addParameterNode METHOD
 Super Class Method
 public boolean Processor::addParameterNode(string name, string source)

this.addParameterNode = function(name,source) {
try {
var _bolReturn = false;
if (_IXSLTDom == null && _IXSLProcessor == null) throw <div>No Valid XML Object Found For Appending Nodes.</div>;
if (typeof(source) != string) throw <div>Invalid Source.</div>;
var _Param = _Append = null;
_Append = SetXMLObject(source,_IProgID,_Suppress);
if (_Append == null) throw <div>Append Document Failed To Be Instantiated</div>;
if (_IXSLProcessor != null) {
_IXSLProcessor.addParameter(name,_Append.documentElement);
delete _Append; _Append = null; CollectGarbage();
_bolReturn = true;
} else {
_Param = _IXSLTDom.selectSingleNode(//xsl:param[@name =' + name + ']);
if (_Param == null) {
_Param = _IXSLTDom.createNode(1,param,_INamespace);
_Param.setAttribute(name) = name;
_IXSLTDom.documentElement.insertBefore(_Param,_IXSLTDom.documentElement.childNodes(2));
}
_Param.appendChild(_Append.documentElement);
delete _Append; _Append = null;
delete _Param; _Param = null; CollectGarbage();
_bolReturn = true;
}
} catch (e) {
if (_Suppress == false) {
with (Response) {
if (e == [object Error]) {
  Write(<div>Runtime Error ( + e.number + ) Encountered.</div>);
  Write(<div>Description: + e.description + </div>);
} else {
  Write(e);
}
}
}
_bolReturn = false;
} finally {
if (typeof(_Append) == object && _Append != null) {
  delete _Append; _Append = null;
}
if (typeof(_Param) == object && _Param != null) {
  delete _Param; _Param = null;
}
CollectGarbage();
return _bolReturn;
}
};
This addParameterNode method also requires careful attention. It adds the documentElement of the xml document loaded via the source argument. Valid values for this argument are any XML text or virtual paths since we are re-using the SetXMLObject (SetXMLObject cf. Section 7) For this process to succeed, the XML document which uses the source argument for loading must successfully load. This method allows for nodes to be appended to cached and uncached stylesheets, regardless of whether the parameter pre-existed or not. In the event that the parameter does not exist in an uncached stylesheet, the parameter will be created using IXMLDOMDocument30's createNode which requires a namespace. This namespace is provided by the _INamespace variable which currently points to the latest XSLT namespace. The created parameter will be inserted in the cached stylesheet just before the second child of the xsl:stylesheet or xsl:transform element. Note that all objects no longer needed are immediately removed from memory. This method returns a boolean value indicating success or failure.

20. appendChild METHOD
 Super Class Method
 public boolean Processor::appendChild(string source)

this.appendChild = function(source) {
try {
var _bolReturn = false;
var _Append = null;
if (_IXMLDom == null) throw <div>XML Document Not Instantiated. Appending Function Aborted</div>;
_Append = SetXMLObject(source,_IProgID,_Suppress);
if (_Append == null) throw <div>Append Function Aborted. Document To Be Appended Failed To Load.</div>
_IXMLDom.documentElement.appendChild(_Append.documentElement);
delete _Append; _Append = null; CollectGarbage()
_bolReturn = true;
} catch (e) {
if (_Suppress == false) {
with (Response) {
if (e == [object Error]) {
  Write(<div>Runtime Error ( + e.number + ) Encountered.</div>);
  Write(<div>Description: + e.description + </div>);
} else {
  Write(e);
}
}
}
_bolReturn = false;
} finally {
if (_Append != null) {
  delete _Append; _Append = null;
}
CollectGarbage();
return _bolReturn;
}
};
This appendChild method is a very handy method for the developer. Instead of resorting to the XSLT document function to insert an entire XML document, you can append XML documents into the Main XML document. Even though it may seem that under the XSLT document function, you can select subsets of the XML document, this not quite true. The entire XML document is loaded whatever your filtering may be. Thus, it is still faster to use this appendChild method which actually emulates the IXMLDOMDocument30's appendChild method. Check SetXMLObject cf. Section 7 for more details. This method returns a boolean value indicating success or failure of the operation.

21. addAttribute METHOD
 Super Class Method
 public boolean Processor::addAttribute(string path, string name, string value)

this.addAttribute = function(path,name,value) {
try {
var _bolReturn = false;
var _Element = null;
if (_IXSLTDom == null) throw <div>Non-Cached Stylesheet Not Instantiated</div>;
_Element = _IXSLTDom.selectSingleNode(path);
if (_Element == null) throw <div>Element Could Not Be Found In Non-Cached Stylesheet.</div>;
_Element.setAttribute(name) = value;
delete _Element; _Element = null; CollectGarbage();
_bolReturn = true;
} catch (e) {
if (_Suppress == false) {
with (Response) {
if (e == [object Error]) {
  Write(<div>Runtime Error ( + e.number + ) Encountered.</div>);
  Write(<div>Description: + e.description + </div>);
} else {
  Write(e);
}
}
}
_bolReturn = false;
} finally {
if ( _Element != null) {
  delete _Element; _Element = null;
}
CollectGarbage();
return _bolReturn;
}
};
The addAttribute method allows you to add attributes to any node in an uncached stylesheet. This flexibility can be rather important when you want to add attributes that may not pre-exist. Contrasted to the setNode method (setNode cf. Section 22) which requires the existence of the attribute node, this method gets the parent element and then sets the attribute value via the IXMLDOMElement's setAttribute method. There is a subtle difference between this method and the setNode method; this method deals solely with attribute nodes and the manner in which the attribute value is set is to discover the parent element before setting the attribute value. The former method allows you to change attribute and text nodes and is more efficient in that it does not seek the parent element, but rather directly references the node that is targetted for change. Because it has no knowledge of the parent's node, it does not add new attributes unlike this method. The setNode method works on the premise that the targetted node: be it an attribute or a text node must pre-exist before a change can be effected. The addAttribute method does not depend on the pre-existence of the attribute node targetted for insertion of values. This method returns a boolean value indicating whether the operation has been successful or not.

22. setNode METHOD
 Super Class Method
 public boolean Processor::setNode(string path, string value)

this.setNode= function(path,value) {
try {
var _bolReturn = false;
var _Node = null;
if (_IXSLTDom == null) throw <div>Stylesheet Object Not Instantiated</div>;
_Node = _IXSLTDom.selectSingleNode(path);
if (_Node == null) throw <div>Node Could Not Be Found In The Stylesheet</div>;
_Node.text = value; delete _Node; _Node = null;
CollectGarbage(); _bolReturn = true;
} catch (e) {
if (_Suppress == false) {
with (Response) {
if (e == [object Error]) {
  Write(<div>Runtime Error ( + e.number + ) Encountered.</div>);
  Write(<div>Description: + e.description + </div>);
} else {
  Write(e);
}
}
}
_bolReturn = false;
} finally {
if (_Node != null) {
  delete _Node; _Node = null;
}
CollectGarbage();
return _bolReturn;
}
};
The setNode method allows you to change attribute and text nodes providing they pre-exist before you attempt to change them. Refer to comments in Section 21 on the differences between this method and the addAttribute method. This method returns a boolean value indicating whether the process of setting the targetted node's value was a success or a failure.

23. transformNode METHOD
 Super Class Core Method
 public variant[string | null] Processor::transformNode()

this.transformNode = function() {
try {
var _bolReturn = false;
var _output = null;
if (_IXMLDom != null && _IXSLTDom != null && _IXSLProcessor == null) {
_output = _IXMLDom.transformNode(_IXSLTDom);
_bolReturn = true;
} else if (_IXMLDom != null && _IXSLProcessor != null && _IXSLTDom == null) {
with (_IXSLProcessor) {
input = _IXMLDom;
transform();
_output = output;
}
_bolReturn = true;
} else if (_IXMLDom != null && _IXSLProcessor != null && _IXSLTDom != null) {
var _IXMLDomResult = Server.CreateObject(_IProgID);
_IXMLDomResult.async = false;
_IXMLDom.transformNodeToObject(_IXSLTDom,_IXMLDomResult);
with (_IXSLProcessor) {
input = _IXMLDomResult;
transform();
_output = output;
}
delete _IXMLDomResult; _IXMLDomResult = null;
CollectGarbage();
_bolReturn = true;
} else {
throw <div>No Valid XML Objects Instantiated For Transform</div>;
}
} catch (e) {
if (_Suppress == false) {
with (Response) {
if (e == [object Error]) {
  Write(<div>Runtime Error ( + e.number + ) Encountered.</div>);
  Write(<div>Description: + e.description + </div>);
} else {
  Write(e);
}
}
}
_bolReturn = false;
} finally {
if (typeof(_IXMLDomResult) == object && _IXMLDomResult != null) {
  delete _IXMLDomResult; _IXMLDomResult = null;
}
CollectGarbage();
if (_bolReturn == false) return null;
return _output;
}
};
This transformNode method mirrors the IXMLDOMDocument30's transformNode method. This allows you to receive the output of an XML Transform applying uncached, cached stylesheets onto the XML document or dual transformations. This method is identical to the transform method but returns the transformation (string) instead of passing the transformation as a text stream to the IReponse Object.

Initially, this method was designed to handle only transformations applying uncached stylesheets but for the sake of completeness and consistency, it performs the same function as the transform method (cf transform() Section 24.). However, this method does not remove all objects within the Processor class after the transformation has been effected because the resulting transform is not sent to the IResponse object. Thus, an explicit call to the cleanup is recommended if you are finished with the super class.

This method returns a string if there are no errors encountered. It will return a null if errors are encountered during transformation. This will allow you to determine if the Transform succeeded or not.

24. transform METHOD
 Super Class Core Method
 public boolean Processor::transform()

this.transform = function() {
try {
var _bolReturn = false;
if (_IXMLDom != null && _IXSLTDom != null && _IXSLProcessor == null ) {
_IXMLDom.transformNodeToObject(_IXSLTDom,Response);
delete _IXMLDom; _IXMLDom = null;
delete _IXSLTDom; _IXSLTDom = null;
CollectGarbage();
_bolReturn = true;
} else if (_IXMLDom != null && _IXSLProcessor != null && _IXSLTDom == null) {
with (_IXSLProcessor) {
input = _IXMLDom;
output = Response;
transform();
}
delete _IXMLDom; _IXMLDom = null;
delete _IXSLProcessor; _IXSLProcessor = null;
CollectGarbage();
_bolReturn = true;
} else if (_IXMLDom != null && _IXSLProcessor != null && _IXSLTDom != null) {
var _IXMLDomResult = Server.CreateObject(_IProgID);
_IXMLDomResult.async = false;
_IXMLDom.transformNodeToObject(_IXSLTDom,_IXMLDomResult);
delete _IXSLTDom; _IXSLTDom = null;
delete _IXMLDom; _IXMLDom = null;
with (_IXSLProcessor) {
input = _IXMLDomResult;
output = Response;
transform();
}
delete _IXMLDomResult; _IXMLDomResult = null;
delete _IXSLProcessor; _IXSLProcessor = null;
CollectGarbage();
_bolReturn = true;
} else {
throw <div>No Valid XML Objects Instantiated For Transform</div>;
}
} catch (e) {
if (_Suppress == false) {
with (Response) {
if (e == [object Error]) {
  Write(<div>Runtime Error ( + e.number + ) Encountered.</div>);
  Write(<div>Description: + e.description + </div>);
} else {
  Write(e);
}
}
}
_bolReturn = false;
} finally {
if (_IXSLProcessor != null) {
  delete _IXSLProcessor; _IXSLProcessor = null;
}
if (_IXMLDom != null) {
  delete _IXMLDom; _IXMLDom = null;
}
if (_IXSLTDom != null) {
  delete _IXSLTDom; _IXSLTDom = null;
}
if (typeof(_IXMLDomResult) == object && _IXMLDomResult != null) {
  delete _IXMLDomResult; _IXMLDomResult = null;
}
CollectGarbage();
return _bolReturn;
}
};
This is the final method available for this class. We saved the best for last. This transform method basically has 3 ways of providing a transformation:
  1. Straight transform between XML document and uncached stylesheet.
  2. Straight transform between XML document and cached stylesheet.
  3. Two transformations: one between the XML document and the secondary, uncached stylesheet. The resulting XML document is transformed by applying the cached stylesheet
This method thus emulates the IXSLProcessor's transform method. Please note in the dual transformation, the stylesheet should be instantiated first and then the processor object. The reason for this is that the processor object is given precedence over the stylesheet object. If both are instantiated, applying the addParameter or addParameterNode method on the stylesheet will fail as the operation will target the processor itself. This method returns a boolean value indicating the status of the operation.

There is one good reason why the transform method will result in the objects being destroyed and for which the cleanup method will be unnecessary to call: we use the Response object to pass the transformation. This may leave the Response object unstable in some cases. Another reason would be that this method is the final method to be invoked after which everything is cleared out.

23. EXAMPLES

The following demonstrates a straight-forward transformation with return value checking, with the cleanup method invoked if an error is encountered:

var Proc = new Processor();
with (Proc) {
if (stylesheet(xslt/data.xsl) != true || input(xml/data.xml) != true) {
cleanup();
Response.Write(<div>Data Unavailable</div>);
} else {
appendChild(<root>1000</root>);
appendChild(xml/data1.xml);
transform();
}
}
delete Proc; Proc = null; CollectGarbage();
The next example shows how to set nodes and parameters to uncached stylesheets:
var Proc = new Processor();
with (Proc) {
if (stylesheet(xslt/data.xsl) != true || input(xml/data.xml) != true) {
cleanup();
Response.Write(<div>Data Unavailable</div>);
} else {
addAttribute(//xsl:output,omit-xml-declaration,yes);
setNode(//xsl:sort[../@mode='test']/@select,key('list',@app)/@name);
addParameter(date,2001-10-10)
transform();
}
}
delete Proc; Proc = null; CollectGarbage();
The following example shows how to use cached stylesheets:
var Proc = new Processor();
with (Proc) {
if (createProcessor(ChartTemplate) != true || input(xml/data.xml) != true) {
cleanup();
Response.Write(<div>Data Unavailable</div>);
} else {
addParameterNode(files,xml/files.xml)
addParameterNode(users,<user>10</user>)
addParameter(date,2001-10-10)
transform();
}
}
delete Proc; Proc = null; CollectGarbage();
The final example shows how to perform the dual transformation:
var Proc = new Processor();
with (Proc) {
if (stylesheet(xslt/default.xsl) != true || input(xml/data.xml) != true) {
cleanup();
Response.Write(<div>Data Unavailable</div>);
} else {
addParameterNode(files,xml/files.xml)
if (createProcessor(Template) != true) {
cleanup();
Response.Write(<div>Data Unavailable</div>);
} else {
addParameter(date,2001-10-10)
transform();
}
}
}
delete Proc; Proc = null; CollectGarbage();

Observations

  1. The CollectGarbage is an undocumented JScript method that releases resources explicitly.
  2. The Processor Super Class could be ported over to ASP VBScript but you would not enjoy the excellent JScript error handling. VBScript.NET now has the try...catch...finally statement.
  3. If for some reason, you want to expose the _IXMLDom, _IXSLTDom and _IXSLProcessor so that you can plug into it without using methods of the Super Class, you could just implicitly declare them as _IXMLDom = _IXSLTDom = _IXSLProcessor = null without using the var keyword in which case after you create the Processor object, these variables are available globally and thus, outside the Processor object. I made them explicitly private to the class because it would violate the raison d'etre for the existence of the Super Class to expose these objects. However, having said that, there could be cases where the rules can be broken.
  4. Although, I could have done away with the SetProcessor method and written the creation of the IXSLProcessor within the anonymous function itself, I thought it best to allow the function to remain external to the Processor object so that if you need to call the IXSLProcessor without wanting to create a new JScript Processor object, you can use the SetProcessor method directly.
  5. The same reasoning applies for the SetXMLObject method of which its re-usability is obvious. The progid is not residing within this function for another reason (besides the reason given that it should reside in the main Processor function for centralisation): if you want to create FreeThreadedDOMDocument objects or any progid with a different version, you can do so by directly calling the SetXMLObject method without creating a JScript Processor object. You then have to pass 3 arguments (2 actually, if you want to suppress the error messages).
  6. Remember to always test and test and test your transforms. Using the syntax Proc.suppressErrors(false) will enable error messages and Proc.debug(stylesheet) willl give you the source text of the uncached stylesheet.
  7. I could go on and on adding new functionality to the object but there comes a cut-off point where too much functionality may render the object generally unusable because of its terrible complexity. So, I have not added any more goodies that what already exists in this super class as it will cater to most needs in a web project, can be readily understood (I can only hope!) and is not too complex for the average developer.

Additional information


Rate this article on a scale of 1 to 10 (0 votes, average 0)

Your vote :  

<< XQuery.NET and XML >>





Leave a comment for this article
Your name
Your email (optional)
Your comment
Optional: Upload an attachment
Enter the code shown:

 
 

    Email TopXML