SharePoint: Non-InfoPath XML Form Property Promotion and Demotion


In a previous post, I described how you could provision SharePoint promoted property columns for InfoPath forms using code, rather than using the InfoPath client designer.

In the same vane, this post demonstrates how to support non-InfoPath XML forms in SharePoint;

  • Using the SharePoint built-in XML parser to automatically determine the content type based on the XML form content
  • Supporting XML property promotion/demotion

Using these techniques allows you to upload XML forms to a Form Library, with SharePoint itself deciding the content type to use for the item based on the XML form content.

It also supports data being promoted out of the XML form, into SharePoint columns (promotion), and out of SharePoint columns into the XML form itself (demotion).

For further infomation on how SharePoint determines the content type to use for an XML form, refer to this article on MSDN. Note that the MSDN article “Specifying Content Type by Content Type ID”, is somewhat misleading, specifically if refers to the ContentTypeID column using the wrong Field ID, and refers to the DocumentTemplate field when this field is properly named TemplateUrl, defined in the document library schema.xml.

If we wish to upload, using code, XML Forms to a SharePoint Form Library, the agent used to do this must perform the following tasks;

  • Determine (or know) the type of XML Form and the content type which should be associated with it.
  • Modify the XML Form content by adding the appropriate XML PI’s (processing instructions)
  • Upload the XML Form to the Form Library

Specifying the Content Type by Content Type ID.

This method is the simplest of the 2, to use this method you have to add the following PI to your XML Form prior to upload;

<?MicrosoftWindowsSharePointServices ContentTypeID="0x010101......" ?>

The ContentTypeID attribute specifies the ID of the content type which will be associated with this XML Form, note that this content type id should be scoped at the Web or Site level, so that list content types (which derive from Web/Site content types) are considered as candiates for the content type association. This means that you don’t need to know the List destination of the form, and therefore what the ID of a specific list content type is.

Specifying the Content Type by Content Type Document Template.

This is the same method used by InfoPath forms to associate an XML Form with a content type. The PI name used in this method, implies that this method was developed into SharePoint specifically for InfoPath.

To use this method add the following PI to your XML Form prior to upload.

<?mso-infoPathSolution href="/_layouts/1033/DocTemplates/MyTemplates/my-form-template.xml" ?>

The href attribute specifies the document template URL which is used to match against the document template URL of the associated content type.

Put simply, the content type with the same document template URL as that specified by the href attribute, will be used as the XML Forms associated content type.

Background.

These 2 methods of content type association are provided for you out of the box, but you can work up your own scheme. If you read the MSDN articles in depth, you’ll note that the column definitions themselves, ContentTypeID and TemplateUrl, specify the target PI names and the target PI attribute names using the following 2 Field Definition attributes;

  • PITarget
  • PIAttribute
<FieldRef
    ID="{4B1BF6C6-4F39-45ac-ACD5-16FE7A214E5E}"
    Name="TemplateUrl"
    PITarget="mso-infoPathSolution"
    PIAttribute="href"/>
<FieldRef
    ID="{03e45e84-1992-4d42-9116-26f756012634}"
    Name="ContentTypeID"
    PITarget="MicrosoftWindowsSharePointServices"
    PIAttribute="ContentTypeID"/>

So following on from this, it should be obvious how you can define your own content type association scheme.

Code.

So this calls for some code, specifically we need to manipulate the XML Form content by examining or adding the PI’s used for content type association.

Given an XMLForm entity class;

public enum SharePointXMLFormDocumentContentTypeSpecifier
{
	Unknown,
	ContentTypeID,
	DocumentTemplate
}

public class XMLFormEntity
{
	private byte[] _docBytes = null;

	public byte[] DocumentBytes
	{
		get
		{ return _docBytes; }
		set
		{ _docBytes = value; }
	}
	....
	....
}

We need a method to set the XML Forms processing instructions for content type association;

