// Version 1.4.2 Type TSDocxFile = Class ///Version: V1.0 2022-09-20 ///适用于 Microsoft Word docx格式文件 ///纯TSL模块实现 ///Word(docx)文件读写接口 ///缺省构造函数 Function Create(); overload; Begin init(); End; ///构造函数,打开已经存在的docx文件 ///alias: string,文件目录别名 ///fname: string,文件名 Function Create(alias, fname); overload; Begin init(); OpenFile(alias, fname, nil); End; ///构造函数,打开已经存在的docx文件 ///alias: string,文件目录别名 ///fname: string,文件名 ///passwd: string,密码 Function Create(alias, fname, passwd); overload; Begin init(); OpenFile(alias, fname, passwd); End; Function Destory(); Begin End; Function init(); Begin DocPrId_ := -1; zipfile_ := new ZipFile(); End; ///打开docx文件 ///alias: string,文件目录别名 ///fname: string,文件名 ///[passwd]: string,密码 ///返回:[err, errmsg] Function OpenFile(alias, fname, passwd); Begin if not ifObj(zipfile_) then return array(-1, 'Create ZipFile object fail.'); if zipfile_.FilesCount() > 0 then zipfile_ := new ZipFile(); [err, errmsg] := zipfile_.Open(alias, fname, passwd); if err=0 then Begin document_ := new docxDocument(zipfile_); End; return array(err, errmsg); End; ///新建docx文件 ///返回:[err, info] Function NewFile(); Begin def := TOfficeTemplate('default.docx', true); [err, errmsg] := zipfile_.LoadFromMem(def); if err=0 then Begin document_ := new docxDocument(zipfile_); End; return array(err, errmsg); End; ///设置密码 Function SetPassword(passwd) Begin zipfile_.Password := passwd; End; ///保存文件 ///返回: [err, info] Function Save(); Begin return zipfile_.Save(); End; ///另存为 ///alias: string,文件目录别名 ///fname: string,文件名 ///返回: [err, info] Function SaveAs(alias, fname); Begin return zipfile_.Save(alias, fname); End; ///另存为二进制流数据 ///返回: [err, fileContent] fileContent 文件内容,为Binary数据类型 Function SaveToMem(); Begin return zipfile_.Save2Mem(); End; ///打开二进制内容 ///data: 二进制数据 ///返回: [err, errmsg] Function LoadFromMem(data); Begin [err, errmsg] := zipfile_.LoadFromMem(data); if err=0 then Begin document_ := new docxDocument(zipfile_); End; return array(err, errmsg); End; ///真实文件名 ///返回:string Function FileName(); Begin return zipfile_.FileName(); End; ///word文档所有段落 ///返回:TParagraph对象数组 Function Paragraphs(); Begin return document_.Body().Paragraphs(); End; ///添加新段落 ///paragraph: TParagraph对象 ///posOpt: 段落位置,0 在DOCX文件开头;-1 文件尾;N 在第N段之后;XmlNode节点对象或DocObject对象 在posOpt之后新添加段落 ///[styleId]: 样式ID(integer或string) ///返回TParagraph对象 Function AddParagraph(paragraph, posOpt, styleId); Begin return document_.Body().AddParagraph(paragraph, getPosNode(posOpt), styleId); End; ///添加标题 ///title: string ///posOpt: 段落位置,0 在DOCX文件开头;-1 文件尾;N 在第N段之后;XmlNode节点对象或DocObject对象 在posOpt之后新添加段落 ///level: int 标题级别(0-9) ///返回TParagraph对象 Function AddHeading(title, posOpt, level); Begin if ifstring(level) then return document_.Body().AddHeading(title, getPosNode(posOpt), level); styleName := level = 0 ? 'Title' : 'Heading ' $ level; style := StyleObject().GetStyle(styleName); if not ifObj(style) and ifInt(level) and level >= 0 and level <= 9 then style := StyleObject().AddDefaultStyle(styleName); return document_.Body().AddHeading(title, getPosNode(posOpt), ifObj(style) ? style.StyleId : nil); End; ///插入分页符 ///posOpt: 段落位置,0 在DOCX文件开头;-1 文件尾;N 在第N段之后;XmlNode节点对象或DocObject对象 在posOpt之后新添加段落 ///返回TParagraph对象 Function AddPageBreak(posOpt); Begin return document_.Body().AddBreak(getPosNode(posOpt), 'page'); End; ///插入换行符 ///posOpt: 段落位置,0 在DOCX文件开头;-1 文件尾;N 在第N段之后;XmlNode节点对象或DocObject对象 在posOpt之后新添加段落 ///返回TParagraph对象 Function AddLineBreak(posOpt); Begin return document_.Body().AddBreak(getPosNode(posOpt), ''); End; ///插入分栏符 ///posOpt: 段落位置,0 在DOCX文件开头;-1 文件尾;N 在第N段之后;XmlNode节点对象或DocObject对象 在posOpt之后新添加段落 ///返回TParagraph对象 Function AddColumnBreak(posOpt); Begin return document_.Body().AddBreak(getPosNode(posOpt), 'column'); End; ///删除指定段落(表格、图片、图表、文本框等) ///posOpt: 段落位置,0 DOCX文件开头;-1 文件尾;N 第N段;posOpt段落 ///返回:true Function DelParagraph(posOpt); Begin return document_.Body().DelParagraph(getPosNode(posOpt)); End; ///word文档所有内容的文本串 ///返回:string Function Text(); Begin return document_.Body().Text(); End; //word文档所有内容的文本串数组,包含段落信息 //返回:array(("pNode":nodeObj, "pIndex":p, "rNode":nodeObj, "rIndex":r)) Function TextArray(); Begin return document_.Body().TextArray(); End; Function Body(); Begin return document_.Body(); End; ///word文档所有表格个数 ///返回:int Function TablesCount(); Begin return document_.Body().TablesCount(); End; ///word文档指定表格 ///n: int 从0开始,第n个表格 ///返回:TTable对象 Function GetTable(n); Begin return document_.Body().GetTable(n); End; ///创建数据表 ///data: table,数据表 ///[IncludeHeader: bool] 是否包括表头,默认FALSE ///[IncludeIndex: bool] 是否自动添加索引号,默认FALSE ///返回: TTable对象 Function CreateTable(data, IncludeHeader, IncludeIndex); Begin tbl := TOfficeObj('TTable'); tbl.SetData(self, data, IncludeHeader, IncludeIndex); return tbl; End; ///插入数据表 ///tbl: TTable对象 ///posOpt: 段落位置,0 在DOCX文件开头;-1 文件尾;N 在第N段之后;XmlNode节点对象或DocObject对象 在posOpt之后新添加表格 ///返回: TTable对象 Function InsertTable(tbl, posOpt); Begin return document_.Body().InsertTable(tbl, getPosNode(posOpt)); End; ///返回CoreProperties对象 Function Properties(); Begin core := TOfficeObj('TCoreProperties'); core.node_ := zipfile_.Get('docProps/core.xml').FirstChildElement('cp:coreProperties'); return core; End; ///返回TDocSection集合 Function Sections();overload; Begin return document_.Body().Sections(); End; ///提供对节和页面设置设置的访问 ///还提供对页眉和页脚的访问 ///index:integer 章节索引 ///返回:TDocSection对象 Function Sections(index);overload; Begin return document_.Body().Sections(index); End; ///添加章节 ///session:TDocSection对象 ///posOpt: 位置,0 在DOCX文件开头;-1 文件尾;N 在第N段之后;XmlNode节点对象或DocObject对象 在posOpt之后新添加章节 ///返回:TDocSection对象 Function AddSection(session, posOpt);overload; Begin return document_.Body().AddSection(session, getPosNode(posOpt)); End; ///插入图片 ///picture: TPicture对象 ///posOpt: 段落位置,0 在DOCX文件开头;-1 文件尾;N 在第N段之后;XmlNode节点对象或DocObject对象 在posOpt之后新添加图片 ///返回:TPicture对象 Function AddPicture(picture, posOpt); Begin picture.Run.Drawing.WInline.ID := GetDocPrId(); return document_.Body().AddPicture(picture, getPosNode(posOpt)); End; ///插入图表 ///chart:TChart对象 ///posOpt: 段落位置,0 在DOCX文件开头;-1 文件尾;N 在第N段之后;XmlNode节点对象或DocObject对象 在posOpt之后新添加图表 ///返回: TChart对象 Function AddChart(chart, posOpt); Begin o := new TDocxChart(self, chart); p := TOfficeObj('TParagraph'); p.Format.rPr.Lang := 'zh-CN'; p := AddParagraph(p, getPosNode(posOpt), nil); chart.pNode := p.node_; p.Node().InsertEndChild(o.GetInnerXml()); TOfficeApi().Set('CurrentShape', p.node_); chart.chartFileName := 'word/charts/chart' $ o.ChartId_ $ '.xml'; return chart; End; ///文档中全部的Chart图 ///返回:TChart对象数组 Function GetCharts();overload; Begin r := array(); uri := 'w:r/w:drawing/wp:inline/a:graphic/a:graphicData/c:chart'; ps := Paragraphs(); for i:=0 to length(ps)-1 do Begin node := class(TSXml).GetNode(ps[i].node_, uri); if ifObj(node) then Begin chart := TOfficeObj('TChart'); chart.Init(self, ps[i].node_, node); r[length(r)] := chart; End; End; return r; End; ///从Excel中Copy指定的chart图到文档中指定位置 ///excelFileName:string xlsx文件名 ///excelSheetName:string sheetname ///chartName:string or integer,chart图名称或当前sheet中chart图索引号 ///Width:chart图宽度,单位cm ///Height:chart图高度,单位cm ///posOpt: 段落位置,0 在DOCX文件开头;-1 文件尾;N 在第N段之后;XmlNode节点对象或DocObject对象 在posOpt之后新添加图片 ///返回TChart对象 Function CopyExcelChart(excelFileName, excelSheetName, chartName, Width, Height, posOpt); Begin excel := new TSExcelFile(); [err, msg] := excel.OpenFile('', excelFileName); if err then return nil; [err, charts] := excel.GetCharts(excelSheetName); if err or length(charts)=0 then return nil; drawingObj := excel.WorkBook().GetXmlFileObj(charts[0].drawingFileName); if not ifObj(drawingObj) then return nil; node := drawingObj.FirstChildElement('xdr:wsDr').FirstChildElement('xdr:twoCellAnchor'); ind := 0; chartRid := ''; while ifObj(node) do Begin findChart := false; cNvPr := class(TSXml).GetNode(node, 'xdr:GraphicFrame/xdr:nvGraphicFramePr/xdr:cNvPr'); name := ifObj(cNvPr) ? cNvPr.GetAttribute('name') : ''; if ifstring(chartName) then Begin if name = chartName then find := true; End else if ifInt(chartName) and ind = chartName then find := true; if find then Begin chartNode := class(TSXml).GetNode(node, 'xdr:GraphicFrame/a:graphic/a:graphicData/c:chart'); if not ifObj(chartNode) then return nil; chartRid := chartNode.GetAttribute('r:id'); break; End; ind ++; node := node.NextElement(); End; for i:=0 to length(charts)-1 do Begin if charts[i].Rid = chartRid then Begin chart := TOfficeObj('TChart'); chart.Width := Width; chart.Height := Height; chart.Name := name; chart.Type := 'line'; chart.ShowBubbleSize := false; chart.ShowPercent := false; chart.DataTable := false; chart.AddSeries('test', array('line1'), array(1,2)); chart := AddChart(chart, getPosNode(posOpt)); xmlObj := Zip().Get(chart.chartFileName); xmlObj.Data := charts[i].xmlObj.Data; return chart; End; End; return nil; End; ///复制Word内容 ///docxObj: TSDocxFile对象 ///posOpt: 段落位置,0 在DOCX文件开头;-1 文件尾;N 在第N段之后;XmlNode节点对象或DocObject对象 在posOpt之后新添加图片 ///返回: [err, TDocxCopy对象] ///TDocxCopy.GetCopiedTable() 返回复制的对象表格数组 ///TDocxCopy.GetCopiedParagraph() 返回复制对象的段落数组 ///TDocxCopy.GetCopiedDrawing() 返回复制对象的图表数组 Function InsertFile(alias, fileName, posOpt); Begin docxObj := new TSDocxFile(); [err, msg] := docxObj.OpenFile(alias, fileName); if err then return array(err, msg); copy_obj := new TDocxCopy(self, docxObj); copy_obj.Init(); copy_obj.Copy(posOpt); return array(0, copy_obj); End; ///遍历文档中所有[TSTAG][/TSTAG]标签,针对每一个TAG执行tagObj.Apply() ///tagName:string 标签名称 ///tagObj:TAG对象方法 ///返回:[err,tslTagCount,errArr]: err 执行错误TAG次数,tslTagCount TAG总数,errArr 错误信息(array(('code':'代码', 'err':'错误信息'))) Function ExecTsTag(tagName, tagObj); Begin return Body().ExecTsTag(self, tagName, tagObj); End; ///文档中全部的批注信息 ///返回:DocComments对象 Function Comments(); Begin return document_.Body().Comments(); End; ///创建新的批注对象 ///返回:TDocComment对象 Function NewComment(author, txt); Begin file := 'word/comments.xml'; files := zipfile_.Files(); isexist := vselect thisrowindex from files where ['FileName']=file end; if ifnil(isexist) then Begin zipfile_.Add(file, ' '); rels := 'word/_rels/document.xml.rels'; xmlfile := zipfile_.Get(rels); [rId, target] := class(TSXml).FindRelationshipRid(xmlfile, ''); rId ++; class(TSXml).AddRelationshipRid(xmlfile, 'comments.xml', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments', 'rId' $ rId); contentType := zipfile_.Get('[Content_Types].xml'); class(TSXml).AddOverrideContentType(contentType, '/word/comments.xml', 'application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml'); End xmlfile := zipfile_.Get(file); id := 0; node := xmlfile.FirstChildElement('w:comments').FirstChildElement('w:comment'); while ifObj(node) do Begin wId := node.GetAttribute('w:id'); if strtoint(wId) >= id then id := strtoint(wId) + 1; node := node.NextElement('w:comment'); End; c := TOfficeObj('TDocComment'); c.p.Run.SetText( txt ); c.Author := author; c.ID := id; xmlfile.FirstChildElement('w:comments').InsertEndChild(c.Marshal()); return c; End; ///格式刷:段落格式 + 字体格式 ///fromParagraph:源段落 ///toParagraph:目标段落 Function CopyFormat(fromParagraph, toParagraph); Begin toParagraph.ClearFormat();//清除目标段落格式、字体格式 CopyParagraphFormat(fromParagraph, toParagraph);//段落格式 //字体格式 if not ifObj(fromParagraph) then return; fromRun := fromParagraph.GetRun(0); if ifObj(fromRun) then Begin runs := toParagraph.GetRuns(); for i:=0 to length(runs)-1 do Begin runs[i].CopyFontFormat(fromRun); End; End; End; ///格式刷:仅段落格式 ///fromParagraph:源段落 ///toParagraph:目标段落 Function CopyParagraphFormat(fromParagraph, toParagraph); Begin pPr := toParagraph.node_.FirstChildElement('w:pPr'); //清除段落格式 if ifObj(pPr) then toParagraph.node_.DeleteChild(pPr); pPr := ifObj(fromParagraph) ? fromParagraph.node_.FirstChildElement('w:pPr') : nil; if ifObj(pPr) then Begin //复制段落格式 arr := pPr.Marshal(); toParagraph.node_.InsertFirstChild(arr[0]); End; End; ///格式刷:仅字体格式 ///fromRun:源段落 ///toRun:目标段落 Function CopyFontFormat(fromRun, toRun); Begin toRun.CopyFontFormat(fromRun); End; ///添加目录 ///[posOpt: 段落位置],在posOpt之后新添加目录(否则在首页添加) ///UpperHeadingLevel:标题最高级别 ///LowerHeadingLevel:标题最低级别 /// 使用 UpperHeadingLevel 属性可设置起始标题级别(最高)。例如,若要设置 TOC 域语法 {TOC \o "1-3"},可将 LowerHeadingLevel 属性设为 3,并将 UpperHeadingLevel 属性设为 1。 ///因为word、wps、openoffice等软件对于页码的计算各不相同,本功能不直接设置页码,需要用相关客户端软件打开文件后,更新目录域。 Function AddTableContent(posOpt, UpperHeadingLevel, LowerHeadingLevel); Begin content := new TTableContent(self); content.SetDefaultFormat(); //缺省目录格式 node := getPosNode(posOpt); content.Add(node, UpperHeadingLevel, LowerHeadingLevel); //标题级别 if ifObj(node) then content.node_ := document_.Body().node_.InsertAfterChild(node, content.Marshal()); else content.node_ := document_.Body().node_.InsertFirstChild(content.Marshal()); //AddPageBreak(content.node_); return content; End; ///获取全部标题列表 ///UpperHeadingLevel:标题最高级别 ///LowerHeadingLevel:标题最低级别 ///返回:array((("Level":level,"Paragraph":"object","Text":title)); Function GetHeadingList(UpperHeadingLevel, LowerHeadingLevel); Begin return document_.Body().GetHeadingListImpl(self, nil, UpperHeadingLevel, LowerHeadingLevel, nil, true); End; //返回Document对象 Function Document(); Begin return document_; End; ///返回:TDocxStyles对象 Function StyleObject(); Begin if not ifObj(styleObj_) then styleObj_ := new TDocxStyles(self); return styleObj_; End; ///返回:TNumbering对象 Function NumberingObject(); Begin if not ifObj(numberingObj_) then numberingObj_ := new TNumbering(self); return numberingObj_; End; ///执行word文档内嵌tsl代码(执行内嵌脚本的环境字符集为GBK) ///返回:[err,tslFuncCount,errArr]: err 执行错误TSL代码段次数,tslFuncCount TSL代码段总数,errArr 执行TSL错误信息(array(('code':'代码', 'err':'错误信息'))) Function ExecInnerTSL(); Begin return Body().ExecInnerTSL(self); End; Function Zip();//兼容excel Begin return zipfile_; End; Function IsWord(); Begin return true; End; Function GetPath(); Begin return ExtractFileDir(ExtractFileDir(PluginPath())); End; Function GetDocPrId(); Begin if DocPrId_ < 0 then Begin DocPrId_ := 0; ps := Paragraphs(); for i:=0 to length(ps)-1 do Begin node := class(TSXml).GetNode(ps[i].node_, 'w:r/w:drawing/wp:inline/wp:docPr'); if not ifObj(node) then node := class(TSXml).GetNode(ps[i].node_, 'w:r/w:pict/v:shape/v:textbox/w:txbxContent/wp:docPr'); if not ifObj(node) then node := class(TSXml).GetNode(ps[i].node_, 'w:r/mc:AlternateContent/mc:Choice/w:drawing/wp:anchor/wp:docPr'); if ifObj(node) then Begin id := class(TSXml).SafeStrToIntDef(node.GetAttribute('id'), 0); if id > DocPrId_ then DocPrId_ := id; break; End; End; End; DocPrId_ ++; return DocPrId_; End; Function GetPosNode(posOpt); Begin node := posOpt; if ifObj(node) and not (node is Class(XmlNode)) then node := node.Node(); return node; End; private zipfile_; //压缩文件对象 document_; //Document对象 styleObj_; numberingObj_; DocPrId_; End;