diff --git a/README.md b/README.md index b3681b1..f15235a 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,12 @@ TSOffice 项目:纯 TSL 代码实现 excel、word 文件读写 对应文件夹的dll文件按如下要求 -- `fmt_pubkrnl_plugin` 放入 tsl 安装根目录下 Plugin 文件夹 - `office_plugin` 放入 tsl 安装根目录下 Plugin 文件夹 +可选: + +- `fmt_pubkrnl_plugin` 放入 tsl 安装根目录下 Plugin 文件夹,若需要执行Demo文件夹下的[ExcelHelp.tsl](./Demo/ExcelHelp.tsl)和[WordHelp.tsl](./Demo/WordHelp.tsl),需要部署改动态库 + ### Linux 根据架构选择aarch64或x86版本将对应文件夹内容进行部署 diff --git a/funcext/TSOffice/TOfficeObj.tsf b/funcext/TSOffice/TOfficeObj.tsf index 1e5edfa..5015631 100644 --- a/funcext/TSOffice/TOfficeObj.tsf +++ b/funcext/TSOffice/TOfficeObj.tsf @@ -1,10 +1,12 @@ -// Version 1.3.0 +// Version 1.3.2 Function TOfficeObj(n); Begin case lowercase(n) of "nodeinfo": return new NodeInfo(""); + "trange": + return new TRange(""); "tfont": return new TFont(); "tcomment": @@ -1353,6 +1355,7 @@ type TChartImpl=class(NodeInfo) ,("field":"Excel","name":"Excel","obj":Excel,"attrEx":"","nodeType":"","attrName":"", "desc":"disable", "class":"") ,("field":"chartFileName","name":"chartFileName","obj":chartFileName,"attrEx":"","nodeType":"","attrName":"", "desc":"disable", "class":"") ,("field":"excelFileName","name":"excelFileName","obj":excelFileName,"attrEx":"","nodeType":"","attrName":"", "desc":"disable", "class":"") + ,("field":"drawingFileName","name":"drawingFileName","obj":drawingFileName,"attrEx":"","nodeType":"","attrName":"", "desc":"disable", "class":"") ) union ExtNodes; End; @@ -1395,6 +1398,7 @@ type TChartImpl=class(NodeInfo) Excel; chartFileName; excelFileName; + drawingFileName; End; /////////////////////////////////////////////////////////////// @@ -9933,7 +9937,7 @@ type TProtect=class(NodeInfo) Function GetAttrs(); override; Begin HandleAttrs(); - return array(("Sheet", "sheet", Sheet, ""),("EditObjects", "objects", EditObjects, ""),("EditScenarios", "scenarios", EditScenarios, ""),("FormatCells", "formatCells", FormatCells, ""),("FormatColumns", "formatColumns", FormatColumns, ""),("FormatRows", "formatRows", FormatRows, ""),("InsertColumns", "insertColumns", InsertColumns, ""),("InsertRows", "insertRows", InsertRows, ""),("InsertHyperlinks", "insertHyperlinks", InsertHyperlinks, ""),("DeleteColumns", "deleteColumns", DeleteColumns, ""),("DeleteRows", "deleteRows", DeleteRows, ""),("SelectLockedCells", "selectLockedCells", SelectLockedCells, ""),("Sort", "sort", Sort, ""),("AutoFilter", "autoFilter", AutoFilter, ""),("PivotTables", "pivotTables", PivotTables, ""),("SelectUnLockedCells", "selectUnlockedCells", SelectUnLockedCells, "")) union ExtAttr; + return array(("Password", "password", Password, ""),("AlgorithmName", "algorithmName", AlgorithmName, ""),("HashValue", "hashValue", HashValue, ""),("SaltValue", "saltValue", SaltValue, ""),("SpinCount", "spinCount", SpinCount, ""),("Sheet", "sheet", Sheet, ""),("EditObjects", "objects", EditObjects, ""),("EditScenarios", "scenarios", EditScenarios, ""),("FormatCells", "formatCells", FormatCells, ""),("FormatColumns", "formatColumns", FormatColumns, ""),("FormatRows", "formatRows", FormatRows, ""),("InsertColumns", "insertColumns", InsertColumns, ""),("InsertRows", "insertRows", InsertRows, ""),("InsertHyperlinks", "insertHyperlinks", InsertHyperlinks, ""),("DeleteColumns", "deleteColumns", DeleteColumns, ""),("DeleteRows", "deleteRows", DeleteRows, ""),("SelectLockedCells", "selectLockedCells", SelectLockedCells, ""),("Sort", "sort", Sort, ""),("AutoFilter", "autoFilter", AutoFilter, ""),("PivotTables", "pivotTables", PivotTables, ""),("SelectUnLockedCells", "selectUnlockedCells", SelectUnLockedCells, "")) union ExtAttr; End; Function GetChildren(); override; @@ -9943,6 +9947,11 @@ type TProtect=class(NodeInfo) End; //Attributes + Password; + AlgorithmName; + HashValue; + SaltValue; + SpinCount; Sheet; EditObjects; EditScenarios; @@ -10149,11 +10158,20 @@ End; ///DOCX文档实现 Type TRange = Class - Function Create(t); + Function Create();overload; + Begin + Create(nil); + End; + + Function Create(t);overload; Begin //array(("pNode":nodeObj, "pIndex":p, "rNode":nodeObj, "rIndex":r)) + Init(t); + End; + + Function Init(t); + Begin RunArr_ := t; - rPr_ := new TwrPr(); End; ///清除全部选中内容 @@ -10165,6 +10183,8 @@ Type TRange = Class Property Font read readFont; Function readFont(); Begin + if ifNil(rPr_) then + rPr_ := new TwrPr(); return rPr_; End; @@ -10186,6 +10206,7 @@ Type TRange = Class ///应用字体样式 Function Apply(); Begin + if not ifObj(rPr_) then return; arr := rPr_.Marshal(); if length(arr['attributes']) or length(arr['children']) then Begin for i:=0 to length(RunArr_)-1 do Begin @@ -10197,11 +10218,9 @@ Type TRange = Class Function _clear(first); Begin - for i:=first to length(RunArr_)-1 do Begin - RunArr_[i]['pNode'].DeleteChild(RunArr_[i]['rNode']); - End; for i:=first to length(RunArr_)-1 do Begin pNode := RunArr_[i]['pNode']; + pNode.DeleteChild(RunArr_[i]['rNode']); rNode := pNode.FirstChildElement('w:r'); if not ifObj(rNode) then pNode.Parent().DeleteChild(pNode); @@ -11323,20 +11342,22 @@ Type TPicture = Class(DocObject, TPictureImpl) ETU := 360045;//1cm单位 maxX := 17.0;//水平方向17cm maxY := 23.0;//垂直方向23cm + imageW := image.Width() / 28.346 * ETU; + imageH := image.Height() / 28.346 * ETU; if Width <= 0 and Height <= 0 then Begin //图像缺省大小 - widthVal := image.Width(); - heightVal := image.Height(); + widthVal := imageW; + heightVal := imageH; End else Begin //用户设置了图片尺寸 widthVal := ETU * Width; heightVal := ETU * Height; - if Width <= 0 and image.Height() and image.Width() then Begin //按照图片比例自动缩放 - scaling_factor := image.Width() / image.Height(); + if Width <= 0 and imageH and imageW then Begin //按照图片比例自动缩放 + scaling_factor := imageW / imageH; widthVal := round(heightVal * scaling_factor); End; - if Height <= 0 and image.Height() and image.Width() then Begin //按照图片比例自动缩放 - scaling_factor := image.Width() / image.Height(); + if Height <= 0 and imageH and imageW then Begin //按照图片比例自动缩放 + scaling_factor := imageW / imageH; heightVal := round(widthVal / scaling_factor); End; End; @@ -11361,7 +11382,7 @@ Type TPicture = Class(DocObject, TPictureImpl) heightVal *= ratio; widthVal *= ratio; End; - //println('w={}-{},maxx={}, h={}-{},maxh={},ratio={}',integer(image.Width()),integer(widthVal),15 * ETU, integer(image.Height()),integer(heightVal),23 * ETU,ratio); + //println('w={}-{},maxx={}, h={}-{},maxh={},ratio={}',integer(imageW),integer(widthVal),15 * ETU, integer(imageH),integer(heightVal),23 * ETU,ratio); return array(integer(widthVal), integer(heightVal)); End; @@ -12112,7 +12133,7 @@ private cnt := length(data); //[err, axis] := ColumnNumberToName(2 * ind + 2); [err, axis] := ColumnNumberToName(ind + 2); - Val.NumRef.F := fmt('Sheet1!${}$2:${}${}',axis, axis, cnt+1); + Val.NumRef.F := format('Sheet1!$%s$2:$%s$%d', axis, axis, cnt+1); Val.NumRef.NumCache.PtCount := cnt; Val.NumRef.NumCache.formatCode := 'General'; for i:=0 to cnt-1 do Begin @@ -12141,8 +12162,7 @@ private else if istable(Categories) then Begin //word 图表 cnt := length(Categories); //[err, axis] := ColumnNumberToName(2 * ind + 1); - //cat.StrRef.F := fmt('Sheet1!${}$2:${}${}',axis, axis, cnt+1); - cat.StrRef.F := fmt('Sheet1!$A$2:$A${}', cnt+1); + cat.StrRef.F := format('Sheet1!$A$2:$A$%d', cnt+1); cat.StrRef.StrCache.PtCount := cnt; for i:=0 to cnt-1 do Begin Tpt := TOfficeObj('Tpt'); @@ -12946,6 +12966,7 @@ Type TDocumentBody = Class(DocObject) Function InsertTable(tbl, posOpt); Begin addPart(posOpt, tbl); + TOfficeApi().Set('CurrentTable', tbl.node_); tblBorders := class(TSXml).GetNode(tbl.node_, 'w:tblPr/w:tblBorders'); tblCellMar := class(TSXml).GetNode(tbl.node_, 'w:tblPr/w:tblCellMar'); if ifObj(tblBorders) or ifObj(tblCellMar) then Begin @@ -13107,6 +13128,7 @@ Type TDocumentBody = Class(DocObject) p := new TPicture(picture.node_); _set_lastParagraph_(posOpt, picture.node_); + TOfficeApi().Set('CurrentShape', picture.node_); return p; End; @@ -13268,8 +13290,9 @@ Type TDocumentBody = Class(DocObject) begParagraphIndex := -1; begPos := 0; begTxtLen := 0; - for i:=0 to length(tArr)-1 do Begin + while i < length(tArr) do Begin txt := ''; + iStep := 1; if ifObj(tArr[i]['rNode']) then Begin run := new TRun(tArr[i]['rNode']); txt := run.Text(); @@ -13342,7 +13365,8 @@ Type TDocumentBody = Class(DocObject) flag := '[TSL]'; '[TSL][/TSL': if c = ']' then Begin - curParagraphNode := tslArr[0]['pNode']; + tslBegParagraphNode := tslArr[0]['pNode']; + tslEndParagraphNode := tArr[i]['pNode']; ind := length(tslArr) - 1; if tslArr[ind]['pIndex'] <> tArr[i]['pIndex'] or tslArr[ind]['rIndex'] <> tArr[i]['rIndex'] then Begin ind ++; @@ -13359,7 +13383,7 @@ Type TDocumentBody = Class(DocObject) oldNode := prevRun; tArr[i]['rNode'] := run._duplicate_r(oldNode); run._adjust_r(tArr[i]['rNode'], wz, txtLen); - i--; + iStep := 0; End; curRunNode := tslArr[0]['rNode']; curRun := new TRun(curRunNode); @@ -13382,84 +13406,68 @@ Type TDocumentBody = Class(DocObject) code := leftstr(code, lengthW(code) - 5 - endPos); CodePage := TOfficeApi().Get('CodePage'); TOfficeApi().Set('Docx', docx); - TOfficeApi().Set('CurrentParagraph', curParagraphNode); + TOfficeApi().Set('CurrentParagraph', tslBegParagraphNode); + TOfficeApi().Set('CurrentPosition', tslBegParagraphNode); TOfficeApi().Set('CurrentRun', curRunNode); TOfficeApi().Set('CodePage', 'gbk'); try tslFuncCount ++; + echo format('run code=[%s]\n',code); if code <> '' then - str := eval(&code); + eval(&code); except - println('run code={},err={}', code, ExceptObject.ErrInfo); + echo format('run code=%s,err=%s\n', code, ExceptObject.ErrInfo); errArr[ length(errArr) ] := array('code':code, 'err':ExceptObject.ErrInfo); - str := ''; End; tNode := curRun.node_.FirstChildElement('w:t'); if not ifObj(tNode) then Begin //没有在外部插入文字 - if not ifstring(str) or str = '' then Begin //删除w:r - curParagraphNode.DeleteChild(curRunNode); - hasrNode := curParagraphNode.FirstChildElement('w:r'); - if not ifObj(hasrNode) then Begin//删除空段落 - curParagraphNode.Parent().DeleteChild(curParagraphNode); - curParagraphNode := nil; - End; - End - else Begin - str := AnsiToUTF8(str); - lines := str2array(str, '\r\n'); - lineCnt := length(lines); - if lineCnt = 1 then Begin //单行 - curRun.SetText(str, true); + tslBegParagraphNode.DeleteChild(curRunNode); + //兼容WordTemplate函数,不删除空段落(TSL脚本段落) + //hasrNode := tslBegParagraphNode.FirstChildElement('w:r'); + //if not ifObj(hasrNode) then Begin//删除空段落 + // tslBegParagraphNode.Parent().DeleteChild(tslBegParagraphNode); + // tslBegParagraphNode := nil; + //End; + End; + //段落炸裂 + curPosotion := TOfficeApi().Get('CurrentPosition'); + if i+iStep < length(tArr) and tArr[i+iStep]['pNode'] = tslEndParagraphNode and tslBegParagraphNode <> curPosotion then Begin + data := tslEndParagraphNode.Marshal(); + NewParagraphNode := tslEndParagraphNode.Parent().InsertAfterChild(curPosotion, data[0]);//复制段落 + rmvArr := array(); + cnt := 0; + curLine := 0; + prevRNode := tArr[i+iStep]['rNode'].PrevElement('w:r'); + while ifObj(prevRNode) do Begin + curLine ++; + prevRNode := prevRNode.PrevElement('w:r'); + End; + rmvN := 0; + nextRNode := tArr[i+iStep]['rNode']; + while ifObj(nextRNode) do Begin + rmvArr[rmvN++] := nextRNode; + nextRNode := nextRNode.NextElement('w:r'); + End; + rNode := NewParagraphNode.FirstChildElement('w:r'); + nI := 0; + rIndex := 0; + while ifObj(rNode) do Begin + cnt ++; + if cnt <= curLine then Begin + rmvArr[rmvN++] := rNode; End - else Begin //多行 - curRun.SetText(lines[0], true); - nextRun := curRun.node_.NextElement('w:r'); - if ifObj(nextRun) then Begin //段落炸裂 - curLine := 0; - prevRNode := curRunNode; - while ifObj(prevRNode) do Begin - curLine ++; - prevRNode := prevRNode.PrevElement('w:r'); - End; - data := curParagraphNode.Marshal(); - LastParagraphNode := curParagraphNode.Parent().InsertAfterChild(curParagraphNode, data[0]);//复制段落 - rmvArr := array(); - cnt := 0; - rNode := LastParagraphNode.FirstChildElement('w:r'); - while ifObj(rNode) do Begin - cnt ++; - if cnt < curLine then - rmvArr[rmvN++] := rNode; - else if cnt = curLine then Begin - rNode.ClearText(); - rNode.SetText(lines[lineCnt-1], true); - lineCnt--; - End; - rNode := rNode.NextElement('w:r'); - End; - cnt := 0; - rNode := curParagraphNode.FirstChildElement('w:r'); - while ifObj(rNode) do Begin - cnt ++; - if cnt > curLine then - rmvArr[rmvN++] := rNode; - rNode := rNode.NextElement('w:r'); - End; - //删除重复的run - for rmvN := 0 to rmvN < length(rmvArr)-1 do begin - rmvArr[rmvN].Parent().DeleteChild(rmvArr[rmvN]); - End; - End;//段落炸裂 - curParagraph := new TParagraph(curParagraphNode); - prev := curParagraph; - for nP:=1 to lineCnt - 1 do Begin //新段落 - if lines[nP]='' then continue; - p := new TParagraph(); - p.Run.SetText(lines[nP], true); - prev := docx.AddParagraph(p, prev.Node()); - docx.CopyFormat(curParagraph, prev);//格式刷 - End; + else Begin + tArr[i+iStep+nI]['pNode'] := NewParagraphNode; + tArr[i+iStep+nI]['rNode'] := rNode; + tArr[i+iStep+nI]['rIndex'] := rIndex++; + nr := new TRun(tArr[i+iStep+nI]['rNode']); + nI ++; End; + rNode := rNode.NextElement('w:r'); + End; + //删除重复的run + for rmvN := 0 to length(rmvArr)-1 do begin + rmvArr[rmvN].Parent().DeleteChild(rmvArr[rmvN]); End; End; TOfficeApi().Set('CodePage', CodePage); @@ -13467,7 +13475,7 @@ Type TDocumentBody = Class(DocObject) flag := ''; code := nil; tslArr := array(); - continue; + break; End else flag := '[TSL]'; @@ -13492,10 +13500,232 @@ Type TDocumentBody = Class(DocObject) if tslArr[ind]['pIndex'] <> tArr[i]['pIndex'] or tslArr[ind]['rIndex'] <> tArr[i]['rIndex'] then tslArr[ind + 1] := tArr[i]; End; + i += iStep; End; return array(length(errArr), tslFuncCount, errArr); End; + Function ExecTsTag(docx, tagName, tagObj); + Begin + //表格 + t := array(); + tslTagCount := 0; + errArr := array(); + tArr := Tables(); + for i:=0 to length(tArr)-1 do Begin + col := tArr[i].Cols(); + row := tArr[i].Rows(); + for r:= 1 to row do Begin + for c:=1 to col do Begin + cell := tArr[i].Cell(r, c); + [err, cnt, err] := cell.ExecTsTag(docx, tagName, tagObj);//递归 + tslTagCount += cnt; + errArr union= err; + End; + End; + End; + + //文本框 + ps := Paragraphs(); + for i:=0 to length(ps)-1 do Begin + boxs := ps[i].TextBoxs(); + for j:=0 to length(boxs)-1 do Begin + [err, cnt, err] := boxs[j].ExecTsTag(docx, tagName, tagObj);//递归 + if cnt then + boxs[j].Apply(); + tslTagCount += cnt; + errArr union= err; + End; + End; + + //页脚、页眉 + sArr := Sections(); + tpArr := array('default','even','first'); + for i:=0 to length(sArr)-1 do Begin + for k, name in tpArr do Begin + h := sArr[i].Header(name); + if ifObj(h) then Begin + [err, cnt, err] := h.ExecTsTag(docx, tagName, tagObj);//递归 + tslTagCount += cnt; + errArr union= err; + End; + + f := sArr[i].Footer(name); + if ifObj(f) then Begin + [err, cnt, err] := f.ExecTsTag(docx, tagName, tagObj);//递归 + tslTagCount += cnt; + errArr union= err; + End; + End; + End; + + [err, cnt, err] := ExecTsTagImpl(docx, tagName, tagObj); + tslTagCount += cnt; + errArr union= err; + return array(length(errArr), tslTagCount, errArr); + End; + + Function ExecTsTagImpl(docx, tagName, tagObj); + Begin + tslTagCount := 0; + errArr := array(); + tagArr := array(); + tArr := TextArray(); + tagAttribute := ''; + tagStr := ''; + tagStatus := '';//array('','head', 'attribute', 'tail'); + tagHead := '[' + tagName; + tagTail := '[/' + tagName; + tmp := array(); + while i < length(tArr) do Begin + txt := ''; + iStep := 1; + if ifObj(tArr[i]['rNode']) then Begin + run := new TRun(tArr[i]['rNode']); + txt := run.Text(); + if class(TSXml).IsUtf8() then + txt := UTF8ToAnsi(txt); + End; + k := 1; + wz := 1; + txtLen := length(txt); + while k <= txtLen do Begin + c := txt[k]; + if c = ']' and (tagStatus = 'head' or tagStatus = 'attribute') and tagStr = tagHead then Begin //TAG头结束标志 + tagStr := ''; + tagArr := tArr[tmp['head-begin-paragraph']:i,:]; + wz++; + tagStatus := 'tail'; + tmp['head-end-paragraph'] := i; + tmp['head-end-pos'] := k++; + tmp['head-end-wz'] := wz; + tmp['head-end-txtlen'] := txtLen; + continue; + End; + case tagStatus of + '': + if c = '[' then Begin + tagStr := '['; + tmp['head-begin-paragraph'] := i; + tmp['head-begin-pos'] := k; + tmp['head-begin-wz'] := wz; + tmp['head-begin-txtlen'] := txtLen; + tagStatus := 'head'; + tagAttribute := ''; + End; + 'head': + if c = ' ' and tagStr = tagHead then Begin //找到TAG属性 + tagStatus := 'attribute'; + End + else if length(tagStr) < length(tagHead) and lowercase(c) = lowercase(tagHead[ length(tagStr) + 1 ]) then Begin + tagStr += tagHead[ length(tagStr) + 1 ]; + End + else //继续寻找TAG开始标志 + tagStatus := ''; + 'attribute': + tagAttribute += c; + 'tail': + if c=']' and tagStr = tagTail then Begin //查找到完整标签 + ind := length(tagArr) - 1; + if tagArr[ind]['pIndex'] <> tArr[i]['pIndex'] or tagArr[ind]['rIndex'] <> tArr[i]['rIndex'] then Begin + ind ++; + tagArr[ind] := tArr[i]; + End; + + //[/tag]后 + if k < txtLen then Begin //后面分割为新的w:r + tArr[i]['rNode'] := run._duplicate_r(tArr[i]['rNode']); + run._adjust_r(tArr[i]['rNode'], wz, txtLen); + iStep := 0; + End; + //前[/tag] + tagInd := length(tagArr) - i + tmp['tail-begin-paragraph'] - 1; + if tmp['tail-begin-pos'] > 1 then Begin + nNode := run._duplicate_r(tagArr[tagInd]['rNode']); + run._adjust_r(tagArr[tagInd]['rNode'], 0, tmp['tail-begin-wz'] - 1); + tagArr[tagInd]['pNode'].DeleteChild(nNode); + tmp['tag-end'] := tagInd; + End + else if tmp['tail-begin-pos'] = 1 then Begin + tmp['tag-end'] := tagInd - 1; + End; + + //[tag]后 + firstNode := tagArr[0]['rNode']; + tagInd := length(tagArr) - i + tmp['head-end-paragraph'] - 1; + if tmp['head-end-pos'] < tmp['head-end-txtlen'] then Begin + oldNode := tagArr[tagInd]['rNode']; + tagArr[tagInd]['rNode'] := run._duplicate_r(oldNode); + run._adjust_r(tagArr[tagInd]['rNode'], tmp['head-end-wz'] - 1, tmp['head-end-txtlen']); + tmp['tag-beg'] := tagInd; + if tmp['head-begin-paragraph'] <> tmp['head-end-paragraph'] then + tagArr[tagInd]['pNode'].DeleteChild(oldNode); + End + else if tmp['head-end-pos'] = tmp['head-end-txtlen'] and tagInd then Begin + tmp['tag-beg'] := tagInd + 1; + End; + //前[tag] + if tmp['head-begin-pos'] > 1 then Begin + run._adjust_r(firstNode, 0, tmp['head-begin-wz'] - 1); + if firstNode = tagArr[0]['rNode'] then + tagArr[0]['rNode'] := nil; + End + else if tagInd=0 and tmp['tag-beg']=0 then + tagArr[0]['pNode'].DeleteChild(firstNode); + + //执行TAG逻辑 + r := array(); + for nI := 0 to length(tagArr)-1 do Begin + if not ifObj(tagArr[nI]['rNode']) then continue; + if nI >= tmp['tag-beg'] and nI <= tmp['tag-end'] then + r[length(r)] := tagArr[nI];//标签中间文字内容 + else Begin //删除标签 + tagArr[nI]['pNode'].DeleteChild(tagArr[nI]['rNode']); + node := tagArr[nI]['pNode'].FirstChildElement('w:r'); + if not ifObj(node) then Begin + tagArr[nI]['pNode'].Parent().DeleteChild(tagArr[nI]['pNode']); + End; + End; + End; + tagObj.Init(tagName, tagAttribute, r); + tagObj.Apply(); + tslTagCount++; + + tagStatus := ''; + tagArr := array(); + break; + End + else if length(tagStr) < length(tagTail) and lowercase(c) = lowercase(tagTail[ length(tagStr) + 1 ]) then Begin + tagStr += tagTail[ length(tagStr) + 1 ]; + if tagStr = '[' then Begin + tmp['tail-begin-paragraph'] := i; + tmp['tail-begin-pos'] := k; + tmp['tail-begin-wz'] := wz; + tmp['tail-begin-txtlen'] := txtLen; + End; + End + else //继续寻找TAG结束标志 + tagStr := ''; + End; + if Ord(c) > 127 then Begin + k ++; + End + k ++; + wz ++; + End; + if length(tagArr) then Begin + if i and i < length(tArr) and tArr[i]['pIndex'] <> tArr[i+1]['pIndex'] then Begin + endPos ++; + End; + ind := length(tagArr) - 1; + if tagArr[ind]['pIndex'] <> tArr[i]['pIndex'] or tagArr[ind]['rIndex'] <> tArr[i]['rIndex'] then + tagArr[ind + 1] := tArr[i]; + End; + i += iStep; + End; + return array(length(errArr), tslTagCount, errArr); + End; + Function GetHeadingListImpl(docx, posOpt, UpperHeadingLevel, LowerHeadingLevel, numIds, bHeadList); Begin r := array(); @@ -13625,6 +13855,7 @@ Type TDocumentBody = Class(DocObject) else Begin raise 'Invalid input param.'; End; + TOfficeApi().Set('CurrentPosition', o.node_); End; Function findNode(posOpt, b); @@ -14131,25 +14362,28 @@ Type TTable = Class(DocObject, TTableImpl) ///设置表格数据 ///data: table,数据表 - ///[IncludeHeader: bool] 是否包括表头,默认FALSE - ///[IncludeIndex: bool] 是否自动添加索引号,默认FALSE - Function SetData(docx, data, IncludeHeader, IncludeIndex); + ///[includeHeader: bool] 是否包括表头,默认FALSE + ///[includeIndex: bool] 是否自动添加索引号,默认FALSE + Function SetData(docx, data, includeHeader, includeIndex); Begin if not IsTable(data) then raise "Invalid Data."; + [rowcount,colcount] := mSize(data);//一维数组 + if colcount = 0 then + data := array(data); fields := FieldNames(data); if IsTable(fields) then Begin r := nils(length(data),length(fields)); r[:,:] := data; data := r; End; - if IncludeIndex then Begin + if includeIndex then Begin n := length(data)-1; r := array(0->n); data := `r | data; End; - if IncludeHeader and IsTable(fields) then Begin - if IncludeIndex then + if includeHeader and IsTable(fields) then Begin + if includeIndex then fields := array("Index") union fields; data := array(fields) union data; End; diff --git a/funcext/TSOffice/TSDocxFile.tsf b/funcext/TSOffice/TSDocxFile.tsf index b8d8b1f..62f3748 100644 --- a/funcext/TSOffice/TSDocxFile.tsf +++ b/funcext/TSOffice/TSDocxFile.tsf @@ -1,4 +1,4 @@ -// Version 1.3.0 +// Version 1.3.2 Type TSDocxFile = Class ///Version: V1.0 2022-09-20 @@ -271,6 +271,8 @@ Type TSDocxFile = Class p := AddParagraph(p, getPosNode(posOpt), nil); chart.pNode := p.node_; p.Node().InsertEndChild(o.GetInnerXml()); + TOfficeApi().Set('CurrentShape', p.node_); + chart.chartFileName := 'word/charts/chart' $ o.ChartId_ $ '.xml'; return chart; End; @@ -292,6 +294,74 @@ Type TSDocxFile = Class return r; End; + ///从Excel中Copy指定的chart图到文档中指定位置 + ///excelFileName:string xlsx文件名 + ///excelSheetName:string sheetname + ///chartName:string or integer,chart图名称或当前sheet中chart图索引号 + ///Width:chart图宽度,单位cm + ///Height:chart图高度,单位cm + ///posOpt: 段落位置,0 在DOCX文件开头;-1 文件尾;N 在第N段之后;XmlNode节点对象或DocObject对象 在posOpt之后新添加图片 + ///返回TChart对象 + Function CopyExcelChart(excelFileName, excelSheetName, chartName, Width, Height, posOpt); + Begin + excel := new TSExcelFile(); + [err, msg] := excel.OpenFile('', excelFileName); + if err then return nil; + [err, charts] := excel.GetCharts(excelSheetName); + if err or length(charts)=0 then return nil; + drawingObj := excel.WorkBook().GetXmlFileObj(charts[0].drawingFileName); + if not ifObj(drawingObj) then return nil; + node := drawingObj.FirstChildElement('xdr:wsDr').FirstChildElement('xdr:twoCellAnchor'); + ind := 0; + chartRid := ''; + while ifObj(node) do Begin + findChart := false; + cNvPr := class(TSXml).GetNode(node, 'xdr:GraphicFrame/xdr:nvGraphicFramePr/xdr:cNvPr'); + name := ifObj(cNvPr) ? cNvPr.GetAttribute('name') : ''; + if ifstring(chartName) then Begin + if name = chartName then + find := true; + End + else if ifInt(chartName) and ind = chartName then + find := true; + if find then Begin + chartNode := class(TSXml).GetNode(node, 'xdr:GraphicFrame/a:graphic/a:graphicData/c:chart'); + if not ifObj(chartNode) then return nil; + chartRid := chartNode.GetAttribute('r:id'); + break; + End; + ind ++; + node := node.NextElement(); + End; + for i:=0 to length(charts)-1 do Begin + if charts[i].Rid = chartRid then Begin + chart := TOfficeObj('TChart'); + chart.Width := Width; + chart.Height := Height; + chart.Name := name; + chart.Type := 'line'; + chart.ShowBubbleSize := false; + chart.ShowPercent := false; + chart.DataTable := false; + chart.AddSeries('test', array('line1'), array(1,2)); + chart := AddChart(chart, getPosNode(posOpt)); + xmlObj := Zip().Get(chart.chartFileName); + xmlObj.Data := charts[i].xmlObj.Data; + return chart; + End; + End; + return nil; + End; + + ///遍历文档中所有[TSTAG][/TSTAG]标签,针对每一个TAG执行tagObj.Apply() + ///tagName:string 标签名称 + ///tagObj:TAG对象方法 + ///返回:[err,tslTagCount,errArr]: err 执行错误TAG次数,tslTagCount TAG总数,errArr 错误信息(array(('code':'代码', 'err':'错误信息'))) + Function ExecTsTag(tagName, tagObj); + Begin + return Body().ExecTsTag(self, tagName, tagObj); + End; + ///文档中全部的批注信息 ///返回:DocComments对象 Function Comments(); diff --git a/funcext/TSOffice/TSExcelFile.tsf b/funcext/TSOffice/TSExcelFile.tsf index 4cb1e79..4c1f283 100644 --- a/funcext/TSOffice/TSExcelFile.tsf +++ b/funcext/TSOffice/TSExcelFile.tsf @@ -1,4 +1,4 @@ -// Version 1.3.0 +// Version 1.3.2 Type TSExcelFile = Class ///Version: V1.0 2022-08-08 @@ -349,10 +349,12 @@ Type TSExcelFile = Class ///sheet: string,工作表名称 ///topLeft: string,左上角坐标,如: "A4" ///bottomRight: string,右下角坐标,如: "B8",为空获取从topLeft开始的整张表 + ///[IncludeHeader: bool] 是否包括表头,默认FALSE + ///[IncludeIndex: bool] 是否包括索引号,默认FALSE ///返回: table - Function GetTable(sheet, topLeft, bottomRight); + Function GetTable(sheet, topLeft, bottomRight, includeHeader, includeIndex, forceSingle); Begin - return workbook_.GetTable(class(TSXml).CurCodePageToUtf8(sheet), topLeft, bottomRight); + return workbook_.GetTable(class(TSXml).CurCodePageToUtf8(sheet), topLeft, bottomRight, includeHeader, includeIndex, forceSingle); End; ///插入列,在指定列前插入空白列 diff --git a/funcext/TSOffice/TSUtils/TOfficeApi.tsf b/funcext/TSOffice/TSUtils/TOfficeApi.tsf index 048ca70..3811999 100644 --- a/funcext/TSOffice/TSUtils/TOfficeApi.tsf +++ b/funcext/TSOffice/TSUtils/TOfficeApi.tsf @@ -73,6 +73,11 @@ Type TOffice = Class hash_['Paragraph-Node-' + name] := hash_['CurrentParagraph']; End; + Function GetCurrentPosition() + Begin + return hash_['CurrentPosition']; + End; + ///获取当前TSL代码段所在段落 ///返回:TParagraph对象 Function GetCurrentParagraph(); diff --git a/funcext/TSOffice/document/TDocxChart.tsf b/funcext/TSOffice/document/TDocxChart.tsf index 4f48740..e879cdf 100644 --- a/funcext/TSOffice/document/TDocxChart.tsf +++ b/funcext/TSOffice/document/TDocxChart.tsf @@ -8,6 +8,7 @@ Type TDocxChart = Class(TSChart) chartId_ := 1 + vselect countof( ['FileName'] ) from docx.Zip().Files() where AnsiStartsText('word/charts/chart', ['FileName']) end; targetFileName := 'charts/chart' $ chartId_ $ '.xml'; chartFile := 'word/' + targetFileName; + chartFileName_ := chartFile; docx.Zip().Add(chartFile, GetDefaultXml()); xmlObj_ := docx.Zip().Get(chartFile); if not chartData_.DisableExcel and istable(chartData.Series) and istable(chartData.Series[0]['Categories']) and istable(chartData.Series[0]['Values']) then Begin @@ -29,16 +30,16 @@ Type TDocxChart = Class(TSChart) Function GetInnerXml(); Begin ETU := 360045;//1cm单位 - return fmt(' + return format(' - + - + - + diff --git a/funcext/TSOffice/document/TSTag.tsf b/funcext/TSOffice/document/TSTag.tsf new file mode 100644 index 0000000..3334a71 --- /dev/null +++ b/funcext/TSOffice/document/TSTag.tsf @@ -0,0 +1,72 @@ +Type TSTag = Class + ///缺省构造函数 + Function Create(); overload; + Begin + End; + + Function Init(tagName, attribute, r); + Begin + tagName_ := tagName; + attribute_ := attribute; + runArr_ := r; + range_ := nil; + map_ := nil; + End; + + Property Range read readRange; + Function readRange(); + Begin + if ifNil(range_) then Begin + range_ := TOfficeObj('TRange'); + range_.Init(runArr_); + End; + return range_; + End; + + ///获取TAG中属性 [font size=12]...[/font] + /// tag.GetAttribute('size'); + ///返回:string + Function GetAttribute(key); + Begin + if not ifArray(map_) then Begin + map_ := array(); + //println('attribute={}',attribute_); + arr := Str2Array(attribute_, ' '); + for i := 0 to length(arr)-1 do Begin + kv := Str2Array(arr[i], '='); + if length(kv)=2 then + map_[kv[0]] := kv[1]; + End; + End; + return map_[key]; + End; + + Function Apply(); virtual; + Begin + case tagName_ of + 'add': + _add(); + 'del': + _del(); + End; + End; + + Function _add(); + Begin + Range.Font.Size := 40; + Range.Font.Color := 'FF0000'; + Range.Font.Bold := true; + Range.Apply(); + End; + + Function _del(); + Begin + Range.Clear(); + End; + + tagName_:string; + attribute_:string; + runArr_; + range_; + map_; +End; diff --git a/funcext/TSOffice/worksheet/xlsxComment.tsf b/funcext/TSOffice/worksheet/xlsxComment.tsf index 669fae1..2d83140 100644 --- a/funcext/TSOffice/worksheet/xlsxComment.tsf +++ b/funcext/TSOffice/worksheet/xlsxComment.tsf @@ -124,7 +124,7 @@ xmlns:v="urn:schemas-microsoft-com:vml"> if ifObj(node) then Begin [err, col, row] := excel_.CellNameToCoordinates(cell); clientData := TOfficeObj('TClientData'); - clientData.Anchor := fmt('{},23,{},0,{},{},{},5',col, row, 1+col+lineCount, colCount+col-1, 1+row+lineCount); + clientData.Anchor := format('%d,23,%d,0,%d,%d,%d,5',col, row, 1+col+lineCount, colCount+col-1, 1+row+lineCount); clientData.Row := row - 1; ClientData.Column := col - 1; node.UnMarshal(clientData.Marshal()); diff --git a/funcext/TSOffice/worksheet/xlsxTable.tsf b/funcext/TSOffice/worksheet/xlsxTable.tsf index f36ca8e..d86ef0d 100644 --- a/funcext/TSOffice/worksheet/xlsxTable.tsf +++ b/funcext/TSOffice/worksheet/xlsxTable.tsf @@ -101,7 +101,7 @@ private name := "列" $ col_index++; excel_.WorkBook().SetCellValue(sheetName_, cell, name); end - table_str += fmt('', id++, name); + table_str += format('', id, name); end table_str += ""; diff --git a/funcext/TSOffice/worksheet/xlsxWorkBook.tsf b/funcext/TSOffice/worksheet/xlsxWorkBook.tsf index a1ee763..5b9395f 100644 --- a/funcext/TSOffice/worksheet/xlsxWorkBook.tsf +++ b/funcext/TSOffice/worksheet/xlsxWorkBook.tsf @@ -138,14 +138,30 @@ Type xlsxWorkBook = Class return class(ErrorMessage).Fail(); End; - Function GetTable(sheet, topLeft, bottomRight); + Function GetTable(sheet, topLeft, bottomRight, includeHeader, includeIndex, forceSingle); Begin o := GetSheetObj(sheet); - if ifObj(o) then return o.Import(topLeft, bottomRight); + 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; - ///创建新sheet - ///sheet: string,工作表名称 Function NewSheet(sheet);overload; Begin lname := LowerCase(sheet); @@ -678,6 +694,7 @@ Type xlsxWorkBook = Class chartFile := ReplaceStr(target, '..', 'xl'); chart := TOfficeObj('TChart'); chart.Rid := node.GetAttribute('Id'); //rid + chart.drawingFileName := drawingFile; setChartInfo(chartFile, chart); charts[i] := chart; i++; diff --git a/更新日志.md b/更新日志.md index 085408c..ac3b9a3 100644 --- a/更新日志.md +++ b/更新日志.md @@ -1,5 +1,9 @@ # 更新日志 +## 2023-6-20 + +更新部署方式,不再依赖`fmt_pubkrnl_plugin.dll`,详情见[README](./README.md) + ## 2023-4-26 ### V1.3.1