public void SetDocumentContentTypeSpecifier(SPWeb targetWeb, SPContentTypeId contentTypeId, SharePointXMLFormDocumentContentTypeSpecifier specifier)
{
	if (targetWeb == null) throw new ArgumentNullException("targetWeb");

	var doc = new XmlDocument();
	doc.Load(new XmlTextReader(new MemoryStream(_docBytes)));

	// first delete the PIs we don't want
	var piNodes = doc.SelectNodes("/processing-instruction()");
	if (piNodes != null)
	{
		foreach (XmlNode piNode in piNodes)
		{
			if (piNode.LocalName == "mso-infoPathSolution" || piNode.LocalName == "MicrosoftWindowsSharePointServices")
				doc.RemoveChild(piNode);
		}
	}

	// create the PI we want
	string piName = string.Empty, piValue = string.Empty;
	switch (specifier)
	{
		case SharePointXMLFormDocumentContentTypeSpecifier.DocumentTemplate:
		{
			var ct = from SPContentType spct in targetWeb.AvailableContentTypes
						where spct.Id == contentTypeId
						select spct;
			if (ct.Count() < 1)
				throw new Exception(string.Format("Content type {0} was not found on Web {1}", contentTypeId, targetWeb.Url));
			piValue = string.Format("href=\"{0}\"", ct.First().DocumentTemplateUrl);
			piName = "mso-infoPathSolution";
		}
		break;
		case SharePointXMLFormDocumentContentTypeSpecifier.ContentTypeID:
		{
			piValue = string.Format("ContentTypeID=\"{0}\"", contentTypeId);
			piName = "MicrosoftWindowsSharePointServices";
		}
		break;
	}
	if (!string.IsNullOrEmpty(piName) && !string.IsNullOrEmpty(piValue))
	{
		var pi = doc.CreateProcessingInstruction(piName, piValue);
		doc.InsertBefore(pi, doc.DocumentElement);
	}

	using (var newms = new MemoryStream())
	{
		doc.Save(newms);
		newms.Flush();
		newms.Seek(0, SeekOrigin.Begin);
		_docBytes = newms.ToArray();
	}
}

And to determine the content type association type of an existing XML Form;

public SharePointXMLFormDocumentContentTypeSpecifier GetDocumentContentTypeSpecifier()
{
	if (_docBytes == null)
		throw new Exception("No document stream!");

	var doc = new XmlDocument();
	doc.Load(new XmlTextReader(new MemoryStream(_docBytes)));

	// first try looking for the doc template PI
	var pi = (XmlProcessingInstruction)doc.SelectSingleNode
									 ("/processing-instruction(\"mso-infoPathSolution\")");
	if (pi != null && !string.IsNullOrEmpty(pi.Value) && pi.Value.Contains("href"))
		return SharePointXMLFormDocumentContentTypeSpecifier.DocumentTemplate;

	// next try for the content type id PI
	pi = (XmlProcessingInstruction)doc.SelectSingleNode
									 ("/processing-instruction(\"MicrosoftWindowsSharePointServices \")");
	if (pi != null && !string.IsNullOrEmpty(pi.Value) && pi.Value.Contains("ContentTypeID"))
		return SharePointXMLFormDocumentContentTypeSpecifier.ContentTypeID;

	return SharePointXMLFormDocumentContentTypeSpecifier.Unknown;
}

Property Promotion/Demotion.

To provision SharePoint columns which support property promotion/demotion, we follow the same techniques as described in my previous post.

A promoted property column definition looks pretty much the same as an InfoPath promoted property column;

<Field ID="5899B983-5E4A-4218-B557-3D20A56ADF1C"
		Type="Text"
		Name="MedicinalTrialPhase"
		DisplayName="Medicinal Trial Phase"
		StaticName="MedicinalTrialPhase"
		SourceID="http://fts.blackman.com/sharepoint"
		Group="Custom Group"
		Node="//Medicinal_Trial_Phase_Choice[1]/value"
		Version="1" />

The only difference, is whether or not your XML Form content uses namespaces, which in turn, affects the way you would write the Node property.

Published by

Phil Harding

SharePoint Consultant, Developer, Father, Husband and Climber.

4 thoughts on “SharePoint: Non-InfoPath XML Form Property Promotion and Demotion

  1. Sorry I believe I posted this comment on the wrong page.

    Hello, I was looking at your example of SharePoint: Non-InfoPath XML Form Property Promotion and Demotion. Could you please explain what the fields of the columnXMLschema represent? When you promote a property in InfoPath the xsd contains a GUID for the column. Is this GUID one of the GUIDs int he columnXMLSchema? Is that essentially how the property is mapped to the column?

    Thank you

  2. Cindy, when InfoPath promotes a property as a column in SharePoint, it creates a Field/Column in SharePoint, and in doing so, it generates a GUID to use as the columns internal field name. The article in part 1 of this series address this problem by provisioning InfoPath promoted property columns using code (which you’d likely use in a feature receiver) and in this way makes these InfoPath forms (and it’s associated content-type) much more re-useable.

  3. Is there a way that I can find out the name or guid of library on sharepoint site where I am creating a xml document from an InfoPath content type?
    When I publish a InfoPath template to a library that document has in its processing instructions the href which gives me the library where published, but when I publish it as content type on a site, and use it in multiple librariers in href I have a folder url where I saved content type. I need guid or href of library where I opened and am to save the xml document based on InfoPath content type. And this infopath content type is client based, not browser-enabled.
    Thank you in advance

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s