C# 处理Word自动生成报告
一、概述

Java 对Word文件的生成(基于Apache POI)

Apache POI 是一个开源的跨平台的对Microsoft
Office格式档案具有读和写功能工具。
在Github上有一个开源的Word模版引擎poi-tl
,这个模版引擎是基于Apache POI。主要是为了解决下面的问题:

  • java操作word使用apache poi的复杂性
  • 使用freemarker,转化为xml操作word的难度
  • 依赖服务器上安装软件openoffice来调用转化
  • 依赖windows的word lib库,不具有跨平台性

注意!
HSSF - 提供读写Microsoft Excel
XLS格式档案的功能。(*.doc),HWPFDocument类
XSSF - 提供读写Microsoft Excel OOXML
XLSX格式档案的功能。(*.docx),XWPFDocument类
因为这个模版引擎是只使用XWPFDocument类,所以只对*.docx文档生效。

C# 处理Word自动生成报告
二、数据源例子

poi-tl demo

C# 处理Word自动生成报告
三、设计模板

调用方法

/*
* datas 是你要渲染的数据
* datas 可以是JavaBean,也可以是Map<String, Object>
*/
XWPFTemplate template = XWPFTemplate.compile("~/file.docx").render(datas);

除了传入模版文件路径,还可以传入模版文件输入流

public static XWPFTemplate compile(InputStream inputStream) {
    .....
    }

C# 处理Word自动生成报告
四、程序处理

datas TO Map<String, Object>

看看数据类转Map的实现

private static Map<String, Object> convert2Map(Object dataSrouce) {
        Map<String, Object> ret = new HashMap<String, Object>();
        try {
            Class<?> clazz = dataSrouce.getClass();
            while (clazz != Object.class) {
                Field[] fields = clazz.getDeclaredFields();
                PropertyDescriptor pd = null;
                for (Field f : fields) {
                    pd = new PropertyDescriptor(f.getName(), dataSrouce.getClass());
                    Name annotation = f.getAnnotation(Name.class);
                    Object value = pd.getReadMethod().invoke(dataSrouce);
                    ret.put(null == annotation ? f.getName() : annotation.value(), value);
                }
                clazz = clazz.getSuperclass();
            }
        } catch (Exception e) {
            logger.error("Convert datasource failed.", e);
            throw new RenderException("Convert datasource failed.");
        }
        return ret;
    }

利用反射,把datas类和它的父类的字段属性转成Map<String,
Object>,Object除外。所以我们传的参数是JavaBean或Map<String,
Object>就可以了(传Map参数,会调用同名的的重载方法)。

 

语法

现在说一下程序处理部分,有点长

普通文本

本来是想做针对doc和docx的模板两个版本,
后来想到可以在生成的时候saveas里设置格式,

渲染数据为String或者TextRenderData

模版文件中使用:{{template}}

...
Map<String, Object> datas = new HashMap<String, Object>();
datas.put("template", "我是渲染的数据");
// 参数1:颜色 9d55b8;参数2:文本内容
datas.put("title", new TextRenderData("9d55b8", "Deeply in love with the things you love,\n just deepoove."));

...         
  • 文本中可用\n 来进行换行

所以此版只支持对docx的模板处理,

图片

想要doc的情况可以选择生成格式为doc的.

渲染数据为:PictureRenderData

模版文件中使用:{{@picture}}

/*
* 参数1:宽度;参数2:高度;参数3:图片路径
*/
datas.put("picture", new PictureRenderData(100, 120, "src/test/resources/logo.png"));

上代码:

表格

必发官网手机版 1必发官网手机版 2

渲染数据为:TableRenderData

