// Version 1.4.0

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;
	
	///遍历文档中所有[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, '<?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, 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;
private
	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;

