Type xlsxWorkBook = Class
	///WorkBook文件操作接口
	///规范1：类首字母小写，表示该类只在模块内部使用，不作为接口提供；
	///规范2：成员变量首字母大写：成员可以被访问；
	///规范3：成员变量首字母小写：不建议外部访问；
	///规范4：成员函数首字母大写：成员函数可以被调用；
	///规范5：成员函数首字母小写：不建议外部调用；
	///规范6：成员变量以_结尾；
	///规范7：字符串用单引号；
	///规范8：模块tsf文件编码格式为UTF8

	///缺省构造函数
	Function Create(z,xml); overload;
	Begin
		rId_ := -1;
		zipfile_ := z;
		xml_ := xml;
		xmlFileObjMap_ := array();
		sheetObjMap_ := array();
		sheetPrefix_ := 'xl/worksheets/sheet';
	End;

	Function Load();
	Begin
		sheetsCount_ := 0;
		sheetNames_ := array();
		sheetIndexMap_ := array();

		//workbook.xml.rels中加载（rid -> sheet-filename）
		rids := array();
		workbook_rels := GetXmlFileObj(xml_.GetFileName('workbook_rels'));
		rels := workbook_rels.FirstChildElement('Relationships').FirstChildElement('Relationship');
		while ifObj(rels) do Begin
			id := rels.GetAttribute('Id');
			target := rels.GetAttribute('Target');
			rids[ id ] := target;
			rels := rels.NextElement();
		End;
		//workbook_rels.Print();

		workbook := GetXmlFileObj('xl/workbook.xml');
		//workbook.Print();
		//workbook.Dump();
		//echo tostn(workbook.dom());
		node := workbook.FirstChildElement('workbook').FirstChildElement('sheets').FirstChildElement();
		while ifObj(node) do Begin
			name := node.GetAttribute("name");
			sheetNames_[sheetsCount_]['name'] := name;
			sheetNames_[sheetsCount_]['sheetId'] := strtoint(node.GetAttribute("sheetId"));
			rid := node.GetAttribute("r:id");
			sheetNames_[sheetsCount_]['rid'] := rid;
			sheetNames_[sheetsCount_]['file'] := 'xl/' + rids[ rid ];
			sheetIndexMap_[ LowerCase(name) ] := sheetsCount_;
			sheetsCount_ ++;
			node := node.NextElement();
		End;

		//echo tostn(sheetNames_);
	End;

	///获取工作表列表
	///返回: arry()
	Function GetSheets();
	Begin
		return sselect ['name'] from sheetNames_ end;
	End;

	///创建新sheet
	///sheet: string，工作表名称
	///返回: [err, info]
	Function NewSheet(sheet);
	Begin
		lname := LowerCase(sheet);
		if ifint(sheetIndexMap_[ lname ]) then return array(1, 'Sheet is already exist.');
		rid := getWorkbookRelsRid();

		//添加文件xl/worksheets/sheetN.xml
		sheetId := vselect maxof(['sheetId']) from sheetNames_ end;
		sheetId := integer(sheetId) + 1;
		fname := sheetPrefix_ $ inttostr(sheetsCount_ + 1) $ '.xml';
		zipfile_.Add(fname, xml_.XmlHeader() + xml_.GetTemplate('sheet1'));

		//设置 workbook.xml.rels
		workbook_rels := GetXmlFileObj(xml_.GetFileName('workbook_rels'));
		rels := workbook_rels.FirstChildElement('Relationships').InsertEndChild('element', 'Relationship');
		rels.SetAttribute('Target', getTarget( sheetsCount_ + 1));
		rels.SetAttribute('Type', xml_.GetTemplate('RelationshipWorkSheet'));
		rels.SetAttribute('Id', rid);
		//workbook_rels.Print;

		//设置 xl/workbook.xml
		workbook := GetXmlFileObj('xl/workbook.xml');
		node := workbook.FirstChildElement('workbook').FirstChildElement('sheets').InsertEndChild('element','sheet');
		node.SetAttribute('r:id', rid);
		node.SetAttribute('sheetId', sheetId);
		node.SetAttribute('name', sheet);
		//workbook.Print();

		//设置docProps/app.xml
		app := GetXmlFileObj(xml_.GetFileName('docProps_app'));
		//app.Print();
		node := app.FirstChildElement('Properties').FirstChildElement('TitlesOfParts');
		if not ifObj(node) then Begin
			node := app.FirstChildElement('Properties').InsertEndChild('element','TitlesOfParts');
		End;
		vector := node.FirstChildElement('vt:vector');
		if not ifObj(vector) then Begin
			vector := node.InsertEndChild('element', 'vt:vector');
			vector.SetAttribute('baseType', 'lpstr');
			vector.SetAttribute('size', length(sheetNames_) + 1);
			for i:=0 to length(sheetNames_)-1 do Begin
				vector.InsertEndChild('element', 'vt:lpstr', sheetNames_[i]['name']);
			End;
		End
		else
			vector.SetAttribute('size', length(sheetNames_) + 1);
		vector.InsertEndChild('element', 'vt:lpstr', sheet);
		//app.Print();

		//设置[Content_Types].xml
		AddContentType(xml_.GetTemplate('SheetContentType'), '/' + fname);

		sheetNames_[sheetsCount_]['name'] := sheet;
		sheetNames_[sheetsCount_]['sheetId'] := sheetId;
		sheetNames_[sheetsCount_]['rid'] := rid;
		sheetNames_[sheetsCount_]['file'] := fname;
		sheetIndexMap_[ LowerCase(sheet) ] := sheetsCount_;
		sheetsCount_ ++;

		SetDefaultSheet(sheet);
		return array(0, '');
	End;

	///删除sheet
	///sheet: string，工作表名称
	///返回: [err, info]
	Function DeleteSheet(sheet);
	Begin
		if sheetsCount_ <= 1 then return array(-1, 'Can not delete the last sheet.');
		ind := sheetIndexMap_[ LowerCase(sheet) ];
		if not ifint(ind) then return array(-2, 'the sheet is not exist.');

		del := sheetNames_[ind];
		defaultSheet := GetDefaultSheet();
		sheetsCount_--;
		sheetNames_ := select * from sheetNames_ where thisrowindex <> ind end;

		//删除文件xl/worksheets/sheetN.xml
		zipfile_.Remove(del['file']);

		//设置 workbook.xml.rels
		files := zipfile_.Files();
		for i:=0 to length(sheetNames_)-1 do Begin
			sheetNames_[i]['rid'] := 'rId' + inttostr(i + 1); //重置rId
			oldname := sheetNames_[i]['file'];
			newname := sheetPrefix_ + inttostr(i + 1) + '.xml';
			sheetNames_[i]['file'] := newname;
			if oldname <> newname then Begin
				//echo 'old=',oldname,',new=',newname,'\n';
				n := vselect thisrowindex from files where ['FileName'] = oldname end;
				if ifint(n) then Begin
					files[n]['New-FileName'] := newname; //重置文件名
				End;
			End;
		End;
		for i:=0 to length(files)-1 do Begin
			if ifstring(files[i]['New-FileName']) then Begin
				f := zipfile_.Get(i);
				//echo '-------------------',f.FileName(),'\n';
				f.FileName := files[i]['New-FileName'];
			End;
		End;
		workbook_rels := GetXmlFileObj(xml_.GetFileName('workbook_rels'));
		parent := workbook_rels.FirstChildElement('Relationships');
		rels := parent.FirstChildElement('Relationship');
		while ifObj(rels) do Begin
			target := rels.GetAttribute('Target');
			if AnsiStartsText('worksheets/sheet', target) then Begin
				parent.DeleteChild(rels);
				break;
			End;
			rels := rels.NextElement();//删除节点
		End;
		//workbook_rels.Print();
		i := 0;
		maxRid := sheetsCount_ + 1;
		node := workbook_rels.FirstChildElement('Relationships').FirstChildElement('Relationship');
		while ifObj(node) do Begin
			target := node.GetAttribute('Target');
			if AnsiStartsText('worksheets/sheet', target) then Begin
				node.SetAttribute('Target', getTarget( i + 1));//重置文件名
				node.SetAttribute('Id', sheetNames_[i]['rid']);//重置rId
				i++;
			End
			Else Begin
				node.SetAttribute('Id', 'rId' $ maxRid);//重置rId
				maxRid ++;
			End;
			node := node.NextElement();
		End;
		//workbook_rels.Print();

		//设置 xl/workbook.xml
		workbook := GetXmlFileObj('xl/workbook.xml');
		workbook.FirstChildElement('workbook').FirstChildElement('sheets').DeleteChildren();
		for i:=0 to length(sheetNames_)-1 do Begin
			node := workbook.FirstChildElement('workbook').FirstChildElement('sheets').InsertEndChild('element','sheet');
			node.SetAttribute('name', sheetNames_[i]['name']);
			node.SetAttribute('sheetId', sheetNames_[i]['sheetId']);
			node.SetAttribute('r:id', sheetNames_[i]['rid']);
		End;
		//workbook.Print();

		//设置docProps/app.xml
		app := GetXmlFileObj(xml_.GetFileName('docProps_app'));
		node := app.FirstChildElement('Properties').FirstChildElement('TitlesOfParts');
		if ifObj(node) then Begin
			vector := node.FirstChildElement('vt:vector');
			if ifObj(vector) then Begin
				vector.SetAttribute('size', sheetsCount_);
				n := vector.FirstChildElement('vt:lpstr');
				while ifObj(n) do Begin
					if n.GetText() = del['name'] then Begin
						vector.DeleteChild(n);
						break;
					End;
					n := n.NextElement();
				End;
			End;
		End;
		//app.Print();

		//设置[Content_Types].xml
		del_partname := '/' + sheetPrefix_ + inttostr(sheetsCount_ + 1) + '.xml';
		DeleteContentType(del_partname);

		xmlFileObjMap_ := array();

		//设置默认工作表
		ind := sheetIndexMap_[sheet];
		for k, v in sheetIndexMap_ do
		Begin
			if v > ind then sheetIndexMap_[k] := v - 1;
		End
		reindex(sheetIndexMap_, array(sheet: nil));
		ind--;
		if ind < 0 then SetDefaultSheet(sheetNames_[0]['name']);
		else if ind = length(sheetIndexMap_) then SetDefaultSheet(sheetNames_[length(sheetIndexMap_)]['name']);
		else SetDefaultSheet(sheetNames_[ind]['name']);
		return array(0, '');
	End;

	Function SetSheetName(sourceName, destName);
	Begin
		ind := sheetIndexMap_[ LowerCase(destName) ];
		if ifint(ind) then return array(-3, sheet2 + ' is already exist.');

		ind := sheetIndexMap_[ LowerCase(sourceName) ];
		if not ifint(ind) then return array(-4, sheet1 + ' is not exist.');

		//设置 xl/workbook.xml
		workbook := GetXmlFileObj('xl/workbook.xml');
		node := workbook.FirstChildElement('workbook').FirstChildElement('sheets').FirstChildElement('Sheet');
		while ifObj(node) do
		Begin
			name := node.GetAttribute('name');
			if name = sourceName then
			Begin
				node.SetAttribute('name', destName);
				break;
			End
			node := node.NextElement();
		End

		//设置docProps/app.xml
		app := GetXmlFileObj(xml_.GetFileName('docProps_app'));
		node := app.FirstChildElement('Properties').FirstChildElement('TitlesOfParts');
		lpstr := node.FirstChildElement('vt:vector').FirstChildElement('vt:lpstr');
		while ifObj(lpstr) do
		Begin
			if lpstr.GetText() = sourceName then lpstr.SetValue(destName);
			lpstr := lpstr.NextElement();
		End

		sheetNames_[ind]['name'] := destName;
		reindex(sheetIndexMap_, array(LowerCase(sourceName) : LowerCase(destName)));
		return array(0, '');
	End;

	Function AddContentType(type, name);overload;
	Begin
		content := GetXmlFileObj(xml_.GetFileName('Content_Types'));
		element := content.FirstChildElement('Types').InsertEndChild('element', 'Override');
		element.SetAttribute('PartName', name);
		element.SetAttribute('ContentType', type);
		//content.Print();
	End;

	Function AddContentVml();overload;
	Begin
		content := GetXmlFileObj(xml_.GetFileName('Content_Types'));
		content := content.FirstChildElement('Types');
		element := content.FirstChildElement('Default');
		prev := element;
		while ifObj(element) do
		Begin
			attr := element.GetAttribute('Extension');
			if attr = 'vml' then return;
			prev := element;
			element := element.NextElement('Default');
		End
		ContentType := 'application/vnd.openxmlformats-officedocument.vmlDrawing';
		element := content.InsertAfterChild(prev, 'element', 'Default');
		element.SetAttribute('Extension', 'vml');
		element.SetAttribute('ContentType', contentType);
	End

	Function DeleteContentType(del_partname);
	Begin
		content := GetXmlFileObj(xml_.GetFileName('Content_Types'));
		types := content.FirstChildElement('Types');
		element := types.FirstChildElement('Override');
		while ifObj(element) do Begin
			partname := element.GetAttribute('PartName');
			if partname = del_partname then Begin
				types.DeleteChild(element);
				break;
			End;
			element := element.NextElement();
		End;
		//content.Print();
	End;

	Function GetRelationshipRid(sheet, prefix);
	Begin
		ind := sheetIndexMap_[ LowerCase(sheet) ];
		file := 'xl/worksheets/_rels/' + ExtractFileName(sheetNames_[ind]['file']) + '.rels';
		files := zipfile_.Files();
		isexist := vselect thisrowindex from files where ['FileName']=file end;
		if ifnil(isexist) then Begin
			zipfile_.Add(file, xml_.XmlHeader() + xml_.GetTemplate('sheet_rels'));
		end
		xmlfile := GetXmlFileObj(file);
		r := class(xlsxXml).FindRelationshipRid(xmlfile, prefix);
		r[2] := sheetNames_[ind]['file'];
		r[3] := file;
		return r;
	End;

	Function AddRelationshipRid(relsfile, target, type, rid);
	Begin
		xmlfile := GetXmlFileObj(relsfile);
		class(xlsxXml).AddRelationshipRid(xmlfile, target, type, rid);
	End;

	Function GetFilesCount(prefix);
	Begin
		files := zipfile_.Files();
		return vselect countof( ['FileName'] ) from files where AnsiStartsText(prefix, ['FileName']) end;
	End;

	Function FileIsExist(fname);
	Begin
		files := zipfile_.Files();
		return vselect countof( ['FileName'] ) from files where ['FileName']=fname end;
	End;

	Function GetSheetXmlfile(sheet);
	Begin
		ind := sheetIndexMap_[ LowerCase(sheet) ];
		if not ifint(ind) then return 0;
		return GetXmlFileObj(sheetNames_[ind]['file']);
	End;

	Function GetSheetObj(sheet);
	Begin
		o := sheetObjMap_[sheet];
		if not ifnil(o) then return o;

		ind := sheetIndexMap_[ LowerCase(sheet) ];
		if not ifint(ind) then return 0;
		f := GetXmlFileObj(sheetNames_[ind]['file']);
		if not ifObj(f) then return 0;
		o := f.Sheet();
		sheetObjMap_[ sheet ] := o;
		return o;
	End;

	///插入列，在指定列前插入空白列
	///sheet: string，工作表名称
	///col: string 列名，如: "D"
	///返回: [err, info]
	Function InsertCol(sheet, col);
	Begin
		sObj := GetSheetObj(sheet);
		if not ifObj(sObj) then return array(-1, 'Can not get sheet object');
		sObj.InsertCol(col);
		return array(0, 'ok');
	End;

	///插入行，在指定列前插入空白行
	///sheet: string，工作表名称
	///row: int
	///返回: [err, info]
	Function InsertRow(sheet, row);
	Begin
		sObj := GetSheetObj(sheet);
		if not ifObj(sObj) then return array(-1, 'Can not get sheet object');
		sObj.InsertRow(row);
		return array(0, 'ok');
	End;

	///删除列
	///sheet: string，工作表名称
	///col: string，如: "D"
	///返回: [err, info]
	Function RemoveCol(sheet, col);
	Begin
		sObj := GetSheetObj(sheet);
		if not ifObj(sObj) then return array(-1, 'Can not get sheet object');
		sObj.RemoveCol(col);
	End;

	///删除行
	///sheet: string，工作表名称
	///row: int
	///返回: [err, info]
	Function RemoveRow(sheet, row);
	Begin
		sObj := GetSheetObj(sheet);
		if not ifObj(sObj) then return array(-1, 'Can not get sheet object');
		sObj.RemoveRow(row);
		//t := sObj.Import(); echo tostn(t);
	End;

	///获取图表列表
	///sheet: string，工作表名称
	///返回: [err, ChartList]
	Function GetCharts(sheet);
	Begin
		ind := sheetIndexMap_[ LowerCase(sheet) ];
		if not ifint(ind) then return array(-1, 'The sheet is not exist.');
		//rid -> xl/worksheets/_rels/sheetN.xml.rels -> ="../drawings/drawingX.xml -> xl/drawings/_rels/drawingX.xml.rels -> charts
		rid := sheetNames_[ind]['rid'];
		id := rightstr(rid, length(rid)-3);
		rels := 'xl/worksheets/_rels/sheet' + id + '.xml.rels';
		if not FileIsExist(rels) then return array(0, array());
		xmlfile := GetXmlFileObj(rels);
		[rid2, target] := class(xlsxXml).FindRelationshipRid(xmlfile, '../drawings/drawing');
		if target = '' then return array(0, array());
		drawingFile := ReplaceStr(target, '..', 'xl');
		drawingRels := 'xl/drawings/_rels/' + ExtractFileName(target) + '.rels';
		if not FileIsExist(drawingRels) then return array(0, array());
		xml := GetXmlFileObj(drawingRels);
		charts := array();
		node := xml.FirstChildElement('Relationships').FirstChildElement('Relationship');

		while ifObj(node) do Begin
			target := node.GetAttribute('Target');
			if AnsiStartsText('../charts/chart', target) then Begin
				id := node.GetAttribute('Id');
				chartFile := ReplaceStr(target, '..', 'xl');
				chart := TOfficeObj('TChart');
				setChartInfo(chartFile, chart);
				charts[i] := new TChartOp(chart);
				charts[i].ChartFile := chartFile;
				charts[i].DrawingFile := drawingFile;
				charts[i].Rid := id;
				i++;
			End;
			node := node.NextElement();
		End;
		if length(charts) = 0 then return array(0, array());
		chartNameMap := array();
		xml := GetXmlFileObj(drawingFile);
		node := xml.FirstChildElement('xdr:wsDr').FirstChildElement('xdr:twoCellAnchor');
		while ifObj(node) do Begin
			name := getNodeValue(node, 'xdr:graphicFrame/xdr:nvGraphicFramePr/xdr:cNvPr', 'name');
			rid := getNodeValue(node, 'xdr:graphicFrame/a:graphic/a:graphicData/c:chart', 'r:id');
			if rid <> '' and name <> '' then
				chartNameMap[rid] := name;
			node := node.NextElement('xdr:twoCellAnchor');
		End;
		for i:=0 to length(charts)-1 do Begin
			charts[i].Chart.Name := chartNameMap[charts[i].Rid];
		End;
		return array(0, charts);
	End;

	Function GetSheetsCount();
	Begin
		return array(0, sheetsCount_);
	End;

	Function GetXmlFileObj(n);
	Begin
		o := xmlFileObjMap_[ n ];
		if not ifnil(o) then return o;
		o := zipfile_.Get(n);
		xmlFileObjMap_[ n ] := o;
		return o;
	End;

	Function SetSheetVisible(sheet, visible);
	Begin
		workbook := GetXmlFileObj('xl/workbook.xml');
		node := workbook.FirstChildElement('workbook').FirstChildElement('sheets').FirstChildElement('sheet');
		hidden := visible = 1 ? 0 : 1;
		while ifObj(node) do
		Begin
			name := node.GetAttribute('name');
			if name = sheet then
			Begin
				if hidden then node.SetAttribute('state', 'hidden');
				else node.DeleteAttribute('state');
				return array(0, '');
			End
			node := node.NextElement();
		End
		return array(1, 'Can\'t not find sheet ' $ sheet);
	End

	Function GetSheetVisible(sheet);
	Begin
		sheet_file := GetSheetXmlfile(sheet);
		if not ifObj(sheet_file) then return array(1, 'The Sheet is not exist!');
		workbook := GetXmlFileObj('xl/workbook.xml');
		node := workbook.FirstChildElement('workbook').FirstChildElement('sheets').FirstChildElement('sheet');
		while ifObj(node) do
		Begin
			name := node.GetAttribute('name');
			if name = sheet then
			Begin
				val := node.GetAttribute('state');
				if val = 'hidden' then return array(0, 0);
				else return array(0, 1);
			End
			node := node.NextElement();
		End
		return array(1, 'The Sheet is not exist!');
	End

	Function SetRowVisible(sheet, row, visible)
	Begin
		hidden := visible = 1 ? 0 : 1;
		sheet_xml_file := GetSheetXmlfile(sheet);
		if not ifObj(sheet_xml_file) then return array(1, 'The Sheet is not exist!');
		sheet_node := sheet_xml_file.FirstChildElement('worksheet').FirstChildElement('sheetData');
		node := sheet_node.FirstChildElement('row');
		flag := 1;
		prev_node := 0;
		while ifObj(node) do
		Begin
			r := strtoint(node.GetAttribute('r'));
			if r = row then
			Begin
				if hidden then node.SetAttribute('hidden', '1');
				else sheet_node.DeleteChild(node);
				return array(0, '');
			End
			if r > row and flag then
			Begin
				flag := 0;
				position_node := prev_node;
			End
			prev_node := node;
			node := node.NextElement();
		End
		if hidden then
		Begin
			arr := array('type': 'element', 'name': 'row', 'attributes': array('r': row, 'hidden': 1, 'x14ac:dyDescent': 0.25));
			if flag or position_node = 0 then sheet_node.InsertFirstChild(arr);
			else sheet_node.InsertAfterChild(position_node, arr);
		End
		return array(0, '');
	End

	Function GetRowVisble(sheet, row)
	Begin
		sheet_xml_file := GetSheetXmlfile(sheet);
		if not ifObj(sheet_xml_file) then return array(1, 'The Sheet is not exist!');
		row := inttostr(row);
		sheet_node := sheet_xml_file.FirstChildElement('worksheet').FirstChildElement('sheetData');
		node := sheet_node.FirstChildElement('row');
		while ifObj(node) do
		Begin
			r := node.GetAttribute('r');
			if r = row then
			Begin
				val := node.GetAttribute('hidden');
				if val = '1' then return array(0, 0);
				break;
			End
			node := node.NextElement();
		End
		return array(0, 1);
	End

	Function SetColVisible(sheet, col, visible)
	Begin
		hidden := visible = 1 ? 0 : 1;
		sheet_xml_file := GetSheetXmlfile(sheet);
		if not ifObj(sheet_xml_file) then return array(1, 'The Sheet is not exist!');
		work_node := sheet_xml_file.FirstChildElement('worksheet');
		col_node := work_node.FirstChildElement('cols');
		if not ifObj(col_node) and hidden then col_node := work_node.InsertAfterChild(work_node.FirstChildElement('sheetFormatPr'),'element', 'cols');
		node := col_node.FirstChildElement('col');
		while ifObj(node) do
		Begin
			min := strtoint(node.GetAttribute('min'));
			max := strtoint(node.GetAttribute('max'));
			val := trystrtoint(node.GetAttribute('hidden'), r) ? '' : r;
			if col >= min and col <= max then
			Begin
				if hidden = val then return array(0, '');
				else Begin
					if col = min and col = max then
						node.SetAttribute('hidden', hidden);
					else if col = min then node.SetAttribute('min', col + 1);
					else if col = max then node.SetAttribute('max', col - 1);
					else Begin
						node2 := col_node.InsertAfterChild(node, node.Marshal()[0]);
						node.SetAttribute('max', col - 1);
						node2.SetAttribute('min', col + 1);
					End
					return array(0, '');
				End
			End
			node := node.NextElement();
		End
		if hidden then
		Begin
			arr := array('type': 'element', 'name': 'col', 'attributes': ('min': col, 'max': col, 'width': 0, 'hidden': 1,
								 'customWidth': 1));
			col_node.InsertEndChild(arr);
		End
		return array(0, '');
	End

	Function GetColVisble(sheet, col);
	Begin
		sheet_xml_file := GetSheetXmlfile(sheet);
		if not ifObj(sheet_xml_file) then return array(1, 'The Sheet is not exist!');
		work_node := sheet_xml_file.FirstChildElement('worksheet');
		col_node := work_node.FirstChildElement('cols');
		if not ifObj(col_node) then return array(0, 1);
		node := col_node.FirstChildElement('col');
		while ifObj(node) do
		Begin
			min := strtoint(node.GetAttribute('min'));
			max := strtoint(node.GetAttribute('max'));
			val := node.GetAttribute('hidden');
			if col >= min and col <= max then
			Begin
				if val = '1' then return array(0, 0);
				break;
			End
			node := node.NextElement();
		End
		return array(0, 1);
	End

	Function SetRowHeight(sheet, row, height);
	Begin
		if not (height >= 0 and height < 409) then return array(1, "行高必须在0-409之间");
		sheet_xml_file := GetSheetXmlfile(sheet);
		if not ifObj(sheet_xml_file) then return array(1, 'The Sheet is not exist!');
		sheet_node := sheet_xml_file.FirstChildElement('worksheet').FirstChildElement('sheetData');
		node := sheet_node.FirstChildElement('row');
		flag := 1;
		prev_node := 0;
		while ifObj(node) do
		Begin
			r := strtofloat(node.GetAttribute('r'));
			if r = row then
			Begin
				node.SetAttribute('customHeight', 1);
				node.SetAttribute('ht', height);
				if height = 0 then node.SetAttribute('hidden', 1);
				else node.SetAttribute('hidden', 0);
				return array(0, '');
			End
			if r > row and flag then
			Begin
				flag := 0;
				position_node := prev_node;
			End
			prev_node := node;
			node := node.NextElement();
		End
		arr := array('r': row, 'customHeight': 1, 'ht': height, 'x14ac:dyDescent': 0.25);
		if height = 0 then arr union= array('hidden': 1);
		node_arr := array('type': 'element', 'name': 'row', 'attributes': arr);
		if flag or position_node = 0 then sheet_node.InsertFirstChild(node_arr);
		else sheet_node.InsertAfterChild(position_node, node_arr);
		return array(0, '');
	End

	Function GetRowHeight(sheet, row)
	Begin
		sheet_xml_file := GetSheetXmlfile(sheet);
		if not ifObj(sheet_xml_file) then return array(1, 'The Sheet is not exist!');
		work_node := sheet_xml_file.FirstChildElement('worksheet');
		sheet_node := work_node.FirstChildElement('sheetData');
		node := sheet_node.FirstChildElement('row');
		while ifObj(node) do
		Begin
			r := strtofloat(node.GetAttribute('r'));
			if r = row then
			Begin
				ht := strtofloat(node.GetAttribute('ht'));
				hidden := node.GetAttribute('hidden');
				if hidden = 0 then return array(0, 0);
				return array(0, ht);
			End
			node := node.NextElement();
		End
		default_ht := work_node.FirstChildElement('sheetFormatPr').GetAttribute('defaultRowHeight');
		return array(0, strtofloat(default_ht));
	End

	Function SetColWidth(sheet, startCol, endCol, width);
	Begin
		if not (width >= 0 and width < 255) then return array(-1, "Column width must be between 0 and 255.");
		startCol := xlsx_call('ColumnNameToNumber', startCol)[1];
		endCol := xlsx_call('ColumnNameToNumber', endCol)[1];
		if startCol > endCol then return array(-1, "First Colunm must be bigger than Last Column.");
		sheet_xml_file := GetSheetXmlfile(sheet);
		if not ifObj(sheet_xml_file) then return array(1, 'The Sheet is not exist!');
		work_node := sheet_xml_file.FirstChildElement('worksheet');
		col_node := work_node.FirstChildElement('cols');
		if not ifObj(col_node) then
			col_node := work_node.InsertAfterChild(work_node.FirstChildElement('sheetFormatPr'), 'element', 'cols');
		node := col_node.FirstChildElement('col');
		while ifObj(node) do
		Begin
			min := strtoint(node.GetAttribute('min'));
			max := strtoint(node.GetAttribute('max'));
			val := trystrtofloat(node.GetAttribute('width'), r) ? r : '';
			if startCol >= min and endCol <= max then
			Begin
				if width = val then return array(0, '');
				if min = max then
				Begin
					node.SetAttribute('width', width);
					node.SetAttribute('customWidth', 1);
					return array(0, '');
				End
				else Begin
					insert_flag := 1;
					node_new := col_node.InsertAfterChild(node, node.Marshal()[0]);
					node.SetAttribute('max', startCol - 1);
					node_new.SetAttribute('min', endCol + 1);
				End
				break;
			End
			node := node.NextElement();
		End
		arr := array('type': 'element', 'name': 'col', 'attributes': ('min': startCol, 'max': endCol, 'width': width,
							 'customWidth': 1));
		if insert_flag then col_node.InsertAfterChild(node, arr);
		else col_node.InsertEndChild(arr);
		return array(0, '');
	End;

	Function GetColWidth(sheet, col);
	Begin
		sheet_xml_file := GetSheetXmlfile(sheet);
		if not ifObj(sheet_xml_file) then return array(1, 'The Sheet is not exist!');
		work_node := sheet_xml_file.FirstChildElement('worksheet');
		col_node := work_node.FirstChildElement('cols');
		default_width := work_node.FirstChildElement('sheetFormatPr').GetAttribute('defaultColWidth');
		default_width := trystrtofloat(default_width, r) ? r: -1;
		if not ifObj(col_node) then return array(0, default_width);

		col := xlsx_call('ColumnNameToNumber', col)[1];
		node := col_node.FirstChildElement('col');
		while ifObj(node) do
		Begin
			min := strtoint(node.GetAttribute('min'));
			max := strtoint(node.GetAttribute('max'));
			if col >= min and col <= max then
			Begin
				width := trystrtofloat(node.GetAttribute('width'), r) ? r : -1;
				if width = -1 then break;
				return array(0, width);
			End
			node := node.NextElement();
		End
		return array(0, default_width);
	End;

	Function MergeCell(sheet, hcell, vcell);
	Begin
		sheet_xml_file := GetSheetXmlfile(sheet);
		if not ifObj(sheet_xml_file) then return array(1, 'The Sheet is not exist.');
		if hcell = vcell then return array(1, 'Duplicate cell.');
		work_node := sheet_xml_file.FirstChildElement('worksheet');
		merge_node := work_node.FirstChildElement('mergeCells');
		if not ifObj(merge_node) then
		Begin
			merge_node := work_node.InsertAfterChild(work_node.FirstChildElement('sheetData'), 'element', 'mergeCells');
			count := 0;
			merge_node.SetAttribute('count', count);
		End
		node := merge_node.FirstChildElement('mergeCell');
		count := count = 0 ? count : trystrtoint(merge_node.GetAttribute('count'), r) ? r : 0;
		hcell_ := xlsx_call('SplitCellName', hcell);
		vcell_ := xlsx_call('SplitCellName', vcell);
		hcell_int := xlsx_call('ColumnNameToNumber', hcell_[1])[1];
		vcell_int := xlsx_call('ColumnNameToNumber', vcell_[1])[1];
		while ifObj(node) do
		Begin
			ref := node.GetAttribute('ref');
			position := pos(':', ref);
			cell1 := xlsx_call('SplitCellName', ref[1:position-1]);
			cell2 := xlsx_call('SplitCellName', ref[position+1:]);
			cell1_int := xlsx_call('ColumnNameToNumber', cell1[1])[1];
			cell2_int := xlsx_call('ColumnNameToNumber', cell2[1])[1];
			if (hcell_[2] >= cell1[2] and hcell_[2] <= cell2[2] and hcell_int >= cell1_int and hcell_int <= cell2_int)
				or (vcell_[2] >= cell1[2] and vcell_[2] <= cell2[2] and vcell_int >= cell1_int and vcell_int <= cell2_int)
			then Begin
				merge_node.DeleteChild(node);
				break;
			End
			node := node.NextElement();
		End
		ref := hcell $  ":" $ vcell;
		node := merge_node.InsertEndChild('element', 'mergeCell');
		node.SetAttribute('ref', ref);
		merge_node.SetAttribute('count', count + 1);

		sheet_obj := GetSheetObj(sheet);
		r := sheet_obj.SetCellStyle(hcell, vcell, "0");
		return array(0, '');
	End

	Function MergeCell1(sheet, hcell, vcell);
	Begin
		sheet_xml_file := GetSheetXmlfile(sheet);
		if not ifObj(sheet_xml_file) then return array(1, 'The Sheet is not exist.');
		work_node := sheet_xml_file.FirstChildElement('worksheet');
		// mergeCells节点
		merge_node := work_node.FirstChildElement('mergeCells');
		// SheetData节点
		sheet_node := work_node.FirstChildElement('sheetData');
		row_node := sheet_node.FirstChildElement('row');
		if not ifObj(merge_node) then
		Begin
			merge_node := work_node.InsertAfterChild(sheet_node, 'element', 'mergeCells');
			count := 0;
			merge_node.SetAttribute('count', count);
		End
		node := merge_node.FirstChildElement('mergeCell');
		count := count = 0 ? count : trystrtoint(merge_node.GetAttribute('count'), r) ? r : 0;
		hcell_ := xlsx_call('SplitCellName', hcell);
		vcell_ := xlsx_call('SplitCellName', vcell);
		hcell_int := xlsx_call('ColumnNameToNumber', hcell_[1])[1];
		vcell_int := xlsx_call('ColumnNameToNumber', vcell_[1])[1];
		spans1 := hcell_int;
		spans2 := vcell_int;
		while ifObj(node) do
		Begin
			ref := node.GetAttribute('ref');
			position := pos(':', ref);
			cell1 := xlsx_call('SplitCellName', ref[1:position-1]);
			cell2 := xlsx_call('SplitCellName', ref[position+1:]);
			cell1_int := xlsx_call('ColumnNameToNumber', cell1[1])[1];
			cell2_int := xlsx_call('ColumnNameToNumber', cell2[1])[1];
			if (hcell_[2] >= cell1[2] and hcell_[2] <= cell2[2] and hcell_int >= cell1_int and hcell_int <= cell2_int)
				or (vcell_[2] >= cell1[2] and vcell_[2] <= cell2[2] and vcell_int >= cell1_int and vcell_int <= cell2_int)
			then Begin
				while ifObj(row_node) do
				Begin
					r := strtoint(row_node.GetAttribute('r'));
					if r >= cell1[2] and r <= cell2[2] then
					Begin
						prev_node := row_node;
						row_node := row_node.NextElement();
						sheet_node.DeleteChild(prev_node);
					End
				End
				spans1 := hcell_int >= cell1_int ? hcell_int : cell1_int;
				spans2 := vcell_int >= cell2_int ? vcell_int : cell2_int;
				merge_node.DeleteChild(node);
				break;
			End
			node := node.NextElement();
		End
		ref := hcell $  ":" $ vcell;
		node := merge_node.InsertEndChild('element', 'mergeCell');
		node.SetAttribute('ref', ref);
		merge_node.SetAttribute('count', count + 1);

		row_node := sheet_node.FirstChildElement('row');
		pr_node := work_node.FirstChildElement('sheetFormatPr');
		x14ac := pr_node.GetAttribute('a14ac:dyDescent') ? : '0.25';
		prev_node := 0;
		r2 := 0;
		while ifObj(row_node) do
		Begin
			r := strtoint(row_node.GetAttribute('r'));
			if r < hcell_[2] and prev_node = 0 then
				end_node := 0;  // InsertFirstChild
			else if hcell_[2] > r2 and hcell_[2] < r then
				end_node := 1;  // InsertAfterChild

			if ifObj(end_node) then break;
			r2 := r;
			prev_node := row_node;
			row_node := row_node.NextElement();
		End

		spans := spans1 $ ":" $ spans2;
		for i:=hcell_[2] to vcell_[2] do
		Begin
			arr := array('type': 'element', 'name': 'row', 'attributes': ('c': i, 'spans': spans, 'x14ac:dyDescent': x14ac));
			arr['children'] := generateRow(hcell_int, vcell_int, i);
			if ifObj(end_node) then end_node := sheet_node.InsertAfterChild(end_node, arr);
			else if end_node = 0 then end_node := sheet_node.InsertFirstChild(arr);
			else if end_node = 1 then end_node := sheet_node.InsertAfterChild(prev_node, arr);
			else sheet_node.InsertEndChild(arr);
		End

		r1 := xlsx_call('ColumnNumberToName', spans1)[1];
		r2 := xlsx_call('ColumnNumberToName', spans2)[1];
		dimension_node := work_node.FirstChildElement('dimension');
		dimension_node.SetAttribute('ref', r1 $ ":" $ r2);
		return array(0, '');
	End

	Function UnMergeCell(sheet, hcell, vcell);
	Begin
		sheet_xml_file := GetSheetXmlfile(sheet);
		if not ifObj(sheet_xml_file) then return array(1, 'The Sheet is not exist!');
		work_node := sheet_xml_file.FirstChildElement('worksheet');
		// mergeCells节点
		merge_node := work_node.FirstChildElement('mergeCells');
		// SheetData节点
		sheet_node := work_node.FirstChildElement('sheetData');
		row_node := sheet_node.FirstChildElement('row');
		if not ifObj(merge_node) then array(0, '');
		node := merge_node.FirstChildElement('mergeCell');
		if not ifObj(node) then return array(0, '');
		count := trystrtoint(merge_node.GetAttribute('count'), r) ? r : 0;
		hcell_ := xlsx_call('SplitCellName', hcell);
		vcell_ := xlsx_call('SplitCellName', vcell);
		while ifObj(node) do
		Begin
			ref := node.GetAttribute('ref');
			position := pos(':', ref);
			cell1 := xlsx_call('SplitCellName', ref[1:position-1]);
			cell2 := xlsx_call('SplitCellName', ref[position+1:]);
			if ref[1:position-1] = hcell and ref[position+1:] = vcell
			then Begin
				while ifObj(row_node) do
				Begin
					r := strtoint(row_node.GetAttribute('r'));
					if r >= cell1[2] and r <= cell2[2] then
					Begin
						prev_node := row_node;
						row_node := row_node.NextElement();
						sheet_node.DeleteChild(prev_node);
					End
				End
				merge_node.DeleteChild(node);
				flag := 1;
				break;
			End
			node := node.NextElement();
		End
		if not flag then return array(0, '');
		if count - 1 <> 0 then
			merge_node.SetAttribute('count', count - 1);
		else
			work_node.DeleteChild(merge_node);
		return array(0, '');
	End;

	Function SetSheetDefaultRowHeight(sheet, height);
	Begin
		sheet_xml_file := GetSheetXmlfile(sheet);
		if not ifObj(sheet_xml_file) then return array(1, 'The Sheet is not exist!');
		work_node := sheet_xml_file.FirstChildElement('worksheet');
		node := work_node.FirstChildElement('sheetFormatPr');
		node.SetAttribute('defaultRowHeight', height);
		return array(0, '');
	End

	Function GetSheetDefaultRowHeight(sheet);
	Begin
		sheet_xml_file := GetSheetXmlfile(sheet);
		if not ifObj(sheet_xml_file) then return array(1, 'The Sheet is not exist!');
		work_node := sheet_xml_file.FirstChildElement('worksheet');
		node := work_node.FirstChildElement('sheetFormatPr');
		flag := trystrtofloat(node.GetAttribute('defaultRowHeight'), height);
		if flag then return array(0, height);
		return array(1, "Can't find attribute : defaultRowHeight!");
	End

	Function SetSheetDefaultColWidth(sheet, width);
	Begin
		sheet_xml_file := GetSheetXmlfile(sheet);
		if not ifObj(sheet_xml_file) then return array(1, 'The Sheet is not exist!');
		work_node := sheet_xml_file.FirstChildElement('worksheet');
		node := work_node.FirstChildElement('sheetFormatPr');
		node.SetAttribute('defaultColWidth', width);
		return array(0, '');
	End

	Function GetSheetDefaultColWidth(sheet);
	Begin
		sheet_xml_file := GetSheetXmlfile(sheet);
		if not ifObj(sheet_xml_file) then return array(1, 'The Sheet is not exist!');
		work_node := sheet_xml_file.FirstChildElement('worksheet');
		node := work_node.FirstChildElement('sheetFormatPr');
		flag := trystrtofloat(node.GetAttribute('defaultColWidth'), width);
		if flag then return array(0, width);
		return array(1, "Can't find attribute : defaultColWidth!");
	End

	Function SetDefaultSheet(sheet);
	Begin
		ind := sheetIndexMap_[ LowerCase(sheet) ];
		if not ifint(ind) then return array('Can\'t not find sheet: ' $ sheet);
		for i:=0 to length(sheetnames_)-1 do
		begin
			name := sheetNames_[i]['name'];
			xml_file := GetXmlFileObj(sheetNames_[i]['file']);
			sheet_node := xml_file.FirstChildElement('worksheet').FirstChildElement('sheetViews').FirstChildElement('sheetView');
			if name = sheet then
			Begin
				sheet_node.SetAttribute('tabselected', 1);
				workbook := GetXmlFileObj('xl/workbook.xml');
				book_view_node := workbook.FirstChildElement('workbook').FirstChildElement('bookViews').FirstChildElement('workbookView');
				book_view_node.SetAttribute('activeTab', ind);
			End
			else sheet_node.SetAttribute('tabselected', 0);
		end
		return array(0, '');
	End

	Function GetDefaultSheet();
	Begin
		for i:=0 to length(sheetnames_)-1 do
		begin
			name := sheetnames_[i]['name'];
			xml_file := getxmlfileobj(sheetnames_[i]['file']);
			sheet_node := xml_file.FirstChildElement('worksheet').FirstChildElement('sheetviews').FirstChildElement('sheetview');
			if sheet_node.GetAttribute('tabSelected') = '1' then return array(0, name);
		end
		return array(1, 'Not found defaultSheet');
	End

