fpspreadsheet: xlsx reads images in header/footer

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@8333 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2022-07-01 20:36:06 +00:00
parent 2ceb5949ee
commit 105099f338
4 changed files with 261 additions and 113 deletions

View File

@ -64,6 +64,7 @@
</Parsing>
<Linking>
<Debugging>
<DebugInfoType Value="dsDwarf2Set"/>
<UseExternalDbgSyms Value="True"/>
</Debugging>
</Linking>

View File

@ -20,6 +20,24 @@ const
begin
Writeln('Starting program "demo_write_headerfooter_images"...');
if not FileExists(image1) then
begin
WriteLn(ExpandFilename(image1) + ' not found.');
Halt;
end;
if not FileExists(image2) then
begin
WriteLn(ExpandFilename(image2) + ' not found.');
Halt;
end;
if not FileExists(image3) then
begin
WriteLn(ExpandFilename(image3) + ' not found.');
Halt;
end;
// Create the spreadsheet
MyWorkbook := TsWorkbook.Create;
try

View File

@ -7,6 +7,9 @@ program demo_read_images;
uses
SysUtils, fpspreadsheet, fpstypes, fpsutils, fpsimages, xlsxooxml, fpsopendocument;
const
FILE_NAME = 'img';
var
workbook: TsWorkbook;
worksheet: TsWorksheet;
@ -24,10 +27,10 @@ begin
// Read spreadsheet file
myDir := ExtractFilePath(ParamStr(0));
{$IFDEF USE_XLSX}
workbook.ReadFromFile(myDir + 'img.xlsx', sfOOXML);
workbook.ReadFromFile(myDir + FILE_NAME + '.xlsx', sfOOXML);
{$ENDIF}
{$IFDEF USE_OPENDOCUMENT}
workbook.ReadFromFile(myDir + 'img.ods', sfOpenDocument);
workbook.ReadFromFile(myDir + FILE_NAME + '.ods', sfOpenDocument);
{$ENDIF}
// Get worksheets
@ -48,6 +51,7 @@ begin
', ScaleX=', img^.ScaleX:0:2,
', ScaleY=', img^.ScaleY:0:2
);
if embObj.FileName <> '' then
embobj.Stream.SaveToFile(ExtractFileName(embobj.FileName));
end;
end;

View File

