访问InfoPath的xml数据的方法
InfoPath是基于XML技术的一种数据录入工具。它主要有三部分组成:数据定义(schema),数据显示(xslt)和数据存储(XML) 。它的数据都是按照xml格式存储的,不过,它提供的开发接口对xml的处理做了自己的封装,不是采用xmlDocument,而是提供了一套XDocument对象。
在真实的应用场景,我们一般把infopath作为客户端,用来录入数据,和服务器交互。 infopath在接受了用户录入的信息后,需要将数据传递给服务器 infopath在接受了用户录入的信息后,需要将数据传递给服务器。infopath可以通过多种途径和服务器通讯,最常用的还是通过web servie。
如何选择数据格式就是一个问题了。由于infopath自己的数据都是xml格式的,很自然就会采用将数据按照xml格式序列化后在infopath和服务器之间传递。如果数据简单,那么直接在服务器端访问xml文件就可以了。不过一般情况下infopath的数据都很复杂,包括很多信息字段。如果都使用xml文件访问方式就比较麻烦了。这时我们就考虑将数据转换为一个数据实体对象,这样访问起来就非常方便了。那么,对infopath数据的访问可以归纳为两类:
第一种: 直接的xml数据访问。
1 利用infopath的XDocument接口访问数据
这里,我们直接访问thisXDocument对象的DOM属性,所有的数据都保存在这个属性里。我们可以按照xml的xpath语法去做数据的定位,但是在访问前要先设置这些必要的属性SelectionLanguage和SelectionNamespaces。
读取一个节点的值
public string Data_GetSingleNodeValue(string xpath) { ((IXMLDOMDocument2)thisXDocument.DOM).setProperty("SelectionLanguage", "XPath"); ((IXMLDOMDocument2)thisXDocument.DOM).setProperty("SelectionNamespaces", "xmlns:dfs=/"" + "http://schemas.microsoft.com/office/infopath/2003/dataFormSolution" + "/" xmlns:my=/"http://schemas.microsoft.com/office/infopath/2003/myXSD/2007-01-11T07-19-53/"" + " xmlns:tns=/"http://tempuri.org//""); IXMLDOMNode node = ((IXMLDOMDocument2)thisXDocument.DOM).documentElement.selectSingleNode(xpath); if (null == node) { throw new Exception(string.Format("Can't locate the node by xpath{0}", xpath)); } return node.text; }设置一个节点的值
public void Data_SetNodeValue(string xpath,string value)
{ ((IXMLDOMDocument2)thisXDocument.DOM).setProperty("SelectionLanguage", "XPath"); ((IXMLDOMDocument2)thisXDocument.DOM).setProperty("SelectionNamespaces", "xmlns:dfs=/"" + "http://schemas.microsoft.com/office/infopath/2003/dataFormSolution" + "/" xmlns:my=/"http://schemas.microsoft.com/office/infopath/2003/myXSD/2007-01-11T07-19-53/"" + " xmlns:tns=/"http://tempuri.org//""); IXMLDOMNode node = ((IXMLDOMDocument2)thisXDocument.DOM).documentElement.selectSingleNode(xpath); if (null == node) { throw new Exception(string.Format("Can't locate the node by xpath{0}", xpath)); } if (node.attributes.getNamedItem("xsi:nil") != null) { node.attributes.removeNamedItem("xsi:nil"); } node.text = value; }2 利用System.XML的标准方法访问数据
在服务器端,如果要访问infopath传递过来的xml数据,也非常简单,将infopath传递过来的xml载入一个标准的xmldocument里,然后对它访问就可以了,不过需要先设置好namespace。
public xmlNode ParseData(string input,string XPath)
{ /// Create a xmldocument to save the input XmlDocument doc = new XmlDocument(); doc.LoadXml(input); /// Create an XmlNamespaceManager for resolving namespaces. XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.NameTable); nsmgr.AddNamespace("my", "http://schemas.microsoft.com/office/infopath/2003/myXSD/2006-04-11T09:28:40");XmlElement root = doc.DocumentElement;
/// get basic info
xmlNode node = root.SelectSingleNode(XPath, nsmgr); return node; }第二种: 通过数据对象访问xml数据。
这种方式更加适合作为客户端和服务器或者多个服务器之间数据传递的解决方式。在程序中对数据的访问按照对象方式进行,而数据的传递则采用xml方式。需要解决的是xml和数据对象之间的转换,也就是序列化和反序列化。 如果已经定义好了xml的schema,微软提供了xsd的工具可以根据schema自动生成一个数据类,大大节省了开发人员的工作。xsd.exe可以在Visual Studio的工具中找到。1 将schema转换为数据类
在命令行执行 xsd.exe myschema.xsd /c 其中,myschema.xsd是infopath自己产生的数据定义文件。如果你直接使用infopath设计一个form,你是找不到这个文件的,只能看到一个xsn文件。这个xsn实际是一组文件打包后的安装文件。你把xsn后缀改为cab后,可以用winzip打开它。这时你就会看到myschema.xsd了。2 在服务端使用数据对象访问数据
将xml转换为数据对象,doc是infopath传递过来的xmlDocuemnt,myType是我们用xsd生成的类名,
protected object DeserializeXmlFile(XmlDocument doc, Type myType) { System.Text.UTF8Encoding uf8 = new System.Text.UTF8Encoding(); Byte[] buffer = uf8.GetBytes(doc.InnerXml); MemoryStream fileStreamReader = new MemoryStream(buffer); XmlReader reader = XmlReader.Create(fileStreamReader); //Serial the xml to a object XmlSerializer serializer = new XmlSerializer(myType); return serializer.Deserialize(reader); } 将数据对象转换为xml,myObject是要序列化的数据对象,myType是数据类 protected string SerializeObject(object myObject, Type myType) { MemoryStream fileStream = new MemoryStream(); XmlWriter writer = XmlWriter.Create(fileStream); //writer.WriteProcessingInstruction( foreach (ProcessingInstructionInfo info in ProcessingInstructionInfoList) { writer.WriteProcessingInstruction(info.szProcessingInstructionName, info.szProcessingInstructionText); } XmlSerializer serializer = new XmlSerializer(myType); serializer.Serialize(writer, myObject); System.Text.UTF8Encoding uf8 = new System.Text.UTF8Encoding(); string xml = uf8.GetString(fileStream.ToArray()); //shit, remove the ? in line1 posiiton 1, I don't know where it come from? return xml.Substring(1); }3 在infopath端使用数据对象访问数据 在infopath端也可以使用数据对象访问数据,这样,如果你有比较负责的数据校验工作,用数据对象的方式要方便很多。不过在infopath里如果要将修改过的数据对象的值写回到xml中,还需要一些转换工作。 #region "data convert between thisXDocument and DataObject EBCEventData"
void testDataObj()
{ EBCEventData InfoPath = GetDataObj(); InfoPath.MyFields.ProjectID = "123"; ReplaceDocumentWithDataObj(InfoPath); }将thisXDocument数据转换为数据对象很简单,新建一个XmlDocument ,把thisXDocument.DOM.xml的数据载入,然后进行反序列化。
/// <summary> /// get a dataObj from thisXDocument /// </summary> /// <returns></returns> EBCEventData GetDataObj() { XmlDocument doc = new XmlDocument(); doc.LoadXml(thisXDocument.DOM.xml); EBCEventData InfoPath = new EBCEventData(doc); return InfoPath; } 在修改了数据对象内容后,需要将数据对象的修改写入到thisXDocument中。由于XDocument不支持直接对xml的修改,只能采取迂回的方法,从xml取出对应的节点,替换XDocument对应节点的内容 /// <summary> /// write the dataObj's data into thisXDocument's xml /// </summary> /// <param name="dataObj"></param> void ReplaceDocumentWithDataObj(EBCEventData dataObj) { XmlDocument doc = new XmlDocument(); doc = dataObj.SaveToXML(); IXMLDOMNode newnode = CloneXmlToIXmlDOC(doc.DocumentElement, thisXDocument.DOM); thisXDocument.DOM.replaceChild(newnode, thisXDocument.DOM.documentElement); } 将xmlDocument节点转换为infopath的IXMLDOMNode。这里对两类节点采用不同的处理,所有的叶子节点(XmlNodeType.Text == childnode.NodeType)直接复制节点的value,而对非叶子节点(XmlNodeType.Element == childnode.NodeType)采用递归方式复制节点的结构,即复制所有的子节点,然后复制节点的值。 /// <summary> /// get xmlnode of Element and convert it into infopath's IXMLDOMNode /// </summary> /// <param name="systemXmlElement"></param> /// <param name="msxmlDocument"></param> /// <returns></returns> IXMLDOMNode CloneXmlToIXmlDOC(XmlElement systemXmlElement, IXMLDOMDocument msxmlDocument) { IXMLDOMNode msxmlResultNode ;// Create a new element from the MSXML DOM using the same
// namespace as the XmlElement. msxmlResultNode = msxmlDocument.createNode( DOMNodeType.NODE_ELEMENT, systemXmlElement.Name, systemXmlElement.NamespaceURI);if (systemXmlElement.HasChildNodes)
{ foreach (XmlNode childnode in systemXmlElement.ChildNodes) { IXMLDOMNode child; if (XmlNodeType.Element == childnode.NodeType) { child = CloneXmlToIXmlDOC((XmlElement)childnode, msxmlDocument); } else { if (XmlNodeType.Text == childnode.NodeType) { child = GetValue(childnode, msxmlDocument); } else { throw new Exception(string.Format("The node type: {0} can't be processed", childnode.NodeType.ToString())); } } msxmlResultNode.appendChild(child); } } // Set the element's value. if (null != systemXmlElement.Value) { msxmlResultNode.text = systemXmlElement.Value.ToString(); } return msxmlResultNode; } /// <summary> /// get xmlnode of TEXTNODE and convert it to infopath's IXMLDOMNode /// </summary> /// <param name="node"></param> /// <param name="msxmlDocument"></param> /// <returns></returns> IXMLDOMNode GetValue(XmlNode node, IXMLDOMDocument msxmlDocument) { IXMLDOMNode msxmlResultNode;msxmlResultNode = msxmlDocument.createNode(
DOMNodeType.NODE_TEXT, node.Name, node.NamespaceURI); // Set the element's value. if (null != node.Value) { msxmlResultNode.text = node.Value.ToString(); } return msxmlResultNode; } #endregion在NET 2。0中,支持xml中定义可选字段。不过如果可选字段的数据类型为bool型,在从xml转化为数据对象时,可以被正确转换,但在将数据对象重新序列化为xml时,可选字段被忽略了,即使里面有值,在重新生成的xml中也被丢弃了。这样,就无法做到无损的数据转化了。目前还没有找到好办法,只好将所有的bool类型字段都定义为不可以为空。
以我的程序为例,在我的infopath数据源中定义了两个bool值。isNDAField是必填项,isAllowPictureField则是可以为空的可选项。
在infopath的schema中这样定义:
<xsd:element name="IsNDA" type="xsd:boolean"/>
<xsd:element name="IsAllowPicture" nillable="true" type="xsd:boolean"/>在xsd.exe自动创建的数据类中,则产生如下结果
private bool isNDAField;private bool isNDAFieldSpecified;private System.Nullable<bool> isAllowPictureField;private bool isAllowPictureFieldSpecified;
请参考XmlSerializer, Xsd.exe, Nullable