You've already forked lazarus-ccr
fpspreadsheet: Read cell comments from xlsx files.
git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3918 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
@@ -10,7 +10,7 @@ program ooxmlwrite;
|
|||||||
{$mode delphi}{$H+}
|
{$mode delphi}{$H+}
|
||||||
|
|
||||||
uses
|
uses
|
||||||
Classes, SysUtils, fpstypes, fpspreadsheet, fpsallformats, fpshelpers;
|
Classes, SysUtils, fpstypes, fpspreadsheet, fpsallformats, fpscell;
|
||||||
|
|
||||||
var
|
var
|
||||||
MyWorkbook: TsWorkbook;
|
MyWorkbook: TsWorkbook;
|
||||||
|
@@ -20,10 +20,13 @@ type
|
|||||||
procedure ReadXMLFile(out ADoc: TXMLDocument; AFileName: String);
|
procedure ReadXMLFile(out ADoc: TXMLDocument; AFileName: String);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure UnzipFile(AZipFileName, AZippedFile, ADestFolder: String);
|
||||||
|
|
||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
uses
|
uses
|
||||||
fpsStreams;
|
fpsStreams, fpsZipper;
|
||||||
|
|
||||||
{ Gets value for the specified attribute. Returns empty string if attribute
|
{ Gets value for the specified attribute. Returns empty string if attribute
|
||||||
not found. }
|
not found. }
|
||||||
@@ -92,6 +95,26 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure UnzipFile(AZipFileName, AZippedFile, ADestFolder: String);
|
||||||
|
var
|
||||||
|
list: TStringList;
|
||||||
|
unzip: TUnzipper;
|
||||||
|
begin
|
||||||
|
list := TStringList.Create;
|
||||||
|
try
|
||||||
|
list.Add(AZippedFile);
|
||||||
|
unzip := TUnzipper.Create;
|
||||||
|
try
|
||||||
|
Unzip.OutputPath := ADestFolder;
|
||||||
|
Unzip.UnzipFiles(AZipFileName, list);
|
||||||
|
finally
|
||||||
|
unzip.Free;
|
||||||
|
end;
|
||||||
|
finally
|
||||||
|
list.Free;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@@ -67,11 +67,13 @@ type
|
|||||||
FThemeColors: array of TsColorValue;
|
FThemeColors: array of TsColorValue;
|
||||||
FSharedFormulas: TStringList;
|
FSharedFormulas: TStringList;
|
||||||
FWrittenByFPS: Boolean;
|
FWrittenByFPS: Boolean;
|
||||||
|
function FindCommentsFileName(ANode: TDOMNode): String;
|
||||||
procedure ReadBorders(ANode: TDOMNode);
|
procedure ReadBorders(ANode: TDOMNode);
|
||||||
procedure ReadCell(ANode: TDOMNode; AWorksheet: TsWorksheet);
|
procedure ReadCell(ANode: TDOMNode; AWorksheet: TsWorksheet);
|
||||||
procedure ReadCellXfs(ANode: TDOMNode);
|
procedure ReadCellXfs(ANode: TDOMNode);
|
||||||
function ReadColor(ANode: TDOMNode): TsColor;
|
function ReadColor(ANode: TDOMNode): TsColor;
|
||||||
procedure ReadCols(ANode: TDOMNode; AWorksheet: TsWorksheet);
|
procedure ReadCols(ANode: TDOMNode; AWorksheet: TsWorksheet);
|
||||||
|
procedure ReadComments(ANode: TDOMNode; AWorksheet: TsWorksheet);
|
||||||
procedure ReadDateMode(ANode: TDOMNode);
|
procedure ReadDateMode(ANode: TDOMNode);
|
||||||
procedure ReadFileVersion(ANode: TDOMNode);
|
procedure ReadFileVersion(ANode: TDOMNode);
|
||||||
procedure ReadFills(ANode: TDOMNode);
|
procedure ReadFills(ANode: TDOMNode);
|
||||||
@@ -186,17 +188,18 @@ const
|
|||||||
// requires / instead of \, even on Windows; see
|
// requires / instead of \, even on Windows; see
|
||||||
// http://www.pkware.com/documents/casestudies/APPNOTE.TXT
|
// http://www.pkware.com/documents/casestudies/APPNOTE.TXT
|
||||||
// 4.4.17.1 All slashes MUST be forward slashes '/' as opposed to backwards slashes '\'
|
// 4.4.17.1 All slashes MUST be forward slashes '/' as opposed to backwards slashes '\'
|
||||||
OOXML_PATH_TYPES = '[Content_Types].xml';
|
OOXML_PATH_TYPES = '[Content_Types].xml';
|
||||||
{%H-}OOXML_PATH_RELS = '_rels/';
|
{%H-}OOXML_PATH_RELS = '_rels/';
|
||||||
OOXML_PATH_RELS_RELS = '_rels/.rels';
|
OOXML_PATH_RELS_RELS = '_rels/.rels';
|
||||||
{%H-}OOXML_PATH_XL = 'xl/';
|
{%H-}OOXML_PATH_XL = 'xl/';
|
||||||
{%H-}OOXML_PATH_XL_RELS = 'xl/_rels/';
|
{%H-}OOXML_PATH_XL_RELS = 'xl/_rels/';
|
||||||
OOXML_PATH_XL_RELS_RELS = 'xl/_rels/workbook.xml.rels';
|
OOXML_PATH_XL_RELS_RELS = 'xl/_rels/workbook.xml.rels';
|
||||||
OOXML_PATH_XL_WORKBOOK = 'xl/workbook.xml';
|
OOXML_PATH_XL_WORKBOOK = 'xl/workbook.xml';
|
||||||
OOXML_PATH_XL_STYLES = 'xl/styles.xml';
|
OOXML_PATH_XL_STYLES = 'xl/styles.xml';
|
||||||
OOXML_PATH_XL_STRINGS = 'xl/sharedStrings.xml';
|
OOXML_PATH_XL_STRINGS = 'xl/sharedStrings.xml';
|
||||||
OOXML_PATH_XL_WORKSHEETS = 'xl/worksheets/';
|
OOXML_PATH_XL_WORKSHEETS = 'xl/worksheets/';
|
||||||
OOXML_PATH_XL_THEME = 'xl/theme/theme1.xml';
|
OOXML_PATH_XL_WORKSHEETS_RELS = 'xl/worksheets/_rels/';
|
||||||
|
OOXML_PATH_XL_THEME = 'xl/theme/theme1.xml';
|
||||||
|
|
||||||
{ OOXML schemas constants }
|
{ OOXML schemas constants }
|
||||||
SCHEMAS_TYPES = 'http://schemas.openxmlformats.org/package/2006/content-types';
|
SCHEMAS_TYPES = 'http://schemas.openxmlformats.org/package/2006/content-types';
|
||||||
@@ -208,7 +211,10 @@ const
|
|||||||
SCHEMAS_STRINGS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings';
|
SCHEMAS_STRINGS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings';
|
||||||
SCHEMAS_SPREADML = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
|
SCHEMAS_SPREADML = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main';
|
||||||
|
|
||||||
{ OOXML mime types constants }
|
{ OOXML relationship type constants }
|
||||||
|
OOXML_RELTYPE_COMMENTS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments';
|
||||||
|
|
||||||
|
{ OOXML mime types constants }
|
||||||
{%H-}MIME_XML = 'application/xml';
|
{%H-}MIME_XML = 'application/xml';
|
||||||
MIME_RELS = 'application/vnd.openxmlformats-package.relationships+xml';
|
MIME_RELS = 'application/vnd.openxmlformats-package.relationships+xml';
|
||||||
MIME_SPREADML = 'application/vnd.openxmlformats-officedocument.spreadsheetml';
|
MIME_SPREADML = 'application/vnd.openxmlformats-officedocument.spreadsheetml';
|
||||||
@@ -430,6 +436,25 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
function TsSpreadOOXMLReader.FindCommentsFileName(ANode: TDOMNode): String;
|
||||||
|
var
|
||||||
|
nodeName: String;
|
||||||
|
s: String;
|
||||||
|
begin
|
||||||
|
while ANode <> nil do
|
||||||
|
begin
|
||||||
|
nodeName := ANode.NodeName;
|
||||||
|
s := GetAttrValue(ANode, 'Type');
|
||||||
|
if s = OOXML_RELTYPE_COMMENTS then
|
||||||
|
begin
|
||||||
|
Result := ExtractFileName(GetAttrValue(ANode, 'Target'));
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
ANode := ANode.NextSibling;
|
||||||
|
end;
|
||||||
|
Result := '';
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TsSpreadOOXMLReader.CreateNumFormatList;
|
procedure TsSpreadOOXMLReader.CreateNumFormatList;
|
||||||
begin
|
begin
|
||||||
FreeAndNil(FNumFormatList);
|
FreeAndNil(FNumFormatList);
|
||||||
@@ -903,6 +928,64 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TsSpreadOOXMLReader.ReadComments(ANode: TDOMNode;
|
||||||
|
AWorksheet: TsWorksheet);
|
||||||
|
var
|
||||||
|
node, txtNode, rNode, rchild: TDOMNode;
|
||||||
|
nodeName: String;
|
||||||
|
cellAddr: String;
|
||||||
|
s: String;
|
||||||
|
r, c: Cardinal;
|
||||||
|
comment: String;
|
||||||
|
list: TStringList;
|
||||||
|
begin
|
||||||
|
comment := '';
|
||||||
|
node := ANode.FirstChild;
|
||||||
|
while node <> nil do
|
||||||
|
begin
|
||||||
|
nodeName := node.NodeName;
|
||||||
|
cellAddr := GetAttrValue(node, 'ref');
|
||||||
|
if cellAddr <> '' then
|
||||||
|
begin
|
||||||
|
txtNode := node.FirstChild;
|
||||||
|
while txtNode <> nil do
|
||||||
|
begin
|
||||||
|
rNode := txtnode.FirstChild;
|
||||||
|
while rNode <> nil do
|
||||||
|
begin
|
||||||
|
nodeName := rnode.NodeName;
|
||||||
|
rchild := rNode.FirstChild;
|
||||||
|
while rchild <> nil do begin
|
||||||
|
nodename := rchild.NodeName;
|
||||||
|
if nodename = 't' then begin
|
||||||
|
s := GetNodeValue(rchild);
|
||||||
|
if comment = '' then comment := s else comment := comment + s;
|
||||||
|
end;
|
||||||
|
rchild := rchild.NextSibling;
|
||||||
|
end;
|
||||||
|
rNode := rNode.NextSibling;
|
||||||
|
end;
|
||||||
|
if (comment <> '') and ParseCellString(cellAddr, r, c) then begin
|
||||||
|
// Fix line endings // #10 --> "LineEnding"
|
||||||
|
comment := UTF8StringReplace(comment, #10, LineEnding, [rfReplaceAll]);
|
||||||
|
{
|
||||||
|
list := TStringList.Create;
|
||||||
|
try
|
||||||
|
list.Text := comment;
|
||||||
|
comment := Copy(list.Text, 1, Length(list.Text) - Length(LineEnding));
|
||||||
|
finally
|
||||||
|
list.Free;
|
||||||
|
end;
|
||||||
|
}
|
||||||
|
AWorksheet.WriteComment(r, c, comment);
|
||||||
|
end;
|
||||||
|
txtNode := txtNode.NextSibling;
|
||||||
|
end;
|
||||||
|
node := node.NextSibling;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TsSpreadOOXMLReader.ReadDateMode(ANode: TDOMNode);
|
procedure TsSpreadOOXMLReader.ReadDateMode(ANode: TDOMNode);
|
||||||
var
|
var
|
||||||
s: String;
|
s: String;
|
||||||
@@ -1227,7 +1310,7 @@ var
|
|||||||
node: TDOMNode;
|
node: TDOMNode;
|
||||||
nodename: String;
|
nodename: String;
|
||||||
sheetName: String;
|
sheetName: String;
|
||||||
//sheetId: String;
|
sheetId: String;
|
||||||
begin
|
begin
|
||||||
node := ANode.FirstChild;
|
node := ANode.FirstChild;
|
||||||
while node <> nil do begin
|
while node <> nil do begin
|
||||||
@@ -1235,8 +1318,8 @@ begin
|
|||||||
if nodename = 'sheet' then
|
if nodename = 'sheet' then
|
||||||
begin
|
begin
|
||||||
sheetName := GetAttrValue(node, 'name');
|
sheetName := GetAttrValue(node, 'name');
|
||||||
//sheetId := GetAttrValue(node, 'sheetId');
|
sheetId := GetAttrValue(node, 'sheetId');
|
||||||
AList.Add(sheetName);
|
AList.AddObject(sheetName, TObject(ptrInt(StrToInt(sheetID))));
|
||||||
end;
|
end;
|
||||||
node := node.NextSibling;
|
node := node.NextSibling;
|
||||||
end;
|
end;
|
||||||
@@ -1391,18 +1474,16 @@ var
|
|||||||
i: Integer;
|
i: Integer;
|
||||||
fn: String;
|
fn: String;
|
||||||
begin
|
begin
|
||||||
//unzip content.xml into AFileName path
|
//unzip "content.xml" of "AFileName" to folder "FilePath"
|
||||||
FilePath := GetTempDir(false);
|
FilePath := GetTempDir(false);
|
||||||
UnZip := TUnZipper.Create;
|
UnZip := TUnZipper.Create;
|
||||||
UnZip.OutputPath := FilePath;
|
|
||||||
FileList := TStringList.Create;
|
FileList := TStringList.Create;
|
||||||
|
|
||||||
FileList.Add(OOXML_PATH_XL_STYLES); // styles
|
|
||||||
FileList.Add(OOXML_PATH_XL_STRINGS); // sharedstrings
|
|
||||||
FileList.Add(OOXML_PATH_XL_WORKBOOK); // workbook
|
|
||||||
FileList.Add(OOXML_PATH_XL_THEME); // theme
|
|
||||||
|
|
||||||
try
|
try
|
||||||
|
FileList.Add(OOXML_PATH_XL_STYLES); // styles
|
||||||
|
FileList.Add(OOXML_PATH_XL_STRINGS); // sharedstrings
|
||||||
|
FileList.Add(OOXML_PATH_XL_WORKBOOK); // workbook
|
||||||
|
FileList.Add(OOXML_PATH_XL_THEME); // theme
|
||||||
|
UnZip.OutputPath := FilePath;
|
||||||
Unzip.UnZipFiles(AFileName,FileList);
|
Unzip.UnZipFiles(AFileName,FileList);
|
||||||
finally
|
finally
|
||||||
FreeAndNil(FileList);
|
FreeAndNil(FileList);
|
||||||
@@ -1453,29 +1534,16 @@ begin
|
|||||||
|
|
||||||
// read worksheets
|
// read worksheets
|
||||||
for i:=0 to SheetList.Count-1 do begin
|
for i:=0 to SheetList.Count-1 do begin
|
||||||
|
// Create worksheet
|
||||||
|
FWorksheet := AData.AddWorksheet(SheetList[i], true);
|
||||||
|
|
||||||
// unzip sheet file
|
// unzip sheet file
|
||||||
FileList := TStringList.Create;
|
fn := OOXML_PATH_XL_WORKSHEETS + Format('sheet%d.xml', [i+1]);
|
||||||
try
|
UnzipFile(AFileName, fn, FilePath);
|
||||||
// The file name is always "sheet<n>.xml", irrespective of the sheet's name!
|
|
||||||
fn := OOXML_PATH_XL_WORKSHEETS + 'sheet' + IntToStr(i+1) + '.xml';
|
|
||||||
FileList.Add(fn);
|
|
||||||
UnZip := TUnZipper.Create;
|
|
||||||
try
|
|
||||||
UnZip.OutputPath := FilePath;
|
|
||||||
Unzip.UnZipFiles(AFileName, FileList);
|
|
||||||
finally
|
|
||||||
FreeAndNil(UnZip);
|
|
||||||
end;
|
|
||||||
finally
|
|
||||||
FreeAndNil(FileList);
|
|
||||||
end;
|
|
||||||
|
|
||||||
ReadXMLFile(Doc, FilePath + fn);
|
ReadXMLFile(Doc, FilePath + fn);
|
||||||
DeleteFile(FilePath + fn);
|
DeleteFile(FilePath + fn);
|
||||||
|
|
||||||
FWorksheet := AData.AddWorksheet(SheetList[i], true);
|
// Sheet data, formats, etc.
|
||||||
|
|
||||||
ReadSheetViews(Doc.DocumentElement.FindNode('sheetViews'), FWorksheet);
|
ReadSheetViews(Doc.DocumentElement.FindNode('sheetViews'), FWorksheet);
|
||||||
ReadSheetFormatPr(Doc.DocumentElement.FindNode('sheetFormatPr'), FWorksheet);
|
ReadSheetFormatPr(Doc.DocumentElement.FindNode('sheetFormatPr'), FWorksheet);
|
||||||
ReadCols(Doc.DocumentElement.FindNode('cols'), FWorksheet);
|
ReadCols(Doc.DocumentElement.FindNode('cols'), FWorksheet);
|
||||||
@@ -1483,6 +1551,35 @@ begin
|
|||||||
ReadMergedCells(Doc.DocumentElement.FindNode('mergeCells'), FWorksheet);
|
ReadMergedCells(Doc.DocumentElement.FindNode('mergeCells'), FWorksheet);
|
||||||
|
|
||||||
FreeAndNil(Doc);
|
FreeAndNil(Doc);
|
||||||
|
|
||||||
|
// Comments:
|
||||||
|
// The comments are stored in separate "comments<n>.xml" files (n = 1, 2, ...)
|
||||||
|
// The relationship which comment belongs to which sheet file must be
|
||||||
|
// retrieved from the "sheet<n>.xls.rels" file (n = 1, 2, ...).
|
||||||
|
fn := OOXML_PATH_XL_WORKSHEETS_RELS + Format('sheet%d.xml.rels', [i+1]);
|
||||||
|
UnzipFile(AFilename, fn, FilePath);
|
||||||
|
if FileExists(FilePath + fn) then begin
|
||||||
|
// find exact name of comments<n>.xml file
|
||||||
|
ReadXMLFile(Doc, FilePath + fn);
|
||||||
|
DeleteFile(FilePath + fn);
|
||||||
|
fn := FindCommentsFileName(Doc.DocumentElement.FindNode('Relationship'));
|
||||||
|
FreeAndNil(Doc);
|
||||||
|
end else
|
||||||
|
if (SheetList.Count = 1) then
|
||||||
|
// if the wookbook has only 1 sheet then the sheet.xml.rels file is missing
|
||||||
|
fn := 'comments1.xml'
|
||||||
|
else
|
||||||
|
// this sheet does not have any cell comments
|
||||||
|
continue;
|
||||||
|
// Extract texts from the comments file found and apply to worksheet.
|
||||||
|
fn := OOXML_PATH_XL + fn;
|
||||||
|
UnzipFile(AFileName, fn, FilePath);
|
||||||
|
if FileExists(FilePath + fn) then begin
|
||||||
|
ReadXMLFile(Doc, FilePath + fn);
|
||||||
|
DeleteFile(FilePath + fn);
|
||||||
|
ReadComments(Doc.DocumentElement.FindNode('commentList'), FWorksheet);
|
||||||
|
FreeAndNil(Doc);
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
finally
|
finally
|
||||||
|
Reference in New Issue
Block a user