InfoPath 2007 solutions contain at least one view to display the form in a window. Additional views may be necessary to send the form by email, to other applications or to a web browser. Views are xslt stylesheets which are packed into the InfoPath 2007 solution file (*.xsn). Remember a xsn file is a cab file. You can access the content by simply renaming the solution file to *.cab.
If you create your own xslt stylesheet or if you use an existing InfoPath view to programmatically transform your form data, you normally will need to format multiline strings, numbers, date and time values. The InfoPath object model offers the method Application.FormatString which can be used for all formatting. Also the InfoPath views try to call the method formatString during transformation. Example:
InfoPath view snippet
<xsl:choose>
<xsl:when test="function-available('xdFormatting:formatString')">
<xsl:value-of select="xdFormatting:formatString(fp:address,"string","plainMultiline")" disable-output-escaping="yes"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="fp:address" disable-output-escaping="yes"/>
</xsl:otherwise>
</xsl:choose>
Problem
The FormatString method is not available in browser-enabled forms. Browser-enabled forms use a different object model.
As you can see from the snippet above the xslt processor tries to call the formatString function which is not defined as member of the processor. It is defined as member of an xslt extension object which is passed as an input parameter.
Task
Write a C# class with the method formatString. The method must be compatible to FormatString method which is defined by the InfoPath object model. See Application.FormatString Method for more information. Better said the method “should be” compatible, if you want to use stylesheets (views) which are designed with the InfoPath editor.
Solution
Below please find the listing MyLib.Xslt.Formatting which realized the xslt extension object. To pass the extension object to the xslt processor:
// Create transformation argument list XsltArgumentList xArgs = new XsltArgumentList(); MyLib.Xslt.Formatting xo = new MyLib.Xslt.Formatting(); xArgs.AddExtensionObject(xo.NamespaceUri, xo);
Conclusion
If you programmatically tranform InfoPath form data you can use an xslt stylesheet together with an an extension object. The extension object contains the method formatString which is can also called by InfoPath designed stylesheets (views). The C# class listing MyLib.Xslt.Formatting below realizes the extension object.
Note: The class below does not offer full 100% of the functionality. Missing parts are commented and will be added later. Sorry, guys, you forced me to publish this article before I could finish all. Feel free to publish a completed or even better solution and let me know.
MyLib.Xslt.Formatting
using System;
using System.Collections.Specialized;
using System.Globalization;
using System.Reflection;
using System.Threading;
using System.Xml.XPath;
namespace MyLib.Xslt
{
/// <summary>
/// This class contains xslt extension methods
/// </summary>
public class Formatting
{
/**************/
/* Properties */
/**************/
public string NamespaceUri
{
get { return "<a href="http://schemas.microsoft.com/office/infopath/2003/xslt/formatting">http://schemas.microsoft.com/office/infopath/2003/xslt/formatting</a>"; }
}
/******************/
/* Public methods */
/******************/
/// <summary>
/// Formats the specified string or XML node according to the specified category and options parameters.
/// This extension method is used by browser-enable InfoPath forms
/// </summary>
/// <param name="input">The string value or XML node to be formatted.</param>
/// <param name="category">The string value that specifies the category used for formatting. Values include number, percentage, currency, date, time, and datetime.</param>
/// <param name="options">The string value that specifies the options used for formatting. Takes the form of a case-sensitive string in the format "optionName:value".</param>
/// <returns>the formatted string</returns>
/// <see cref="http://msdn.microsoft.com/en-us/library/bb229767.aspx"/>
public string formatString(object input, string category, string options)
{
// Get expected type of MS.Internal.Xml.XPath.XPathArrayIterator
Type t = input.GetType();
// Move to first node and examine its value
if ((bool)t.InvokeMember("MoveNext", BindingFlags.InvokeMethod, null, input, null))
{
XPathNavigator xNav = (XPathNavigator)t.InvokeMember("Current", BindingFlags.GetProperty, null, input, null);
string val = xNav.Value;
NameValueCollection nvcOptions = getOptions(options);
switch (category)
{
case "number":
return fmtNumber(val, nvcOptions);
case "percentage":
return fmtPercentage(val, nvcOptions);
case "currency":
return fmtCurrency(val, nvcOptions);
case "date":
return fmtDate(val, nvcOptions);
case "time":
return fmtTime(val, nvcOptions);
case "datetime":
return fmtDateTime(val, nvcOptions);
case "string":
return fmtString(val, nvcOptions);
default:
throw new NotImplementedException();
}
}
else return string.Empty;
}
/*******************/
/* Private methods */
/*******************/
/// <summary>
/// Format a numerical value.
/// </summary>
/// <param name="value">Value to be formatted</param>
/// <param name="options">Valid options include locale, numDigits, leadingZero, grouping, decimalSep, thousandSep, and negativeOrder</param>
/// <returns>the formatted value</returns>
private string fmtNumber(string value, NameValueCollection options)
{
// Set culture
string option = options["locale"];
CultureInfo culture = (option == null) ? Thread.CurrentThread.CurrentUICulture : CultureInfo.GetCultureInfo(int.Parse(option));
// Get number format provider
NumberFormatInfo nfi = culture.NumberFormat.Clone() as NumberFormatInfo;
// Set number of decimal digits
option = options["numDigits"];
if (option != null) nfi.NumberDecimalDigits = int.Parse(option);
//// Specifies whether to use leading zeros in decimal fields.
//// Specify 0 to indicate no leading zeros and 1 to indicate leading zeros.
//// Defaults to the corresponding value in regional settings if not specified.
//int leadingZero = 0;
// Specifies the size of each group of digits to the left of the decimal.
// Values in the range 0–9 and value 32 are valid. Value 32 indicates that the grouping is three digits, then two digits thereafter.
// Defaults to the corresponding value in regional settings if not specified.
option = options["grouping"];
if (option != null)
{
int groupSize = int.Parse(option);
if (groupSize < 10) nfi.NumberGroupSizes = new int[] {groupSize};
else nfi.NumberGroupSizes = new int[] {3, 2};
}
// Set the decimal separator string
option = options["decimalSep"];
if (option != null) nfi.NumberDecimalSeparator = option;
// Set the thousands separator string.
option = options["thousandSep"];
if (option != null) nfi.NumberGroupSeparator = option;
//// Specifies the negative number mode. Defaults to the corresponding value in regional settings if not specified.
//int negativeOrder = 1;
return string.Format(nfi, "{0:N}", double.Parse(value, new CultureInfo("en-US")));
}
/// <summary>
/// Format percentage value.
/// </summary>
/// <param name="val"></param>
/// <param name="nvcOptions">Valid options for this category include locale, numDigits, leadingZero, grouping, decimalSep, thousandSep, and negativeOrder.</param>
/// <returns></returns>
private string fmtPercentage(string value, NameValueCollection options)
{
// Set culture
string option = options["locale"];
CultureInfo culture = (option == null) ? Thread.CurrentThread.CurrentUICulture : CultureInfo.GetCultureInfo(int.Parse(option));
// Get number format provider
NumberFormatInfo nfi = culture.NumberFormat.Clone() as NumberFormatInfo;
// Set number of decimal digits
option = options["numDigits"];
if (option != null) nfi.PercentDecimalDigits = int.Parse(option);
//// Specifies whether to use leading zeros in decimal fields.
//// Specify 0 to indicate no leading zeros and 1 to indicate leading zeros.
//// Defaults to the corresponding value in regional settings if not specified.
//int leadingZero = 0;
// Specifies the size of each group of digits to the left of the decimal.
// Values in the range 0–9 and value 32 are valid. Value 32 indicates that the grouping is three digits, then two digits thereafter.
// Defaults to the corresponding value in regional settings if not specified.
option = options["grouping"];
if (option != null)
{
int groupSize = int.Parse(option);
if (groupSize < 10) nfi.PercentGroupSizes = new int[] { groupSize };
else nfi.NumberGroupSizes = new int[] { 3, 2 };
}
// Set the decimal separator string
option = options["decimalSep"];
if (option != null) nfi.PercentDecimalSeparator = option;
// Set the thousands separator string.
option = options["thousandSep"];
if (option != null) nfi.PercentGroupSeparator = option;
//// Specifies the negative number mode. Defaults to the corresponding value in regional settings if not specified.
//int negativeOrder = 1;
return string.Format(nfi, "{0:P}", double.Parse(value, new CultureInfo("en-US")));
}
/// <summary>
/// Format currency value
/// </summary>
/// <param name="val"></param>
/// <param name="nvcOptions">Valid options for this category include locale, numDigits, leadingZero, grouping, decimalSep, thousandSep, negativeOrder, positiveOrder, and currencyLocale.</param>
/// <returns></returns>
private string fmtCurrency(string value, NameValueCollection options)
{
throw new NotImplementedException();
}
/// <summary>
///
/// </summary>
/// <param name="val"></param>
/// <param name="nvcOptions">Valid options for this category include locale, dateFormat, useAltCalendar, and useEnglishStringsAlways.</param>
/// <returns></returns>
private string fmtDate(string value, NameValueCollection options)
{
// Set culture
string option = options["locale"];
CultureInfo culture = (option == null) ? Thread.CurrentThread.CurrentUICulture : CultureInfo.GetCultureInfo(int.Parse(option));
// Get date time format provider
DateTimeFormatInfo dtfi = culture.DateTimeFormat.Clone() as DateTimeFormatInfo;
string baseFormat = "{{0:{0}}}";
// dateFormat: Specifies a format picture string that is used to form the date string.
// The values Short Date, Long Date, Year Month, and none may also be used to indicate
// short date format, long date format, year month format, and no format, respectively.
// Short Date, Long Date, and Year Month are the default formats provided by the
// operating system's regional and language settings.
string fmt = "{0}";
switch (options["dateFormat"])
{
case "Short Date":
fmt = string.Format(baseFormat, dtfi.ShortDatePattern);
break;
case "Long Date":
fmt = string.Format(baseFormat, dtfi.LongDatePattern);
break;
case "Year Month":
fmt = string.Format(baseFormat, dtfi.YearMonthPattern);
break;
case "none":
fmt = "{0}";
break;
default:
fmt = string.Format(baseFormat, dtfi.ShortDatePattern);
break;
}
// useAltCalendar: Specifies whether to use an alternate calendar for date formatting.
// Specify 0 to use the normal calendar and 1 to use the alternate calendar.
// Defaults to 0 if not specified.
// useEnglishStringsAlways: Specifies whether to always use English strings for date formatting.
// Specify 0 to use the language specified by locale and 1 to always use English.
// Defaults to 0 if not specified.
// Output result
return string.Format(dtfi, fmt, DateTime.Parse(value));
}
/// <summary>
/// Format time values.
/// </summary>
/// <param name="value">Value to be formatted</param>
/// <param name="options">Valid options for this category include locale, timeFormat, and noSeconds.</param>
/// <returns>the formatted value</returns>
private string fmtTime(string value, NameValueCollection options)
{
// Set culture
string option = options["locale"];
CultureInfo culture = (option == null) ? Thread.CurrentThread.CurrentUICulture : CultureInfo.GetCultureInfo(int.Parse(option));
// Get date time format provider
DateTimeFormatInfo dtfi = culture.DateTimeFormat.Clone() as DateTimeFormatInfo;
string baseFormat = "{{0:{0}}}";
// noSeconds: Specifies whether to not use seconds.
// Specify 0 to use seconds and 1 to not use seconds. Defaults to 0 if not specified.
option = options["noSeconds"];
string fmt = string.Format(baseFormat, dtfi.LongTimePattern);
if (option != null && int.Parse(option) == 1) fmt = string.Format(baseFormat, dtfi.ShortTimePattern);
// timeFormat: Specifies a format string that is used to form the time string.
// The value none may also be used to indicate no format.
// Defaults to the time format in regional settings if not specified.
option = options["timeFormat"];
if (option != null) fmt = string.Format(baseFormat, option);
// Output result
return string.Format(dtfi, fmt, DateTime.Parse(value));
}
/// <summary>
/// Format DateTime values
/// </summary>
/// <param name="value">Value to be formatted</param>
/// <param name="nvcOptions">Valid options for this category include locale, dateFormat, timeFormat, noSeconds, useAltCalendar, and useEnglishStringAlways.</param>
/// <returns></returns>
private string fmtDateTime(string val, NameValueCollection nvcOptions)
{
throw new NotImplementedException();
}
/// <summary>
/// Formats a string
/// </summary>
/// <param name="value">Value to be formatted</param>
/// <param name="options">Valid options for this category include plainMultiline</param>
/// <returns>the formatted string</returns>
private string fmtString(string value, NameValueCollection options)
{
string option = options["plainMultiline"];
if (option != null)
{
return (value == string.Empty) ? "<br/>" : value.Replace("\r\n","<br/>");
}
return value;
}
/// <summary>
/// Returns the format options as a name value collection
/// </summary>
private NameValueCollection getOptions(string options)
{
NameValueCollection nvc = new NameValueCollection();
string[] nameValues = options.Split(new char[] { ':', ';' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < nameValues.Length; i++)
{
string name = nameValues[i++];
string val = (i < nameValues.Length) ? nameValues[i] : string.Empty;
nvc.Add(name, val);
}
return nvc;
}
}
}