OpenXml相對於用MS提供的COM組件來生成WORD,有如下優勢:
1.相對於MS 的COM組件,因為版本帶來的不兼容問題,及各種會生成WORD半途會崩潰的問題.
2.對比填滿一張30多頁的WORD來說(包含圖,表等),用COM組件來生成會占用20秒,Openxml1秒.
3.MS Word軟件太貴了,你的客戶裝的是開源WORD,如LibreOffice,OpenOffice.這樣你就只能用Openxml生成的WORD文檔,各種支持MS Word都能打開,避免客戶機器上安裝MS Word.
簡單來說OpenXml的各個操作.
首先用OpenXml打開一張報表.
public void CreateOpenXMLFile(string filePath) { using (WordprocessingDocument objWordDocument = WordprocessingDocument.Create(filePath, WordprocessingDocumentType.Document)) { MainDocumentPart objMainDocumentPart = objWordDocument.AddMainDocumentPart(); objMainDocumentPart.Document = new Document(new Body()); Body objBody = objMainDocumentPart.Document.Body; //創建一些需要用到的樣式,如標題3,標題4,在OpenXml里面,這些樣式都要自己來創建的 //ReportExport.CreateParagraphStyle(objWordDocument); SectionProperties sectionProperties = new SectionProperties(); PageSize pageSize = new PageSize(); PageMargin pageMargin = new PageMargin(); Columns columns = new Columns() { Space = "220" };//720 DocGrid docGrid = new DocGrid() { LinePitch = 100 };//360 //創建頁面的大小,頁距,頁面方向一些基本的設置,如A4,B4,Letter, //GetPageSetting(PageSize,PageMargin); //在這里填充各個Paragraph,與Table,頁面上第一級元素就是段落,表格. objBody.Append(new Paragraph()); objBody.Append(new Table()); objBody.Append(new Paragraph()); //我會告訴你這里的順序很重要嗎?下面才是把上面那些設置放到Word里去.(大家可以試試把這下面的代碼放上面,會不會出現打開openxml文件有誤,因為內容有誤) sectionProperties.Append(pageSize, pageMargin, columns, docGrid); objBody.Append(sectionProperties); //如果有頁眉,在這里添加頁眉. if (IsAddHead) { //添加頁面,如果有圖片,這個圖片和上面添加在objBody方式有點不一樣,這里搞了好久. //ReportExport.AddHeader(objMainDocumentPart, image); } objMainDocumentPart.Document.Save(); } }
發現上面有點注解說錯,那個順序不影響Word,但是影響如LibreOffice軟件打開后看到的格式不正確.改里面太麻煩,直接在這說了.
從這個總綱里,把上面的各個步驟再來仔細說下.
首先是在Openxml創建標題3,標題4.
// 為文檔創建段落樣式
public static void CreateParagraphStyle(WordprocessingDocument doc) { // 進入文檔控制樣式部分 StyleDefinitionsPart styleDefinitionsPart; styleDefinitionsPart = doc.MainDocumentPart.AddNewPart<StyleDefinitionsPart>(); Styles root = new Styles(); root.Save(styleDefinitionsPart); Styles styles = styleDefinitionsPart.Styles; if (styles == null) { styleDefinitionsPart.Styles = new Styles(); styleDefinitionsPart.Styles.Save(); } Style style3 = CreateTitleStyle(3); Style style4 = CreateTitleStyle(4); // 把樣式添加入文檔中 styles.Append(style3); styles.Append(style4); } private static Style CreateTitleStyle(int titleIndex) { string titleID = titleIndex.ToString(); string rsid = string.Empty; string before = string.Empty; string after = string.Empty; string line = string.Empty; string val = string.Empty; int outline = titleIndex - 1; if (titleIndex == 3) { rsid = "00BA1E98"; before = "130";//"260" after = "0"; line = "286";//"416" val = "32"; } else if (titleIndex == 4) { rsid = "00BA1E98"; before = "88"; after = "0"; line = "288";//"376" val = "28"; } Style style2 = new Style() { Type = StyleValues.Paragraph, StyleId = titleID }; StyleName styleName2 = new StyleName() { Val = "heading " + titleID }; BasedOn basedOn1 = new BasedOn() { Val = "a" }; NextParagraphStyle nextParagraphStyle1 = new NextParagraphStyle() { Val = "a" }; LinkedStyle linkedStyle1 = new LinkedStyle() { Val = titleID + "Char" }; UIPriority uIPriority1 = new UIPriority() { Val = 9 }; PrimaryStyle primaryStyle2 = new PrimaryStyle(); Rsid rsid2 = new Rsid() { Val = rsid }; style2.Append(styleName2); style2.Append(basedOn1); style2.Append(nextParagraphStyle1); style2.Append(linkedStyle1); style2.Append(uIPriority1); style2.Append(primaryStyle2); style2.Append(rsid2); StyleParagraphProperties styleParagraphProperties2 = new StyleParagraphProperties(); KeepNext keepNext1 = new KeepNext(); KeepLines keepLines1 = new KeepLines(); SpacingBetweenLines spacingBetweenLines1 = new SpacingBetweenLines() { Before = before, After = after, Line = line, LineRule = LineSpacingRuleValues.Auto }; OutlineLevel outlineLevel1 = new OutlineLevel() { Val = outline }; styleParagraphProperties2.Append(keepNext1); styleParagraphProperties2.Append(keepLines1); styleParagraphProperties2.Append(spacingBetweenLines1); styleParagraphProperties2.Append(outlineLevel1); style2.Append(styleParagraphProperties2); StyleRunProperties styleRunProperties1 = new StyleRunProperties(); Bold bold1 = new Bold(); BoldComplexScript boldComplexScript1 = new BoldComplexScript(); // Kern kern2 = new Kern() { Val = (UInt32)44U }; FontSize fontSize2 = new FontSize() { Val = val }; FontSizeComplexScript fontSizeComplexScript2 = new FontSizeComplexScript() { Val = val }; styleRunProperties1.Append(bold1); styleRunProperties1.Append(boldComplexScript1); //styleRunProperties1.Append(kern2); styleRunProperties1.Append(fontSize2); styleRunProperties1.Append(fontSizeComplexScript2); style2.Append(styleRunProperties1); return style2; }
然后是對頁面大小,頁面方向,頁距設置.(懶的和報表里的一個個對應,里面的頁距有很多大家可以精確設置.)
public static void GetPageSetting(ref PageSize pageSize, ref PageMargin pageMargin) { bool val = IsPaperOrientation; string str_paperSize = "Letter";//A4,B4 UInt32Value width = 15840U; UInt32Value height = 12240U; int top = 1440; UInt32Value left = 1440U; if (str_paperSize == "A4") { width = 16840U; height = 11905U; } else if (str_paperSize == "B4") { width = 20636U; height = 14570U; } if (!val) { UInt32Value sweep = width; width = height; height = sweep; int top_sweep = top; top = (int)left.Value; left = (uint)top_sweep; } pageSize.Width = width; pageSize.Height = height; pageSize.Orient = new EnumValue<PageOrientationValues>(val ? PageOrientationValues.Landscape : PageOrientationValues.Portrait); pageMargin.Top = top; pageMargin.Bottom = top; pageMargin.Left = left; pageMargin.Right = left; pageMargin.Header = (UInt32Value)720U; pageMargin.Footer = (UInt32Value)720U; pageMargin.Gutter = (UInt32Value)0U; }
然后重點來了,大家對各元素如何在OpenXml添加的.
我先說下,在Openxml里相關元素的關系,在Word里,按一下Enter,轉一行,對應的一行元素就是Paragraph,那如果一行文字在里面如何存放.Paragraph->Run->Text,圖表Paragraph->Run->Drawing,表格Table->TableRow->TableCell->Paragraph->Run->Text與Drawing.在關系上說,還是很簡潔的.來看一下具體的操作,我們先定義一個類.對應OpenXML里的基本樣式設置.
public class ReportCommon { public const float defaultSize = 12f; public const float H1 = 20f; public const float H3 = 16f; public const float H4 = 14f; private string text = string.Empty; public ReportCommon() { Alignment = -1; Size = defaultSize; } //規定-1左對齊,0中間,1右對齊 public int Alignment { get; set; } public virtual float Size { get; set; } public string Text { get; set; } public virtual bool IsBold { get; set; } }
其中我定義這個類的三個子類,分別是ReportValue:主要是這種A: B,ReportImage:包含一個圖片的路徑.ReportText:只有一個文本.
private static List<Run> GetRuns(ReportCommon common) { List<Run> runs = new List<Run>(); if (common is ReportValue) { ReportValue reportvalue = common as ReportValue; Run r = new Run(); RunProperties rP = GetRunProperties(reportvalue, true); r.Append(rP); string text = reportvalue.Text; if (text.EndsWith(":")) text = text + " "; if (!text.EndsWith(": ")) text = text + ": "; Text t = CreateText(text); r.Append(t); runs.Add(r); r = new Run(); rP = GetRunProperties(reportvalue, false); r.Append(rP); r.Append(CreateText(reportvalue.Value)); runs.Add(r); } else if (common is ReportImage) { ReportImage reportImage = common as ReportImage; Run r = new Run(); RunProperties rP = GetRunProperties(reportImage); Drawing image = GetImageToBody(reportImage.RId, reportImage.Width * 600, reportImage.Height * 800); //Drawing image = new Drawing(); //image.Append(new A.Blip() { Embed = new StringValue(reportImage.RId) }); r.Append(rP); r.Append(image); runs.Add(r); } else if (common is ReportText) { Run r = new Run(); RunProperties rP = GetRunProperties(common); r.Append(rP); r.Append(CreateText(common.Text)); runs.Add(r); } return runs; }
看了這里,問題是不是越來越多.圖片具體操作先不說.上面的RunProperties,與CreateText分別是指什么.
RunProperties是指包含這段Text,你要設置的一些字體大小,顏色,文本對齊設置.
而CreateText是因為我們在Openxml你在后面加個空格,而會給你過濾掉,空格要對應到XML的具體設置,看如下代碼.
public static RunProperties GetRunProperties(ReportCommon common, bool bBold = false) { RunProperties rPr = new RunProperties(); //Color color = new Color() { Val = "FF0000" }; // the color is red RunFonts rFont = new RunFonts(); rFont.Ascii = "Arial"; // the font is Arial //rPr.Append(color); //rPr.Append(rFont); if (common.IsBold || bBold) rPr.Append(new Bold()); // it is Bold //TextAlignment rPr.Append(new FontSize() { Val = new StringValue((common.Size * 2).ToString()) }); //font size (in 1/72 of an inch) return rPr; } private static Text CreateText(string text) { if (text == null) text = string.Empty; Text t = new Text(text); if (text.EndsWith(" ")) { t.Space = new EnumValue<SpaceProcessingModeValues>(SpaceProcessingModeValues.Preserve); } if (text.StartsWith(" ")) { t.Space = new EnumValue<SpaceProcessingModeValues>(SpaceProcessingModeValues.Default); } return t; }
不知這么多代碼大家看煩沒,因為我這人喜歡不是業務與大綱的事,都喜歡直接看代碼來說,比人講的清楚.所以講的時候也喜歡,直接上代碼.廢話不說了,說下圖片的問題,Openxml插入圖片比較麻煩,先貼一段代碼.
private static Drawing GetImageToBody(string relationshipId, int x = 914400, int y = 360000) { // Define the reference of the image. var element = new Drawing( new DW.Inline( new DW.Extent() { Cx = x, Cy = y }, new DW.EffectExtent() { LeftEdge = 0L, TopEdge = 0L, RightEdge = 0L, BottomEdge = 0L }, new DW.DocProperties() { Id = (UInt32Value)1U, Name = "Picture 1" }, new DW.NonVisualGraphicFrameDrawingProperties( new A.GraphicFrameLocks() { NoChangeAspect = true }), new A.Graphic( new A.GraphicData( new PIC.Picture( new PIC.NonVisualPictureProperties( new PIC.NonVisualDrawingProperties() { Id = (UInt32Value)0U, Name = "New Bitmap Image.jpg" }, new PIC.NonVisualPictureDrawingProperties()), new PIC.BlipFill( new A.Blip( new A.BlipExtensionList( new A.BlipExtension() { Uri = "{28A0092B-C50C-407E-A947-70E740481C1C}" }) ) { Embed = relationshipId, CompressionState = A.BlipCompressionValues.Print }, new A.Stretch( new A.FillRectangle())), new PIC.ShapeProperties( new A.Transform2D( new A.Offset() { X = 0L, Y = 0L }, new A.Extents() { Cx = x, Cy = y }), new A.PresetGeometry( new A.AdjustValueList() ) { Preset = A.ShapeTypeValues.Rectangle })) ) { Uri = "http://schemas.openxmlformats.org/drawingml/2006/picture" }) ) { DistanceFromTop = (UInt32Value)0U, DistanceFromBottom = (UInt32Value)0U, DistanceFromLeft = (UInt32Value)0U, DistanceFromRight = (UInt32Value)0U, EditId = "50D07946" }); // Append the reference to body, the element should be in a Run. var blip = element.Descendants<DocumentFormat.OpenXml.Drawing.Blip>() .FirstOrDefault<DocumentFormat.OpenXml.Drawing.Blip>(); return element; }
這段代碼里,東東比較多,大家主要看這一節, Embed = relationshipId,也是參數里要求傳入的,我們可以這么理解,在OpenXML插入一張電腦的圖片,插入數據到word后,word然后把保存這個圖片的一個標識量給我們,讓我們來用,就是relationshipId.說到這,我們好像還沒看如何把一張路徑下的圖片插入word.如下
public static void CreateImageRid(ReportImage reportImage, MainDocumentPart objMainDocumentPart) { ImagePartType imagetype = ImagePartType.Jpeg; FileInfo newImg = new FileInfo(reportImage.Value); ImagePart newImgPart = objMainDocumentPart.AddImagePart(imagetype); //插入圖片數據到Word里去. using (FileStream stream = newImg.OpenRead()) { newImgPart.FeedData(stream); } //Word返回給我們插入數據的標識符. reportImage.RId = objMainDocumentPart.GetIdOfPart(newImgPart); }
這里圖片插入有先后關系,要先調用上面這段,插入數據到word,然后才能調用上上段的那段代碼來生成Drawing元素.
大家如果要把圖片的寬度,設為當前Word的可用寬度.Int32Value width = (int)(pageSize.Width - pageMargin.Right - pageMargin.Left);
好吧,大家會發現上上段那里的長度特大,這里發現這里的值要很大才能顯現比較好看的圖片,一般我在原來的基礎寬度*600,基礎長度*800.原因嗎,我也不清楚,有些猜測,沒有驗證就不說了,這個要求OpenXML生成報表比較急,我把這幾天所有操作先總結一下.后面再來修改,如果有知道的道友,不妨說一下.
在這里,文字與圖片如何生成Paragraph就很簡單了.
public static Paragraph GetParagraph(ReportCommon common) { Paragraph p = new Paragraph(); ParagraphProperties pPr = GetParagraphProperties(common); p.Append(pPr); List<Run> run = GetRuns(common); foreach (var r in run) { p.Append(r); } return p; }
好吧,最后說到如何在OpenXML生成一張表.生成圖表我用的基礎數據是List<List<string>>,上面的ReportTable數據就放在這個里面.當然還有一些基本的定義屬性就不說.具體如下看代碼.
public static Table GetParagraph(ReportTable reportTable, Int32Value width) { Table table = new Table(); TableProperties tblProp = new TableProperties( new TableBorders( new TopBorder() { Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 4 }, new BottomBorder() { Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 4 }, new LeftBorder() { Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 4 }, new RightBorder() { Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 4 }, new InsideHorizontalBorder() { Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 4 }, new InsideVerticalBorder() { Val = new EnumValue<BorderValues>(BorderValues.Single), Size = 4 } ) ); tblProp.TableWidth = new TableWidth() { Width = width.ToString(), Type = TableWidthUnitValues.Dxa }; table.Append(tblProp); int count = reportTable.Value.Count; int cols = reportTable.Column; int j = 0; foreach (List<string> strs in reportTable.Value) { TableRow row = new TableRow(); for (int i = 0; i < cols; i++) { TableCell cell = new TableCell(); TableCellProperties tableCellProperties = new TableCellProperties(); TableCellMargin margin = new TableCellMargin(); margin.LeftMargin = new LeftMargin() { Width = "100", Type = TableWidthUnitValues.Dxa }; margin.RightMargin = new RightMargin() { Width = "100", Type = TableWidthUnitValues.Dxa }; tableCellProperties.Append(margin); Paragraph par = new Paragraph(); Run run = new Run(); if (j == 0 && reportTable.IsHaveColumn) { RunProperties rPr = new RunProperties(); rPr.Append(new Bold()); run.Append(rPr); } if (strs.Count != cols && i >= strs.Count - 1) { HorizontalMerge verticalMerge = new HorizontalMerge(); if (i == strs.Count - 1) { RunProperties rPr = new RunProperties(); rPr.Append(new Bold()); run.Append(rPr); verticalMerge.Val = MergedCellValues.Restart; run.Append(CreateText(strs[i])); } else { verticalMerge.Val = MergedCellValues.Continue; } tableCellProperties.Append(verticalMerge); } else { run.Append(CreateText(strs[i])); } par.Append(run); cell.Append(tableCellProperties); cell.Append(par); row.Append(cell); } j++; table.Append(row); } return table; }
代碼可以簡單的多,只是因為有一些要求,比如你表每行是5個數據,但是有一行,數據只有四個,有二個表格合並了,這里會默認把最后二格合並,具體意思大家可以改改代碼看.
好吧,到這里,就差不多,元素添加完后,然后是把相關頁面大小的設置加到objBody,最后是添加頁眉.
public static void AddHeader(MainDocumentPart mainDocPart, ReportImage reportImge) { // Delete the existing header parts. mainDocPart.DeleteParts(mainDocPart.HeaderParts); // Create a new header part and get its relationship id. HeaderPart newHeaderPart = mainDocPart.AddNewPart<HeaderPart>(); string rId = mainDocPart.GetIdOfPart(newHeaderPart); ImagePart imagepart = newHeaderPart.AddImagePart(ImagePartType.Jpeg); FileInfo newImg = new FileInfo(reportImge.Value); using (FileStream stream = newImg.OpenRead()) { imagepart.FeedData(stream); } string imageRID = newHeaderPart.GetIdOfPart(imagepart); reportImge.RId = imageRID; Header header = GeneratePageHeaderPart(reportImge); header.Save(newHeaderPart); foreach (SectionProperties sectProperties in mainDocPart.Document.Descendants<SectionProperties>()) { // Delete any existing references to headers. foreach (HeaderReference headerReference in sectProperties.Descendants<HeaderReference>()) sectProperties.RemoveChild(headerReference); HeaderReference newHeaderReference = new HeaderReference() { Id = rId, Type = HeaderFooterValues.Default }; sectProperties.Append(newHeaderReference); } header.Save(); } // Creates an header instance and adds its children. private static Header GeneratePageHeaderPart(ReportImage reportImge) { var runs = GetRuns(reportImge); Paragraph paragraph = new Paragraph(); paragraph.Append(GetParagraphProperties(reportImge)); foreach (var run in runs) { paragraph.Append(run); } paragraph.Append(new Run(new Text() { Text = "" })); Header header = new Header(); header.Append(paragraph); return header; }
上面的代碼主要注意,Image所指的路徑存放的和在前面的Document里不一樣,這里存放在Header里,從Word文件解壓來說,二都是不同的XML文檔,你把圖片數據寫在Document里,得到的標識符在Header是空的.
好了.OpenXML的主要操作都在這了,但是大家如果想生成這種樣式,應該如何處理?
上面每一個元素如Payload Mass (LBS): 0.22046,就是前面的ReportValue,如何以這種對齊方式來插入了?引入一個新的結構,ReportValueList,主要就是ReportValue的鍵表信息.
public static List<Paragraph> GetParagraph(ReportValueList valueList, Int32Value width, int column = 2) { if (column < 1) column = 1; List<Paragraph> list = new List<Paragraph>(); int currentcolumn = 0; Paragraph currentParagraph = null; foreach (var reportvalue in valueList.Values) { reportvalue.Size = valueList.Size; if (currentcolumn == 0) { currentParagraph = new Paragraph(); ParagraphProperties pPr = new ParagraphProperties(); //添加標簽類 Tabs tabs = new Tabs(); Int32Value eachWidth = width / (new Int32Value(column)); for (int i = 1; i < column; i++) { TabStop stop = new TabStop(); stop.Val = new EnumValue<TabStopValues>(TabStopValues.Left); stop.Position = eachWidth * i; tabs.Append(stop); } pPr.Append(tabs); currentParagraph.Append(pPr); list.Add(currentParagraph); } List<Run> runs = GetRuns(reportvalue); foreach (var run in runs) { currentParagraph.Append(run); } currentcolumn++; if (currentcolumn < column) { Run run = new Run(); run.Append(new TabChar()); currentParagraph.Append(run); } if (currentcolumn >= column) { currentcolumn = 0; } } return list; }
主要是對元素TabStop的運用,仔細的大家可以去查查文檔
寫到這里,忘記寫要用到的命名空間.
using A = DocumentFormat.OpenXml.Drawing;
using PIC = DocumentFormat.OpenXml.Drawing.Pictures;
using DW = DocumentFormat.OpenXml.Drawing.Wordprocessing;
using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
這個就到這里了.下面如果有時間,我會講一下,如果對整張報表來進行模版化,意思是在word里的每一個表,一個圖片,一個文字,在軟件上用生成的結果來替換相應的元素.
原文地址:https://blog.csdn.net/u011394397/article/details/78142860