Introduction
When working with XML and XSLT, it becomes tedious testing changes since there is no built-in support for executing the transformation. The MSXML SDK includes an XSLT command-line utility but unless the source and transforming stylesheet are in the same directory you must enter the paths for the inputs. You could write a script, as I have done many times, but this will need to be updated each time you change inputs or outputs.
This utility takes some of the pain out of writing and testing stylesheets. The output of the transformation is saved to disk and opened in its own process using notepad. This output is overwritten each time a transformation is done so you will need to 'Save As' to keep the document before closing notepad. I decided to use notepad because a transformation does not always result in valid XML.
There are a number of great articles that cover shell extensions quite well so I won't re-hash that information. I will cover simple transformations and parse errors using the DOMDocument
in MSXML4.
Using the code
To use this utility, copy and register the dll. In Explorer, right click on an XML, XSL, or XSLT file to get the 'Transform with...' option. You will be prompted for an XSL/XSLT file to do the transformation. Notepad will display the results, being valid output or parser error information.
Any valid xml text can be loaded into a DOMDocument
: XML, stylesheets and schemas. I have chosen to make this functionality available only to XML and stylesheets. To make this available to other file types, update the registrar script and rebuild. The following will include this option for XML schemas.
NoRemove xsdfile{
NoRemove Shellex{
NoRemove ContextMenuHandlers{
RegSvrEx = s '{737E08D6-7EEA-4AD6-B15F-373B2BCDF5B7}'
} } }
The Transformation
Both input files need to be loaded into a DOMDocument
before we can perform a transformation. The steps to do this are simple: create a DOMDocument
object, turn off asynchronous loading, load the file and check for parse errors.
MSXML2::IXMLDOMDocument2Ptr spStyleSheet;
spStyleSheet.CreateInstance(__uuidof(MSXML2::DOMDocument));
spStyleSheet->put_async(VARIANT_FALSE);
if( spStyleSheet->load(_variant_t(szStyleSheet)) == VARIANT_FALSE)
{
ReportParseError(spStyleSheet->parseError, szStyleSheet);
return S_FALSE;
}
If everything goes well, the XML is transformed using the transformNode
method of the DOMDocument
and the results are displayed. If there are parse errors, it is helpful to have as much information as we can.
This is accomplished in ReportParseError
. The DOMDocument
exposes an interface to the parseError
object. With this, we can get all error information, including the unparsed source text. I use the line
and linepos
properties to get a snippet of text surrounding the point where the parser stopped.
CString src = pParseError->srcText.GetBSTR();
int badChar = 0;
int maxLen = 0;
if(src.GetLength() < 120)
maxLen = src.GetLength();
else
{
for(int x=0; x < pParseError->line - 1; x++)
badChar = src.Find(_T("\n"), badChar);
badChar -= (60 - pParseError->linepos);
maxLen = 120;
}
.
.
.
src.Mid(badChar, maxLen)
The parser will report an error on the first character found to be invalid. In most cases, this is a symptom of the real issue so we want to display text prior to that point. Something interesting to note is that srcTxt
doesn't always contain all of the source. The SDK states that "this property returns an empty string if the error is caused by XML that is not well-formed and cannot be assigned to a specific line", but I had cases where it returns a partial string that doesn't contian enough text to see the actual error.
Points of Interest
This Shell Extension dll is an ATL project written using Visual Studio .NET and compiled for Unicode. To rebuild for ASCII, you will need to make changes using the conversion macros in order to interact with _bstr_t
.
MSXML4 is a required dependency and can be downloaded, along with the SDK from MSDN.