公司两年前开发了一个项目电子平台,其中有一个模块是"项目问题清单",这个模块主要是让工程师记录项目的问题并跟踪,但是由于很多工程师不能在外网访问和录入表单时不够方便,所以用户提出了采用Excel导入功能。
这个问题清单主要功能有以下几点:
1.表结构是主从表关系,一个问题对应对个行动计划
2.一个问题中可以插入图片和附件
3.邮件提醒功能
4.允许将问题清单导入和导出(图片和附件必须跟上)
其中,最为纠结的是Excel中插入或导出附件这个功能。
最新的解决方案是用NPOI方式来操作,但是经分析,NPOI仅支持图片的导出,虽然有返回图片的对象,但是不能获取到指定单元格的图片,还有一个就是不支持2007和附件的插入
后来采用了DocumentFormat.OpenXml.dll这个东西。
以下具体讲讲这个中间的过程。
1.用Open Xml SDK 2.0将做好的模板生成代码,对代码分析和封装
说说其中的对象:SharedStringTablePart 共享数据部件
DrawingsPart 图片部件
ThemePart 主题部件
WorkbookStylesPart 样式部件
WorksheetPart sheet部件
VmlDrawingPart 绘图对象
EmbeddedPackagePart 嵌入包部件
2.导出遇到的问题
2.1 导出的图片自动旋转180了,主要修改这段代码 A.Transform2D transform2D = new A.Transform2D()即可;
2.2 附件.pptx次序颠倒,最后一个附件跑到第一个位置了,研究发现有一段是拼接字符串的东东,代码封装如下:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 private void GenerateVmlDrawingPart1Content(VmlDrawingPart vmlDrawingPart1, DataTable dt)
2 {
3 int tmpInt = 1026;//默认种子附件编号
4 System.Xml.XmlTextWriter writer = new System.Xml.XmlTextWriter(vmlDrawingPart1.GetStream(System.IO.FileMode.Create), System.Text.Encoding.UTF8);
5 StringBuilder str = new StringBuilder();
6 str.Append("<xml xmlns:v=\"urn:schemas-microsoft-com:vml\"\r\n xmlns:o=\"urn:schemas-microsoft-com:office:office\"\r\n xmlns:x=\"urn:schemas-microsoft-com:office:excel\">\r\n <o:shapelayout v:ext=\"edit\">\r\n <o:idmap v:ext=\"edit\" data=\"1\"/>\r\n </o:shapelayout><v:shapetype id=\"_x0000_t75\" coordsize=\"21600,21600\" o:spt=\"75\"\r\n o:preferrelative=\"t\" path=\"m@4@5l@4@11@9@11@9@5xe\" filled=\"f\" stroked=\"f\">\r\n <v:stroke joinstyle=\"miter\"/>\r\n <v:formulas>\r\n <v:f eqn=\"if lineDrawn pixelLineWidth 0\"/>\r\n <v:f eqn=\"sum @0 1 0\"/>\r\n <v:f eqn=\"sum 0 0 @1\"/>\r\n <v:f eqn=\"prod @2 1 2\"/>\r\n <v:f eqn=\"prod @3 21600 pixelWidth\"/>\r\n <v:f eqn=\"prod @3 21600 pixelHeight\"/>\r\n <v:f eqn=\"sum @0 0 1\"/>\r\n <v:f eqn=\"prod @6 1 2\"/>\r\n <v:f eqn=\"prod @7 21600 pixelWidth\"/>\r\n <v:f eqn=\"sum @8 21600 0\"/>\r\n <v:f eqn=\"prod @7 21600 pixelHeight\"/>\r\n <v:f eqn=\"sum @10 21600 0\"/>\r\n </v:formulas>\r\n <v:path o:extrusionok=\"f\" gradientshapeok=\"t\" o:connecttype=\"rect\"/>\r\n <o:lock v:ext=\"edit\" aspectratio=\"t\"/>\r\n </v:shapetype>");
7 str.Append("<v:shape id=\"_x0000_s1025\" type=\"#_x0000_t75\" style=\'position:absolute;\r\n margin-left:215.25pt;margin-top:2.25pt;width:62.25pt;height:39pt;z-index:1\'\r\n filled=\"t\" fillcolor=\"window [65]\" stroked=\"t\" strokecolor=\"windowText [64]\"\r\n o:insetmode=\"auto\">\r\n <v:fill color2=\"window [65]\"/>\r\n <v:imagedata o:relid=\"rPPT_title\" o:title=\"\"/>\r\n <x:ClientData ObjectType=\"Pict\">\r\n <x:SizeWithCells/>\r\n <x:Anchor>\r\n 4, 3, 0, 3, 4, 86, 0, 55</x:Anchor>\r\n <x:CF>Pict</x:CF>\r\n </x:ClientData>\r\n </v:shape>");
8 for (int row = 0; row < dt.Rows.Count; row++)
9 {
10 if (dt.Rows[row]["FILE_PPTX_PATH"].ToString() != "")
11 {
12 str.AppendFormat("<v:shape id=\"_x0000_s{0}\" type=\"#_x0000_t75\" style=\'position:absolute;\r\n margin-left:596.25pt;margin-top:{1}pt;width:53.25pt;height:35.25pt;\r\n z-index:{3}\' filled=\"t\" fillcolor=\"window [65]\" stroked=\"t\" strokecolor=\"windowText [64]\"\r\n o:insetmode=\"auto\">\r\n <v:fill color2=\"window [65]\"/>\r\n <v:imagedata o:relid=\"rPPT_details\" o:title=\"\"/>\r\n <x:ClientData ObjectType=\"Pict\">\r\n <x:SizeWithCells/>\r\n <x:Anchor>\r\n 10, 5, {2}, 5, 10, 76, {2}, 52</x:Anchor>\r\n <x:CF>Pict</x:CF>\r\n </x:ClientData>\r\n </v:shape>", tmpInt, (69.75 + (42.75 * row)).ToString(), row + 2, row + 2);
13 tmpInt++;
14 }
15 }
16 str.Append("</xml>");
17 writer.WriteRaw(str.ToString());
18 writer.Flush();
19 writer.Close();
20 }
纠结个的地方 shape id=\"_x0000_s{0}\"和<x:Anchor>\r\n 10, 5, {2}, 5, 10, 76, {2}, 52</x:Anchor>\r\
shape 嵌入的东西是以1025开始的,1026表示第二个...Anchor 记录附件的位置,个人理解左上角是1,3 右下角是5,7(导入的时候要读入该内容,判断是否有附件)
所有的附件采用用同一个图片
3.导入遇到的问题
3.1Excel中的附件无法抓取,后来解析<x:Anchor>这个东西才得以判断当前单元格是否有附件
3.2附件与Uri对应不上,即shape 与Uri 没有直接的关系,后来分析需要这个OleObjects对象
另附上部分代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 //获取单元格中的值
2 private String GetValue(Cell cell, SharedStringTablePart stringTablePart)
3 {
4 if (cell == null)
5 {
6 return "";//
7 }
8 if (cell.ChildElements.Count == 0)
9 {
10 return "";
11 }
12 String value = cell.CellValue.InnerText;
13 if ((cell.DataType != null) && (cell.DataType == CellValues.SharedString))
14 {
15 value = stringTablePart.SharedStringTable.ChildElements[Int32.Parse(value)].InnerText;
16 }
17 return value;
18 }
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 //Excel抓取图片
2 private void ImportImg(WorksheetDrawing dp, string _Embed, string filePath)
3 {
4 ImagePart ip = dp.DrawingsPart.Parts.Single(c => c.RelationshipId == _Embed).OpenXmlPart as ImagePart;
5 //imgExtension = System.IO.Path.GetExtension(ip.Uri.ToString());
6 using (Stream stream = ip.GetStream(FileMode.Open, FileAccess.Read))
7 {
8 //string path = ip.Uri.OriginalString;//图片路径
9 using (BinaryReader reader = new BinaryReader(stream))
10 {
11 byte[] buffer = new byte[reader.BaseStream.Length];
12 reader.Read(buffer, 0, Convert.ToInt32(buffer.Length));
13 //string base64String = Convert.ToBase64String(buffer);
14
15 using (MemoryStream streamBitmap = new MemoryStream(buffer))
16 {
17 Bitmap bitImage = new Bitmap((Bitmap)System.Drawing.Image.FromStream(streamBitmap));
18 bitImage.Save(filePath, bitImage.RawFormat);
19 bitImage.Dispose();
20 //Bitmap tmpbmp = new Bitmap(bitImage.Width, bitImage.Height);
21 //using(Graphics g = Graphics.FromImage(tmpbmp))
22 //{
23 // g.DrawImage(bitImage, 0, 0);
24 // tmpbmp.Save(filePath, bitImage.RawFormat);
25 //}
26 //bitImage.Dispose();
27 //tmpbmp.Dispose();
28 }
29 }
30 }
31 }
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
//是否存在附件
private void IsAttaach(ref EmbeddedPackagePart emb, string rowIndex, string colIndex)
{
var item = (from a in lt_Dictionary
join b in lt_AttachRef on a.Shapeid equals b.Shapeid
where a.RowIndex == rowIndex && a.ColIndex == colIndex
select new { b.Uri }).SingleOrDefault();
if (item != null)
{
emb = item.Uri;
}
}
//Excel中抓取附件
private void ImportPptx(EmbeddedPackagePart Uri, string filePath)
{
using (Stream stream = Uri.GetStream(FileMode.Open, FileAccess.Read))
{
using (BinaryReader reader = new BinaryReader(stream))
{
byte[] buffer = new byte[reader.BaseStream.Length];
reader.Read(buffer, 0, Convert.ToInt32(buffer.Length));
FileStream fs = new FileStream(filePath, FileMode.Create);
fs.Write(buffer, 0, buffer.Length);
fs.Flush();
fs.Close();
}
}
}
这段是定位分析附件的
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
//将vml文本加载到xml中,获取行列的定位
VmlDrawingPart vml = documentPack.WorkbookPart.WorksheetParts.First().VmlDrawingParts.First();
Stream workbookstr = vml.GetStream();
System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
doc.Load(workbookstr);
string[] strArr = doc.InnerText.Replace("\r\n", "|").Replace("Pict", "").Remove(0, 1).Trim().Split('|');
for (int i = 0; i < doc.FirstChild.ChildNodes.Count; i++)
{
if (doc.FirstChild.ChildNodes[i].Name == "v:shape")
{
var item = doc.FirstChild.ChildNodes[i];
string[] str = item.InnerText.Replace("\r\n", "|").Trim().Split('|')[1].Split(',');
lt_Dictionary.Add(new DictionaryClass
{
Shapeid = item.Attributes["id"].Value.Split('s').Last(),
ColIndex = str[0].Trim(),
RowIndex = str[2].Trim()
});
}
}
小结:
1.主要用到了附件与Base64String 互转
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
/// <summary>
/// Author:严智远 Remark:将流填充到ImagePart对象中 Date:2012-02-18
/// </summary>
/// <param name="imagePart3"></param>
private void GenerateImagePartContent(ImagePart imagePart, string filePath)
{
//将图片转换成字符
System.Drawing.Bitmap bmp = (System.Drawing.Bitmap)System.Drawing.Image.FromFile(HttpContext.Current.Server.MapPath("~")+filePath);
MemoryStream stream = new MemoryStream();
bmp.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);
stream.Position = 0;
byte[] buff = new byte[stream.Length];
stream.Read(buff, 0, (int)stream.Length);
stream.Close();
System.IO.Stream data = GetBinaryDataStream(System.Convert.ToBase64String(buff, 0, buff.Length));
imagePart.FeedData(data);
data.Close();
}
2.IO操作、linq操作对象
3..vml后缀文件的了解
4.如何查看及分析问题