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

	///缺省构造函数
	Function Create(z); overload;
	Begin
		zipfile_ := z;
		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(class(TSXml).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('sheet');
		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('sheet');
		End;

		files := zipfile_.Files();
		table_files := sselect ['FileName'] from files where AnsiContainsStr(['FileName'], 'tables/table') end;
		for i:=0 to length(table_files)-1 do
		begin
			table_xml := GetXmlFileObj(table_files[i]);
			if ifObj(table_xml) then
			begin
				display_name := table_xml.FirstChildElement('table').GetAttribute('displayName');
				TOfficeApi().Set('Table-' $ display_name, true);
			end
		end
		//echo tostn(sheetNames_);
	End;

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

	Function GetSheetName(index);
	Begin
		name := GetSheets()[index];
		return ifString(name) ? name : '';
	End;

	Function TotalCols(sheet);
	Begin
		o := GetSheetObj(sheet);
		if ifObj(o) then return o.TotalCols();
	End;

	Function TotalRows(sheet);
	Begin
		o := GetSheetObj(sheet);
		if ifObj(o) then return o.TotalRows();
	End;

	Function GetCellValue(sheet, axis);
	Begin
		o := GetSheetObj(sheet);
		if ifObj(o) then return o.GetCellValue(axis);
		return class(ErrorMessage).Fail();
	End;

	Function GetCellValueType(sheet, axis);
	Begin
		o := GetSheetObj(sheet);
		if ifObj(o) then return o.GetAttribute(axis, '', 't')[1];
		return '';
	End;

	Function SetCellValue(sheet, axis, val, opt);
	Begin
		o := GetSheetObj(sheet);
		if ifObj(o) then
			ret := o.SetCellValue(axis, val, opt);
		return ret;
	End;

	Function SetCellStyleOpt(sheet, axis, opt);
	Begin
		o := GetSheetObj(sheet);
		if ifObj(o) then
		begin
			if ifnil(opt['s']) and not o.CellIsExists(axis) then
			begin
				[err, col, row] := CellNameToCoordinates(axis);
				xml := GetSheetXmlFile(sheet);
				cols_node := xml.FirstChildElement('worksheet').FirstChildElement('cols');
				if ifObj(cols_node) then
				begin
					col_node := cols_node.FirstChildElement();
					while ifObj(col_node) do
					begin
						min := strtoint(col_node.GetAttribute('min'));
						max := strtoint(col_node.GetAttribute('max'));
						style := col_node.GetAttribute('style');
						if col >= min and col <= max and style <> '' then
						begin
							opt['s'] := style;
							break;
						end
						col_node := col_node.NextElement();
					end
				end
			end
		end
	End;

	Function GetCellRichText(sheet, axis);
	Begin
		o := GetSheetObj(sheet);
		if ifObj(o) then return o.GetCellValue(axis, 'RichText');
		return class(ErrorMessage).Fail();
	End;

	Function ClearCell(sheet, topLeft, bottomRight);
	Begin
		o := GetSheetObj(sheet);
		if ifObj(o) then o.ClearCell(topLeft, bottomRight);
	End;

	Function SetCellFormula(sheet, axis, formula);
	Begin
		o := GetSheetObj(sheet);
		if not o.CellIsExists(axis) then SetCellValue(sheet, axis, '');
		if ifObj(o) then o.SetCellFormula(axis, formula);
	End;

	Function GetCellFormula(sheet, axis);
	Begin
		o := GetSheetObj(sheet);
		if ifObj(o) then return o.GetCellFormula(axis);
		return class(ErrorMessage).Fail();
	End;

	Function GetTable(sheet, topLeft, bottomRight, includeHeader, includeIndex, forceSingle);
	Begin
		o := GetSheetObj(sheet);
		if ifObj(o) then
		begin
			data := o.Import(topLeft, bottomRight, includeHeader, includeIndex, forceSingle);
			if not TOfficeApi().IsUtf8() then
			begin
				fields := fieldnames(data);
				if ifarray(fields) and length(fields) then
				begin
					map := array();
					for i:=0 to length(fields)-1 do
						map[fields[i]] := class(TSXml).Utf8ToCurCodePage(fields[i]);
					reindex(data, nil, map);
				end
				data::begin
					if ifstring(mcell) then mcell := class(TSXml).Utf8ToCurCodePage(mcell);
				end
			end
			return data;
		end
	End;

	Function NewSheet(sheet);overload;
	Begin
		lname := LowerCase(sheet);
		if ifint(sheetIndexMap_[ lname ]) then return 'The sheet already exists.';

		fname := sheetPrefix_ $ inttostr(sheetsCount_ + 1) $ '.xml';
		sheetId := addSheetN(fname);
		rid := setWorkbookRels();
		setWorkbookSheet(sheet, sheetId, rid);
		setDocPropsApp(sheet);
		setContentTypes(fname);

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

	Function addSheetN(fname);
	Begin
		//添加文件xl/worksheets/sheetN.xml
		sheetId := vselect maxof(['sheetId']) from sheetNames_ end;
		sheetId := integer(sheetId) + 1;
		zipfile_.Add(fname, class(TSXml).XmlHeader() + class(TSXml).GetTemplate('sheet1'));
		return sheetId;
	End;

	Function setWorkbookRels();
	Begin
		rid := getWorkbookRelsRid();
		//设置 workbook.xml.rels
		workbook_rels := GetXmlFileObj('xl/_rels/workbook.xml.rels');
		rels := workbook_rels.FirstChildElement('Relationships').InsertEndChild('element', 'Relationship');
		rels.SetAttribute('Target', getTarget( sheetsCount_ + 1));
		rels.SetAttribute('Type', class(TSXml).GetTemplate('RelationshipWorkSheet'));
		rels.SetAttribute('Id', rid);
		return rid;
	End;

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

	Function setDocPropsApp(sheet);
	Begin
		//设置docProps/app.xml
		app := GetXmlFileObj(class(TSXml).GetFileName('docProps_app'));
		properties_node := app.FirstChildElement('Properties');
		// HeadingPairs
		variant_node := class(TSXml).GetNode(properties_node, 'HeadingPairs/vt:vector/vt:variant');
		while ifObj(variant_node) do
		begin
			vt_node := variant_node.FirstChildElement('vt:i4');
			if ifObj(vt_node) then
			begin
				vt_node.SetValue(length(sheetNames_) + 1);
				break;
			end
			variant_node := variant_node.NextElement();
		end
		
		// TitlesOfParts
		vector := class(TSXml).GetNode(properties_node, 'TitlesOfParts/vt:vector');
		vector.SetAttribute('size', length(sheetNames_) + 1);
		vector.InsertEndChild('element', 'vt:lpstr', sheet);
	End;

	Function setContentTypes(fname);
	Begin
		//设置[Content_Types].xml
		content_xml := GetXmlFileObj(class(TSXml).GetFileName('Content_Types'));
		class(TSXml).AddOverrideContentType(content_xml, '/' + fname, class(TSXml).GetTemplate('sheetContentType'));
	End;

	Function insertWorkbookSheet(targetSheetName, newSheetName, sheetId, rId, direction);
	Begin
		workbook := GetXmlFileObj('xl/workbook.xml');
		node := class(TSXml).GetNode(workbook, 'workbook/sheets');
		sheet_node := node.FirstChildElement('sheet');
		while ifObj(sheet_node) do
		begin
			if sheet_node.GetAttribute('name') = targetSheetName then
			begin
				if direction = "after" then
					sheet_node := node.InsertAfterChild(sheet_node, 'element', 'sheet');
				else
					sheet_node := node.InsertBeforeChild(sheet_node, 'element', 'sheet');
				sheet_node.SetAttribute('name', newSheetName);
				sheet_node.SetAttribute('sheetId', sheetId);
				sheet_node.SetAttribute('r:id', rid);
				break;
			end
			sheet_node := sheet_node.NextElement();
		end
	End;

	Function insertDocPropsApp(targetSheetName, newSheetName, direction);
	Begin
		//设置docProps/app.xml
		app := GetXmlFileObj(class(TSXml).GetFileName('docProps_app'));
		properties_node := app.FirstChildElement('Properties');
		// HeadingPairs
		variant_node := class(TSXml).GetNode(properties_node, 'HeadingPairs/vt:vector/vt:variant');
		while ifObj(variant_node) do
		begin
			vt_node := variant_node.FirstChildElement('vt:i4');
			if ifObj(vt_node) then
			begin
				vt_node.SetValue(length(sheetNames_) + 1);
				break;
			end
			variant_node := variant_node.NextElement();
		end
		
		// TitlesOfParts
		vector := class(TSXml).GetNode(properties_node, 'TitlesOfParts/vt:vector');
		vector.SetAttribute('size', length(sheetNames_) + 1);
		lpstr := vector.FirstChildElement('vt:lpstr');
		while ifObj(lpstr) do
		begin
			if LowerCase(lpstr.GetText()) = LowerCase(targetSheetName) then
			begin
				if direction = 'after' then
					lpstr := vector.InsertAfterChild(lpstr, 'element', 'vt:lpstr');
				else
					lpstr := vector.InsertBeforeChild(lpstr, 'element', 'vt:lpstr');
				lpstr.SetValue(newSheetName);
				break;
			end
			lpstr := lpstr.NextElement();
		end
	End;

	Function NewSheet(sourceSheet, destSheet);overload;
	Begin
		lname := LowerCase(destSheet);
		if ifint(sheetIndexMap_[ lname ]) then return 'destSheet already exists.';
		sname := LowerCase(sourceSheet);
		if not ifint(sheetIndexMap_[ sname ]) then return 'sourceSheet does not exists';

		fname := sheetPrefix_ $ inttostr(sheetsCount_ + 1) $ '.xml';
		sheetId := addSheetN(fname);
		rid := setWorkbookRels();
		insertWorkbookSheet(sourceSheet, destSheet, sheetId, rid, 'after');
		insertDocPropsApp(sourceSheet, destSheet, 'after');
		setContentTypes(fname);

		ind := sheetIndexMap_[sname];
		for i:=sheetsCount_ downto ind+2 do
		begin
			sheetNames_[i]['name'] := sheetNames_[i-1]['name'];
			sheetNames_[i]['sheetId'] := sheetNames_[i-1]['sheetId'];
			sheetNames_[i]['rid'] := sheetNames_[i-1]['rid'];
			sheetNames_[i]['file'] := sheetNames_[i-1]['file'];
			sheetIndexMap_[ LowerCase(sheetNames_[i]['name']) ] := i;
		end
		sheetIndexMap_[lname] := ind + 1;
		sheetNames_[ind+1]['name'] := destSheet;
		sheetNames_[ind+1]['sheetId'] := sheetId;
		sheetNames_[ind+1]['rid'] := rid;
		sheetNames_[ind+1]['file'] := fname;
		sheetsCount_ ++;
		SetDefaultSheet(destSheet);
	End;

	Function InsertSheet(sourceSheet, destSheet);
	Begin
		lname := LowerCase(destSheet);
		if ifint(sheetIndexMap_[ lname ]) then return 'destSheet already exists.';
		sname := LowerCase(sourceSheet);
		if not ifint(sheetIndexMap_[ sname ]) then return 'sourceSheet does not exists';

		fname := sheetPrefix_ $ inttostr(sheetsCount_ + 1) $ '.xml';
		sheetId := addSheetN(fname);
		rid := setWorkbookRels();
		insertWorkbookSheet(sourceSheet, destSheet, sheetId, rid, 'before');
		insertDocPropsApp(sourceSheet, destSheet, 'before');
		setContentTypes(fname);

		ind := sheetIndexMap_[sname];
		for i:=sheetsCount_ downto ind+1 do
		begin
			sheetNames_[i]['name'] := sheetNames_[i-1]['name'];
			sheetNames_[i]['sheetId'] := sheetNames_[i-1]['sheetId'];
			sheetNames_[i]['rid'] := sheetNames_[i-1]['rid'];
			sheetNames_[i]['file'] := sheetNames_[i-1]['file'];
			sheetIndexMap_[ LowerCase(sheetNames_[i]['name']) ] := i;
		end
		sheetIndexMap_[lname] := ind;
		sheetNames_[ind]['name'] := destSheet;
		sheetNames_[ind]['sheetId'] := sheetId;
		sheetNames_[ind]['rid'] := rid;
		sheetNames_[ind]['file'] := fname;
		sheetsCount_ ++;
		SetDefaultSheet(destSheet);
	End;

	Function DeleteSheet(sheet);
	Begin
		if sheetsCount_ <= 1 then return 'Cant not delete the last sheet.';
		ind := sheetIndexMap_[ LowerCase(sheet) ];
		if not ifint(ind) then return ;

		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
				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);
				f.FileName := files[i]['New-FileName'];
			End;
		End;
		workbook_rels := GetXmlFileObj(class(TSXml).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(class(TSXml).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);

		//删除calcChain.xml
		zipfile_.Remove('xl/calcChain.xml');

		xmlFileObjMap_ := array();

		//设置默认工作表
		sheet_name := LowerCase(sheet);
		ind := sheetIndexMap_[sheet_name];
		for k, v in sheetIndexMap_ do
		Begin
			if v > ind then sheetIndexMap_[k] := v - 1;
		End
		reindex(sheetIndexMap_, array(sheet_name: 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']);

		xmlFileObjMap_ := array();

	End;

	Function CopySheet(sourceSheet, destSheet);
	Begin
		ind := sheetIndexMap_[ LowerCase(sourceSheet) ];
		if not ifint(ind) then return sourceSheet $ ' does not exists.';
		dest_ind := sheetIndexMap_[ LowerCase(destSheet) ];

		//copy sheet
		if ifint(dest_ind) then
		begin
			fname := sheetNames_[dest_ind]['file'];
			zipfile_.Remove(fname);
		end
		else begin
			fname := sheetPrefix_ $ inttostr(sheetsCount_ + 1) $ '.xml';
		end
		sheet := GetSheetXmlFile(sourceSheet);
		zipfile_.Add(fname, sheet.Data());
		xml_file := GetXmlFileObj(fname);
		sheet_node := xml_file.FirstChildElement('worksheet').FirstChildElement('sheetviews').FirstChildElement('sheetview');
		if sheet_node.GetAttribute('tabSelected') = '1' then sheet_node.SetAttribute('tabSelected', 0);

		// sheetN.xml.rels
		DrawingFun := Function(obj, target, count);
		Begin
			rels_name := 'xl/drawings/_rels' + ReplaceStr(target, '../drawings', '') + '.rels';
			rels_obj := GetXmlFileObj(rels_name);
			rels_new_file := 'xl/drawings/_rels/drawing' $ count $ '.xml.rels';
			if ifObj(rels_obj) then
			begin
				zipfile_.Add(rels_new_file, rels_obj.Data());
				rels_file := GetXmlFileObj(rels_new_file);
				relationship := rels_file.FirstChildElement('Relationships').FirstChildElement('Relationship');
				while ifObj(relationship) do
				begin
					target := relationship.GetAttribute('Target');
					if AnsiContainsStr(target, 'chart') then copyFile(relationship, target, 'chartContentType');
					relationship := relationship.NextElement();
				end
			end
		End;
		sheet_rels := GetSheetRelsFile(sourceSheet);
		if ifObj(sheet_rels) then
		begin
			if ifint(dest_ind) then
			begin
				rels_name := 'xl/worksheets/_rels/' + ExtractFileName(sheetNames_[dest_ind]['file']) + '.rels';
				zipfile_.Remove(rels_name);
			end
			else begin
				rels_name := 'xl/worksheets/_rels/' + 'sheet' + inttostr(sheetsCount_ + 1) + '.xml.rels';
			end
			zipfile_.Add(rels_name, sheet_rels.Data());
			rels_file := GetXmlFileObj(rels_name);
			relationship := rels_file.FirstChildElement('Relationships').FirstChildElement('Relationship');
			while ifObj(relationship) do
			begin
				target := relationship.GetAttribute('Target');
				if AnsiContainsStr(target, 'vmlDrawing') then copyFile(relationship, target);
				else if AnsiContainsStr(target, 'drawing') then
				begin
					[count, file] := copyFile(relationship, target, 'drawingContentType');
					##DrawingFun(relationship, target, count);
				end
				else if AnsiContainsStr(target, 'comments') then copyFile(relationship, target, 'commentContentType');
				else if AnsiContainsStr(target, 'table') then
				begin
					[count, file] := copyFile(relationship, target, 'tableContentType');
					table_xml := GetXmlFileObj(file).FirstChildElement('table');
					table_xml.SetAttribute('id', count);
					display_name := table_xml.GetAttribute('displayName');
					display_name := class(xlsxTable).GenerateValidDisplayName(display_name, count);
					table_xml.SetAttribute('displayName', display_name);
				end
				relationship := relationship.NextElement();
			end
		end

		if ifint(dest_ind) then return;

		//workbook.xml.rels
		rid := getWorkbookRelsRid();
		workbook_rels := GetXmlFileObj('xl/_rels/workbook.xml.rels');
		rels := workbook_rels.FirstChildElement('Relationships').InsertEndChild('element', 'Relationship');
		rels.SetAttribute('Target', getTarget( sheetsCount_ + 1));
		rels.SetAttribute('Type', class(TSXml).GetTemplate('RelationshipWorkSheet'));
		rels.SetAttribute('Id', rid);

		//xl/workbook.xml
		sheetId := vselect maxof(['sheetId']) from sheetNames_ end;
		sheetId := integer(sheetId) + 1;
		workbook := GetXmlFileObj('xl/workbook.xml');
		node := workbook.FirstChildElement('workbook').FirstChildElement('sheets').InsertEndChild('element','sheet');
		node.SetAttribute('name', destSheet);
		node.SetAttribute('sheetId', sheetId);
		node.SetAttribute('r:id', rid);

		//app.xml
		app := GetXmlFileObj(class(TSXml).GetFileName('docProps_app'));
		prop := app.FirstChildElement('Properties');
		vector := prop.FirstChildElement('TitlesOfParts').FirstChildElement('vt:vector');
		vector.SetAttribute('size', sheetsCount_+1);
		vector.InsertEndChild('element', 'vt:lpstr', destSheet);
		vector := prop.FirstChildElement('HeadingPairs').FirstChildElement('vt:vector');
		if ifObj(vector) then
		begin
			variant := vector.FirstChildElement('vt:variant').NextElement('vt:variant');
			i4 := variant.FirstChildElement('vt:i4');
			i4.SetValue(sheetsCount_ + 1);
		end

		//[Content_Types].xml
		content_xml := GetXmlFileObj(class(TSXml).GetFileName('Content_Types'));
		class(TSXml).AddOverrideContentType(content_xml, '/' + fname, class(TSXml).GetTemplate('sheetContentType'));

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

	Function SetSheetName(sourceName, destName);
	Begin
		ind := sheetIndexMap_[ LowerCase(destName) ];
		if ifint(ind) then return destName $ ' already exists.';
		ind := sheetIndexMap_[ LowerCase(sourceName) ];
		if not ifint(ind) then return sourceName $ ' does not exists.';

		//设置 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 LowerCase(name) = LowerCase(sourceName) then
			Begin
				node.SetAttribute('name', destName);
				break;
			End
			node := node.NextElement();
		End

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

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

	Function InsertCol(sheet, col);
	Begin
		sObj := GetSheetObj(sheet);
		if ifObj(sObj) then sObj.InsertCol(col);
	End;

	Function InsertRow(sheet, row);
	Begin
		sObj := GetSheetObj(sheet);
		if ifObj(sObj) then sObj.InsertRow(row);
	End;

	Function RemoveCol(sheet, col);
	Begin
		sObj := GetSheetObj(sheet);
		if ifObj(sObj) then sObj.RemoveCol(col);
	End;

	Function RemoveRow(sheet, row);
	Begin
		sObj := GetSheetObj(sheet);
		if ifObj(sObj) then sObj.RemoveRow(row);
	End;

	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(TSXml).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
				chartFile := ReplaceStr(target, '..', 'xl');
				chart := TOfficeObj('TChart');
				chart.Rid := node.GetAttribute('Id');	//rid
				chart.drawingFileName := drawingFile;
				setChartInfo(chartFile, chart);
				charts[i] := chart;
				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].Name := chartNameMap[charts[i].Rid];
		End;
		return array(0, charts);
	End;

	Function GetSheetsCount();
	Begin
		return sheetsCount_;
	End;

	Function SetSheetVisible(sheet, visible);
	Begin
		workbook := GetXmlFileObj('xl/workbook.xml');
		node := class(TSXml).GetNode(workbook, 'workbook/sheets/sheet');
		if GetSheetsCount() = 1 then return "Only one sheet, can't set visible.";
		sheet := lowercase(sheet);
		default_sheet_name := lowercase(GetDefaultSheet());
		if default_sheet_name = sheet and not visible then
		begin
			ind := sheetIndexMap_[LowerCase(default_sheet_name)];
			default_name := ind = 0 ? sheetNames_[1]['name'] : sheetNames_[ind - 1]['name'];
			SetDefaultSheet(default_name);
		end
		if visible then SetDefaultSheet(sheet);
		state := visible ? "nohidden" : "hidden";
		while ifObj(node) do
		Begin
			name := lowercase(node.GetAttribute('name'));
			if name = sheet then
			Begin
				node.SetAttribute('state', state);
				break;
			End
			node := node.NextElement();
		End
		return 'Can not find sheet: ' $ sheet;
	End

	Function GetSheetVisible(sheet);
	Begin
		sheet_file := GetSheetXmlFile(sheet);
		if not ifObj(sheet_file) then return 0;
		sheet := lowercase(sheet);
		workbook := GetXmlFileObj('xl/workbook.xml');
		node := class(TSXml).GetNode(workbook, 'workbook/sheets/sheet');
		while ifObj(node) do
		Begin
			name := lowercase(node.GetAttribute('name'));
			if name = sheet then
			Begin
				val := node.GetAttribute('state');
				if val = 'hidden' then return 0;
				return 1;
			End
			node := node.NextElement();
		End
		return 0;
	End

	Function SetRowVisible(sheet, row, visible)
	Begin
		hidden := visible = 1 ? 0 : 1;
		sheet_obj := GetSheetObj(sheet);
		is_exists := sheet_obj.RowIsExists(row);
		if not is_exists then
		begin
			[err, cell] := CoordinatesToCellName(1, row);
			sheet_obj.SetCellValue(cell, '');
		end
		sheet_obj.SetAttribute(row, array("hidden": hidden));
	End

	Function GetRowVisble(sheet, row)
	Begin
		sheet_obj := GetSheetObj(sheet);
		if not ifObj(sheet_obj) then return class(ErrorMessage).SheetNotExist(sheet);
		is_exists := sheet_obj.RowIsExists(row);
		if is_exists then
		begin
			[err, hidden] := sheet_obj.GetAttribute(row, 'hidden');
			if not err then
				return array(0, hidden = '1' ? 0 : 1);
		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 '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 'ok';
				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 'ok';
	End

	Function GetColVisble(sheet, col);
	Begin
		sheet_xml_file := GetSheetXmlFile(sheet);
		if not ifObj(sheet_xml_file) then return class(ErrorMessage).SheetNotExist();
		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
		obj := GetSheetObj(sheet);
		obj.SetAttribute(row, array('ht': height, "customHeight": 1));
	End

	Function GetRowHeight(sheet, row)
	Begin
		obj := GetSheetObj(sheet);
		ret := obj.GetAttribute(row, "ht");
		if !ret[0] and trystrtofloat(ret[1], height) then return height;
		sheet_xml_file := GetSheetXmlFile(sheet);
		work_node := sheet_xml_file.FirstChildElement('worksheet');
		default_ht := work_node.FirstChildElement('sheetFormatPr').GetAttribute('defaultRowHeight');
		return strtofloat(default_ht);
	End

	Function SetColWidth(sheet, bCol, eCol, width);
	Begin
		startCol := bCol;
		endCol := eCol;
		startCol := ColumnNameToNumber(startCol)[1];
		endCol := ColumnNameToNumber(endCol)[1];
		if startCol > endCol then return;
		sheet_xml_file := GetSheetXmlFile(sheet);
		work_node := sheet_xml_file.FirstChildElement('worksheet');
		col_node := class(TSXml).GetWorkSheetNode(work_node, '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
				node.SetAttribute('width', width);
				node.SetAttribute('customWidth', 1);
				return;
			end
			else if startCol <= min and endCol >= max then
			begin
				node.SetAttribute('width', width);
				node.SetAttribute('customWidth', 1);
				node.SetAttribute('min', startCol);
				node.SetAttribute('max', endCol);
				node := node.NextElement();
				while ifObj(node) do
				begin
					max := strtoint(node.GetAttribute('max'));
					min := strtoint(node.GetAttribute('min'));
					if max <= endCol and min <= endCol then
					begin
						delete_node := node;
						node := node.NextElement();
						col_node.DeleteChild(delete_node);
					end
					else node := node.NextElement();
				end
				return;
			end
			else 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;
				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);
	End;

	Function GetColWidth(sheet, col);
	Begin
		sheet_xml_file := GetSheetXmlFile(sheet);
		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_number := 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_number >= min and col_number <= max then
			Begin
				width := trystrtofloat(node.GetAttribute('width'), r) ? r : -1;
				if width = -1 then break;
				return width;
			End
			node := node.NextElement();
		End
		return default_width;
	End;

	Function SetCellStyle(sheet, topLeft, bottomRight, styleid);
	Begin
		o := GetSheetObj(sheet);
		if ifObj(o) then return o.SetCellStyle(topLeft, bottomRight, styleid);
	End;

	Function GetCellStyle(sheet, axis);
	Begin
		sheet_obj := GetSheetObj(sheet);
		if ifObj(sheet_obj) then
		begin
			[err, styleId] := sheet_obj.GetCellStyle(axis);
			if not err then return styleId;
		end
		return '';
	End;

	Function MergeCell(sheet, hcell, vcell);
	Begin
		sheet_xml_file := GetSheetXmlFile(sheet);
		if not ifObj(sheet_xml_file) then return 'The sheet is not exist.';
		if hcell = vcell then return;
		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
		else begin
			count := trystrtoint(merge_node.GetAttribute('count'), r) ? r : 0;
		end
		node := merge_node.FirstChildElement('mergeCell');
		hcell_ := SplitCellName(hcell);
		vcell_ := SplitCellName(vcell);
		hcell_int := ColumnNameToNumber(hcell_[1])[1];
		vcell_int := ColumnNameToNumber(vcell_[1])[1];
		while ifObj(node) do
		Begin
			ref := node.GetAttribute('ref');
			position := pos(':', ref);
			cell1 := SplitCellName(ref[1:position-1]);
			cell2 := SplitCellName(ref[position+1:]);
			cell1_int := ColumnNameToNumber(cell1[1])[1];
			cell2_int := 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);
		style_id := GetCellStyle(sheet, hcell);
		sheet_obj.SetCellStyle(hcell, vcell, style_id = '' ? '0' : style_id);
	End

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

	Function SetSheetDefaultColWidth(sheet, width);
	Begin
		sheet_xml_file := GetSheetXmlFile(sheet);
		if not ifObj(sheet_xml_file) then return;
		work_node := sheet_xml_file.FirstChildElement('worksheet');
		node := work_node.FirstChildElement('sheetFormatPr');
		node.SetAttribute('defaultColWidth', width);
	End

	Function GetSheetDefaultColWidth(sheet);
	Begin
		sheet_xml_file := GetSheetXmlFile(sheet);
		if not ifObj(sheet_xml_file) then return nil;
		work_node := sheet_xml_file.FirstChildElement('worksheet');
		node := work_node.FirstChildElement('sheetFormatPr');
		flag := trystrtofloat(node.GetAttribute('defaultColWidth'), width);
		return flag ? width : nil;
	End

	Function SetDefaultSheet(sheet);
	Begin
		ind := sheetIndexMap_[ LowerCase(sheet) ];
		if not ifint(ind) then return 'Can not find sheet: ' $ sheet;
		for i:=0 to length(sheetnames_)-1 do
		begin
			name := sheetNames_[i]['name'];
			file := sheetNames_[i]['file'];
			xml_file := GetXmlFileObj(file);
			if AnsiContainsText(file, 'chart') then root_node := xml_file.FirstChildElement('chartsheet');
			else root_node := xml_file.FirstChildElement('worksheet');
			sheet_node := class(TSXml).GetWorkSheetNode(root_node, 'sheetViews');
			node := sheet_node.FirstChildElement('sheetView');
			if not ifObj(node) then
			begin
				node := sheet_node.InsertFirstChild('element', 'sheetView');
				node.SetAttribute('workbookViewId', 0);
			end
			if lowercase(name) = lowercase(sheet) then
			Begin
				node.SetAttribute('tabSelected', 1);
				workbook := GetXmlFileObj('xl/workbook.xml');
				view_node := class(TSXml).GetNode(workbook, 'workbook/bookViews/workbookView');
				view_node.SetAttribute('activeTab', ind);
			End
			else
			begin
				node := root_node.FirstChildElement('sheetViews').FirstChildElement('sheetView');
				node.SetAttribute('tabSelected', 0);
			end
		end
	End

	Function GetDefaultSheet();
	Begin
		for i:=0 to length(sheetnames_)-1 do
		begin
			name := sheetnames_[i]['name'];
			file := sheetNames_[i]['file'];
			xml_file := GetXmlFileObj(file);
			if AnsiContainsText(file, 'chart') then root_node := xml_file.FirstChildElement('chartsheet');
			else root_node := xml_file.FirstChildElement('worksheet');
			sheet_node := root_node.FirstChildElement('sheetviews').FirstChildElement('sheetview');
			if sheet_node.GetAttribute('tabSelected') = '1' then return name;
		end
	End

	Function ProtectSheet(sheet, protect);
	Begin
		sheet_obj := GetSheetXmlfile(sheet);
		work_node := sheet_obj.FirstChildElement('worksheet');
		sheet_protection_node := work_node.FirstChildElement('sheetProtection');
		if ifObj(sheet_protection_node) then work_node.DeleteChild(sheet_protection_node);
		prev_node := class(TSXml).GetWorkSheetPrevNode(work_node, 'sheetProtection');
		protect.Sheet := 1;
		work_node.InsertAfterChild(prev_node, protect.Marshal());
	End;

	Function UnProtectSheet(sheet);
	Begin
		sheet_obj := GetSheetXmlfile(sheet);
		work_node := sheet_obj.FirstChildElement('worksheet');
		sheet_protection_node := work_node.FirstChildElement('sheetProtection');
		if ifObj(sheet_protection_node) then work_node.DeleteChild(sheet_protection_node);
	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 DeleteContentType(del_partname);
	Begin
		content := GetXmlFileObj(class(TSXml).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, class(TSXml).XmlHeader() + class(TSXml).GetTemplate('sheet_rels'));
		end
		xmlfile := GetXmlFileObj(file);
		r := class(TSXml).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(TSXml).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 GetSheetRelsFile(sheet);
	Begin
		ind := sheetIndexMap_[LowerCase(sheet)];
		file := 'xl/worksheets/_rels/' + ExtractFileName(sheetNames_[ind]['file']) + '.rels';
		if not FileIsExist(file) then
			zipfile_.Add(file, class(TSXml).XmlHeader() + class(TSXml).GetTemplate('sheet_rels'));
		return GetXmlFileObj(file);
	End;

	Function GetDrawingRelsFile(drawingId);
	Begin
		file := 'xl/drawings/_rels/drawing' + inttostr(drawingId) + '.xml.rels';
		if not FileIsExist(drawing_rels) then
			zipfile_.Add(file, class(TSXml).XmlHeader() + class(TSXml).GetTemplate('drawing_rels'));
		return GetXmlFileObj(file);
	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;

	Function GetSheetDrawing(sheet);
	Begin
		[rid, drawing_file_name, sheet_file_name, relsfile] := GetRelationshipRid(sheet, '../drawings/drawing');
		if drawing_file_name = '' then
		begin
			rid ++;
			drawing_rid := GetFilesCount('xl/drawings/drawing') + 1;
			drawing_file_name := '../drawings/drawing' + inttostr(drawing_rid) + '.xml';
			drawing_xl_file_name := 'xl/drawings/drawing' + inttostr(drawing_rid) + '.xml';
			zipfile_.Add(drawing_xl_file_name, class(TSXml).XmlHeader() + '<xdr:wsDr xmlns:xdr="http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"></xdr:wsDr>');
			AddRelationshipRid(relsfile, drawing_file_name, 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing', 'rId' $ inttostr(rid));
			content_xml := GetXmlFileObj(class(TSXml).GetFileName('Content_Types'));
			class(TSXml).AddOverrideContentType(content_xml, '/' + drawing_xl_file_name, 'application/vnd.openxmlformats-officedocument.drawing+xml');
			sheet_xml := GetXmlFileObj(sheet_file_name);
			node := sheet_xml.FirstChildElement('worksheet');
			prev_node := class(TSXml).GetWorkSheetPrevNode(node, 'drawing');
			if ifObj(prev_node) then drawing_node := node.InsertAfterChild(prev_node, 'element', 'drawing');
			else drawing_node := node.InsertEndChild('element', 'drawing');
			drawing_node.SetAttribute('r:id', 'rId' + inttostr(rid));
		end
		else begin
			drawing_xl_file_name := 'xl/drwaings/' + ExtractFileName(drawing_file_name);
			s := RightStr(drawing_xl_file_name, length(drawing_xl_file_name) - 19);
			drawing_rid := strtoint(LeftStr(s, length(s) - 4));
		end
		return drawing_rid;
	End

	Function InsertPageBreak(sheet, row);
	Begin
		if row <= 1 then return 'Row must be greater than 1.';
		sheet_xml := GetSheetXmlFile(sheet);
		worksheet := sheet_xml.FirstChildElement('worksheet');
		page_node := worksheet.FirstChildElement('rowBreaks');
		tbreak := TOfficeObj('TBreak');
		tbreak.Id := row;
		if not ifObj(page_node) then
		begin
			node := class(TSXml).GetWorkSheetPrevNode(worksheet, 'rowBreaks');
			node := worksheet.InsertAfterChild(node, 'element', 'rowBreaks');
			node.SetAttribute('count', 1);
			node.SetAttribute('manualBreakCount', 1);
			node.InsertFirstChild(tbreak.Marshal());
			return '';
		end
		node := page_node.FirstChildElement('brk');
		prev := nil;
		prev_id := 0;
		while ifObj(node) do
		begin
			id := strtoint(node.GetAttribute('id'));
			if id = row then return array(0, '');
			if row > prev_id and row < id then break;
			prev_id := id;
			prev := node;
			node := node.NextElement();
		end
		if ifObj(prev) then page_node.InsertAfterChild(prev, tbreak.Marshal());
		else page_node.InsertFirstChild(tbreak.Marshal());
		count := strtoint(page_node.GetAttribute('count'));
		manual_count := strtoint(page_node.GetAttribute('manualBreakCount'));
		page_node.SetAttribute('count', count + 1);
		page_node.SetAttribute('manualBreakCount', manual_count + 1);

		return '';
	End;

	Function RemovePageBreak(sheet, row);
	Begin
		sheet_xml := GetSheetXmlFile(sheet);
		page_node := sheet_xml.FirstChildElement('worksheet').FirstChildElement('rowBreaks');
		if not ifObj(page_node) then return ;
		node := page_node.FirstChildElement('brk');
		while ifObj(node) do
		begin
			id := strtoint(node.GetAttribute('id'));
			if id = row then
			begin
				page_node.DeleteChild(node);
				return;
			end
			node := node.NextElement();
		end
		return;
	End;

	Function NewSheetPane();
	Begin
		workbook := GetXmlFileObj('xl/workbook.xml');
		book_view_node := workbook.FirstChildElement('workbook').FirstChildElement('bookViews');
		workbook_node := book_view_node.FirstChildElement('workbookView');
		book_view_node.InsertEndChild(workbook_node.Marshal()[0]);
		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');
			sheet_view := sheet_node.LastChildElement('sheetView');
			view_id := strtoint(sheet_view.GetAttribute('workbookViewId')) + 1;
			view_obj := TOfficeObj('TSheetView');
			view_obj.WorkbookViewId := view_id;
			sheet_node.InsertEndChild(view_obj.Marshal());
		end
		return view_id;
	End;

	Function SetDefaultFont(font);
	Begin
		style_xml := GetXmlFileObj('xl/styles.xml');
		fonts_node := style_xml.FirstChildElement('styleSheet').FirstChildElement('fonts');
		first_node := fonts_node.FirstChildElement('font');
		marshal := font.Marshal();
		class(TSXml).UpdateNode(first_node, marshal['attributes'], marshal['children']);
	End;

	Function GetDefaultFont();
	Begin
		style_xml := GetXmlFileObj('xl/styles.xml');
		fonts_node := style_xml.FirstChildElement('styleSheet').FirstChildElement('fonts');
		first_node := fonts_node.FirstChildElement('font');
		tfont := TOfficeObj('TFont');
		tfont.RootObj := first_node;
		return tfont;
	End;

	Function SetCalcOptions(calcPr);
	Begin
		workbook_xml := GetXmlFileObj('xl/workbook.xml');
		workbook_node := workbook_xml.FirstChildElement('workbook');
		calc_node := workbook_node.FirstChildElement('calcPr');
		marshal := calcPr.Marshal();
		class(TSXml).UpdateNode(calc_node, marshal['attributes'], marshal['children']);
	End;

	Function GetCalcOptions();
	Begin
		workbook_xml := GetXmlFileObj('xl/workbook.xml');
		node := workbook_xml.FirstChildElement('workbook').FirstChildElement('calcPr');
		calcPr := TOfficeObj('TCalcPr');
		calcPr.RootObj := node;
		return calcPr;
	End;

	Function SetPageMargins(sheet, margins);
	Begin
		sheet_xml := GetSheetXmlFile(sheet);
		work_node := sheet_xml.FirstChild('worksheet');
		node := work_node.FirstChild('pageMargins');
		if not ifObj(node) then
		begin
			prev_node := class(TSXml).GetWorkSheetPrevNode(work_node, 'pageMargins');
			node := work_node.InsertAfterChild(prev_node, 'element', 'pageMargins');
		end
		marshal := margins.Marshal();
		class(TSXml).UpdateNode(node, marshal['attributes'], marshal['children']);
	End;

	Function GetPageMargins(sheet);
	Begin
		sheet_xml := GetSheetXmlFile(sheet);
		node := sheet_xml.FirstChild('worksheet').FirstChild('pageMargins');
		margins := TOfficeObj('tmargins');
		margins.RootObj := node;
		return margins;
	End;

	Function SetRowOutlineLevel(sheet, row, level);
	Begin
		sheet_obj := GetSheetObj(sheet);
		o := GetSheetObj(sheet);
		if not sheet_obj.RowIsExists(row) then
		begin
			[err, cell] := CoordinatesToCellName(1, row);
			sheet_obj.SetCellValue(cell, '');
		end
		sheet_obj.SetAttribute(row, array("outlineLevel": level));
	End;

	Function GetRowOutlineLevel(sheet, row);
	Begin
		sheet_obj := GetSheetObj(sheet);
		o := GetSheetObj(sheet);
		if not sheet_obj.RowIsExists(row) then return 0;
		[err, level] := sheet_obj.GetAttribute(row, 'outlineLevel');
		return level = '' ? 0 : strtoint(level);
	End;

	Function SetColOutlineLevel(sheet, col, level);
	Begin
		sheet_xml_file := GetSheetXmlFile(sheet);
		if not ifObj(sheet_xml_file) then return 'The Sheet is not exist.';
		work_node := sheet_xml_file.FirstChildElement('worksheet');
		col_node := work_node.FirstChildElement('cols');
		prev_node := class(TSXml).GetWorkSheetPrevNode(work_node, 'cols');
		if not ifObj(col_node) then col_node := work_node.InsertAfterChild(prev_node,'element', 'cols');
		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
				level_val := node.GetAttribute('outlineLevel');
				if strtoint(level_val) = level then return;
				else Begin
					if col = min and col = max then node.SetAttribute('outlineLevel', level);
					else begin
						node2 := col_node.InsertAfterChild(node, node.Marshal()[0]);
						if col = min then
						begin
							node.SetAttribute('max', col);
							node.SetAttribute('outlineLevel', level);
							node2.SetAttribute('min', col-1);
						end
						else if col = max then
						begin
							node.SetAttribute('max', col-1);
							node2.SetAttribute('outlineLevel', level);
							node2.SetAttribute('min', col);
						end
						else begin
							node3 := col_node.InsertAfterChild(node, node.Marshal()[0]);
							node.SetAttribute('max', col - 1);
							node2.SetAttribute('min', col + 1);
							node3.SetAttribute('min', col);
							node3.SetAttribute('max', col);
							node3.SetAttribute('outlineLevel', level);
						end
					end
					return 'ok';
				End
			End
			node := node.NextElement();
		End
		arr := array('type': 'element', 'name': 'col', 'attributes': ('min': col, 'max': col, 'outlineLevel': level, 'customWidth': 1, 'width': 9.06640625));
		col_node.InsertEndChild(arr);
		return 'ok';
	End;

	Function GetColOutlineLevel(sheet, col);
	Begin
		sheet_xml_file := GetSheetXmlFile(sheet);
		if not ifObj(sheet_xml_file) then return "Sheet error";
		work_node := sheet_xml_file.FirstChildElement('worksheet');
		col_node := work_node.FirstChildElement('cols');
		if not ifObj(col_node) then return 0;
		node := col_node.FirstChildElement('col');
		while ifObj(node) do
		begin
			min := strtoint(node.GetAttribute('min'));
			max := strtoint(node.GetAttribute('max'));
			val := node.GetAttribute('outlineLevel');
			if col >= min and col <= max then return val = '' ? 0 : strtoint(val);
			node := node.NextElement();
		end
		return 0;
	End;


private
	Function generateRow(c1, c2, r);
	Begin
		arr := array();
		sr := inttostr(r);
		for i:=c1 to c2 do
		Begin
			name := 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
		workbook_rels := GetXmlFileObj(class(TSXml).GetFileName('workbook_rels'));
		[rId, find] := class(TSXml).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', '');
		chart.plotAreaNode := xml.FirstChildElement(getC(chart) + 'chartSpace').FirstChildElement(getC(chart) + 'chart').FirstChildElement(getC(chart) + 'plotArea');
	End;

	Function copyFile(obj, target, content);
	Begin
		file_name := ReplaceStr(target, '..', 'xl');
		counts := GetFilesCount(ReplaceStrByReg(file_name, "\\d+.*ml", '')) + 1;
		xml := GetXmlFileObj(file_name);
		new_file_postfix := ReplaceStrByReg(file_name[3:], "\\d+", inttostr(counts));
		zipfile_.Add('xl' + new_file_postfix, xml.Data());
		obj.SetAttribute('Target', '..' + new_file_postfix);

		if content then
		begin
			content_xml := GetXmlFileObj(class(TSXml).GetFileName('Content_Types'));
			class(TSXml).AddOverrideContentType(content_xml, '/xl' + new_file_postfix, class(TSXml).GetTemplate(content));
		end
		return array(counts, 'xl' + new_file_postfix);
	End;

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