private
	Function generateRow(c1, c2, r);
	Begin
		arr := array();
		sr := inttostr(r);
		for i:=c1 to c2 do
		Begin
			name := xlsx_call('ColumnNumberToName', i);
			if not name[0] then name := name[1];
			name += sr;
			arr union= array(('type': 'element', 'name': 'r', 'attributes': ('r': name, 's': '1')));
		End
		return arr;
	End

	Function getWorkbookRelsRid();
	Begin
		if rId_ >= 0 then Begin
			rId_ ++;
			return 'rId' $ rId_;
		End;
		workbook_rels := GetXmlFileObj(xml_.GetFileName('workbook_rels'));
		[rId_, find] := class(xlsxXml).FindRelationshipRid(workbook_rels, '');
		rId_ ++;
		return 'rId' $ rId_;
	End;

	Function getTarget(i);
	Begin
		return 'worksheets/sheet' + inttostr(i) + '.xml';
	End;

	Function getNodeValue(nd, uri, key);
	Begin
		node := nd;
		arr := str2array(uri, '/');
		for i:=0 to length(arr)-1 do Begin
			node := node.FirstChildElement(arr[i]);
			if not ifObj(node) then return '';
		End;
		if key = '' then
			return node.FirstChildElement().GetValue();
		return node.GetAttribute(key);
	End;

	Function getC(chart);
	Begin
		return chart.C ? 'c:' : '';
	End;

	Function setChartInfo(chartFile, chart);
	Begin
		xml := GetXmlFileObj(chartFile);
		chart.xmlObj := xml;
		c := xml.FirstChildElement('c:chartSpace');
		chart.C := ifObj(c) ? true : false;
		if not ifObj(xml) then return;
		chart.Title := getNodeValue(xml, chart.C ? 'c:chartSpace/c:chart/c:title/c:tx/c:rich/a:p/a:r/a:t' : 'chartSpace/chart/title/tx/rich/a:p/a:r/a:t', '');
		node := xml.FirstChildElement(getC(chart) + 'chartSpace').FirstChildElement(getC(chart) + 'chart').FirstChildElement(getC(chart) + 'plotArea');
		if ifObj(node) then Begin
			node := node.FirstChildElement();
			while ifObj(node) do Begin
				if ifObj(node.FirstChildElement(getC(chart) + 'ser')) then
					break;
				node := node.NextElement();
			End;
			if ifObj(node) then Begin
				chart.ChartNode := node;
				chart.Type := node.GetName();
				ser := node.FirstChildElement(getC(chart) + 'ser');
				Series := array();
				while ifObj(ser) do Begin
					name := getNodeValue(ser, chart.C ? 'c:tx/c:strRef/c:f' : 'tx/strRef/f', '');
					categories := getNodeValue(ser, chart.C ? 'c:cat/c:strRef/c:f' : 'cat/strRef/f', '');
					val := getNodeValue(ser, chart.C ? 'c:val/c:numRef/c:f' : 'val/numRef/f', '');
					Series union= array(("Name":name,"Categories":categories,"Values":val,"Node":ser));
					ser := ser.NextElement(getC(chart) + 'ser');
				End;
				chart.Series := Series;
			End;
		End;
		//println('title={},Series={},DefaultSer={}', chart.Title,chart.Series,DefaultSer);
	End;

	sheetsCount_:integer;
	rId_:integer;
	sheetNames_;
	sheetIndexMap_;
	sheetPrefix_:string;
	zipfile_;
	xml_;
	xmlFileObjMap_; //XmlFile对象存储
	sheetObjMap_;   //XmlSheet对象存储