模版文件中使用:{{#table}}

/**
     * @param headers 表格头
     * @param datas 表格数据
     * @param noDatadesc 没有数据显示的文案
     * @param width 宽度
     */
    public TableRenderData(List<RenderData> headers, List<Object> datas,
            String noDatadesc, int width) {
        this.headers = headers;
        this.datas = datas;
        this.noDatadesc = noDatadesc;
        this.width = width;
    }

// 有表格头 有数据
datas.put("table", new TableRenderData(new ArrayList<RenderData>() {
        {
          add(new TextRenderData("1E915D", "province"));
          add(new TextRenderData("1E915D", "city"));
        }
      }, new ArrayList<Object>() {
        {
          add("beijing;beijing");
          add("zhejiang;hangzhou");
        }
      }, "no datas", 0));
    }

如果没有数据,表格会显示“no datas”
更加详细的请参考:poi-tl处理Word表格(Table)的最佳实践

 public class WordHelper
    {
        private Word.Application wordApp = null;
        private Word.Document wordDoc = null;
        private DataSet dataSource = null;
        private object line = Word.WdUnits.wdLine;
        private string errorMsg = "";

        /// <summary>
        /// 根据模板文件,创建数据报告
        /// </summary>
        /// <param name="templateFile">模板文件名(含路径)</param>
        /// <param name="newFilePath">新文件路径)</param>
        /// <param name="dataSource">数据源,包含多个datatable</param>
        /// <param name="saveFormat">新文件格式:</param>
        public bool CreateReport(string templateFile, DataSet dataSource, out string errorMsg, string newFilePath, ref string newFileName, int saveFormat = 16)
        {
            this.dataSource = dataSource;
            errorMsg = this.errorMsg;
            bool rtn = OpenTemplate(templateFile)
                && SetContent(new WordElement(wordDoc.Range(), dataRow: dataSource.Tables[dataSource.Tables.Count - 1].Rows[0]))
                && UpdateTablesOfContents()
                && SaveFile(newFilePath, ref newFileName, saveFormat);

            CloseAndClear();
            return rtn;
        }
        private bool OpenTemplate(string templateFile)
        {
            if (!File.Exists(templateFile))
            {
                return false;
            }

            wordApp = new Word.ApplicationClass();
            wordApp.Visible = false;//使文档可见,调试用
            wordApp.DisplayAlerts = Word.WdAlertLevel.wdAlertsNone;
            object file = templateFile;
            wordDoc = wordApp.Documents.Open(ref file, ReadOnly: true);
            return true;
        }

        private bool SetContent(WordElement element)
        {
            string currBookMarkName = string.Empty;
            string startWith = "loop_" + (element.Level + 1).ToString() + "_";
            foreach (Word.Bookmark item in element.Range.Bookmarks)
            {
                currBookMarkName = item.Name;

                if (currBookMarkName.StartsWith(startWith) && (!currBookMarkName.Equals(element.ElementName)))
                {
                    SetLoop(new WordElement(item.Range, currBookMarkName, element.DataRow, element.GroupBy));
                }

            }

            SetLabel(element);

            SetTable(element);

            SetChart(element);

            return true;
        }
        private bool SetLoop(WordElement element)
        {
            DataRow[] dataRows = dataSource.Tables[element.TableIndex].Select(element.GroupByString);
            int count = dataRows.Count();
            element.Range.Select();

            //第0行作为模板  先从1开始  循环后处理0行;
            for (int i = 0; i < count; i++)
            {

                element.Range.Copy();  //模板loop复制
                wordApp.Selection.InsertParagraphAfter();//换行 不会清除选中的内容,TypeParagraph 等同于回车,若当前有选中内容会被清除. TypeParagraph 会跳到下一行,InsertParagraphAfter不会, 所以movedown一下.
                wordApp.Selection.MoveDown(ref line, Missing.Value, Missing.Value);
                wordApp.Selection.Paste(); //换行后粘贴复制内容
                int offset = wordApp.Selection.Range.End - element.Range.End; //计算偏移量

                //复制书签,书签名 = 模板书签名 + 复制次数
                foreach (Word.Bookmark subBook in element.Range.Bookmarks)
                {
                    if (subBook.Name.Equals(element.ElementName))
                    {
                        continue;
                    }

                    wordApp.Selection.Bookmarks.Add(subBook.Name + "_" + i.ToString(), wordDoc.Range(subBook.Start + offset, subBook.End + offset));
                }

                SetContent(new WordElement(wordDoc.Range(wordApp.Selection.Range.End - (element.Range.End - element.Range.Start), wordApp.Selection.Range.End), element.ElementName + "_" + i.ToString(), dataRows[i], element.GroupBy));
            }

            element.Range.Delete();

            return true;
        }
        private bool SetLabel(WordElement element)
        {
            if (element.Range.Bookmarks != null && element.Range.Bookmarks.Count > 0)
            {
                string startWith = "label_" + element.Level.ToString() + "_";
                string bookMarkName = string.Empty;
                foreach (Word.Bookmark item in element.Range.Bookmarks)
                {
                    bookMarkName = item.Name;

                    if (bookMarkName.StartsWith(startWith))
                    {
                        bookMarkName = WordElement.GetName(bookMarkName);

                        item.Range.Text = element.DataRow[bookMarkName].ToString();
                    }
                }
            }

            return true;
        }
        private bool SetTable(WordElement element)
        {
            if (element.Range.Tables != null && element.Range.Tables.Count > 0)
            {
                string startWith = "table_" + element.Level.ToString() + "_";
                foreach (Word.Table table in element.Range.Tables)
                {
                    if (!string.IsNullOrEmpty(table.Title) && table.Title.StartsWith(startWith))
                    {
                        WordElement tableElement = new WordElement(null, table.Title, element.DataRow);

                        TableConfig config = new TableConfig(table.Descr);

                        object dataRowTemplate = table.Rows[config.DataRow];
                        Word.Row SummaryRow = null;
                        DataRow SummaryDataRow = null;
                        DataTable dt = dataSource.Tables[tableElement.TableIndex];
                        DataRow[] dataRows = dataSource.Tables[tableElement.TableIndex].Select(tableElement.GroupByString); ;

                        if (config.SummaryRow > 0)
                        {
                            SummaryRow = table.Rows[config.SummaryRow];
                            SummaryDataRow = dt.Select(string.IsNullOrEmpty(tableElement.GroupByString) ? config.SummaryFilter : tableElement.GroupByString + " and  " + config.SummaryFilter).FirstOrDefault();
                        }

                        foreach (DataRow row in dataRows)
                        {
                            if (row == SummaryDataRow)
                            {
                                continue;
                            }

                            Word.Row newRow = table.Rows.Add(ref dataRowTemplate);
                            for (int j = 0; j < table.Columns.Count; j++)
                            {
                                newRow.Cells[j + 1].Range.Text = row[j].ToString(); ;
                            }

                        }

                        ((Word.Row)dataRowTemplate).Delete();

                        if (config.SummaryRow > 0 && SummaryDataRow != null)
                        {
                            for (int j = 0; j < SummaryRow.Cells.Count; j++)
                            {
                                string temp = SummaryRow.Cells[j + 1].Range.Text.Trim().Replace("\r\a", "");

                                if (!string.IsNullOrEmpty(temp) && temp.Length > 2 && dt.Columns.Contains(temp.Substring(1, temp.Length - 2)))
                                {
                                    SummaryRow.Cells[j + 1].Range.Text = SummaryDataRow[temp.Substring(1, temp.Length - 2)].ToString();
                                }
                            }
                        }

                        table.Title = tableElement.Name;
                    }


                }
            }

            return true;
        }
        private bool SetChart(WordElement element)
        {
            if (element.Range.InlineShapes != null && element.Range.InlineShapes.Count > 0)
            {
                List<Word.InlineShape> chartList = element.Range.InlineShapes.Cast<Word.InlineShape>().Where(m => m.Type == Word.WdInlineShapeType.wdInlineShapeChart).ToList();
                string startWith = "chart_" + element.Level.ToString() + "_";
                foreach (Word.InlineShape item in chartList)
                {
                    Word.Chart chart = item.Chart;
                    if (!string.IsNullOrEmpty(chart.ChartTitle.Text) && chart.ChartTitle.Text.StartsWith(startWith))
                    {
                        WordElement chartElement = new WordElement(null, chart.ChartTitle.Text, element.DataRow);

                        DataTable dataTable = dataSource.Tables[chartElement.TableIndex];
                        DataRow[] dataRows = dataTable.Select(chartElement.GroupByString);

                        int columnCount = dataTable.Columns.Count;
                        List<int> columns = new List<int>();

                        foreach (var dr in dataRows)
                        {
                            for (int i = chartElement.ColumnStart == -1 ? 0 : chartElement.ColumnStart - 1; i < (chartElement.ColumnEnd == -1 ? columnCount : chartElement.ColumnEnd); i++)
                            {
                                if (columns.Contains(i) || dr[i] == null || string.IsNullOrEmpty(dr[i].ToString()))
                                {

                                }
                                else
                                {
                                    columns.Add(i);
                                }
                            }
                        }
                        columns.Sort();
                        columnCount = columns.Count;
                        int rowsCount = dataRows.Length;

                        Word.ChartData chartData = chart.ChartData;

                        //chartData.Activate();
                        //此处有个比较疑惑的问题, 不执行此条,生成的报告中的图表无法再次右键编辑数据. 执行后可以, 但有两个问题就是第一会弹出Excel框, 处理完后会自动关闭. 第二部分chart的数据range设置总不对
                        //不知道是不是版本的问题, 谁解决了分享一下,谢谢

                        Excel.Workbook dataWorkbook = (Excel.Workbook)chartData.Workbook;
                        dataWorkbook.Application.Visible = false;

                        Excel.Worksheet dataSheet = (Excel.Worksheet)dataWorkbook.Worksheets[1];
                        //设定范围  
                        string a = (chartElement.ColumnNameForHead ? rowsCount + 1 : rowsCount) + "|" + columnCount;
                        Console.WriteLine(a);

                        Excel.Range tRange = dataSheet.Range["A1", dataSheet.Cells[(chartElement.ColumnNameForHead ? rowsCount + 1 : rowsCount), columnCount]];
                        Excel.ListObject tbl1 = dataSheet.ListObjects[1];
                        //dataSheet.ListObjects[1].Delete(); //想过重新删除再添加  这样 原有数据清掉了, 但觉得性能应该会有所下降
                        //Excel.ListObject tbl1 = dataSheet.ListObjects.AddEx();
                        tbl1.Resize(tRange);
                        for (int j = 0; j < rowsCount; j++)
                        {
                            DataRow row = dataRows[j];
                            for (int k = 0; k < columnCount; k++)
                            {
                                dataSheet.Cells[j + 2, k + 1].FormulaR1C1 = row[columns[k]];
                            }
                        }

                        if (chartElement.ColumnNameForHead)
                        {
                            for (int k = 0; k < columns.Count; k++)
                            {
                                dataSheet.Cells[1, k + 1].FormulaR1C1 = dataTable.Columns[columns[k]].ColumnName;
                            }
                        }
                        chart.ChartTitle.Text = chartElement.Name;
                        //dataSheet.Application.Quit();
                    }
                }
            }

            return true;
        }
        private bool UpdateTablesOfContents()
        {
            foreach (Word.TableOfContents item in wordDoc.TablesOfContents)
            {
                item.Update();
            }

            return true;
        }
        private bool SaveFile(string newFilePath, ref string newFileName, int saveFormat = 16)
        {
            if (string.IsNullOrEmpty(newFileName))
            {
                newFileName = DateTime.Now.ToString("yyyyMMddHHmmss");

                switch (saveFormat)
                {
                    case 0:// Word.WdSaveFormat.wdFormatDocument
                        newFileName += ".doc";
                        break;
                    case 16:// Word.WdSaveFormat.wdFormatDocumentDefault
                        newFileName += ".docx";
                        break;
                    case 17:// Word.WdSaveFormat.wdFormatPDF
                        newFileName += ".pdf";
                        break;
                    default:
                        break;
                }
            }

            object newfile = Path.Combine(newFilePath, newFileName);
            object wdSaveFormat = saveFormat;
            wordDoc.SaveAs(ref newfile, ref wdSaveFormat);
            return true;
        }

        private void CloseAndClear()
        {
            if (wordApp == null)
            {
                return;
            }
            wordDoc.Close(Word.WdSaveOptions.wdDoNotSaveChanges);
            wordApp.Quit(Word.WdSaveOptions.wdDoNotSaveChanges);
            System.Runtime.InteropServices.Marshal.ReleaseComObject(wordDoc);
            System.Runtime.InteropServices.Marshal.ReleaseComObject(wordApp);
            wordDoc = null;
            wordApp = null;
            GC.Collect();
            KillProcess("Excel","WINWORD");
        }

        private void KillProcess(params string[] processNames)
        {
            //Process myproc = new Process();
            //得到所有打开的进程  
            try
            {
                foreach (string name in processNames)
                {
                    foreach (Process thisproc in Process.GetProcessesByName(name))
                    {
                        if (!thisproc.CloseMainWindow())
                        {
                            if (thisproc != null)
                                thisproc.Kill();
                        }
                    }
                }
            }
            catch (Exception)
            {
                //throw Exc;
                // msg.Text+=  "杀死"  +  processName  +  "失败!";  
            }
        }
    }

    public class WordElement
    {
        public WordElement(Word.Range range, string elementName = "", DataRow dataRow = null, Dictionary<string, string> groupBy = null, int tableIndex = 0)
        {
            this.Range = range;
            this.ElementName = elementName;
            this.GroupBy = groupBy;
            this.DataRow = dataRow;
            if (string.IsNullOrEmpty(elementName))
            {
                this.Level = 0;
                this.TableIndex = tableIndex;
                this.Name = string.Empty;
                this.ColumnNameForHead = false;
            }
            else
            {
                string[] element = elementName.Split('_');
                this.Level = int.Parse(element[1]);
                this.ColumnNameForHead = false;
                this.ColumnStart = -1;
                this.ColumnEnd = -1;

                if (element[0].Equals("label"))
                {
                    this.Name = element[2];
                    this.TableIndex = 0;
                }
                else
                {
                    this.Name = element[4];
                    this.TableIndex = int.Parse(element[2]) - 1;

                    if (!string.IsNullOrEmpty(element[3]))
                    {
                        string[] filters = element[3].Split(new string[] { "XX" }, StringSplitOptions.RemoveEmptyEntries);
                        if (this.GroupBy == null)
                        {
                            this.GroupBy = new Dictionary<string, string>();
                        }
                        foreach (string item in filters)
                        {
                            if (!this.GroupBy.Keys.Contains(item))
                            {
                                this.GroupBy.Add(item, dataRow[item].ToString());
                            }

                        }
                    }

                    if (element[0].Equals("chart") && element.Count() > 5)
                    {
                        this.ColumnNameForHead = element[5].Equals("1");
                        this.ColumnStart = string.IsNullOrEmpty(element[6]) ? -1 : int.Parse(element[6]);
                        this.ColumnEnd = string.IsNullOrEmpty(element[7]) ? -1 : int.Parse(element[7]);
                    }
                }
            }
        }

        public Word.Range Range { get; set; }
        public int Level { get; set; }
        public int TableIndex { get; set; }
        public string ElementName { get; set; }

        public DataRow DataRow { get; set; }
        public Dictionary<string, string> GroupBy { get; set; }

        public string Name { get; set; }

        public bool ColumnNameForHead { get; set; }
        public int ColumnStart { get; set; }
        public int ColumnEnd { get; set; }

        public string GroupByString
        {
            get
            {
                if (GroupBy == null || GroupBy.Count == 0)
                {
                    return string.Empty;
                }

                string rtn = string.Empty;
                foreach (string key in this.GroupBy.Keys)
                {
                    rtn += "and " + key + " = '" + GroupBy[key] + "' ";
                }
                return rtn.Substring(3);
            }
        }

        public static string GetName(string elementName)
        {
            string[] element = elementName.Split('_');


            if (element[0].Equals("label"))
            {
                return element[2];
            }
            else
            {
                return element[4];
            }
        }
    }

    public class TableConfig
    {
        public TableConfig(string tableDescr = "")
        {
            this.DataRow = 2;
            this.SummaryRow = -1;

            if (!string.IsNullOrEmpty(tableDescr))
            {
                string[] element = tableDescr.Split(',');
                foreach (string item in element)
                {
                    if (!string.IsNullOrEmpty(item))
                    {
                        string[] configs = item.Split(':');
                        if (configs.Length == 2)
                        {
                            switch (configs[0].ToLower())
                            {
                                case "data":
                                case "d":
                                    this.DataRow = int.Parse(configs[1]);
                                    break;
                                case "summary":
                                case "s":
                                    this.SummaryRow = int.Parse(configs[1]);
                                    break;
                                case "summaryfilter":
                                case "sf":
                                    this.SummaryFilter = configs[1];
                                    break;
                                default:
                                    break;
                            }
                        }
                    }
                }
            }

        }
        public int DataRow { get; set; }
        public int SummaryRow { get; set; }
        public string SummaryFilter { get; set; }
    }

列表

View Code

渲染数据为:NumbericRenderData

模版文件中使用:*{{numbering}}\

    /**
     * @param numFmt 编号字符
     * @param fmtStyle 编号样式
     * @param numbers 列表内容
     */
    public NumbericRenderData(Pair<Enum, String> numFmt, Style fmtStyle, List<TextRenderData> numbers) {
        this.numFmt = numFmt;
        this.numbers = numbers;
        this.fmtStyle = fmtStyle;
    }

datas.put("unorderlist", new NumbericRenderData(new ArrayList<TextRenderData>(){{
                add(new TextRenderData("Deeply in love with the things you love, just deepoove."));
                add(new TextRenderData("Deeply in love with the things you love, just deepoove."));
                add(new TextRenderData("Deeply in love with the things you love, just deepoove."));
            }}));
datas.put("orderlist", new NumbericRenderData(NumbericRenderData.FMT_DECIMAL, new ArrayList<TextRenderData>(){{
                add(new TextRenderData("Deeply in love with the things you love, just deepoove."));
                add(new TextRenderData("Deeply in love with the things you love, just deepoove."));
                add(new TextRenderData("Deeply in love with the things you love, just deepoove."));
            }}));

NumbericRenderData类中有编号的符号常量

    /**
     * 1. 2. 3.
     */
    public static final Pair<Enum, String> FMT_DECIMAL = Pair.of(STNumberFormat.DECIMAL, "%1.");
    /**
     * 1) 2) 3)
     */
    public static final Pair<Enum, String> FMT_DECIMAL_PARENTHESES = Pair.of(STNumberFormat.DECIMAL,
            "%1)");
    /**
     * ● ● ●
     */
    public static final Pair<Enum, String> FMT_BULLET = Pair.of(STNumberFormat.BULLET, "●");
    /**
     * a. b. c.
     */
    public static final Pair<Enum, String> FMT_LOWER_LETTER = Pair.of(STNumberFormat.LOWER_LETTER,
            "%1.");
    /**
     * i ⅱ ⅲ
     */
    public static final Pair<Enum, String> FMT_LOWER_ROMAN = Pair.of(STNumberFormat.LOWER_ROMAN,
            "%1.");
    /**
     * A. B. C.
     */
    public static final Pair<Enum, String> FMT_UPPER_LETTER = Pair.of(STNumberFormat.UPPER_LETTER,
            "%1.");
    /**
     * Ⅰ Ⅱ Ⅲ
     */
    public static final Pair<Enum, String> FMT_UPPER_ROMAN = Pair.of(STNumberFormat.UPPER_ROMAN,
            "%1.");

 

样式

后续问题: 1.部署: 目前我的开发环境里装的是office2016 的office365版
引用的是  Microsoft Word 16.0 Object Library,
对应的Microsoft.Office.Interop.Word.dll版本是15.0…  

Style类

主要样式如下:

  • 颜色
  • 字体
  • 字号
  • 粗体
  • 斜体
  • 删除线

public class Style {
   //颜色
    private String color;
    //字体
    private String fontFamily;
    //字号
    private int fontSize;
    //粗体
    private Boolean isBold;
    //斜体
    private Boolean isItalic;
    //删除线
    private Boolean isStrike;

    public Style() {
    }

    public Style(String color) {
        this.color = color;
    }

    public Style(String fontFamily, int fontSize) {
        this.fontFamily = fontFamily;
        this.fontSize = fontSize;
    }

    ......
}

                  发现office2013版本也是15, 只是小版本不同, 没找到office
2016 和2013的 primary interop assembly,
莫非部署的环境里也要安装完整的office?

poi-tl 的 Change log

v1.2.0 2017-10-12

  1. 新增api:XWPFTemplate compile(InputStream inputStream)
  2. 不兼容升级:文本模板换行符由原先的\\n替换成更符合语言的\n

v1.1.0 2017-09-15

  1. 修复老版本office打开表格模板时出错
  2. 新增列表字符样式:设置编号颜色、字体、粗体、斜体等

v1.0.0

  1. 以插件的思想进行了重新设计
  2. 高度扩展性:语法即插件,像新增插件一样新增语法
  3. 新增工具类BytePictureUtils,便于操作图片的byte[]数据
  4. 新增Annotation @Name
  5. NiceXWPFDocument新增插入段落insertNewParagraph方法
  6. 新增代码生成工具类CodeGenUtils

V0.0.5

  1. bugfix: 解决0.0.4版本解析模板时CTSignedTwips类加载不到的问题
  2. new feature: 新增列表语法*,支持对有序列表和无序列表的插入

V0.0.4

  1. 增加新的api:XWPFTemplate.compile
  2. 渲染数据除了支持Map以外,还支持JavaBean渲染
  3. 升级poi组件至最新版本3.16

V0.0.3

  1. 新增表单语法#
  2. 支持表单插入
  3. 渲染器支持对table动态处理DynamicTableRenderPolicy
  4. 支持单元格的合并
  5. 丰富文本样式

                  为了避免文件占用问题,打开模板采用了只读方式打开wordDoc
= wordApp.Documents.Open(ref file, ReadOnly: true); 
在office2016(office365)版测试通过, 但专业版测试失败. 

Office Open XML — OOXML

为了让微软的office用其他软件打开不会出现错乱的问题,所以出现了docx格式的文档(xlsx,pptx也是)。微软的OOXML文档格式已被批准为全球行业标准。
如果要了解比apache poi 更低层的ooxml,可以访问office open
xml

在office open
xml网站里,你会知道文件是怎样实现的,颜色、字体、粗体等是怎么设置的。

docx 文档转成xml后,普通文本就是这个样子的:

<w:r>
    <w:rPr>
        <w:b/>
        <w:i/>
    </w:rPr>
    <w:t>我是文本</w:t>
</w:r>

设置文本为粗体:

<w:r>
    <w:rPr>
        <w:b w:val="true"/>
        <w:i/>
    </w:rPr>
    <w:t>我是文本</w:t>
</w:r>

基本所有的属性,在office open
xml都有详细的解析。

                  ReadOnly: false的情况下, office2016 
office2013均测试通过.

Word文档生成目录问题

我一直在找POI生成目录的方法,Jacob可以实现。如果你知道,麻烦告诉我!谢谢

                2. word中嵌入的Excel图表的问题,
虽然生成结果中的图表数据是正确的, 但无法右键再次编辑数据, 只可修改样式.

                3. 性能问题: 处理速度较慢.

希望有知道的看到给个回复.  打算有时间研究一下OpenXML,
希望能完美解决上面的问题.

相关文章