Latest tool: XPath query field extractor for InfoPath

Update: This tool seems to be only relevant with InfoPath 2003. A co-worker pointed out that due the customer pains around a lack of a feature like this, in InfoPath 2007 you can right-click a node in the data source and choose "Copy XPath". Cool! Thanks to Scott Heim for pointing this out.

So, I love making tools. One-off things that process something. Batch files that lessen monotony. Sexy little WinForm apps for displaying data. XSL transformations that regurgitate and pre-process XML.

My latest tool is a small WinForms app that pulls out a list of fields form an InfoPath document so they can be referenced as XPath. This is pretty neat because my client needed a documentation artifact, and this saved quite a bit of tedium. Getting this data also proved difficult because the Namespace URI for the "my" namespace is always changing because it includes a timestamp (see solution below).

Here's a sample of the output. You can use each of these lines as XPath queries against the document:

 my:MyFields/my:secMain
my:MyFields/my:secMain/my:secPhysicalDescriptionHeader
my:MyFields/my:secMain/my:secDualCitizenshipMain
my:MyFields/my:secMain/my:secDualCitizenshipMain/my:secDualCitizenshipName
my:MyFields/my:secMain/my:secDualCitizenshipMain/my:secDualCitizenship
.
.
.

The tool uses the CAB SDK's extract.exe via a command shell to pull out the template.xml file from the InfoPath XSN file.

The code for using the SDK was pretty straightforward. In this case (since we want the XML template in the InfoPath, the variable ExtractionFileName is defined as:

 private const string ExtractionFileName = "template.xml";

And fileName is the location of the XSN file. The code for the extraction is:

 // Use the CAB SDK to grab the template file from the InfoPath XSN package
Process proc = new Process();
proc.StartInfo.FileName = ".\\EXTRACT.EXE";
proc.StartInfo.Arguments = string.Format("\"{0}\" {1}", fileName, ExtractionFileName);
proc.StartInfo.UseShellExecute = true;
proc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
proc.Start();
proc.WaitForExit();

Dealing with the ever-changing "my" namespace

One particular challenge arose. Since the "my" namespace in InfoPath is defined with a timestamp, every time you change the schema the namespace URI changes. So, I had to do a little preprocessing before I could read the fields (since they're all in the "my" namespace). Thankfully the timestamp is of fixed length.

 XmlDocument doc = new XmlDocument();
doc.Load(extractionFilePath);

// Use a low-tech way to get the "my:" namespace URI, since it 
// differs by form and when changes are made
string fileContents = File.ReadAllText(extractionFilePath);
int myIndex = fileContents.IndexOf("xmlns:my=") + 10;
string myNamespace = fileContents.Substring(myIndex, 75);

XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable);
mgr.AddNamespace("my", myNamespace);

XmlNode myFieldsNode = doc.SelectSingleNode("//my:myFields", mgr);

After you get the "myFieldsNode" you can recursively iterate through the nodes and output the fields.