TSOffice/funcext/TSOffice/TSDocxFile.tsf

634 lines
21 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Version 1.6.3
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, TOfficeApi().CurCodePageToGBK(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, TOfficeApi().CurCodePageToGBK(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;
///word文档最后一个段落
///返回TParagraph对象
Function LastParagraph();
Begin
return document_.Body().LastParagraph();
End;
///添加新段落
///paragraph: TParagraph对象
///posOpt: 段落位置0 在DOCX文件开头-1 文件尾N 在第N段之后XmlNode节点对象或DocObject对象 在posOpt之后新添加段落
///[styleId]: 样式IDinteger或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之后新添加表格
///customCell: 二维数组,自定义指定单元格的样式
/// 如一行一列arr[0][0] := array(twtcPr, twpPr, twrPr);其中twtcPr是twtcPr对象twpPr是twpPr对象twrPr是twrPr对象
///返回: TTable对象
Function InsertTable(tbl, posOpt, customCell);
Begin
return document_.Body().InsertTable(tbl, getPosNode(posOpt), customCell);
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;
///提供对节和页面设置设置的访问
///还提供对页眉和页脚的访问
///indexinteger 章节索引
///返回TDocSection对象
Function Sections(index);overload;
Begin
return document_.Body().Sections(index);
End;
///添加章节
///sessionTDocSection对象
///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图到文档中指定位置
///excelFileNamestring xlsx文件名
///excelSheetNamestring sheetname
///chartNamestring or integerchart图名称或当前sheet中chart图索引号
///Widthchart图宽度单位cm
///Heightchart图高度单位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内容
///alias: string文件目录别名
///fileName: string文件名
///posOpt: 段落位置0 在DOCX文件开头-1 文件尾N 在第N段之后XmlNode节点对象或DocObject对象 在posOpt之后新添加图片
///返回: [err, TDocxCopy对象]
///TDocxCopy.GetCopiedTable() 返回复制的对象表格数组
///TDocxCopy.GetCopiedParagraph() 返回复制对象的段落数组
///TDocxCopy.GetCopiedDrawing() 返回复制对象的图表数组
Function InsertFile(alias, fileName, posOpt);overload;
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;
///复制Word内容
///docxObj: TSDocxFile对象
///posOpt: 段落位置0 在DOCX文件开头-1 文件尾N 在第N段之后XmlNode节点对象或DocObject对象 在posOpt之后新添加图片
///返回: [err, TDocxCopy对象]
///TDocxCopy.GetCopiedTable() 返回复制的对象表格数组
///TDocxCopy.GetCopiedParagraph() 返回复制对象的段落数组
///TDocxCopy.GetCopiedDrawing() 返回复制对象的图表数组
Function InsertFile(docxObj, posOpt);overload;
Begin
copy_obj := new TDocxCopy(self, docxObj);
copy_obj.Init();
copy_obj.Copy(posOpt);
return array(0, copy_obj);
End;
///遍历文档中所有[TSTAG][/TSTAG]标签针对每一个TAG执行tagObj.Apply()
///tagNamestring 标签名称
///tagObjTAG对象方法
///返回:[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, '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<w:comments xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:wpsCustomData="http://www.wps.cn/officeDocument/2013/wpsCustomData" mc:Ignorable="w14 w15 wp14"></w:comments>');
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);
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;
///返回TFootnotes对象
Function FootNotesObject();
Begin
if not ifObj(footnotesObj_) then
begin
footnotesObj_ := new TOfficeObj('TFootnoteBody');
footnotesObj_.InitFootnotes(self.Zip());
end
return footnotesObj_;
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_;
footnotesObj_;
numberingObj_;
DocPrId_;
End;