End;

type TChartOp = Class
	Function Create(c);
	Begin
		Chart := c;
	End;

	Function RemoveSeries(i);
	Begin
		cnt := length(Chart.Series);
		if i < 0 or i >= cnt then return array(-1, 'Out of range.');
		println('ChartFile={}',ChartFile);
		r := Chart.Series;
		Chart.ChartNode.DeleteChild(r[i]['Node']);
		for k:=i+1 to length(r)-1 do Begin
			setVal(r[k]['Node'], Chart.C ? 'c:idx' : 'idx', -1);
			setVal(r[k]['Node'], Chart.C ? 'c:order' : 'order', -1);
		End;

		tmp := r[:i-1,:];
		tmp union= r[i+1:,:];
		Chart.Series := tmp;
		return array(0, '');
	End;

	Function AddSeries(Name, Categories, Val);
	Begin
		if length(Chart.Series) = 0 then return array(-1, 'Chart has no Series.');
		r := Chart.Series;
		i := length(r);
		r[i]['Name'] := Name;
		r[i]['Categories'] := Categories;
		r[i]['Values'] := Val;
		DefaultSer := r[ length(r)-1]['Node'].Marshal();
		node := Chart.ChartNode.InsertEndChild(DefaultSer);
		r[i]['Node'] := node;
		Chart.Series := r;
		setVal(node, Chart.C ? 'c:idx' : 'idx', 1);
		setVal(node, Chart.C ? 'c:order' : 'order', 1);
		class(xlsxXml).RemoveNode(node, Chart.C ? 'c:tx/c:strCache' : 'tx/strCache');
		class(xlsxXml).RemoveNode(node, Chart.C ? 'c:cat/c:strCache' : 'cat/strCahce');
		class(xlsxXml).RemoveNode(node, Chart.C ? 'c:val/c:numCache' : 'val/numCache');
		return UpdateSeries(i, Name, Categories, Val);
	End;

	Function UpdateSeries(i, Name, Categories, Val);
	Begin
		r := Chart.Series;
		r[i]['Name'] := Name;
		r[i]['Categories'] := Categories;
		r[i]['Values'] := Val;
		Chart.Series := r;
		task := array((Chart.C ? 'c:tx/c:strRef/c:f/' : 'tx/strRef/f/', 'pcdata', Name),
					(Chart.C ? 'c:cat/c:strRef/c:f/' : 'cat/strRef/f/', 'pcdata', Categories),
					(Chart.C ? 'c:val/c:numRef/c:f/' : 'val/numRef/f/', 'pcdata', Val),
					);
		for k:=0 to length(task)-1 do Begin
			class(xlsxXml).SetNodeValue(r[i]['Node'], task[k]);
		End;
		return array(0, '');
	End;

	Function UpdateTitle(i, title);
	Begin
		task := array(Chart.C ? 'c:chartSpace/c:chart/c:title/c:tx/c:rich/a:p/a:r/a:t' : 'chartSpace/chart/title/tx/rich/a:p/a:r/a:t', 'pcdata', title);
		class(xlsxXml).SetNodeValue(Chart.xmlObj, task);
		return array(0, '');
	End;

	Function setVal(node, k, deta);
	Begin
		val := node.FirstChildElement(k).GetAttribute('val');
		val := strtoint(val) + deta;
		node.FirstChildElement(k).SetAttribute('val', val);
	End;

	Chart;
	ChartFile;
	DrawingFile;
	Rid;
End;