@ -105,10 +105,10 @@ type
procedure ReadDifferentialFormat(ANode: TDOMNode);
procedure ReadDifferentialFormats(ANode: TDOMNode);
procedure ReadDimension(ANode: TDOMNode; AWorksheet: TsBasicWorksheet);
procedure ReadDrawing(ANode: TDOMNode; ASheet: TsBasicWorksheet);
function ReadDrawingFileName(AStream: TStream; ASheetRel: String): String;
procedure ReadDrawing(AStream: TStream; ARelsFile: String; ANode: TDOMNode; AWorksheet: TsBasicWorksheet);
procedure ReadDrawingRels(ANode: TDOMNode; ASheet: TsBasicWorksheet);
procedure ReadEmbeddedObjs(AStream: TStream);
function ReadFileNameFromRels(AStream: TStream; ARelsFile, ARelType, ARelID: String): String;
procedure ReadFileVersion(ANode: TDOMNode);
procedure ReadFills(ANode: TDOMNode);
function ReadFont(ANode: TDOMNode): Integer;
@ -129,9 +129,12 @@ type
procedure ReadSheetList(ANode: TDOMNode);
procedure ReadSheetPr(ANode: TDOMNode; AWorksheet: TsBasicWorksheet);
procedure ReadSheetProtection(ANode: TDOMNode; AWorksheet: TsBasicWorksheet);
procedure ReadSheetRelFileNames(AStream: TStream; AWorksheet: TsBasicWorksheet);
procedure ReadSheetRels(ANode: TDOMNode; AWorksheet: TsBasicWorksheet);
procedure ReadSheetViews(ANode: TDOMNode; AWorksheet: TsBasicWorksheet);
procedure ReadThemeElements(ANode: TDOMNode);
procedure ReadThemeColors(ANode: TDOMNode);
procedure ReadThemeElements(ANode: TDOMNode);
procedure ReadVmlDrawing(AStream: TStream; ARelsFile: String; ANode: TDOMNode; AWorksheet: TsBasicWorksheet);
procedure ReadWorkbookProtection(ANode: TDOMNode);
procedure ReadWorksheet(ANode: TDOMNode; AWorksheet: TsBasicWorksheet);
protected
@ -392,10 +395,14 @@ type
RelID: String;
MediaName: String;
FileName: String;
Worksheet: TsBasicWorksheet;
ImgIndex: Integer;
IsHeaderFooter: Boolean;
Worksheet: TsBasicWorksheet;
// This part is for images embedded in to worksheet
FromRow, FromCol, ToRow, ToCol: Cardinal;
FromRowOffs, FromColOffs, ToRowOffs, ToColOffs: Double;
// This part is for header/footer images.
// ... to be completed ...
end;
THyperlinkListData = class
@ -419,7 +426,10 @@ type
Name: String;
ID: String;
Hidden: Boolean;
DrawingFile: String;
Drawing_relID: String;
Drawing_File: String;
VmlDrawing_relID: String;
VmlDrawing_File: String;
end;
TSharedObjData = class
@ -526,6 +536,18 @@ begin
Result[2] := 'F'; // --> "FFrrggbb"
end;
function RelsFileFor(AFileName: String): String;
var
path: String;
fn: String;
p: Integer;
begin
p := RPos('/', AFileName);
path := Copy(AFileName, 1, p);
fn := copy(AFileName, p+1, MaxInt);
Result := path + '_rels/' + fn + '.rels';
end;
function StrToFillStyle(s: String): TsFillStyle;
var
fs: TsFillStyle;
@ -2471,7 +2493,12 @@ begin
end;
end;
procedure TsSpreadOOXMLReader.ReadDrawing(ANode: TDOMNode; ASheet: TsBasicWorksheet);
{ Reads the parameters of the embedded images defined as children of the
specified node which is in a drawingX.xml file.
ARelsFile is the associated rels file, drawingX.xml.rels in the _rels folder,
and contains the media file names of the images. }
procedure TsSpreadOOXMLReader.ReadDrawing(AStream: TStream; ARelsFile: String;
ANode: TDOMNode; AWorksheet: TsBasicWorksheet);
var
node, child, child2: TDOMNode;
nodeName: String = '';
@ -2482,6 +2509,7 @@ var
begin
if ANode = nil then
exit;
ANode := ANode.FirstChild;
while Assigned(ANode) do
begin
@ -2572,8 +2600,10 @@ begin
data.ToRowOffs := toRowOffs;
data.RelId := rId;
data.FileName := fileName;
data.MediaName := ReadFileNameFromRels(AStream, ARelsFile, SCHEMAS_IMAGE, rID);
data.ImgIndex := -1;
data.Worksheet := ASheet;
data.Worksheet := AWorksheet;
data.IsHeaderFooter := false;
FEmbeddedObjList.Add(data);
end;
@ -2581,39 +2611,6 @@ begin
end;
end;
function TsSpreadOOXMLReader.ReadDrawingFileName(AStream: TStream; ASheetRel: String): String;
var
XMLStream: TStream;
doc: TXMLDocument;
node: TDOMNode;
relType: String;
relTarget: String;
begin
Result := '';
doc := nil;
XMLStream := CreateXMLStream;
try
if not UnzipToStream(AStream, ASheetRel, XMLStream) then
raise EFPSpreadsheetReader.CreateFmt(rsDefectiveInternalFileStructure, ['xlsx']);
ReadXMLStream(doc, XMLStream);
node := doc.DocumentElement.FindNode('Relationship');
while Assigned(node) do begin
relType := GetAttrValue(node, 'Type');
if relType = SCHEMAS_DRAWING then
begin
relTarget := GetAttrValue(node, 'Target'); // --> '../drawings/drawing1.xml'
// Replace '..' by 'xl' (needed by the unzipper to extract the file)
Result := MakeXLPath(relTarget); // --> 'xl/drawings/drawing1.xml'
exit;
end;
node := node.NextSibling;
end;
finally
XMLStream.Free;
doc.Free;
end;
end;
procedure TsSpreadOOXMLReader.ReadDrawingRels(ANode: TDOMNode; ASheet: TsBasicWorksheet);
var
nodeName: String;
@ -2656,78 +2653,58 @@ end;
procedure TsSpreadOOXMLReader.ReadEmbeddedObjs(AStream: TStream);
var
i, j: Integer;
fn, s: String;
fn, relsFn, s: String;
XMLStream: TStream;
doc: TXMLDocument;
sheet: TsWorksheet;
data: TEmbeddedObjData;
SheetRels: TStrings;
sheetData: TSheetData;
w, h: Double;
img: TsEmbeddedObj;
scaleX, scaleY: Double;
begin
SheetRels := TStringList.Create;
try
// Get the name of the files in xl/worksheet/_rels.
// This should be "sheet1.xml.rels", "sheet2.xml.rels", etc.
// They belong to the 1st, 2nd etc. worksheet and contain the name of
// the drawing.xml files describing the embedded images.
ListFileNamesInDir(sheetRels, OOXML_PATH_XL_WORKSHEETS_RELS);
// Get the name of the drawing files and store them in the SheetData
for i := 0 to sheetRels.Count-1 do
begin
// Get index in sheet-rel file. Decremented by 1 this is the index of the
// worksheet.
fn := SheetRels[i];
Delete(fn, 1, Length(OOXML_PATH_XL_WORKSHEETS_RELS + 'sheet'));
s := Copy(fn, 1, pos('.', fn)-1);
j := StrToInt(s) - 1;
sheetData := TSheetData(FSheetList[j]) ;
// Store the name of the drawing.xml file in the SheetData
sheetData.DrawingFile := ReadDrawingFileName(AStream, SheetRels[i]);
end;
finally
SheetRels.Free;
end;
doc := nil;
j := 1;
try
for i := 0 to FSheetList.Count-1 do
begin
fn := TSheetData(FSheetList[i]).DrawingFile;
if fn = '' then
Continue;
sheetData := TSheetData(FSheetList[i]);
sheet := (FWorkbook as TsWorkbook).GetWorksheetByIndex(i);
// Read the drawings.xml file
fn := sheetData.Drawing_File;
if fn <> '' then
begin
XMLStream := CreateXMLStream;
try
// Read drawings parameters and store them in the FEmbeddedObjList.
if not UnzipToStream(AStream, fn, XMLStream) then
raise EFPSpreadsheetReader.CreateFmt(rsDefectiveInternalFileStructure, ['xlsx']);
ReadXMLStream(doc, XMLStream);
// Read drawings parameters and store them in the FEmbeddedObjList.
ReadDrawing(doc.DocumentElement, sheet);
relsFn := RelsFileFor(fn);
ReadDrawing(AStream, relsFn, doc.DocumentElement, sheet);
finally
XMLStream.Free;
end;
end;
// Now repeat the same with the vmlDrawings file
fn := sheetData.VmlDrawing_File;
if fn <> '' then
begin
XMLStream := CreateXMLStream;
try
// construct filename of drawing.xml.rels file
Delete(fn, 1, Length(OOXML_PATH_XL_DRAWINGS));
fn := OOXML_PATH_XL_DRAWINGS_RELS + fn + '.rels';
// Read vmDrawings parameters and store them in the FEmbeddedObjList, too.
if not UnzipToStream(AStream, fn, XMLStream) then
raise EFPSpreadsheetReader.CreateFmt(rsDefectiveInternalFileStructure, ['xlsx']);
ReadXMLStream(doc, XMLStream);
// Read rId value for this sheet and look up the media file name.
// Store it in the FEmbeddedObjList.
ReadDrawingRels(Doc.DocumentElement.FindNode('Relationship'), sheet);
relsFn := RelsFileFor(fn);
ReadVmlDrawing(AStream, relsFn, doc.DocumentElement, sheet);
finally
XMLStream.Free;
end;
end;
end;
// Read the embedded streams, add them to the workbook...
ReadMedia(AStream);
@ -2740,6 +2717,11 @@ begin
if (sheet <> nil) and (data.ImgIndex > -1) then
begin
img := TsWorkbook(FWorkbook).GetEmbeddedObj(data.ImgIndex);
if data.IsHeaderFooter then
begin
// to do: add header/footer processing here.
end else
begin
w := -data.FromColOffs + data.ToColOffs;
h := -data.FromRowOffs + data.ToRowOffs;
for j := data.FromCol to data.ToCol-1 do
@ -2760,11 +2742,50 @@ begin
);
end;
end;
end;
finally
doc.Free;
end;
end;
function TsSpreadOOXMLReader.ReadFileNameFromRels(AStream: TStream;
ARelsFile, ARelType, ARelID: String): String;
var
XMLStream: TStream;
doc: TXMLDocument;
node: TDOMNode;
relID: String;
relType: String;
relTarget: String;
begin
Result := '';
if ARelID = '' then
exit;
doc := nil;
XMLStream := CreateXMLStream;
try
if not UnzipToStream(AStream, ARelsFile, XMLStream) then
raise EFPSpreadsheetReader.CreateFmt(rsDefectiveInternalFileStructure, ['xlsx']);
ReadXMLStream(doc, XMLStream);
node := doc.DocumentElement.FindNode('Relationship');
while Assigned(node) do begin
relType := GetAttrValue(node, 'Type');
relID := GetAttrValue(node, 'Id');
if (relType = ARelType) and (relID = ARelID) then
begin
relTarget := GetAttrValue(node, 'Target');
Result := MakeXLPath(relTarget);
exit;
end;
node := node.NextSibling;
end;
finally
XMLStream.Free;
doc.Free;
end;
end;
procedure TsSpreadOOXMLReader.ReadFileVersion(ANode: TDOMNode);
begin
FWrittenByFPS := GetAttrValue(ANode, 'appName') = 'fpspreadsheet';
@ -3129,6 +3150,7 @@ var
unzip: TStreamUnzipper;
i: Integer;
data: TEmbeddedObjData;
ext: String;
begin
unzip := TStreamUnzipper.Create(AStream);
try
@ -3142,7 +3164,15 @@ begin
unzip.UnzipFile(data.MediaName, memStream);
memStream.Position := 0;
if memStream.Size > 0 then
begin
data.ImgIndex := (FWorkbook as TsWorkbook).AddEmbeddedObj(memStream, ExtractFileName(data.Filename));
ext := ExtractFileExt(data.MediaName);
if data.FileName = '' then
data.FileName := ExtractFileName(data.MediaName)
else
if ExtractFileExt(data.FileName) = '' then
data.FileName := data.FileName + ext;
end;
memStream.Free;
end;
end;
@ -3800,6 +3830,45 @@ begin
end;
end;
// Get the names of the files associated with the specified worksheet by means
// of Relationship IDs.
procedure TsSpreadOOXMLReader.ReadSheetRelFileNames(AStream: TStream;
AWorksheet: TsBasicWorksheet);
var
sheetData: TSheetData;
sheetIndex: Integer;
relsFile: String;
begin
sheetIndex := TsWorksheet(AWorksheet).Index;
sheetData := TSheetData(FSheetList[sheetIndex]);
relsFile := OOXML_PATH_XL_WORKSHEETS_RELS + 'sheet' + IntToStr(sheetIndex+1) + '.xml.rels';
sheetData.Drawing_File := ReadFileNameFromRels(AStream, relsFile, SCHEMAS_DRAWING, sheetData.Drawing_relID);
sheetData.VMLDrawing_File := ReadFileNameFromRels(AStream, relsFile, SCHEMAS_VMLDRAWING, sheetData.VMLDrawing_relID);
end;
{ Extract the Relationship-IDs of the files related to the specified worksheet }
procedure TsSpreadOOXMLReader.ReadSheetRels(ANode: TDOMNode;
AWorksheet: TsBasicWorksheet);
var
nodeName: string;
sheetData: TSheetData;
sheetIndex: Integer;
begin
sheetIndex := TsWorksheet(AWorksheet).Index;
sheetData := TSheetData(FSheetList[sheetIndex]);
ANode := ANode.FirstChild;
while Assigned(ANode) do
begin
nodeName := ANode.NodeName;
if nodeName = 'drawing' then
sheetData.Drawing_relID := GetAttrValue(ANode, 'r:id')
else if nodeName = 'legacyDrawingHF' then
sheetData.VMLDrawing_relID := GetAttrValue(ANode, 'r:id');
ANode := ANode.NextSibling;
end;
end;
procedure TsSpreadOOXMLReader.ReadSheetViews(ANode: TDOMNode;
AWorksheet: TsBasicWorksheet);
var
@ -3949,6 +4018,60 @@ begin
end;
end;
{ Reads the parameters of the header/footer images defined as children of the
specified node which is in a vmlDrawingX.xml file.
ARelsFile is the associated rels file, vmlDrawingX.xml.rels in the _rels folder,
and contains the media file names of the images. }
procedure TsSpreadOOXMLReader.ReadVmlDrawing(AStream: TStream; ARelsFile: String;
ANode: TDOMNode; AWorksheet: TsBasicWorksheet);
var
nodeName: String;
node: TDOMNode;
relID: String;
title: String;
data: TEmbeddedObjData;
begin
ANode := ANode.FirstChild;
while Assigned(ANode) do
begin
nodeName := ANode.NodeName;
if nodeName = 'v:shape' then
begin
node := ANode.FirstChild;
while Assigned(node) do
begin
nodeName := node.NodeName;
if nodeName = 'v:imagedata' then
begin
relID := GetAttrValue(node, 'o:relid');
title := GetAttrValue(node, 'o:title');
end;
node := node.NextSibling;
end;
if relID <> '' then
begin
data := TEmbeddedObjData.Create;
data.FromCol := UNASSIGNED_ROW_COL_INDEX;
data.FromColOffs := 0.0;
data.ToCol := UNASSIGNED_ROW_COL_INDEX;
data.ToColOffs := 0.0;
data.FromRow := UNASSIGNED_ROW_COL_INDEX;
data.FromRowOffs := 0.0;
data.ToRow := UNASSIGNED_ROW_COL_INDEX;
data.ToRowOffs := 0.0;
data.RelId := relId;
data.FileName := title;
data.MediaName := ReadFileNameFromRels(AStream, ARelsFile, SCHEMAS_IMAGE, relID);
data.ImgIndex := -1;
data.Worksheet := AWorksheet;
data.IsHeaderFooter := true;
FEmbeddedObjList.Add(data);
end;
end;
ANode := ANode.NextSibling;
end;
end;
procedure TsSpreadOOXMLReader.ReadWorkbookProtection(ANode: TDOMNode);
var
s : string;
@ -4179,6 +4302,8 @@ begin
ReadColRowBreaks(Doc_FindNode('rowBreaks'), FWorksheet);
ReadColRowBreaks(Doc_FindNode('colBreaks'), FWorksheet);
ReadHeaderFooter(Doc_FindNode('headerFooter'), FWorksheet);
ReadSheetRels(Doc.DocumentElement, FWorksheet);
ReadSheetRelFileNames(AStream, FWorksheet);
FreeAndNil(Doc);