fpspreadsheet: Modify hyperlink infrastructure to support non-label cells as well. Add hyperlinks to biff8 reader. Show hyperlink tooltip in TsWorksheetGrid.

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@3971 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2015-02-28 23:46:08 +00:00
parent 48490fd3c5
commit 58031aba3a
13 changed files with 484 additions and 282 deletions

View File

@ -9,7 +9,6 @@
<Title Value="demo_ctrls"/> <Title Value="demo_ctrls"/>
<ResourceType Value="res"/> <ResourceType Value="res"/>
<UseXPManifest Value="True"/> <UseXPManifest Value="True"/>
<Icon Value="-1"/>
</General> </General>
<i18n> <i18n>
<EnableI18N LFM="False"/> <EnableI18N LFM="False"/>

View File

@ -559,7 +559,7 @@ object MainForm: TMainForm
TabOrder = 0 TabOrder = 0
object CellIndicator: TsCellIndicator object CellIndicator: TsCellIndicator
Left = 0 Left = 0
Height = 27 Height = 23
Top = 0 Top = 0
Width = 138 Width = 138
Align = alTop Align = alTop

View File

@ -405,7 +405,7 @@ end;
procedure TMainForm.ToolButton4Click(Sender: TObject); procedure TMainForm.ToolButton4Click(Sender: TObject);
begin begin
WorkbookSource.Worksheet.WriteHyperlink(0, 0, hkCell, 'B5', 'Go to B5'); WorkbookSource.Worksheet.WriteHyperlink(0, 0, '#Sheet2!B5', 'Go to B5');
end; end;
procedure TMainForm.WorksheetGridClickHyperlink(Sender: TObject; procedure TMainForm.WorksheetGridClickHyperlink(Sender: TObject;

View File

@ -219,8 +219,7 @@ end;
procedure TCellHelper.SetHyperlink(const AValue: TsHyperlink); procedure TCellHelper.SetHyperlink(const AValue: TsHyperlink);
begin begin
Worksheet.WriteHyperlink(@self, AValue.Kind, AValue.Target, Worksheet.WriteHyperlink(@self, AValue.Target, AValue.Tooltip);
Worksheet.ReadAsUTF8Text(@self), AValue.Tooltip);
end; end;
procedure TCellHelper.SetNumberFormat(const AValue: TsNumberFormat); procedure TCellHelper.SetNumberFormat(const AValue: TsNumberFormat);

View File

@ -156,7 +156,8 @@ type
FPointSeparatorSettings: TFormatSettings; FPointSeparatorSettings: TFormatSettings;
// Streams with the contents of files // Streams with the contents of files
FSMeta, FSSettings, FSStyles, FSContent, FSMimeType, FSMetaInfManifest: TStream; FSMeta, FSSettings, FSStyles, FSContent, FSMimeType, FSMetaInfManifest: TStream;
// Helpers
{ Helpers }
procedure CreateNumFormatList; override; procedure CreateNumFormatList; override;
procedure CreateStreams; procedure CreateStreams;
procedure DestroyStreams; procedure DestroyStreams;
@ -164,7 +165,8 @@ type
procedure ListAllNumFormats; override; procedure ListAllNumFormats; override;
procedure ListAllRowStyles; procedure ListAllRowStyles;
procedure ResetStreams; procedure ResetStreams;
// Routines to write those files
{ Routines to write those files }
procedure WriteContent; procedure WriteContent;
procedure WriteMimetype; procedure WriteMimetype;
procedure WriteMetaInfManifest; procedure WriteMetaInfManifest;
@ -172,23 +174,22 @@ type
procedure WriteSettings; procedure WriteSettings;
procedure WriteStyles; procedure WriteStyles;
procedure WriteWorksheet(AStream: TStream; CurSheet: TsWorksheet); procedure WriteWorksheet(AStream: TStream; CurSheet: TsWorksheet);
{ Record writing methods } { Record writing methods }
procedure WriteBlank(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteBlank(AStream: TStream; const ARow, ACol: Cardinal;
ACell: PCell); override; ACell: PCell); override;
procedure WriteBool(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteBool(AStream: TStream; const ARow, ACol: Cardinal;
const AValue: Boolean; ACell: PCell); override; const AValue: Boolean; ACell: PCell); override;
procedure WriteDateTime(AStream: TStream; const ARow, ACol: Cardinal;
const AValue: TDateTime; ACell: PCell); override;
procedure WriteError(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteError(AStream: TStream; const ARow, ACol: Cardinal;
const AValue: TsErrorValue; ACell: PCell); override; const AValue: TsErrorValue; ACell: PCell); override;
procedure WriteFormula(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteFormula(AStream: TStream; const ARow, ACol: Cardinal;
ACell: PCell); override; ACell: PCell); override;
procedure WriteHyperlink(AStream: TStream; const ARow, ACol: Cardinal;
ACell: PCell); override;
procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal;
const AValue: string; ACell: PCell); override; const AValue: string; ACell: PCell); override;
procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal;
const AValue: double; ACell: PCell); override; const AValue: double; ACell: PCell); override;
procedure WriteDateTime(AStream: TStream; const ARow, ACol: Cardinal;
const AValue: TDateTime; ACell: PCell); override;
public public
constructor Create(AWorkbook: TsWorkbook); override; constructor Create(AWorkbook: TsWorkbook); override;
@ -768,7 +769,7 @@ var
begin begin
Result := false; Result := false;
if ACell^.ContentType = cctHyperlink then if FWorksheet.IsHyperlink(ACell) then
FWorksheet.WriteFont(ACell, HYPERLINK_FONTINDEX) FWorksheet.WriteFont(ACell, HYPERLINK_FONTINDEX)
else else
begin begin
@ -1466,7 +1467,7 @@ var
nodeName: String; nodeName: String;
s: String; s: String;
cell: PCell; cell: PCell;
hyperlink: TsHyperlink; hyperlink: string;
begin begin
{ We were forced to activate PreserveWhiteSpace in the DOMParser in order to { We were forced to activate PreserveWhiteSpace in the DOMParser in order to
catch the spaces inserted in formatting texts. However, this adds lots of catch the spaces inserted in formatting texts. However, this adds lots of
@ -1477,7 +1478,7 @@ begin
while Assigned(childnode) do while Assigned(childnode) do
begin begin
nodeName := childNode.NodeName; nodeName := childNode.NodeName;
hyperlink.Kind := hkNone; hyperlink := '';
if nodeName = 'text:p' then begin if nodeName = 'text:p' then begin
subnode := childnode.FirstChild; subnode := childnode.FirstChild;
while Assigned(subnode) do while Assigned(subnode) do
@ -1486,28 +1487,7 @@ begin
if nodename = 'text:a' then begin if nodename = 'text:a' then begin
s := GetAttrValue(subnode, 'xlink:type'); s := GetAttrValue(subnode, 'xlink:type');
if s = 'simple' then if s = 'simple' then
begin hyperlink := GetAttrValue(subnode, 'xlink:href');
s := GetAttrValue(subnode, 'xlink:href');
if s <> '' then
begin
if s[1]='#' then
begin
hyperlink.Kind := hkCell;
hyperlink.Target := Copy(s, 2, Length(s));
end else
begin
hyperlink.Kind := hkURI;
hyperlink.Target := s;
{
if IsAbsoluteUri(s) then
hyperlink.Target := s
else
hyperlink.Target := FileNameToUri(s);
}
end;
hyperlink.Tooltip := '';
end;
end;
end; end;
subnode := subnode.NextSibling; subnode := subnode.NextSibling;
end; end;
@ -1528,13 +1508,7 @@ begin
spanNode := spanNode.NextSibling; spanNode := spanNode.NextSibling;
end; end;
end; end;
{
case childnode.NodeType of
TEXT_NODE, COMMENT_NODE, PROCESSING_INSTRUCTION_NODE: ; // ignored
else
cellText := cellText + childnode.TextContent;
end;
}
childnode := childnode.NextSibling; childnode := childnode.NextSibling;
end; end;
@ -1544,10 +1518,10 @@ begin
cell := @FVirtualCell; cell := @FVirtualCell;
end else end else
cell := FWorksheet.GetCell(ARow, ACol); cell := FWorksheet.GetCell(ARow, ACol);
if hyperlink.Kind = hkNone then
FWorkSheet.WriteUTF8Text(cell, cellText) FWorkSheet.WriteUTF8Text(cell, cellText);
else if hyperlink <> '' then
FWorksheet.WriteHyperlink(cell, hyperlink.Kind, hyperlink.Target, cellText, hyperlink.Tooltip); FWorksheet.WriteHyperlink(cell, hyperlink);
styleName := GetAttrValue(ACellNode, 'table:style-name'); styleName := GetAttrValue(ACellNode, 'table:style-name');
ApplyStyleToCell(cell, stylename); ApplyStyleToCell(cell, stylename);
@ -3495,6 +3469,10 @@ var
begin begin
Unused(ARow, ACol); Unused(ARow, ACol);
// Hyperlink
if FWorksheet.IsHyperlink(ACell) then
FWorkbook.AddErrorMsg(rsODSHyperlinksOfTextCellsOnly, [GetCellString(ARow, ACol)]);
// Comment // Comment
comment := WriteCommentXMLAsString(FWorksheet.ReadComment(ACell)); comment := WriteCommentXMLAsString(FWorksheet.ReadComment(ACell));
@ -3571,6 +3549,10 @@ begin
DisplayStr := rsFALSE; DisplayStr := rsFALSE;
end; end;
// Hyperlink
if FWorksheet.IsHyperlink(ACell) then
FWorkbook.AddErrorMsg(rsODSHyperlinksOfTextCellsOnly, [GetCellString(ARow, ACol)]);
AppendToStream(AStream, Format( AppendToStream(AStream, Format(
'<table:table-cell office:value-type="%s" office:boolean-value="%s" %s %s >' + '<table:table-cell office:value-type="%s" office:boolean-value="%s" %s %s >' +
comment + comment +
@ -4068,6 +4050,10 @@ begin
end else end else
spannedStr := ''; spannedStr := '';
// Hyperlink
if FWorksheet.IsHyperlink(ACell) then
FWorkbook.AddErrorMsg(rsODSHyperlinksOfTextCellsOnly, [GetCellString(ARow, ACol)]);
// Convert string formula to the format needed by ods: semicolon list separators! // Convert string formula to the format needed by ods: semicolon list separators!
parser := TsSpreadsheetParser.Create(FWorksheet); parser := TsSpreadsheetParser.Create(FWorksheet);
try try
@ -4150,15 +4136,6 @@ begin
end; end;
{@@ ----------------------------------------------------------------------------
Writes a hyperlink
-------------------------------------------------------------------------------}
procedure TsSpreadOpenDocWriter.WriteHyperlink(AStream: TStream;
const ARow, ACol: Cardinal; ACell: PCell);
begin
WriteLabel(AStream, ARow, ACol, ACell^.UTF8StringValue, ACell);
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Writes a cell with text content Writes a cell with text content
@ -4174,9 +4151,10 @@ var
spannedStr: String; spannedStr: String;
r1,c1,r2,c2: Cardinal; r1,c1,r2,c2: Cardinal;
txt: ansistring; txt: ansistring;
textp, link, comment: String; textp, target, comment: String;
fmt: TsCellFormat; fmt: TsCellFormat;
hyperlink: TsHyperlink; hyperlink: PsHyperlink;
u: TUri;
begin begin
Unused(ARow, ACol); Unused(ARow, ACol);
@ -4208,17 +4186,24 @@ begin
GetCellString(ARow, ACol) GetCellString(ARow, ACol)
]); ]);
if ACell^.ContentType = cctHyperlink then if FWorksheet.IsHyperlink(ACell) then
begin begin
hyperlink := FWorksheet.ReadHyperlink(ACell); hyperlink := FWorksheet.FindHyperlink(ACell);
case hyperlink.Kind of target := hyperlink^.Target;
hkCell: link := '#' + hyperlink.Target; if target[1] <> '#' then
hkURI : link := hyperlink.Target; begin
u := ParseURI(target);
if u.Protocol = '' then begin
//UriToFileName(hyperlink^.Target, target);
target := 'file:///' + ExpandFileName(target);
ValidXMLText(target);
// if not IsAbsoluteURI(target) then target := '..\' + target;
end;
end; end;
textp := Format( textp := Format(
'<text:p>'+ '<text:p>'+
'<text:a xlink:href="%s" xlink:type="simple">%s</text:a>'+ '<text:a xlink:href="%s" xlink:type="simple">%s</text:a>'+
'</text:p>', [link, txt]); '</text:p>', [target, txt]);
end else end else
textp := '<text:p>' + txt + '</text:p>'; textp := '<text:p>' + txt + '</text:p>';
@ -4283,6 +4268,10 @@ begin
DisplayStr := FloatToStr(AValue); // Uses locale decimal separator DisplayStr := FloatToStr(AValue); // Uses locale decimal separator
end; end;
// Hyperlink
if FWorksheet.IsHyperlink(ACell) then
FWorkbook.AddErrorMsg(rsODSHyperlinksOfTextCellsOnly, [GetCellString(ARow, ACol)]);
AppendToStream(AStream, Format( AppendToStream(AStream, Format(
'<table:table-cell office:value-type="%s" office:value="%s" %s %s >' + '<table:table-cell office:value-type="%s" office:value="%s" %s %s >' +
comment + comment +
@ -4335,6 +4324,10 @@ begin
// Comment // Comment
comment := WriteCommentXMLAsString(FWorksheet.ReadComment(ACell)); comment := WriteCommentXMLAsString(FWorksheet.ReadComment(ACell));
// Hyperlink
if FWorksheet.IsHyperlink(ACell) then
FWorkbook.AddErrorMsg(rsODSHyperlinksOfTextCellsOnly, [GetCellString(ARow, ACol)]);
// nfTimeInterval is a special case - let's handle it first: // nfTimeInterval is a special case - let's handle it first:
if (fmt.NumberFormat = nfTimeInterval) then if (fmt.NumberFormat = nfTimeInterval) then

View File

@ -487,11 +487,13 @@ type
function IsHyperlink(ACell: PCell): Boolean; function IsHyperlink(ACell: PCell): Boolean;
function ReadHyperlink(ARow, ACol: Cardinal): TsHyperlink; overload; function ReadHyperlink(ARow, ACol: Cardinal): TsHyperlink; overload;
function ReadHyperlink(ACell: PCell): TsHyperlink; function ReadHyperlink(ACell: PCell): TsHyperlink;
procedure RemoveHyperlink(ACell: PCell; AKeepText: Boolean); procedure RemoveHyperlink(ACell: PCell);
function WriteHyperlink(ARow, ACol: Cardinal; AKind: TsHyperlinkKind; procedure SplitHyperlink(AValue: String; out ATarget, ABookmark: String);
ATarget: String; ADisplayText: String = ''; ATooltip: String = ''): PCell; overload; function ValidHyperlink(AValue: String; out AErrMsg: String): Boolean;
procedure WriteHyperlink(ACell: PCell; AKind: TsHyperlinkKind; function WriteHyperlink(ARow, ACol: Cardinal; ATarget: String;
ATarget: String; ADisplayText: String = ''; ATooltip: String = ''); overload; ATooltip: String = ''): PCell; overload;
procedure WriteHyperlink(ACell: PCell; ATarget: String;
ATooltip: String = ''); overload;
{ Merged cells } { Merged cells }
procedure MergeCells(ARow1, ACol1, ARow2, ACol2: Cardinal); overload; procedure MergeCells(ARow1, ACol1, ARow2, ACol2: Cardinal); overload;
@ -859,7 +861,7 @@ procedure DumpFontsToFile(AWorkbook: TsWorkbook; AFileName: String);
implementation implementation
uses uses
Math, StrUtils, TypInfo, lazutf8, Math, StrUtils, TypInfo, lazutf8, URIParser,
fpsPatches, fpsStrings, fpsStreams, uvirtuallayer_ole, fpsPatches, fpsStrings, fpsStreams, uvirtuallayer_ole,
fpsUtils, fpsreaderwriter, fpsCurrency, fpsExprParser, fpsUtils, fpsreaderwriter, fpsCurrency, fpsExprParser,
fpsNumFormat, fpsNumFormatParser; fpsNumFormat, fpsNumFormatParser;
@ -1626,7 +1628,7 @@ end;
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
function TsWorksheet.IsHyperlink(ACell: PCell): Boolean; function TsWorksheet.IsHyperlink(ACell: PCell): Boolean;
begin begin
Result := (ACell <> nil) and (ACell^.ContentType = cctHyperlink); Result := (ACell <> nil) and (cfHyperlink in ACell^.Flags);
end; end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
@ -1658,7 +1660,8 @@ begin
Result := hyperlink^ Result := hyperlink^
else else
begin begin
Result.Kind := hkNone; Result.Row := ACell^.Row;
Result.Col := ACell^.Col;
Result.Target := ''; Result.Target := '';
Result.Tooltip := ''; Result.Tooltip := '';
end; end;
@ -1669,7 +1672,7 @@ end;
the associated TsHyperlink record. Cell content type is converted to the associated TsHyperlink record. Cell content type is converted to
cctUTF8String. cctUTF8String.
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
procedure TsWorksheet.RemoveHyperlink(ACell: PCell; AKeepText: Boolean); procedure TsWorksheet.RemoveHyperlink(ACell: PCell);
var var
hyperlink: TsHyperlink; hyperlink: TsHyperlink;
AVLNode: TAvlTreeNode; AVLNode: TAvlTreeNode;
@ -1683,49 +1686,114 @@ begin
if AVLNode <> nil then begin if AVLNode <> nil then begin
Dispose(PsHyperlink(AVLNode.Data)); Dispose(PsHyperlink(AVLNode.Data));
FHyperlinks.Delete(AVLNode); FHyperlinks.Delete(AVLNode);
if AKeepText then Exclude(ACell^.Flags, cfHyperlink);
ACell^.ContentType := cctUTF8String
else
ACell^.ContentType := cctEmpty;
end; end;
end; end;
{@@ ----------------------------------------------------------------------------
Separates the target and bookmark parts of a hyperlink (separated by '#').
-------------------------------------------------------------------------------}
procedure TsWorksheet.SplitHyperlink(AValue: String; out ATarget, ABookmark: String);
var
p: Integer;
begin
p := pos('#', AValue);
if p = 0 then
begin
ATarget := AValue;
ABookmark := '';
end else
begin
ATarget := Copy(AValue, 1, p-1);
ABookmark := Copy(AValue, p+1, Length(AValue));
end;
end;
{@@ ----------------------------------------------------------------------------
Checks whether the passed string represents a valid hyperlink target
@param AValue String to be checked. Must be either a fully qualified URI,
or a # followed by a cell address in the current workbook
@param AErrMsg Error message in case that the string is not correct.
@returns TRUE if the string is correct, FALSE otherwise
-------------------------------------------------------------------------------}
function TsWorksheet.ValidHyperlink(AValue: String; out AErrMsg: String): Boolean;
var
uri: TUri;
mark: String;
sheet: TsWorksheet;
r, c: Cardinal;
begin
Result := false;
AErrMsg := '';
if AValue = '' then
begin
AErrMsg := rsEmptyHyperlink;
exit;
end else
if (AValue[1] = '#') then
begin
Delete(AValue, 1, 1);
if not FWorkbook.TryStrToCell(AValue, sheet, r, c) then
begin
AErrMsg := Format(rsNoValidHyperlinkInternal, ['#'+AValue]);
exit;
end;
end else
begin
uri := ParseURI(AValue);
if SameText(uri.Protocol, 'mailto') then
begin
Result := true; // To do: Check email address here...
exit;
end else
begin
Result := true;
exit;
end;
end;
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Assigns a hyperlink to the cell at the specified row and column Assigns a hyperlink to the cell at the specified row and column
Cell content is not affected by the presence of a hyperlink.
@param ARow Row index of the cell considered @param ARow Row index of the cell considered
@param ACol Column index of the cell considered @param ACol Column index of the cell considered
@param AKind Hyperlink type (to cell, external file, URL) @param ATarget Hyperlink address given as a fully qualitifed URI for
@param ATarget Depending on AKind: cell address, filename, or URL external links, or as a # followed by a cell address
if empty the hyperlink is removed from the cell. for internal links.
@param ADisplayText Text shown in cell. If empty the destination is shown
@param ATooltip Text for popup tooltip hint used by Excel @param ATooltip Text for popup tooltip hint used by Excel
@return Pointer to the cell with the hyperlink @returns Pointer to the cell with the hyperlink
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
function TsWorksheet.WriteHyperlink(ARow, ACol: Cardinal; AKind: TsHyperlinkKind; function TsWorksheet.WriteHyperlink(ARow, ACol: Cardinal; ATarget: String;
ATarget: String; ADisplayText: String = ''; ATooltip: String = ''): PCell; ATooltip: String = ''): PCell;
begin begin
Result := GetCell(ARow, ACol); Result := GetCell(ARow, ACol);
WriteHyperlink(Result, AKind, ATarget, ADisplayText, ATooltip); WriteHyperlink(Result, ATarget, ATooltip);
end; end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Assigns a hyperlink to the specified cell. Assigns a hyperlink to the specified cell.
@param ACell Pointer to the cell considered @param ACell Pointer to the cell considered
@param AKind Hyperlink type (to cell, external file, URL) @param ATarget Hyperlink address given as a fully qualitifed URI for
@param ATarget Depending on AKind: cell address, filename, or URL external links, or as a # followed by a cell address
if empty the hyperlink is removed from the cell. for internal links. An existing hyperlink is removed if
@param ADisplayText Text shown in cell. If empty the destination is shown ATarget is empty.
@param ATooltip Text for popup tooltip hint used by Excel @param ATooltip Text for popup tooltip hint used by Excel
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
procedure TsWorksheet.WriteHyperlink(ACell: PCell; AKind: TsHyperlinkKind; procedure TsWorksheet.WriteHyperlink(ACell: PCell; ATarget: String;
ATarget: String; ADisplayText: String = ''; ATooltip: String = ''); ATooltip: String = '');
var var
hyperlink: PsHyperlink; hyperlink: PsHyperlink;
addNew: Boolean; addNew: Boolean;
row, col: Cardinal; row, col: Cardinal;
r, c: Cardinal;
fmt: TsCellFormat; fmt: TsCellFormat;
fn: String;
err: String;
begin begin
if ACell = nil then if ACell = nil then
exit; exit;
@ -1734,25 +1802,32 @@ begin
col := ACell^.Col; col := ACell^.Col;
// Remove the hyperlink if an empty destination is passed // Remove the hyperlink if an empty destination is passed
if ATarget = '' then if (ATarget = '') then
RemoveHyperlink(ACell, false) RemoveHyperlink(ACell)
else else
begin begin
{
if not ValidHyperlink(ATarget, err) then
raise Exception.Create(err);
}
hyperlink := FindHyperlink(ACell); hyperlink := FindHyperlink(ACell);
addNew := (hyperlink = nil); addNew := (hyperlink = nil);
if addNew then New(hyperlink); if addNew then New(hyperlink);
hyperlink^.Row := row; hyperlink^.Row := row;
hyperlink^.Col := col; hyperlink^.Col := col;
hyperlink^.Kind := AKind;
hyperlink^.Target := ATarget; hyperlink^.Target := ATarget;
hyperlink^.Tooltip := ATooltip; hyperlink^.Tooltip := ATooltip;
if addNew then FHyperlinks.Add(hyperlink); if addNew then FHyperlinks.Add(hyperlink);
Include(ACell^.Flags, cfHyperlink);
ACell^.ContentType := cctHyperlink; if ACell^.ContentType = cctEmpty then
if ADisplayText <> '' then begin
ACell^.UTF8StringValue := ADisplayText ACell^.ContentType := cctUTF8String;
if UriToFileName(hyperlink^.Target, fn) then
ACell^.UTF8StringValue := fn
else else
ACell^.UTF8StringValue := ATarget; ACell^.UTF8StringValue := hyperlink^.Target;
end;
fmt := ReadCellFormat(ACell); fmt := ReadCellFormat(ACell);
if fmt.FontIndex = DEFAULT_FONTINDEX then if fmt.FontIndex = DEFAULT_FONTINDEX then
@ -2744,6 +2819,7 @@ function TsWorksheet.ReadAsUTF8Text(ACell: PCell;
var var
fmt: PsCellFormat; fmt: PsCellFormat;
hyperlink: PsHyperlink;
begin begin
Result := ''; Result := '';
@ -2755,8 +2831,7 @@ begin
case ContentType of case ContentType of
cctNumber: cctNumber:
Result := FloatToStrNoNaN(NumberValue, fmt^.NumberFormat, fmt^.NumberFormatStr); Result := FloatToStrNoNaN(NumberValue, fmt^.NumberFormat, fmt^.NumberFormatStr);
cctUTF8String, cctUTF8String:
cctHyperlink:
Result := UTF8StringValue; Result := UTF8StringValue;
cctDateTime: cctDateTime:
Result := DateTimeToStrNoNaN(DateTimeValue, fmt^.NumberFormat, fmt^.NumberFormatStr); Result := DateTimeToStrNoNaN(DateTimeValue, fmt^.NumberFormat, fmt^.NumberFormatStr);
@ -2775,6 +2850,11 @@ begin
end; end;
else else
Result := ''; Result := '';
if IsHyperlink(ACell) then
begin
hyperlink := FindHyperlink(ACell);
if hyperlink <> nil then Result := hyperlink^.Target;
end;
end; end;
end; end;
@ -4602,44 +4682,9 @@ begin
exit; exit;
end; end;
if IsHyperlink(ACell) then
begin
// Preserve hyperlinks. Modify only the display test.
WriteUTF8Text(ACell, AValue);
ACell^.ContentType := cctHyperlink;
end else
WriteUTF8Text(ACell, AValue); WriteUTF8Text(ACell, AValue);
end; end;
(*
{@@ ----------------------------------------------------------------------------
Assigns a comment to a cell
@param ARow Cell row index
@param ACol Cell column index
@param AComment Text to be used as comment. Can contain line-breaks.
@return Pointer to the cell
-------------------------------------------------------------------------------}
function TsWorksheet.WriteComment(ARow, ACol: Cardinal;
const AComment: String): PCell;
begin
Result := GetCell(ARow, ACol);
WriteComment(Result, AComment);
end;
{@@ ----------------------------------------------------------------------------
Assigns a comment to a cell
@param ACell Pointer to the cell
@param AComment Text to be used as comment. Can contain line-breaks.
-------------------------------------------------------------------------------}
procedure TsWorksheet.WriteComment(ACell: PCell; const AComment: String);
begin
if ACell <> nil then begin
ACell^.Comment := AComment;
ChangedCell(ACell^.Row, ACell^.Col);
end;
end;
*)
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Writes a currency value to a given cell. Its number format can be provided Writes a currency value to a given cell. Its number format can be provided
optionally by specifying various parameters. optionally by specifying various parameters.

View File

@ -2648,6 +2648,7 @@ end;
procedure TsSpreadsheetInspector.UpdateCellValue(ACell: PCell; AStrings: TStrings); procedure TsSpreadsheetInspector.UpdateCellValue(ACell: PCell; AStrings: TStrings);
var var
hyperlink: PsHyperlink; hyperlink: PsHyperlink;
comment: String;
s: String; s: String;
begin begin
if ACell = nil then if ACell = nil then
@ -2680,22 +2681,6 @@ begin
AStrings.Add(Format('UTF8StringValue=%s', [ACell^.UTF8StringValue])); AStrings.Add(Format('UTF8StringValue=%s', [ACell^.UTF8StringValue]));
if ACell^.ContentType = cctBool then if ACell^.ContentType = cctBool then
AStrings.Add(Format('BoolValue=%s', [BoolToStr(ACell^.BoolValue)])); AStrings.Add(Format('BoolValue=%s', [BoolToStr(ACell^.BoolValue)]));
if ACell^.ContentType = cctHyperlink then
begin
AStrings.Add(Format('UTF8StringValue=%s', [ACell^.UTF8StringValue]));
hyperlink := Worksheet.FindHyperlink(ACell);
if hyperlink <> nil then
begin
s := hyperlink^.Target;
case hyperlink^.Kind of
hkNone: s := s + ' <error>';
hkCell: s := s + ' (internal cell reference)';
hkURI : s := s + ' (external URI)';
end;
end else
s := '<error>';
AStrings.Add(Format('Hyperlink=%s', [s]));
end;
if ACell^.ContentType = cctError then if ACell^.ContentType = cctError then
AStrings.Add(Format('ErrorValue=%s', [GetEnumName(TypeInfo(TsErrorValue), ord(ACell^.ErrorValue))])); AStrings.Add(Format('ErrorValue=%s', [GetEnumName(TypeInfo(TsErrorValue), ord(ACell^.ErrorValue))]));
AStrings.Add(Format('FormulaValue=%s', [Worksheet.ReadFormulaAsString(ACell, true)])); AStrings.Add(Format('FormulaValue=%s', [Worksheet.ReadFormulaAsString(ACell, true)]));
@ -2705,6 +2690,23 @@ begin
AStrings.Add(Format('SharedFormulaBase=%s', [GetCellString( AStrings.Add(Format('SharedFormulaBase=%s', [GetCellString(
ACell^.SharedFormulaBase^.Row, ACell^.SharedFormulaBase^.Col) ACell^.SharedFormulaBase^.Row, ACell^.SharedFormulaBase^.Col)
])); ]));
if (cfHyperlink in ACell^.Flags) then
begin
hyperlink := Worksheet.FindHyperlink(ACell);
if hyperlink <> nil then
begin
if hyperlink^.Tooltip <> '' then
s := hyperlink^.Target + ' (tooltip: ' + hyperlink^.Tooltip + ')'
else
s := hyperlink^.Target;
AStrings.Add(Format('Hyperlink=%s', [s]));
end;
end;
if (cfHasComment in ACell^.Flags) then
begin
comment := Worksheet.ReadComment(ACell);
AStrings.Add(Format('Comment=%s', [comment]));
end;
end; end;
end; end;

View File

@ -1024,7 +1024,7 @@ begin
cell := FDrawingCell; cell := FDrawingCell;
// Nothing to do in these cases (like in Excel): // Nothing to do in these cases (like in Excel):
if (cell = nil) or not (cell^.ContentType in [cctUTF8String, cctHyperlink]) then // ... non-label cells if (cell = nil) or not (cell^.ContentType in [cctUTF8String]) then // ... non-label cells
exit; exit;
fmt := Workbook.GetPointerToCellFormat(cell^.FormatIndex); fmt := Workbook.GetPointerToCellFormat(cell^.FormatIndex);
@ -1840,13 +1840,13 @@ begin
cell := Worksheet.FindCell(sr, GetWorksheetCol(gc)); cell := Worksheet.FindCell(sr, GetWorksheetCol(gc));
// Empty cell --> proceed with next cell to the left // Empty cell --> proceed with next cell to the left
if (cell = nil) or (cell^.ContentType = cctEmpty) or if (cell = nil) or (cell^.ContentType = cctEmpty) or
((cell^.ContentType in [cctUTF8String, cctHyperLink]) and (cell^.UTF8StringValue = '')) ((cell^.ContentType = cctUTF8String) and (cell^.UTF8StringValue = ''))
then then
Continue; Continue;
// Overflow possible from non-merged, non-right-aligned, horizontal label cells // Overflow possible from non-merged, non-right-aligned, horizontal label cells
fmt := Workbook.GetPointerToCellFormat(cell^.FormatIndex); fmt := Workbook.GetPointerToCellFormat(cell^.FormatIndex);
if (not Worksheet.IsMerged(cell)) and if (not Worksheet.IsMerged(cell)) and
(cell^.ContentType in [cctUTF8String, cctHyperlink]) and (cell^.ContentType = cctUTF8String) and
not (uffTextRotation in fmt^.UsedFormattingFields) and not (uffTextRotation in fmt^.UsedFormattingFields) and
(uffHorAlign in fmt^.UsedFormattingFields) and (fmt^.HorAlignment <> haRight) (uffHorAlign in fmt^.UsedFormattingFields) and (fmt^.HorAlignment <> haRight)
then then
@ -1867,13 +1867,13 @@ begin
cell := Worksheet.FindCell(sr, GetWorksheetCol(gcLast)); cell := Worksheet.FindCell(sr, GetWorksheetCol(gcLast));
// Empty cell --> proceed with next cell to the right // Empty cell --> proceed with next cell to the right
if (cell = nil) or (cell^.ContentType = cctEmpty) or if (cell = nil) or (cell^.ContentType = cctEmpty) or
((cell^.ContentType in [cctUTF8String, cctHyperlink]) and (cell^.UTF8StringValue = '')) ((cell^.ContentType = cctUTF8String) and (cell^.UTF8StringValue = ''))
then then
continue; continue;
// Overflow possible from non-merged, horizontal, non-left-aligned label cells // Overflow possible from non-merged, horizontal, non-left-aligned label cells
fmt := Workbook.GetPointerToCellFormat(cell^.FormatIndex); fmt := Workbook.GetPointerToCellFormat(cell^.FormatIndex);
if (not Worksheet.IsMerged(cell)) and if (not Worksheet.IsMerged(cell)) and
(cell^.ContentType in [cctUTF8String, cctHyperlink]) and (cell^.ContentType = cctUTF8String) and
not (uffTextRotation in fmt^.UsedFormattingFields) and not (uffTextRotation in fmt^.UsedFormattingFields) and
(uffHorAlign in fmt^.UsedFormattingFields) and (fmt^.HorAlignment <> haLeft) (uffHorAlign in fmt^.UsedFormattingFields) and (fmt^.HorAlignment <> haLeft)
then then
@ -2161,6 +2161,7 @@ end;
procedure TsCustomWorksheetGrid.ExecuteHyperlink; procedure TsCustomWorksheetGrid.ExecuteHyperlink;
var var
hyperlink: TsHyperlink; hyperlink: TsHyperlink;
target, bookmark: String;
sheetname: String; sheetname: String;
sheet: TsWorksheet; sheet: TsWorksheet;
r, c: Cardinal; r, c: Cardinal;
@ -2169,12 +2170,10 @@ begin
exit; exit;
hyperlink := Worksheet.ReadHyperlink(FHyperlinkCell); hyperlink := Worksheet.ReadHyperlink(FHyperlinkCell);
case hyperlink.Kind of Worksheet.SplitHyperlink(hyperlink.Target, target, bookmark);
hkNone: if target = '' then begin
; // nothing to do // Goes to a cell within the current workbook
hkCell: if ParseSheetCellString(bookmark, sheetname, r, c) then
// Goes to a cell (unlike Excel, we don't support range here)
if ParseSheetCellString(hyperlink.Target, sheetname, r, c) then
begin begin
if sheetname <> '' then if sheetname <> '' then
begin begin
@ -2185,12 +2184,11 @@ begin
end; end;
Worksheet.SelectCell(r, c); Worksheet.SelectCell(r, c);
end else end else
raise Exception.CreateFmt(rsHyperlinkNotAValidCell, [hyperlink.Target]); raise Exception.CreateFmt(rsNoValidHyperlinkInternal, [hyperlink.Target]);
else end else
// Fires the OnClickHyperlink event which should open a file or a URL // Fires the OnClickHyperlink event which should open a file or a URL
if Assigned(FOnClickHyperlink) then FOnClickHyperlink(self, hyperlink); if Assigned(FOnClickHyperlink) then FOnClickHyperlink(self, hyperlink);
end; end;
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
@ -2637,16 +2635,44 @@ end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
This function defines the text to be displayed as a cell hint. By default, it This function defines the text to be displayed as a cell hint. By default, it
is the comment attached to a cell; it can further be modified by using the is the comment and/or the hyperlink attached to a cell; it can further be
OnGetCellHint event. modified by using the OnGetCellHint event.
Option goCellHints must be active for the cell hint feature to work. Option goCellHints must be active for the cell hint feature to work.
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
function TsCustomWorksheetGrid.GetCellHintText(ACol, ARow: Integer): String; function TsCustomWorksheetGrid.GetCellHintText(ACol, ARow: Integer): String;
var var
cell: PCell; cell: PCell;
hyperlink: PsHyperlink;
comment: String;
begin begin
cell := Worksheet.FindCell(GetWorksheetRow(ARow), GetWorksheetCol(ACol)); cell := Worksheet.FindCell(GetWorksheetRow(ARow), GetWorksheetCol(ACol));
Result := Worksheet.ReadComment(cell); if cell = nil then
Result := ''
else
begin
// Read comment
comment := Worksheet.ReadComment(cell);
// Read hyperlink info
if Worksheet.IsHyperlink(cell) then begin
hyperlink := Worksheet.FindHyperlink(cell);
if hyperlink <> nil then
begin
if hyperlink^.ToolTip <> '' then
Result := hyperlink^.ToolTip
else
Result := Format('Hyperlink: %s' + LineEnding + rsStdHyperlinkTooltip,
[hyperlink^.Target]
);
end;
end;
// Combine comment and hyperlink
if (Result <> '') and (comment <> '') then
Result := comment + LineEnding + LineEnding + Result
else
if (Result = '') and (comment <> '') then
Result := comment;
end;
if Assigned(OnGetCellHint) then if Assigned(OnGetCellHint) then
OnGetCellHint(self, ACol, ARow, Result); OnGetCellHint(self, ACol, ARow, Result);
end; end;
@ -3772,7 +3798,7 @@ var
fmt: PsCellFormat; fmt: PsCellFormat;
begin begin
Result := Worksheet.ReadAsUTF8Text(ACell); Result := Worksheet.ReadAsUTF8Text(ACell);
if (Result = '') or ((ACell <> nil) and (ACell^.ContentType in [cctUTF8String, cctHyperlink])) if (Result = '') or ((ACell <> nil) and (ACell^.ContentType = cctUTF8String))
then then
exit; exit;

View File

@ -115,8 +115,6 @@ type
const AValue: TsErrorValue; ACell: PCell); virtual; abstract; const AValue: TsErrorValue; ACell: PCell); virtual; abstract;
procedure WriteFormula(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteFormula(AStream: TStream; const ARow, ACol: Cardinal;
ACell: PCell); virtual; ACell: PCell); virtual;
procedure WriteHyperlink(AStream: TStream; const ARow, ACol: Cardinal;
ACell: PCell); virtual;
procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal;
const AValue: string; ACell: PCell); virtual; abstract; const AValue: string; ACell: PCell); virtual; abstract;
procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal;
@ -582,8 +580,6 @@ begin
WriteNumber(AStream, ACell^.Row, ACell^.Col, ACell^.NumberValue, ACell); WriteNumber(AStream, ACell^.Row, ACell^.Col, ACell^.NumberValue, ACell);
cctUTF8String: cctUTF8String:
WriteLabel(AStream, ACell^.Row, ACell^.Col, ACell^.UTF8StringValue, ACell); WriteLabel(AStream, ACell^.Row, ACell^.Col, ACell^.UTF8StringValue, ACell);
cctHyperlink:
WriteHyperlink(AStream, ACell^.Row, ACell^.Col, ACell);
end; end;
if FWorksheet.ReadComment(ACell) <> '' then if FWorksheet.ReadComment(ACell) <> '' then
@ -636,23 +632,6 @@ begin
Unused(ARow, ACol, ACell); Unused(ARow, ACol, ACell);
end; end;
{@@ ----------------------------------------------------------------------------
Basic method which is called when writing a hyperlink to a stream.
Present implementation does nothing. Needs to be overridden by descendants.
@param AStream Stream to be written
@param ARow Row index of the cell containing the hyperlink
@param ACol Column index of the cell containing the formula
@param ACell Pointer to the cell containing the hyperlink and
being written to the stream
-------------------------------------------------------------------------------}
procedure TsCustomSpreadWriter.WriteHyperlink(AStream: TStream;
const ARow, ACol: Cardinal; ACell: PCell);
begin
Unused(AStream);
Unused(ARow, ACol, ACell);
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Default file writing method. Default file writing method.

View File

@ -55,7 +55,11 @@ resourcestring
rsIndexInSSTOutOfRange = 'Index %d in SST out of range (0-%d).'; rsIndexInSSTOutOfRange = 'Index %d in SST out of range (0-%d).';
rsAmbiguousDecThouSeparator = 'Assuming usage of decimal separator in "%s".'; rsAmbiguousDecThouSeparator = 'Assuming usage of decimal separator in "%s".';
rsCodePageNotSupported = 'Code page "%s" is not supported. Using "cp1252" (Latin 1) instead.'; rsCodePageNotSupported = 'Code page "%s" is not supported. Using "cp1252" (Latin 1) instead.';
rsHyperlinkNotAValidCell = 'Hyperlink target "%s" is not a valid cell address.'; rsNoValidHyperlinkInternal = 'The hyperlink "%s" is not a valid cell address.';
rsNoValidHyperlinkURI = 'The hyperlink "%s" is not a valid URI.';
rsEmptyHyperlink = 'The hyperlink is not specified.';
rsODSHyperlinksOfTextCellsOnly = 'Cell %s: OpenDocument supports hyperlinks for text cells only.';
rsStdHyperlinkTooltip = 'Press the left mouse button a bit longer to activate the hyperlink.';
rsTRUE = 'TRUE'; // wp: Do we really want to translate these strings? rsTRUE = 'TRUE'; // wp: Do we really want to translate these strings?

View File

@ -130,7 +130,7 @@ type
{@@ Describes the <b>type of content</b> in a cell of a TsWorksheet } {@@ Describes the <b>type of content</b> in a cell of a TsWorksheet }
TCellContentType = (cctEmpty, cctFormula, cctNumber, cctUTF8String, TCellContentType = (cctEmpty, cctFormula, cctNumber, cctUTF8String,
cctDateTime, cctBool, cctError, cctHyperlink); cctDateTime, cctBool, cctError);
{@@ The record TsComment describes a comment attached to a cell. {@@ The record TsComment describes a comment attached to a cell.
@param Row (0-based) row index of the cell @param Row (0-based) row index of the cell
@ -143,22 +143,19 @@ type
{@@ Pointer to a TsComment record } {@@ Pointer to a TsComment record }
PsComment = ^TsComment; PsComment = ^TsComment;
(*
{@@ Specifies whether a hyperlink refers to a cell address within the current {@@ Specifies whether a hyperlink refers to an internal cell address
workbook, or a URI } within the current workbook, or a URI (file://, http://, mailto, etc). }
TsHyperlinkKind = (hkNone, hkCell, hkURI); TsHyperlinkKind = (hkNone, hkInternal, hkURI);
*)
{@@ The record TsHyperlink contains info on a hyperlink in a cell {@@ The record TsHyperlink contains info on a hyperlink in a cell
@param Row Row index of the cell containing the hyperlink @param Row Row index of the cell containing the hyperlink
@param Col Column index of the cell containing the hyperlink @param Col Column index of the cell containing the hyperlink
@param Kind Specifies whether clicking on the hyperlink results in @param Target Target of hyperlink: URI of file, web link, mail; or:
jumping the a cell address within the current workbook, internal link (# followed by cell address)
or opens a URL
@param Target Target of hyperlink (cell address, filename, URL)
@param Note Text displayed as a popup hint by Excel } @param Note Text displayed as a popup hint by Excel }
TsHyperlink = record TsHyperlink = record
Row, Col: Cardinal; Row, Col: Cardinal;
Kind: TsHyperlinkKind;
Target: String; Target: String;
Tooltip: String; Tooltip: String;
end; end;
@ -450,7 +447,7 @@ type
TsCalcState = (csNotCalculated, csCalculating, csCalculated); TsCalcState = (csNotCalculated, csCalculating, csCalculated);
{@@ Cell flag } {@@ Cell flag }
TsCellFlag = (cfCalculating, cfCalculated, cfHasComment, cfMerged); TsCellFlag = (cfCalculating, cfCalculated, cfHasComment, cfHyperlink, cfMerged);
{@@ Set of cell flags } {@@ Set of cell flags }
TsCellFlags = set of TsCellFlag; TsCellFlags = set of TsCellFlag;

View File

@ -87,6 +87,8 @@ type
procedure ReadCONTINUE(const AStream: TStream); procedure ReadCONTINUE(const AStream: TStream);
procedure ReadFONT(const AStream: TStream); procedure ReadFONT(const AStream: TStream);
procedure ReadFORMAT(AStream: TStream); override; procedure ReadFORMAT(AStream: TStream); override;
procedure ReadHyperLink(AStream: TStream);
procedure ReadHyperlinkToolTip(AStream: TStream);
procedure ReadLABEL(AStream: TStream); override; procedure ReadLABEL(AStream: TStream); override;
procedure ReadLabelSST(const AStream: TStream); procedure ReadLabelSST(const AStream: TStream);
procedure ReadMergedCells(const AStream: TStream); procedure ReadMergedCells(const AStream: TStream);
@ -135,12 +137,10 @@ type
procedure WriteFont(AStream: TStream; AFont: TsFont); procedure WriteFont(AStream: TStream; AFont: TsFont);
procedure WriteFonts(AStream: TStream); procedure WriteFonts(AStream: TStream);
procedure WriteIndex(AStream: TStream); procedure WriteIndex(AStream: TStream);
procedure WriteHyperlink(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteHyperlink(AStream: TStream; AHyperlink: PsHyperlink;
ACell: PCell); override;
procedure WriteHYPERLINKRecord(AStream: TStream; AHyperlink: PsHyperlink;
AWorksheet: TsWorksheet); AWorksheet: TsWorksheet);
procedure WriteHyperlinks(AStream: TStream; AWorksheet: TsWorksheet); procedure WriteHyperlinks(AStream: TStream; AWorksheet: TsWorksheet);
procedure WriteHYPERLINKTOOLTIP(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteHyperlinkToolTip(AStream: TStream; const ARow, ACol: Cardinal;
const ATooltip: String); const ATooltip: String);
procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal;
const AValue: string; ACell: PCell); override; const AValue: string; ACell: PCell); override;
@ -259,7 +259,7 @@ var
implementation implementation
uses uses
Math, lconvencoding, URIParser, DOS, Math, lconvencoding, URIParser,
fpsStrings, fpsStreams, fpsReaderWriter, fpsExprParser, xlsEscher; fpsStrings, fpsStreams, fpsReaderWriter, fpsExprParser, xlsEscher;
const const
@ -270,7 +270,7 @@ const
INT_EXCEL_ID_LABELSST = $00FD; // BIFF8 only INT_EXCEL_ID_LABELSST = $00FD; // BIFF8 only
INT_EXCEL_ID_TXO = $01B6; // BIFF8 only INT_EXCEL_ID_TXO = $01B6; // BIFF8 only
INT_EXCEL_ID_HYPERLINK = $01B8; // BIFF8 only INT_EXCEL_ID_HYPERLINK = $01B8; // BIFF8 only
INT_EXCEL_ID_HYPERLINKTOOLTIP = $0800; // BIFF8 only INT_EXCEL_ID_HLINKTOOLTIP = $0800; // BIFF8 only
{%H-}INT_EXCEL_ID_FORCEFULLCALCULATION = $08A3; {%H-}INT_EXCEL_ID_FORCEFULLCALCULATION = $08A3;
{ Excel OBJ subrecord IDs } { Excel OBJ subrecord IDs }
@ -688,6 +688,8 @@ begin
INT_EXCEL_ID_BOOLERROR : ReadBool(AStream); INT_EXCEL_ID_BOOLERROR : ReadBool(AStream);
INT_EXCEL_ID_CONTINUE : ReadCONTINUE(AStream); INT_EXCEL_ID_CONTINUE : ReadCONTINUE(AStream);
INT_EXCEL_ID_FORMULA : ReadFormula(AStream); INT_EXCEL_ID_FORMULA : ReadFormula(AStream);
INT_EXCEL_ID_HYPERLINK : ReadHyperlink(AStream);
INT_EXCEL_ID_HLINKTOOLTIP: ReadHyperlinkToolTip(AStream);
INT_EXCEL_ID_LABEL : ReadLabel(AStream); INT_EXCEL_ID_LABEL : ReadLabel(AStream);
INT_EXCEL_ID_MULBLANK : ReadMulBlank(AStream); INT_EXCEL_ID_MULBLANK : ReadMulBlank(AStream);
INT_EXCEL_ID_NOTE : ReadNOTE(AStream); INT_EXCEL_ID_NOTE : ReadNOTE(AStream);
@ -1418,11 +1420,182 @@ begin
NumFormatList.AnalyzeAndAdd(fmtIndex, fmtString); NumFormatList.AnalyzeAndAdd(fmtIndex, fmtString);
end; end;
{@@ ----------------------------------------------------------------------------
Reads a HYPERLINK record
-------------------------------------------------------------------------------}
procedure TsSpreadBIFF8Reader.ReadHyperlink(AStream: TStream);
var
row, col, row1, col1, row2, col2: word;
guid: TGUID;
flags: DWord;
widestr: widestring;
len: DWord;
isInternal: Boolean;
link: String;
linkDos: String;
mark: String;
dirUpCount: Word;
ansistr: ansistring;
size: DWord;
buf: array of byte;
begin
{ Row and column index range of cells using the hyperlink }
row1 := WordLEToN(AStream.ReadWord);
row2 := WordLEToN(AStream.ReadWord);
col1 := WordLEToN(AStream.ReadWord);
col2 := WordLEToN(AStream.ReadWord);
{ GUID of standard link }
AStream.ReadBuffer(guid, SizeOf(guid));
{ unknown DWord }
AStream.ReadDWord;
{ Flags }
flags := DWordLEToN(AStream.ReadDWord);
{ Description }
if flags and MASK_HLINK_DESCRIPTION = MASK_HLINK_DESCRIPTION then
begin
// not used because there is always a "normal" cell to which the hyperlink is associated.
// character count of description incl trailing zero
len := DWordLEToN(AStream.ReadDWord);
// Character array (16-bit characters, with trailing zero word)
SetLength(wideStr, len);
AStream.ReadBuffer(wideStr[1], len*SizeOf(wideChar));
end;
{ Target frame: external link (URI or local file) }
link := '';
if flags and MASK_HLINK_LINK <> 0 then
// if flags and (MASK_HLINK_LINK or MASK_HLINK_ABSOLUTE) = (MASK_HLINK_LINK or MASK_HLINK_ABSOLUTE) then
begin
AStream.ReadBuffer(guid, SizeOf(guid));
// Check for URL
if GuidToString(guid) = '{79EAC9E0-BAF9-11CE-8C82-00AA004BA90B}' then
begin
// Character count incl trailing zero
len := DWordLEToN(AStream.ReadDWord);
// Character array of URL (16-bit-characters, with trailing zero word)
SetLength(wideStr, len);
AStream.ReadBuffer(wideStr[1], len*SizeOf(wideChar));
link := UTF8Encode(wideStr);
end else
// Check for local file
if GuidToString(guid) = '{00000303-0000-0000-C000-000000000046}' then
begin
dirUpCount := WordLEToN(AStream.ReadWord);
// Character count of the shortened file path and name, incl trailing zero byte
len := DWordLEToN(AStream.ReadDWord);
// Character array of the shortened file path and name in 8.3-DOS-format.
// This field can be filled with a long file name too.
// No Unicode string header, always 8-bit characters, zeroterminated.
SetLength(ansiStr, len);
AStream.ReadBuffer(ansiStr[1], len*SizeOf(ansiChar));
linkDos := AnsiToUTF8(ansiStr);
while dirUpCount > 0 do
begin
linkDos := '..\' + linkDos;
dec(dirUpCount);
end;
// 6 unknown DWord values
AStream.ReadDWord;
AStream.ReadDWord;
AStream.ReadDWord;
AStream.ReadDWord;
AStream.ReadDWord;
AStream.ReadDWord;
// Size of the following file link field including string length field
// and additional data field
size := DWordLEToN(AStream.ReadDWord);
if size > 0 then
begin
// Size of the extended file path and name.
size := DWordLEToN(AStream.ReadDWord);
len := size div 2;
// Unknown
AStream.ReadWord;
// Character array of the extended file path and name
// no Unicode string header, always 16-bit characters, not zero-terminated
SetLength(wideStr, len);
AStream.ReadBuffer(wideStr[1], len*SizeOf(wideChar));
link := UTF8Encode(widestr);
end else
link := linkDos;
end;
end;
{ Text mark }
if flags and MASK_HLINK_TEXTMARK = MASK_HLINK_TEXTMARK then
begin
// Character count of the text mark, including trailing zero word
len := DWordLEToN(AStream.ReadDWord);
// Character array of the text mark without "#" sign
// no Unicode string header, always 16-bit characters, zero-terminated
SetLength(wideStr, len);
AStream.ReadBuffer(wideStr[1], len*SizeOf(wideChar));
mark := UTF8Encode(wideStr);
end;
// Add bookmark to hyperlink target
if (link <> '') and (mark <> '') then
link := link + '#' + mark
else
if (link = '') then
link := '#' + mark;
// Add hyperlink(s) to worksheet
for row := row1 to row2 do
for col := col1 to col2 do
FWorksheet.WriteHyperlink(row, col, link);
end;
{@@ ----------------------------------------------------------------------------
Reads a HYPERLINK TOOLTIP record
-------------------------------------------------------------------------------}
procedure TsSpreadBIFF8Reader.ReadHyperlinkToolTip(AStream: TStream);
var
txt: String;
widestr: widestring;
row, col, row1, col1, row2, col2: Word;
hyperlink: PsHyperlink;
numbytes: Integer;
begin
{ Record type; this matches the BIFF record type }
AStream.ReadWord;
{ Row and column index range of cells using the hyperlink tooltip }
row1 := WordLEToN(AStream.ReadWord);
row2 := WordLEToN(AStream.ReadWord);
col1 := WordLEToN(AStream.ReadWord);
col2 := WordLEToN(AStream.ReadWord);
{ Hyperlink tooltip, a null-terminated unicode string }
numbytes := RecordSize - 5*SizeOf(word);
SetLength(wideStr, numbytes div 2);
AStream.ReadBuffer(wideStr[1], numbytes);
txt := UTF8Encode(wideStr);
{ Add tooltip to hyperlinks }
for row := row1 to row2 do
for col := col1 to col2 do
begin
hyperlink := FWorksheet.FindHyperlink(row, col);
if hyperlink <> nil then
hyperlink^.ToolTip := txt;
end;
end;
{------------------------------------------------------------------------------} {------------------------------------------------------------------------------}
{ TsSpreadBIFF8Writer } { TsSpreadBIFF8Writer }
{------------------------------------------------------------------------------} {------------------------------------------------------------------------------}
{@@ ----------------------------------------------------------------------------
Constructor of the Excel 8 writer
-------------------------------------------------------------------------------}
constructor TsSpreadBIFF8Writer.Create(AWorkbook: TsWorkbook); constructor TsSpreadBIFF8Writer.Create(AWorkbook: TsWorkbook);
begin begin
inherited Create(AWorkbook); inherited Create(AWorkbook);
@ -2171,22 +2344,10 @@ begin
{ OBS: It seems to be no problem just ignoring this part of the record } { OBS: It seems to be no problem just ignoring this part of the record }
end; end;
{@@ ----------------------------------------------------------------------------
Inherited method for writing a hyperlink
Just writes the cell text; the hyperlink is written together with the other
hyperlinks later.
-------------------------------------------------------------------------------}
procedure TsSpreadBIFF8Writer.WriteHyperlink(AStream: TStream;
const ARow, ACol: Cardinal; ACell: PCell);
begin
WriteLabel(AStream, ARow, ACol, FWorksheet.ReadAsUTF8Text(ACell), ACell);
ACell^.ContentType := cctHyperlink;
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Writes an Excel 8 HYPERLINK record Writes an Excel 8 HYPERLINK record
-------------------------------------------------------------------------------} -------------------------------------------------------------------------------}
procedure TsSpreadBIFF8Writer.WriteHYPERLINKRecord(AStream: TStream; procedure TsSpreadBIFF8Writer.WriteHyperlink(AStream: TStream;
AHyperlink: PsHyperlink; AWorksheet: TsWorksheet); AHyperlink: PsHyperlink; AWorksheet: TsWorksheet);
var var
temp: TStream; temp: TStream;
@ -2196,16 +2357,18 @@ var
descr: String; descr: String;
fn: String; fn: String;
flags: DWord; flags: DWord;
markpos: Integer;
size: Integer; size: Integer;
cell: PCell; cell: PCell;
isInternal: Boolean;
target, bookmark: String;
begin begin
cell := AWorksheet.FindCell(AHyperlink^.Row, AHyperlink^.Col); cell := AWorksheet.FindCell(AHyperlink^.Row, AHyperlink^.Col);
if (cell = nil) or (AHyperlink^.Kind = hkNone) then if (cell = nil) or (AHyperlink^.Target='') then
exit; exit;
descr := AWorksheet.ReadAsUTF8Text(cell); // Hyperlink description descr := AWorksheet.ReadAsUTF8Text(cell); // Hyperlink description
markpos := UTF8Pos('#', AHyperlink^.Target); // Position of # in hyperlink target AWorksheet.SplitHyperlink(AHyperlink^.Target, target, bookmark);
isInternal := (target = '');
// Since the length of the record is not known in the first place we write // Since the length of the record is not known in the first place we write
// the data to a temporary stream at first. // the data to a temporary stream at first.
@ -2226,16 +2389,14 @@ begin
{ option flags } { option flags }
flags := 0; flags := 0;
case AHyperlink^.Kind of if isInternal then
hkCell: flags := MASK_HLINK_TEXTMARK or MASK_HLINK_DESCRIPTION
flags := MASK_HLINK_TEXTMARK or MASK_HLINK_DESCRIPTION; else
hkURI:
flags := MASK_HLINK_LINK or MASK_HLINK_ABSOLUTE; flags := MASK_HLINK_LINK or MASK_HLINK_ABSOLUTE;
end;
if descr <> AHyperlink^.Target then if descr <> AHyperlink^.Target then
flags := flags or MASK_HLINK_DESCRIPTION; // has description flags := flags or MASK_HLINK_DESCRIPTION; // has description
if markpos > 0 then // has # in target if bookmark <> '' then
flags := flags or MASK_HLINK_TEXTMARK; flags := flags or MASK_HLINK_TEXTMARK; // link contains a bookmark
temp.WriteDWord(DWordToLE(flags)); temp.WriteDWord(DWordToLE(flags));
{ description } { description }
@ -2248,9 +2409,9 @@ begin
temp.WriteBuffer(wideStr[1], (Length(wideStr)+1)*SizeOf(widechar)); temp.WriteBuffer(wideStr[1], (Length(wideStr)+1)*SizeOf(widechar));
end; end;
if AHyperlink^.Kind = hkURI then if target <> '' then
begin begin
if URIToFilename(AHyperlink^.Target, fn) then // URI is a local file if URIToFilename(target, fn) then // URI is a local file
begin begin
{ GUID of file moniker } { GUID of file moniker }
guid := StringToGuid('{00000303-0000-0000-C000-000000000046}'); guid := StringToGuid('{00000303-0000-0000-C000-000000000046}');
@ -2286,7 +2447,7 @@ begin
end; end;
end end
else begin { Hyperlink target is a URL } else begin { Hyperlink target is a URL }
widestr := UTF8Decode(AHyperlink^.Target); widestr := UTF8Decode(target);
{ GUID of URL Moniker } { GUID of URL Moniker }
guid := StringToGUID('{79EAC9E0-BAF9-11CE-8C82-00AA004BA90B}'); guid := StringToGUID('{79EAC9E0-BAF9-11CE-8C82-00AA004BA90B}');
temp.WriteBuffer(guid, SizeOf(guid)); temp.WriteBuffer(guid, SizeOf(guid));
@ -2298,13 +2459,10 @@ begin
end; // hkURI end; // hkURI
// Hyperlink contains a text mark (#) // Hyperlink contains a text mark (#)
if flags and MASK_HLINK_TEXTMARK <> 0 then if bookmark <> '' then
begin begin
// Extract text mark without "#" and convert to 16-bit characters // Convert to 16-bit characters
if markpos > 0 then widestr := UTF8Decode(bookmark);
widestr := UTF8Decode(UTF8Copy(AHyperlink^.Target, markpos+1, Length(AHyperlink^.Target)))
else if AHyperlink^.Kind = hkCell then
widestr := UTF8Decode(AHyperlink^.Target);
{ Character count of text mark, incl trailing zero } { Character count of text mark, incl trailing zero }
temp.WriteDWord(DWordToLE(Length(wideStr) + 1)); temp.WriteDWord(DWordToLE(Length(wideStr) + 1));
{ Character array (16-bit characters) plus trailing zeros } { Character array (16-bit characters) plus trailing zeros }
@ -2340,7 +2498,7 @@ procedure TsSpreadBIFF8Writer.WriteHyperlinksCallback(AHyperlink: PsHyperlink;
AStream: TStream); AStream: TStream);
begin begin
{ Write HYPERLINK record } { Write HYPERLINK record }
WriteHyperlinkRecord(AStream, AHyperlink, FWorksheet); WriteHyperlink(AStream, AHyperlink, FWorksheet);
{ Write HYPERLINK TOOLTIP record } { Write HYPERLINK TOOLTIP record }
if AHyperlink^.Tooltip <> '' then if AHyperlink^.Tooltip <> '' then
@ -2358,11 +2516,11 @@ begin
widestr := UTF8Decode(ATooltip); widestr := UTF8Decode(ATooltip);
{ BIFF record header } { BIFF record header }
WriteBiffHeader(AStream, INT_EXCEL_ID_HYPERLINKTOOLTIP, WriteBiffHeader(AStream, INT_EXCEL_ID_HLINKTOOLTIP,
10 + (Length(wideStr)+1) * SizeOf(widechar)); 10 + (Length(wideStr)+1) * SizeOf(widechar));
{ Repeated record ID } { Repeated record ID }
AStream.WriteWord(WordToLe(INT_EXCEL_ID_HYPERLINKTOOLTIP)); AStream.WriteWord(WordToLe(INT_EXCEL_ID_HLINKTOOLTIP));
{ Cell range using the same hyperlink tooltip - we support only single cells } { Cell range using the same hyperlink tooltip - we support only single cells }
AStream.WriteWord(WordToLE(ARow)); // first row AStream.WriteWord(WordToLE(ARow)); // first row

View File

@ -180,8 +180,6 @@ type
const AValue: TsErrorValue; ACell: PCell); override; const AValue: TsErrorValue; ACell: PCell); override;
procedure WriteFormula(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteFormula(AStream: TStream; const ARow, ACol: Cardinal;
ACell: PCell); override; ACell: PCell); override;
procedure WriteHyperlink(AStream: TStream; const ARow, ACol: Cardinal;
ACell: PCell); override;
procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteLabel(AStream: TStream; const ARow, ACol: Cardinal;
const AValue: string; ACell: PCell); override; const AValue: string; ACell: PCell); override;
procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal; procedure WriteNumber(AStream: TStream; const ARow, ACol: Cardinal;
@ -351,8 +349,8 @@ type
THyperlinkListData = class THyperlinkListData = class
ID: String; ID: String;
CellRef: String; CellRef: String;
Kind: TsHyperlinkKind; Target: String;
Location: String; TextMark: String;
Display: String; Display: String;
Tooltip: String; Tooltip: String;
end; end;
@ -531,7 +529,13 @@ begin
for r := r1 to r2 do for r := r1 to r2 do
for c := c1 to c2 do for c := c1 to c2 do
with hyperlinkData do with hyperlinkData do
AWorksheet.WriteHyperlink(r, c, Kind, Location, Display, ToolTip); if Target = '' then
AWorksheet.WriteHyperlink(r, c, '#'+TextMark, ToolTip)
else
if TextMark = '' then
AWorksheet.WriteHyperlink(r, c, Target, ToolTip)
else
AWorksheet.WriteHyperlink(r, c, Target+'#'+TextMark, ToolTip);
end; end;
end; end;
@ -1276,10 +1280,10 @@ begin
hyperlinkData := THyperlinkListData.Create; hyperlinkData := THyperlinkListData.Create;
hyperlinkData.CellRef := GetAttrValue(node, 'ref'); hyperlinkData.CellRef := GetAttrValue(node, 'ref');
hyperlinkData.ID := GetAttrValue(node, 'r:id'); hyperlinkData.ID := GetAttrValue(node, 'r:id');
hyperlinkData.Location := GetAttrValue(node, 'location'); hyperlinkData.Target := '';
hyperlinkData.TextMark := GetAttrValue(node, 'location');
hyperlinkData.Display := GetAttrValue(node, 'display'); hyperlinkData.Display := GetAttrValue(node, 'display');
hyperlinkData.Tooltip := GetAttrValue(node, 'tooltip'); hyperlinkData.Tooltip := GetAttrValue(node, 'tooltip');
hyperlinkData.Kind := hkCell;
end; end;
FHyperlinkList.Add(hyperlinkData); FHyperlinkList.Add(hyperlinkData);
node := node.NextSibling; node := node.NextSibling;
@ -1302,10 +1306,13 @@ begin
hyperlinkData := FindHyperlinkID(s); hyperlinkData := FindHyperlinkID(s);
if hyperlinkData <> nil then begin if hyperlinkData <> nil then begin
s := GetAttrValue(node, 'Target'); s := GetAttrValue(node, 'Target');
if s <> '' then hyperlinkData.Location := s; if s <> '' then hyperlinkData.Target := s;
s := GetAttrValue(node, 'TargetMode'); s := GetAttrValue(node, 'TargetMode');
if s = 'External' then if s <> 'External' then // Only "External" accepted!
hyperlinkData.Kind := hkURI begin
hyperlinkData.Target := '';
hyperlinkData.TextMark := '';
end;
end; end;
end; end;
end; end;
@ -2182,6 +2189,7 @@ procedure TsSpreadOOXMLWriter.WriteHyperlinks(AStream: TStream;
AWorksheet: TsWorksheet); AWorksheet: TsWorksheet);
var var
hyperlink: PsHyperlink; hyperlink: PsHyperlink;
target, bookmark: String;
s: String; s: String;
txt: String; txt: String;
AVLNode: TAVLTreeNode; AVLNode: TAVLTreeNode;
@ -2198,14 +2206,15 @@ begin
AVLNode := AWorksheet.Hyperlinks.FindLowest; AVLNode := AWorksheet.Hyperlinks.FindLowest;
while AVLNode <> nil do begin while AVLNode <> nil do begin
hyperlink := PsHyperlink(AVLNode.Data); hyperlink := PsHyperlink(AVLNode.Data);
AWorksheet.SplitHyperlink(hyperlink^.Target, target, bookmark);
s := Format('ref="%s"', [GetCellString(hyperlink^.Row, hyperlink^.Col)]); s := Format('ref="%s"', [GetCellString(hyperlink^.Row, hyperlink^.Col)]);
if hyperlink^.Kind <> hkCell then if target <> '' then
begin begin
s := Format('%s r:id="rId%d"', [s, FNext_rId]); s := Format('%s r:id="rId%d"', [s, FNext_rId]);
inc(FNext_rId); inc(FNext_rId);
end; end;
if hyperlink^.Kind = hkCell then if target = '' then
s := Format('%s location="%s"', [s, hyperlink^.Target]); s := Format('%s location="%s"', [s, bookmark]);
txt := AWorksheet.ReadAsUTF8Text(hyperlink^.Row, hyperlink^.Col); txt := AWorksheet.ReadAsUTF8Text(hyperlink^.Row, hyperlink^.Col);
if (txt <> '') and (txt <> hyperlink^.Target) then if (txt <> '') and (txt <> hyperlink^.Target) then
s := Format('%s display="%s"', [s, txt]); s := Format('%s display="%s"', [s, txt]);
@ -2695,12 +2704,10 @@ begin
while Assigned(AVLNode) do while Assigned(AVLNode) do
begin begin
hyperlink := PsHyperlink(AVLNode.Data); hyperlink := PsHyperlink(AVLNode.Data);
if hyperlink^.Kind <> hkCell then if hyperlink^.Target <> '' then
begin begin
s := Format('Id="rId%d" Type="%s" Target="%s"', s := Format('Id="rId%d" Type="%s" Target="%s" TargetMode="External"',
[FNext_rId, SCHEMAS_HYPERLINKS, hyperlink^.Target]); [FNext_rId, SCHEMAS_HYPERLINKS, hyperlink^.Target]);
if hyperlink^.Kind <> hkCell then
s := s + ' TargetMode="External"';
AppendToStream(FSSheetRels[FCurSheetNum], AppendToStream(FSSheetRels[FCurSheetNum],
'<Relationship ' + s + ' />'); '<Relationship ' + s + ' />');
inc(FNext_rId); inc(FNext_rId);
@ -3289,13 +3296,6 @@ begin
end; end;
end; end;
procedure TsSpreadOOXMLWriter.WriteHyperlink(AStream: TStream;
const ARow, ACol: Cardinal; ACell: PCell);
begin
if FWorksheet.IsHyperlink(ACell) then
WriteLabel(AStream, ARow, ACol, FWorksheet.ReadAsUTF8Text(ACell), ACell);
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Writes a string to the stream Writes a string to the stream