Infopath是微软Office系列的一个新的成员,其主要作用就是用于制作各种表单。对于一个表单而言,主要就是两类文件,表单模版(.XSN)以及相关的数据文件(.XML)。在此,我主要介绍一下数据文件。Infopath的数据文件,就是xml文件,那么他为什么能用Infopath打开,并用相应的模板查看数据呢,奥妙就在他们文件头上。在文件头上,指定了xml是一个Infopath的表单,同时,他也指明了打开这个表单,用于展示数据的模板。下面是一个Infopath数据文件的头部信息。从这个投中,我们可以了解到,这是一个Infopath 2007的表单,其模板是My Document下的UserProfile.xsn。
<?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的相关处理信息。对于带有附件的表单文件,<?mso-infoPath-file-attachment-present?> 是必不可少的,如果没有加这一段,即使你把附件加到表单中,用户也无法打开该附件。对于如何生成每个字段的内容,并填入数据,不是本文的重点,有机会,另文叙述。那么言归正传,回到本文的主题,怎么用程序的方式把一个文件加到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 FIXED_HEADER = 16;
private int fileSize;
private int attachmentNameLength;
private string attachmentName;
private byte[] decodedAttachment;
/// <summary>
/// 解码基于Base64编码的Infopath附件字符串,
///并将解码后的文件名和文件内容保存为内部私有变量
/// </summary>
/// <param name="theBase64EncodedString">Base64编码的字符串</param>
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 fullyQualifiedFileName;
/// <summary>
/// 将指定文件编码成Infopath可识别Base64字符串
/// </summary>
/// <param name="fullyQualifiedFileName">文件全路径</param>
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;
}
/// <summary>
/// 获取Based64编码字串.
/// </summary>
/// <returns>String</returns>
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