Infopath是微软Office系列的一个新的成员,其主要作用就是用于制作各种表单。对于一个表单而言,主要就是两类文件,表单模版(.XSN)以及相关的数据文件(.XML)。在此,我主要介绍一下数据文件。Infopath的数据文件,就是xml文件,那么他为什么能用Infopath打开,并用相应的模板查看数据呢,奥妙就在他们文件头上。在文件头上,指定了xml是一个Infopath的表单,同时,他也指明了打开这个表单,用于展示数据的模板。下面是一个Infopath数据文件的头部信息。从这个投中,我们可以了解到,这是一个Infopath 2007的表单,其模板是My Document下的UserProfile.xsn。
- <?xml version="1.0" encoding="UTF-8"?>
- <?mso-infoPathSolution solutionVersion="1.0.0.1" productVersion="12.0.0" PIVersion="1.0.0.0" href="file:///D:/My%20Documents/UserProfile.xsn"
- name="urn:schemas-microsoft-com:office:infopath:UserProfile:-myXSD-2008-12-28T18-07-15" ?>
- <?mso-application progid="InfoPath.Document" versionProgid="InfoPath.Document.2"?>
- <?mso-infoPath-file-attachment-present?>
- <my:myFields xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2008-12-28T18:07:15"
- xml:lang="zh-cn">
- <my:Name>Xinhai</my:Name>
- <my:Age xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">30</my:Age>
- <my:Gander>M</my:Gander>
- <my:Phone>000000</my:Phone>
- <my:Email>abc@abc.com</my:Email>
- <my:CV xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">x0lGQRQAAAABAAAAAAAAAAgAAAALAAAAWABpAG4AaABhAGkALgB0AHgAdAAAAGxpeGluaGFp</my:CV>
- </my:myFields>
那么,我们在写程序创建一个指定模板的Infopath数据文档时,可以通过简单的创建一个xml文本文档的方式在创建一个Infopath数据文档,关键的就是要在XML的头部加上Infopath的相关处理信息。对于带有附件的表单文件, 是必不可少的,如果没有加这一段,即使你把附件加到表单中,用户也无法打开该附件。对于如何生成每个字段的内容,并填入数据,不是本文的重点,有机会,另文叙述。那么言归正传,回到本文的主题,怎么用程序的方式把一个文件加到Infopath数据文档中去。
首先,我们需要了解XML文档是如何存储一个文件的。不管是文本文档,还是二进制文件,XML都会将其编码成Base64编码,插入到XML节点当中。在RFC2045(http://www.ietf.org/rfc/rfc2045.txt)中Base64被定义为:Base64内容传送编码被设计用来把任意序列的8位字节描述为一种不易被人直接识别的形式。(The Base64 Content-Transfer-Encoding is designed to represent arbitrary sequences of octets in a form that need not be humanly readable.)字面上的意思就是把要传送的内容简单的加密,不能让人直接识别。Base64是一中纯ASCII编码,及高位为0的ASCII编码,因此,不会出现乱码或者与XML预定义字符冲突的现象。在C#语言中,可以很方便的通过Convert.ToBase64String(byte[])和Convert.FromBase64String(String)将一个二进制数据流与一个Base64的字符串进行互换。那么是不是只要简单的讲一个文件流转换成一个Base64字符串加到XML表单数据文件中,Infopath就能解析出文件来呢?答案是否定的。
其次,Infopath有其自己的格式来保存文件。我们再次查看上面的XML样本,仔细看看my:CV一节。显然,这是一个Base64的字符串,包含了文件的内容,但是奇怪的是,我们看不出文件名在哪。但实际上,我们用Infopath是可以看到附件的文件名的。那么,Infopath是怎么获取到文件名的呢?原来,Infopath在把文件序列化成Base64字串前,把文件名等信息也加入到其中了。Infopath建立了一个头部结构来保留文件相关信息,这个头包含如下内容:
- BYTE[4]. 文件签名.
(decimal) | 199 | 73 | 70 | 65 |
(hexadecimal) | C7 | 49 | 46 | 41 |
(ASCII C notation) | /307 | I | F | A |
注意:这四位标示着一个文件的启示,没有实际的意义
- DWORD. 头部结构的字节数,该大小包括本字段以及后面的四个字段。对于Infopath来说,这个字段的值一般都是20。
- DWORD. 头部结构版本,对于Infopath 2003 SP1来说,这个值为1,经测试,Infopath 2007也可以将这个值设为1。
- DWORD. 保留字段
- DWORD. 文件大小
- DWORD. 文件名长度
- File name buffer. 可变大小
在这个头部结构之后,就是相应的文件数据了。
我们在了解了Infopath的附件格式之后,就能用程序去读取或者写入附件了。
读取附件的代码部分:
- private const int SP1Header_Size = 20;
- private const int FIXED_HEADER = 16;
- private int fileSize;
- private int attachmentNameLength;
- private string attachmentName;
- private byte[] decodedAttachment;
- ///
- /// 解码基于Base64编码的Infopath附件字符串,
- ///并将解码后的文件名和文件内容保存为内部私有变量
- ///
- /// Base64编码的字符串
- public void InfoPathAttachmentDecode(string theBase64EncodedString)
- {
- byte[] theData = Convert.FromBase64String(theBase64EncodedString);
- using (MemoryStream ms = new MemoryStream(theData))
- {
- BinaryReader theReader = new BinaryReader(ms);
- DecodeAttachment(theReader);
- }
- }
- private void DecodeAttachment(BinaryReader theReader)
- {
- //获取头部结构数据
- byte[] headerData = new byte[FIXED_HEADER];
- headerData = theReader.ReadBytes(headerData.Length);
- fileSize = (int)theReader.ReadUInt32();
- attachmentNameLength = (int)theReader.ReadUInt32() * 2;
- //获取文件名流
- byte[] fileNameBytes = theReader.ReadBytes(attachmentNameLength);
- //InfoPath 使用的是UTF8编码,将文件名转换为UTF8字符串
- Encoding enc = Encoding.Unicode;
- attachmentName = enc.GetString(fileNameBytes, 0, attachmentNameLength - 2);
- //获取附件内容,并将其保存为二进制数组
- decodedAttachment = theReader.ReadBytes(fileSize);
- }
将附件转化为Infopath可识别Base64字串的代码段。
- private string base64EncodedFile = string.Empty;
- private string fullyQualifiedFileName;
- ///
- /// 将指定文件编码成Infopath可识别Base64字符串
- ///
- /// 文件全路径
- public InfoPathAttachmentEncoder(string fullyQualifiedFileName)
- {
- if (fullyQualifiedFileName == string.Empty)
- throw new ArgumentException("Must specify file name", "fullyQualifiedFileName");
- if (!File.Exists(fullyQualifiedFileName))
- throw new FileNotFoundException("File does not exist: " + fullyQualifiedFileName, fullyQualifiedFileName);
- this.fullyQualifiedFileName = fullyQualifiedFileName;
- }
- ///
- /// 获取Based64编码字串.
- ///
- /// String
- public string ToBase64String()
- {
- if (base64EncodedFile != string.Empty)
- return base64EncodedFile;
- // MemoryStream将用于保存文件附件数据流
- MemoryStream ms = new MemoryStream();
- // 获取文件信息.
- using (BinaryReader br = new BinaryReader(File.Open(fullyQualifiedFileName, FileMode.Open, FileAccess.Read, FileShare.Read)))
- {
- string fileName = Path.GetFileName(fullyQualifiedFileName);
- uint fileNameLength = (uint)fileName.Length + 1;
- byte[] fileNameBytes = Encoding.Unicode.GetBytes(fileName);
- using (BinaryWriter bw = new BinaryWriter(ms))
- {
- // 写入Infopath文件签名信息
- bw.Write(new byte[] { 0xC7, 0x49, 0x46, 0x41 });
- // 写入默认的头信息.
- bw.Write((uint)0x14); // size
- bw.Write((uint)0x01); // version
- bw.Write((uint)0x00); // reserved
- // 写入文件大小
- bw.Write((uint)br.BaseStream.Length);
- // 写入文件名长度.
- bw.Write((uint)fileNameLength);
- // 写入文件名 (Unicode编码).
- bw.Write(fileNameBytes);
- //写入文件名终止符(两个空字符)
- bw.Write(new byte[] { 0, 0 });
- //写入文件内容
- byte[] data = new byte[64 * 1024];
- int bytesRead = 1;
- while (bytesRead > 0)
- {
- bytesRead = br.Read(data, 0, data.Length);
- bw.Write(data, 0, bytesRead);
- }
- }
- }
- return base64EncodedFile = Convert.ToBase64String(ms.ToArray());
- }
最后,请注意,即使在程序写入Infopath表单是没有限制,尽量不要将大文件作为附件附加到Infopath,以免影响性能。此外,Infopath本身,对于附件有很多限制,如下列附加名的文件是不允许作为附件的。同样,在用程序是现实,也没有限制,不过还是请在附件文件是做一个简单的校验,防止通过程序在Infopath表单中加入非法文件。
ade, adp, app, asp, bas, bat, cer, chm, cmd, com, cpl, crt, csh, exe, fxp, hlp, hta, inf, ins, isp, its, js, jse, ksh, lnk, mad, maf, mag, mam, maq, mar, mas, mat, mau, mav, maw, mda, mdb, mde, mdt, mdw, mdz, msc, msi, msp, mst, ops, pcd, pif, prf, prg, pst, reg, scf, scr, sct, shb, shs, tmp, url, vb, vbe, vbs, vsmacros, vss, vst, vsw, ws, wsc, wsf, wsh