fpspreadsheet: Initial support for reading html files (data only, no formats, no nested tables).

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@4236 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2015-08-01 22:11:44 +00:00
parent fb75c2bef1
commit 7c8e0a8b3d
18 changed files with 1383 additions and 333 deletions

View File

@@ -23,7 +23,7 @@ begin
MyDir := ExtractFilePath(ParamStr(0)); MyDir := ExtractFilePath(ParamStr(0));
InputFileName := MyDir + 'test' + STR_COMMA_SEPARATED_EXTENSION; InputFileName := MyDir + 'test' + STR_COMMA_SEPARATED_EXTENSION;
if not FileExists(InputFileName) then begin if not FileExists(InputFileName) then begin
WriteLn('Input file ', InputFileName, ' does not exist. Please run excel2write first.'); WriteLn('Input file ', InputFileName, ' does not exist. Please run csvwrite first.');
Halt; Halt;
end; end;

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<CONFIG>
<ProjectOptions>
<Version Value="9"/>
<PathDelim Value="\"/>
<General>
<Flags>
<LRSInOutputDirectory Value="False"/>
</Flags>
<SessionStorage Value="InProjectDir"/>
<MainUnit Value="0"/>
<Title Value="htmlread"/>
<UseAppBundle Value="False"/>
</General>
<VersionInfo>
<StringTable ProductVersion=""/>
</VersionInfo>
<BuildModes Count="1">
<Item1 Name="default" Default="True"/>
</BuildModes>
<PublishOptions>
<Version Value="2"/>
<IgnoreBinaries Value="False"/>
<IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/>
<ExcludeFileFilter Value="*.(bak|ppu|ppw|o|so);*~;backup"/>
</PublishOptions>
<RunParams>
<local>
<FormatVersion Value="1"/>
<LaunchingApplication PathPlusParams="\usr\X11R6\bin\xterm -T 'Lazarus Run Output' -e $(LazarusDir)\tools\runwait.sh $(TargetCmdLine)"/>
</local>
</RunParams>
<RequiredPackages Count="1">
<Item1>
<PackageName Value="LazUtils"/>
</Item1>
</RequiredPackages>
<Units Count="1">
<Unit0>
<Filename Value="htmlread.lpr"/>
<IsPartOfProject Value="True"/>
</Unit0>
</Units>
</ProjectOptions>
<CompilerOptions>
<Version Value="11"/>
<PathDelim Value="\"/>
<Target>
<Filename Value="htmlread"/>
</Target>
<SearchPaths>
<OtherUnitFiles Value="..\..\.."/>
<UnitOutputDirectory Value="..\..\lib\$(TargetCPU)-$(TargetOS)"/>
</SearchPaths>
<Parsing>
<SyntaxOptions>
<UseAnsiStrings Value="False"/>
</SyntaxOptions>
</Parsing>
<Linking>
<Debugging>
<DebugInfoType Value="dsDwarf2Set"/>
</Debugging>
</Linking>
</CompilerOptions>
</CONFIG>

View File

@@ -0,0 +1,70 @@
{
htmlread.dpr
Demonstrates how to read a html file using the fpspreadsheet library.
IMPORTANT: Requires the output file of the htmlwrite demo.
}
program htmlread;
{$mode delphi}{$H+}
uses
Classes, SysUtils, LazUTF8, fpstypes, fpsutils, fpspreadsheet, fpshtml;
var
MyWorkbook: TsWorkbook;
MyWorksheet: TsWorksheet;
InputFilename: string;
MyDir: string;
i: Integer;
CurCell: PCell;
begin
// Open the input file
MyDir := ExtractFilePath(ParamStr(0));
InputFileName := MyDir + 'test' + STR_HTML_EXTENSION;
if not FileExists(InputFileName) then begin
WriteLn('Input file ', InputFileName, ' does not exist. Please run htmlwrite first.');
Halt;
end;
WriteLn('Opening input file ', InputFilename);
// Parameters
HTMLParams.TableIndex := 0;
// Create the spreadsheet
MyWorkbook := TsWorkbook.Create;
try
MyWorkbook.Options := MyWorkbook.Options + [boReadFormulas];
MyWorkbook.ReadFromFile(InputFilename, sfHTML);
MyWorksheet := MyWorkbook.GetFirstWorksheet;
// Write all cells with contents to the console
WriteLn('');
WriteLn('Contents of the first worksheet of the file:');
WriteLn('');
for CurCell in MyWorksheet.Cells do
begin
WriteLn(
'Row: ', CurCell^.Row,
' Col: ', CurCell^.Col,
' Value: ', UTF8ToConsole(MyWorkSheet.ReadAsUTF8Text(CurCell^.Row, CurCell^.Col))
);
end;
finally
// Finalization
MyWorkbook.Free;
end;
{$IFDEF MSWINDOWS}
WriteLn;
WriteLn('Press ENTER to exit.');
ReadLn;
{$ENDIF}
end.

View File

@@ -1 +1,3 @@
This demo demonstrates how to use fpspreadsheet to read and write html files which can be opened by the browser. This demo demonstrates how to use fpspreadsheet to read and write html files which can be opened by the browser.
Please run the write demo before the read demo in order to create the required spreadsheet file.

View File

@@ -641,7 +641,7 @@ object MainForm: TMainForm
end end
object OpenDialog: TOpenDialog object OpenDialog: TOpenDialog
DefaultExt = '.xls' DefaultExt = '.xls'
Filter = 'All spreadsheet files|*.xls;*.xlsx;*.ods;*.csv|All Excel files (*.xls, *.xlsx)|*.xls;*.xlsx|Excel XML spreadsheet (*.xlsx)|*.xlsx|Excel 97-2003 spreadsheets (*.xls)|*.xls|Excel 5 spreadsheet (*.xls)|*.xls|Excel 2.1 spreadsheets (*.xls)|*.xls|LibreOffice/OpenOffice spreadsheet (*.ods)|*.ods|Comma-delimited files (*.csv)|*.csv' Filter = 'All spreadsheet files|*.xls;*.xlsx;*.ods;*.csv|All Excel files (*.xls, *.xlsx)|*.xls;*.xlsx|Excel XML spreadsheet (*.xlsx)|*.xlsx|Excel 97-2003 spreadsheets (*.xls)|*.xls|Excel 5 spreadsheet (*.xls)|*.xls|Excel 2.1 spreadsheets (*.xls)|*.xls|LibreOffice/OpenOffice spreadsheet (*.ods)|*.ods|HTML files (*.html; *.htm)|*.html;*.htm|Comma-delimited files (*.csv)|*.csv'
Options = [ofExtensionDifferent, ofEnableSizing, ofViewDetail] Options = [ofExtensionDifferent, ofEnableSizing, ofViewDetail]
left = 312 left = 312
top = 160 top = 160
@@ -943,7 +943,7 @@ object MainForm: TMainForm
object AcFileOpen: TFileOpen object AcFileOpen: TFileOpen
Category = 'File' Category = 'File'
Caption = '&Open ...' Caption = '&Open ...'
Dialog.Filter = 'All spreadsheet files|*.xls;*.xlsx;*.ods;*.csv|All Excel files (*.xls, *.xlsx)|*.xls;*.xlsx|Excel XML spreadsheet (*.xlsx)|*.xlsx|Excel 97-2003 spreadsheets (*.xls)|*.xls|Excel 5 spreadsheet (*.xls)|*.xls|Excel 2.1 spreadsheets (*.xls)|*.xls|LibreOffice/OpenOffice spreadsheet (*.ods)|*.ods|Comma-delimited files (*.csv)|*.csv' Dialog.Filter = 'All supported spreadsheet files|*.xls;*.xlsx;*.ods;*.csv;*.html;*.htm|All Excel files (*.xls, *.xlsx)|*.xls;*.xlsx|Excel XML spreadsheet (*.xlsx)|*.xlsx|Excel 97-2003 spreadsheets (*.xls)|*.xls|Excel 5 spreadsheet (*.xls)|*.xls|Excel 2.1 spreadsheets (*.xls)|*.xls|LibreOffice/OpenOffice spreadsheet (*.ods)|*.ods|Comma-delimited files (*.csv)|*.csv|HTML files (*.html; *.htm)|*.html;*.htm'
Dialog.Options = [ofExtensionDifferent, ofFileMustExist, ofEnableSizing, ofViewDetail] Dialog.Options = [ofExtensionDifferent, ofFileMustExist, ofEnableSizing, ofViewDetail]
Hint = 'Open spreadsheet file' Hint = 'Open spreadsheet file'
ImageIndex = 44 ImageIndex = 44

View File

@@ -460,6 +460,7 @@ var
F: TNumFormatForm; F: TNumFormatForm;
sample: Double; sample: Double;
begin begin
Unused(AWorkbook);
F := TNumFormatForm.Create(nil); F := TNumFormatForm.Create(nil);
try try
F.Position := poMainFormCenter; F.Position := poMainFormCenter;

0
components/fpspreadsheet/fpolestorage.pas Executable file → Normal file
View File

View File

@@ -13,11 +13,6 @@ type
private private
FWorksheetName: String; FWorksheetName: String;
FFormatSettings: TFormatSettings; FFormatSettings: TFormatSettings;
function IsBool(AText: String; out AValue: Boolean): Boolean;
function IsDateTime(AText: String; out ADateTime: TDateTime;
out ANumFormat: TsNumberFormat): Boolean;
function IsNumber(AText: String; out ANumber: Double; out ANumFormat: TsNumberFormat;
out ADecimals: Integer; out ACurrencySymbol, AWarning: String): Boolean;
function IsQuotedText(var AText: String): Boolean; function IsQuotedText(var AText: String): Boolean;
procedure ReadCellValue(ARow, ACol: Cardinal; AText: String); procedure ReadCellValue(ARow, ACol: Cardinal; AText: String);
protected protected
@@ -96,93 +91,7 @@ implementation
uses uses
//StrUtils, //StrUtils,
DateUtils, LConvEncoding, Math, DateUtils, LConvEncoding, Math,
fpsUtils, fpsCurrency, fpsNumFormat; fpsUtils, fpsNumFormat;
{ Initializes the FormatSettings of the CSVParams to default values which
can be replaced by the FormatSettings of the workbook's FormatSettings }
procedure InitCSVFormatSettings;
var
i: Integer;
begin
with CSVParams.FormatSettings do
begin
CurrencyFormat := Byte(-1);
NegCurrFormat := Byte(-1);
ThousandSeparator := #0;
DecimalSeparator := #0;
CurrencyDecimals := Byte(-1);
DateSeparator := #0;
TimeSeparator := #0;
ListSeparator := #0;
CurrencyString := '';
ShortDateFormat := '';
LongDateFormat := '';
TimeAMString := '';
TimePMString := '';
ShortTimeFormat := '';
LongTimeFormat := '';
for i:=1 to 12 do
begin
ShortMonthNames[i] := '';
LongMonthNames[i] := '';
end;
for i:=1 to 7 do
begin
ShortDayNames[i] := '';
LongDayNames[i] := '';
end;
TwoDigitYearCenturyWindow := Word(-1);
end;
end;
procedure ReplaceFormatSettings(var AFormatSettings: TFormatSettings;
const ADefaultFormats: TFormatSettings);
var
i: Integer;
begin
if AFormatSettings.CurrencyFormat = Byte(-1) then
AFormatSettings.CurrencyFormat := ADefaultFormats.CurrencyFormat;
if AFormatSettings.NegCurrFormat = Byte(-1) then
AFormatSettings.NegCurrFormat := ADefaultFormats.NegCurrFormat;
if AFormatSettings.ThousandSeparator = #0 then
AFormatSettings.ThousandSeparator := ADefaultFormats.ThousandSeparator;
if AFormatSettings.DecimalSeparator = #0 then
AFormatSettings.DecimalSeparator := ADefaultFormats.DecimalSeparator;
if AFormatSettings.CurrencyDecimals = Byte(-1) then
AFormatSettings.CurrencyDecimals := ADefaultFormats.CurrencyDecimals;
if AFormatSettings.DateSeparator = #0 then
AFormatSettings.DateSeparator := ADefaultFormats.DateSeparator;
if AFormatSettings.TimeSeparator = #0 then
AFormatSettings.TimeSeparator := ADefaultFormats.TimeSeparator;
if AFormatSettings.ListSeparator = #0 then
AFormatSettings.ListSeparator := ADefaultFormats.ListSeparator;
if AFormatSettings.CurrencyString = '' then
AFormatSettings.CurrencyString := ADefaultFormats.CurrencyString;
if AFormatSettings.ShortDateFormat = '' then
AFormatSettings.ShortDateFormat := ADefaultFormats.ShortDateFormat;
if AFormatSettings.LongDateFormat = '' then
AFormatSettings.LongDateFormat := ADefaultFormats.LongDateFormat;
if AFormatSettings.ShortTimeFormat = '' then
AFormatSettings.ShortTimeFormat := ADefaultFormats.ShortTimeFormat;
if AFormatSettings.LongTimeFormat = '' then
AFormatSettings.LongTimeFormat := ADefaultFormats.LongTimeFormat;
for i:=1 to 12 do
begin
if AFormatSettings.ShortMonthNames[i] = '' then
AFormatSettings.ShortMonthNames[i] := ADefaultFormats.ShortMonthNames[i];
if AFormatSettings.LongMonthNames[i] = '' then
AFormatSettings.LongMonthNames[i] := ADefaultFormats.LongMonthNames[i];
end;
for i:=1 to 7 do
begin
if AFormatSettings.ShortDayNames[i] = '' then
AFormatSettings.ShortDayNames[i] := ADefaultFormats.ShortDayNames[i];
if AFormatSettings.LongDayNames[i] = '' then
AFormatSettings.LongDayNames[i] := ADefaultFormats.LongDayNames[i];
end;
if AFormatSettings.TwoDigitYearCenturyWindow = Word(-1) then
AFormatSettings.TwoDigitYearCenturyWindow := ADefaultFormats.TwoDigitYearCenturyWindow;
end;
function LineEndingAsString(ALineEnding: TsCSVLineEnding): String; function LineEndingAsString(ALineEnding: TsCSVLineEnding): String;
begin begin
@@ -207,152 +116,6 @@ begin
ReplaceFormatSettings(FFormatSettings, FWorkbook.FormatSettings); ReplaceFormatSettings(FFormatSettings, FWorkbook.FormatSettings);
end; end;
function TsCSVReader.IsBool(AText: String; out AValue: Boolean): Boolean;
begin
if SameText(AText, CSVParams.TrueText) then
begin
AValue := true;
Result := true;
end else
if SameText(AText, CSVParams.FalseText) then
begin
AValue := false;
Result := true;
end else
Result := false;
end;
function TsCSVReader.IsDateTime(AText: String; out ADateTime: TDateTime;
out ANumFormat: TsNumberFormat): Boolean;
{ Test whether the text is formatted according to a built-in date/time format.
Converts the obtained date/time value back to a string and compares. }
function TestFormat(lNumFmt: TsNumberFormat): Boolean;
var
fmt: string;
begin
fmt := BuildDateTimeFormatString(lNumFmt, FFormatSettings);
Result := FormatDateTime(fmt, ADateTime, FFormatSettings) = AText;
if Result then ANumFormat := lNumFmt;
end;
begin
Result := TryStrToDateTime(AText, ADateTime, FFormatSettings);
if Result then
begin
ANumFormat := nfCustom;
if abs(ADateTime) > 1 then // this is most probably a date
begin
if TestFormat(nfShortDateTime) then
exit;
if TestFormat(nfLongDate) then
exit;
if TestFormat(nfShortDate) then
exit;
end else
begin // this case is time-only
if TestFormat(nfLongTimeAM) then
exit;
if TestFormat(nfLongTime) then
exit;
if TestFormat(nfShortTimeAM) then
exit;
if TestFormat(nfShortTime) then
exit;
end;
end;
end;
function TsCSVReader.IsNumber(AText: String; out ANumber: Double;
out ANumFormat: TsNumberFormat; out ADecimals: Integer;
out ACurrencySymbol, AWarning: String): Boolean;
var
p: Integer;
DecSep, ThousSep: Char;
begin
Result := false;
AWarning := '';
// To detect whether the text is a currency value we look for the currency
// string. If we find it, we delete it and convert the remaining string to
// a number.
ACurrencySymbol := FFormatSettings.CurrencyString;
if RemoveCurrencySymbol(ACurrencySymbol, AText) then
begin
if IsNegative(AText) then
begin
if AText = '' then
exit;
AText := '-' + AText;
end;
end else
ACurrencySymbol := '';
if CSVParams.AutoDetectNumberFormat then
Result := TryStrToFloatAuto(AText, ANumber, DecSep, ThousSep, AWarning)
else begin
Result := TryStrToFloat(AText, ANumber, FFormatSettings);
if Result then
begin
if pos(FFormatSettings.DecimalSeparator, AText) = 0
then DecSep := #0
else DecSep := FFormatSettings.DecimalSeparator;
if pos(CSVParams.FormatSettings.ThousandSeparator, AText) = 0
then ThousSep := #0
else ThousSep := FFormatSettings.ThousandSeparator;
end;
end;
// Try to determine the number format
if Result then
begin
if ThousSep <> #0 then
ANumFormat := nfFixedTh
else
ANumFormat := nfGeneral;
// count number of decimal places and try to catch special formats
ADecimals := 0;
if DecSep <> #0 then
begin
// Go to the decimal separator and search towards the end of the string
p := pos(DecSep, AText) + 1;
while (p <= Length(AText)) do begin
// exponential format
if AText[p] in ['+', '-', 'E', 'e'] then
begin
ANumFormat := nfExp;
break;
end else
// percent format
if AText[p] = '%' then
begin
ANumFormat := nfPercentage;
break;
end else
begin
inc(p);
inc(ADecimals);
end;
end;
if (ADecimals > 0) and (ADecimals < 9) and (ANumFormat = nfGeneral) then
// "no formatting" assumed if there are "many" decimals
ANumFormat := nfFixed;
end else
begin
p := Length(AText);
while (p > 0) do begin
case AText[p] of
'%' : ANumFormat := nfPercentage;
'e', 'E': ANumFormat := nfExp;
else dec(p);
end;
break;
end;
end;
end else
ACurrencySymbol := '';
end;
{ Checks if text is quoted; strips any starting and ending quotes } { Checks if text is quoted; strips any starting and ending quotes }
function TsCSVReader.IsQuotedText(var AText: String): Boolean; function TsCSVReader.IsQuotedText(var AText: String): Boolean;
begin begin
@@ -399,7 +162,8 @@ begin
end; end;
// Check for a NUMBER or CURRENCY cell // Check for a NUMBER or CURRENCY cell
if IsNumber(AText, dblValue, nf, decs, currSym, warning) then if IsNumberValue(AText, CSVParams.AutoDetectNumberFormat, FFormatSettings,
dblValue, nf, decs, currSym, warning) then
begin begin
if currSym <> '' then if currSym <> '' then
FWorksheet.WriteCurrency(cell, dblValue, nfCurrency, decs, currSym) FWorksheet.WriteCurrency(cell, dblValue, nfCurrency, decs, currSym)
@@ -412,14 +176,14 @@ begin
// Check for a DATE/TIME cell // Check for a DATE/TIME cell
// No idea how to apply the date/time formatsettings here... // No idea how to apply the date/time formatsettings here...
if IsDateTime(AText, dtValue, nf) then if IsDateTimeValue(AText, FFormatSettings, dtValue, nf) then
begin begin
FWorksheet.WriteDateTime(cell, dtValue, nf); FWorksheet.WriteDateTime(cell, dtValue, nf);
exit; exit;
end; end;
// Check for a BOOLEAN cell // Check for a BOOLEAN cell
if IsBool(AText, boolValue) then if IsBoolValue(AText, CSVParams.TrueText, CSVParams.FalseText, boolValue) then
begin begin
FWorksheet.WriteBoolValue(cell, boolValue); FWorksheet.WriteBoolValue(cell, boolValue);
exit; exit;
@@ -656,7 +420,7 @@ end;
initialization initialization
InitCSVFormatSettings; InitFormatSettings(CSVParams.FormatSettings);
RegisterSpreadFormat(TsCSVReader, TsCSVWriter, sfCSV); RegisterSpreadFormat(TsCSVReader, TsCSVWriter, sfCSV);
end. end.

View File

@@ -8,30 +8,38 @@ uses
Classes, SysUtils, fasthtmlparser, Classes, SysUtils, fasthtmlparser,
fpstypes, fpspreadsheet, fpsReaderWriter; fpstypes, fpspreadsheet, fpsReaderWriter;
type (* type
TsHTMLTokenKind = (htkTABLE, htkTR, htkTH, htkTD, htkDIV, htkSPAN, htkP);
{
TsHTMLToken = class
Kind: TsHTMLTokenKind;
Parent: TsHTMLToken;
Children
}
TsHTMLReader = class(TsCustomSpreadReader) TsHTMLReader = class(TsCustomSpreadReader)
private private
FWorksheetName: String;
FFormatSettings: TFormatSettings; FFormatSettings: TFormatSettings;
function IsBool(AText: String; out AValue: Boolean): Boolean; parser: THTMLParser;
function IsDateTime(AText: String; out ADateTime: TDateTime; FInTable: Boolean;
out ANumFormat: TsNumberFormat): Boolean; FInSubTable: Boolean;
function IsNumber(AText: String; out ANumber: Double; out ANumFormat: TsNumberFormat; FInCell: Boolean;
out ADecimals: Integer; out ACurrencySymbol, AWarning: String): Boolean; FInSpan: Boolean;
function IsQuotedText(var AText: String): Boolean; FInA: Boolean;
procedure ReadCellValue(ARow, ACol: Cardinal; AText: String); FInHeader: Boolean;
FTableCounter: Integer;
FCurrRow, FCurrCol: LongInt;
FCelLText: String;
procedure TagFoundHandler(NoCaseTag, ActualTag: string);
procedure TextFoundHandler(AText: String);
protected protected
procedure ReadBlank(AStream: TStream); override; procedure ProcessCellValue(ARow, ACol: LongInt; AText: String);
procedure ReadFormula(AStream: TStream); override;
procedure ReadLabel(AStream: TStream); override;
procedure ReadNumber(AStream: TStream); override;
public public
constructor Create(AWorkbook: TsWorkbook); override; constructor Create(AWorkbook: TsWorkbook); override;
procedure ReadFromFile(AFileName: String); override; destructor Destroy; override;
procedure ReadFromStream(AStream: TStream); override; procedure ReadFromStream(AStream: TStream); override;
procedure ReadFromStrings(AStrings: TStrings); override; procedure ReadFromStrings(AStrings: TStrings); override;
end; end;
*)
TsHTMLWriter = class(TsCustomSpreadWriter) TsHTMLWriter = class(TsCustomSpreadWriter)
private private
FPointSeparatorSettings: TFormatSettings; FPointSeparatorSettings: TFormatSettings;
@@ -78,26 +86,523 @@ type (*
end; end;
TsHTMLParams = record TsHTMLParams = record
TableIndex: Integer; // R: Index of the table in the HTML file
SheetIndex: Integer; // W: Index of the sheet to be written SheetIndex: Integer; // W: Index of the sheet to be written
ShowRowColHeaders: Boolean; // RW: Show row/column headers ShowRowColHeaders: Boolean; // RW: Show row/column headers
DetectContentType: Boolean; // R: try to convert strings to content types
NumberFormat: String; // W: if empty write numbers like in sheet, otherwise use this format
AutoDetectNumberFormat: Boolean; // R: automatically detects decimal/thousand separator used in numbers
TrueText: String; // RW: String for boolean TRUE TrueText: String; // RW: String for boolean TRUE
FalseText: String; // RW: String for boolean FALSE FalseText: String; // RW: String for boolean FALSE
FormatSettings: TFormatSettings; // RW: add'l parameters for conversion
end; end;
var var
HTMLParams: TsHTMLParams = ( HTMLParams: TsHTMLParams = (
TableIndex: -1; // -1 = all tables
SheetIndex: -1; // -1 = active sheet, MaxInt = all sheets SheetIndex: -1; // -1 = active sheet, MaxInt = all sheets
ShowRowColHeaders: false; ShowRowColHeaders: false;
DetectContentType: true;
NumberFormat: '';
AutoDetectNumberFormat: true;
TrueText: 'TRUE'; TrueText: 'TRUE';
FalseText: 'FALSE'; FalseText: 'FALSE';
); {%H-});
implementation implementation
uses uses
LazUTF8, URIParser, Math, StrUtils, LazUTF8, URIParser, StrUtils,
fpsUtils; fpsUtils, fpsHTMLUtils, fpsNumFormat;
(*
type
THTMLEntity = record
E: String;
Ch: String;
end;
const
HTMLEntities: array[0..251] of THTMLEntity = (
// A
(E: 'Acirc'; Ch: 'Â'), // 0
(E: 'acirc'; Ch: 'â'),
(E: 'acute'; Ch: '´'),
(E: 'AElig'; Ch: 'Æ'),
(E: 'aelig'; Ch: 'æ'),
(E: 'Agrave'; Ch: 'À'),
(E: 'agrave'; Ch: 'à'),
(E: 'alefsym';Ch: 'ℵ'),
(E: 'Alpha'; Ch: 'Α'),
(E: 'alpha'; Ch: 'α'),
(E: 'amp'; Ch: '&'), // 10
(E: 'and'; Ch: '∧'),
(E: 'ang'; Ch: '∠'),
(E: 'apos'; Ch: ''''),
(E: 'Aring'; Ch: 'Å'),
(E: 'aring'; Ch: 'å'),
(E: 'asymp'; Ch: '≈'),
(E: 'Atilde'; Ch: 'Ã'),
(E: 'atilde'; Ch: 'ã'),
(E: 'Auml'; Ch: 'Ä'),
(E: 'auml'; Ch: 'ä'), // 20
// B
(E: 'bdquo'; Ch: '„'), // 21
(E: 'Beta'; Ch: 'Β'),
(E: 'beta'; Ch: 'β'),
(E: 'brvbar'; Ch: '¦'),
(E: 'bull'; Ch: '•'),
// C
(E: 'cap'; Ch: '∩'), // 26
(E: 'Ccedil'; Ch: 'Ç'),
(E: 'ccedil'; Ch: 'ç'),
(E: 'cedil'; Ch: '¸'),
(E: 'cent'; Ch: '¢'), // 39
(E: 'Chi'; Ch: 'Χ'),
(E: 'chi'; Ch: 'χ'),
(E: 'circ'; Ch: 'ˆ'),
(E: 'clubs'; Ch: '♣'),
(E: 'cong'; Ch: '≅'), // approximately equal
(E: 'copy'; Ch: '©'),
(E: 'crarr'; Ch: '↵'), // carriage return
(E: 'cup'; Ch: '∪'),
(E: 'curren'; Ch: '¤'),
// D
(E: 'Dagger'; Ch: '‡'), // 40
(E: 'dagger'; Ch: '†'),
(E: 'dArr'; Ch: '⇓'), // wide down-arrow
(E: 'darr'; Ch: '↓'), // narrow down-arrow
(E: 'deg'; Ch: '°'),
(E: 'Delta'; Ch: 'Δ'),
(E: 'delta'; Ch: 'δ'),
(E: 'diams'; Ch: '♦'),
(E: 'divide'; Ch: '÷'),
// E
(E: 'Eacute'; Ch: 'É'),
(E: 'eacute'; Ch: 'é'),
(E: 'Ecirc'; Ch: 'Ê'),
(E: 'ecirc'; Ch: 'ê'),
(E: 'Egrave'; Ch: 'È'),
(E: 'egrave'; Ch: 'è'),
(E: 'empty'; Ch: '∅'),
(E: 'emsp'; Ch: ' '), // Space character width of "m"
(E: 'ensp'; Ch: ' '), // Space character width of "n"
(E: 'Epsilon';Ch: 'Ε'), // capital epsilon
(E: 'epsilon';Ch: 'ε'),
(E: 'equiv'; Ch: '≡'),
(E: 'Eta'; Ch: 'Η'),
(E: 'eta'; Ch: 'η'),
(E: 'ETH'; Ch: 'Ð'),
(E: 'eth'; Ch: 'ð'),
(E: 'Euml'; Ch: 'Ë'),
(E: 'euml'; Ch: 'ë'),
(E: 'euro'; Ch: '€'),
(E: 'exist'; Ch: '∃'),
// F
(E: 'fnof'; Ch: 'ƒ'),
(E: 'forall'; Ch: '∀'),
(E: 'frac12'; Ch: '½'),
(E: 'frac14'; Ch: '¼'),
(E: 'frac34'; Ch: '¾'),
(E: 'frasl'; Ch: '⁄'),
// G
(E: 'Gamma'; Ch: 'Γ'),
(E: 'gamma'; Ch: 'γ'),
(E: 'ge'; Ch: '≥'),
(E: 'gt'; Ch: '>'),
// H
(E: 'hArr'; Ch: '⇔'), // wide horizontal double arrow
(E: 'harr'; Ch: '↔'), // narrow horizontal double arrow
(E: 'hearts'; Ch: '♥'),
(E: 'hellip'; Ch: '…'),
// I
(E: 'Iacute'; Ch: 'Í'),
(E: 'iacute'; Ch: 'í'),
(E: 'Icirc'; Ch: 'Î'),
(E: 'icirc'; Ch: 'î'),
(E: 'iexcl'; Ch: '¡'),
(E: 'Igrave'; Ch: 'Ì'),
(E: 'igrave'; Ch: 'ì'),
(E: 'image'; Ch: 'ℑ'), //
(E: 'infin'; Ch: '∞'),
(E: 'int'; Ch: '∫'),
(E: 'Iota'; Ch: 'Ι'),
(E: 'iota'; Ch: 'ι'),
(E: 'iquest'; Ch: '¿'),
(E: 'isin'; Ch: '∈'),
(E: 'Iuml'; Ch: 'Ï'),
(E: 'iuml'; Ch: 'ï'),
// K
(E: 'Kappa'; Ch: 'Κ'),
(E: 'kappa'; Ch: 'κ'),
// L
(E: 'Lambda'; Ch: 'Λ'),
(E: 'lambda'; Ch: 'λ'),
(E: 'lang'; Ch: '⟨'), // Left-pointing angle bracket
(E: 'laquo'; Ch: '«'),
(E: 'lArr'; Ch: '⇐'), // Left-pointing wide arrow
(E: 'larr'; Ch: '←'),
(E: 'lceil'; Ch: '⌈'), // Left ceiling
(E: 'ldquo'; Ch: '“'),
(E: 'le'; Ch: '≤'),
(E: 'lfloor'; Ch: '⌊'), // Left floor
(E: 'lowast'; Ch: '∗'), // Low asterisk
(E: 'loz'; Ch: '◊'),
(E: 'lrm'; Ch: '‎'), // Left-to-right mark
(E: 'lsaquo'; Ch: '‹'),
(E: 'lsquo'; Ch: '‘'),
(E: 'lt'; Ch: '<'),
// M
(E: 'macr'; Ch: '¯'),
(E: 'mdash'; Ch: '—'),
(E: 'micro'; Ch: 'µ'),
(E: 'middot'; Ch: '·'),
(E: 'minus'; Ch: '−'),
(E: 'Mu'; Ch: 'Μ'),
(E: 'mu'; Ch: 'μ'),
// N
(E: 'nabla'; Ch: '∇'),
(E: 'nbsp'; Ch: ' '),
(E: 'ndash'; Ch: '–'),
(E: 'ne'; Ch: '≠'),
(E: 'ni'; Ch: '∋'),
(E: 'not'; Ch: '¬'),
(E: 'notin'; Ch: '∉'), // math: "not in"
(E: 'nsub'; Ch: '⊄'), // math: "not a subset of"
(E: 'Ntilde'; Ch: 'Ñ'),
(E: 'ntilde'; Ch: 'ñ'),
(E: 'Nu'; Ch: 'Ν'),
(E: 'nu'; Ch: 'ν'),
// O
(E: 'Oacute'; Ch: 'Ó'),
(E: 'oacute'; Ch: 'ó'),
(E: 'Ocirc'; Ch: 'Ô'),
(E: 'ocirc'; Ch: 'ô'),
(E: 'OElig'; Ch: 'Œ'),
(E: 'oelig'; Ch: 'œ'),
(E: 'Ograve'; Ch: 'Ò'),
(E: 'ograve'; Ch: 'ò'),
(E: 'oline'; Ch: '‾'),
(E: 'Omega'; Ch: 'Ω'),
(E: 'omega'; Ch: 'ω'),
(E: 'Omicron';Ch: 'Ο'),
(E: 'omicron';Ch: 'ο'),
(E: 'oplus'; Ch: '⊕'), // Circled plus
(E: 'or'; Ch: '∨'),
(E: 'ordf'; Ch: 'ª'),
(E: 'ordm'; Ch: 'º'),
(E: 'Oslash'; Ch: 'Ø'),
(E: 'oslash'; Ch: 'ø'),
(E: 'Otilde'; Ch: 'Õ'),
(E: 'otilde'; Ch: 'õ'),
(E: 'otimes'; Ch: '⊗'), // Circled times
(E: 'Ouml'; Ch: 'Ö'),
(E: 'ouml'; Ch: 'ö'),
// P
(E: 'para'; Ch: '¶'),
(E: 'part'; Ch: '∂'),
(E: 'permil'; Ch: '‰'),
(E: 'perp'; Ch: '⊥'),
(E: 'Phi'; Ch: 'Φ'),
(E: 'phi'; Ch: 'φ'),
(E: 'Pi'; Ch: 'Π'),
(E: 'pi'; Ch: 'π'), // lower-case pi
(E: 'piv'; Ch: 'ϖ'),
(E: 'plusmn'; Ch: '±'),
(E: 'pound'; Ch: '£'),
(E: 'Prime'; Ch: '″'),
(E: 'prime'; Ch: '′'),
(E: 'prod'; Ch: '∏'),
(E: 'prop'; Ch: '∝'),
(E: 'Psi'; Ch: 'Ψ'),
(E: 'psi'; Ch: 'ψ'),
// Q
(E: 'quot'; Ch: '"'),
// R
(E: 'radic'; Ch: '√'),
(E: 'rang'; Ch: '⟩'), // right-pointing angle bracket
(E: 'raquo'; Ch: '»'),
(E: 'rArr'; Ch: '⇒'),
(E: 'rarr'; Ch: '→'),
(E: 'rceil'; Ch: '⌉'), // right ceiling
(E: 'rdquo'; Ch: '”'),
(E: 'real'; Ch: 'ℜ'), // R in factura
(E: 'reg'; Ch: '®'),
(E: 'rfloor'; Ch: '⌋'), // Right floor
(E: 'Rho'; Ch: 'Ρ'),
(E: 'rho'; Ch: 'ρ'),
(E: 'rlm'; Ch: ''), // right-to-left mark
(E: 'rsaquo'; Ch: '›'),
(E: 'rsquo'; Ch: '’'),
// S
(E: 'sbquo'; Ch: '‚'),
(E: 'Scaron'; Ch: 'Š'),
(E: 'scaron'; Ch: 'š'),
(E: 'sdot'; Ch: '⋅'), // math: dot operator
(E: 'sect'; Ch: '§'),
(E: 'shy'; Ch: ''), // conditional hyphen
(E: 'Sigma'; Ch: 'Σ'),
(E: 'sigma'; Ch: 'σ'),
(E: 'sigmaf'; Ch: 'ς'),
(E: 'sim'; Ch: '∼'), // similar
(E: 'spades'; Ch: '♠'),
(E: 'sub'; Ch: '⊂'),
(E: 'sube'; Ch: '⊆'),
(E: 'sum'; Ch: '∑'),
(E: 'sup'; Ch: '⊃'),
(E: 'sup1'; Ch: '¹'),
(E: 'sup2'; Ch: '²'),
(E: 'sup3'; Ch: '³'),
(E: 'supe'; Ch: '⊇'),
(E: 'szlig'; Ch: 'ß'),
//T
(E: 'Tau'; Ch: 'Τ'),
(E: 'tau'; Ch: 'τ'),
(E: 'there4'; Ch: '∴'),
(E: 'Theta'; Ch: 'Θ'),
(E: 'theta'; Ch: 'θ'),
(E: 'thetasym';Ch: 'ϑ'),
(E: 'thinsp'; Ch: ' '), // thin space
(E: 'THORN'; Ch: 'Þ'),
(E: 'thorn'; Ch: 'þ'),
(E: 'tilde'; Ch: '˜'),
(E: 'times'; Ch: '×'),
(E: 'trade'; Ch: '™'),
// U
(E: 'Uacute'; Ch: 'Ú'),
(E: 'uacute'; Ch: 'ú'),
(E: 'uArr'; Ch: '⇑'), // wide up-arrow
(E: 'uarr'; Ch: '↑'),
(E: 'Ucirc'; Ch: 'Û'),
(E: 'ucirc'; Ch: 'û'),
(E: 'Ugrave'; Ch: 'Ù'),
(E: 'ugrave'; Ch: 'ù'),
(E: 'uml'; Ch: '¨'),
(E: 'upsih'; Ch: 'ϒ'),
(E: 'Upsilon';Ch: 'Υ'),
(E: 'upsilon';Ch: 'υ'),
(E: 'Uuml'; Ch: 'Ü'),
(E: 'uuml'; Ch: 'ü'),
// W
(E: 'weierp'; Ch: '℘'), // Script Capital P; Weierstrass Elliptic Function
// X
(E: 'Xi'; Ch: 'Ξ'),
(E: 'xi'; Ch: 'ξ'),
// Y
(E: 'Yacute'; Ch: 'Ý'),
(E: 'yacute'; Ch: 'ý'),
(E: 'yen'; Ch: '¥'),
(E: 'Yuml'; Ch: 'Ÿ'),
(E: 'yuml'; Ch: 'ÿ'),
// Z
(E: 'Zeta'; Ch: 'Ζ'),
(E: 'zeta'; Ch: 'ζ'),
(E: 'zwj'; Ch: ''), // Zero-width joiner
(E: 'zwnj'; Ch: ''), // Zero-width non-joiner
(E: '#160'; Ch: ' ') // numerical value of "&nbsp;"
);
*)
{==============================================================================}
{ TsHTMLReader }
{==============================================================================}
constructor TsHTMLReader.Create(AWorkbook: TsWorkbook);
begin
inherited Create(AWorkbook);
FFormatSettings := HTMLParams.FormatSettings;
ReplaceFormatSettings(FFormatSettings, FWorkbook.FormatSettings);
FTableCounter := -1;
end;
destructor TsHTMLReader.Destroy;
begin
FreeAndNil(parser);
inherited Destroy;
end;
procedure TsHTMLReader.ReadFromStream(AStream: TStream);
var
list: TStringList;
begin
list := TStringList.Create;
try
list.LoadFromStream(AStream);
ReadFromStrings(list);
if FWorkbook.GetWorksheetCount = 0 then
begin
FWorkbook.AddErrorMsg('Requested table not found, or no tables in html file');
FWorkbook.AddWorksheet('Dummy');
end;
finally
list.Free;
end;
end;
procedure TsHTMLReader.ReadFromStrings(AStrings: TStrings);
begin
// Create html parser
FreeAndNil(parser);
parser := THTMLParser.Create(AStrings.Text);
parser.OnFoundTag := @TagFoundHandler;
parser.OnFoundText := @TextFoundHandler;
// Execute the html parser
parser.Exec;
end;
procedure TsHTMLReader.ProcessCellValue(ARow, ACol: LongInt; AText: String);
var
cell: PCell;
dblValue: Double;
dtValue: TDateTime;
boolValue: Boolean;
nf: TsNumberFormat;
decs: Integer;
currSym: String;
warning: String;
begin
// Empty strings are blank cells -- nothing to do
if (AText = '') then
exit;
cell := FWorksheet.AddCell(ARow, ACol);
// Do not try to interpret the strings. --> everything is a LABEL cell.
if not HTMLParams.DetectContentType then
begin
FWorksheet.WriteUTF8Text(cell, AText);
exit;
end;
// Check for a NUMBER or CURRENCY cell
if IsNumberValue(AText, HTMLParams.AutoDetectNumberFormat, FFormatSettings,
dblValue, nf, decs, currSym, warning) then
begin
if currSym <> '' then
FWorksheet.WriteCurrency(cell, dblValue, nfCurrency, decs, currSym)
else
FWorksheet.WriteNumber(cell, dblValue, nf, decs);
if warning <> '' then
FWorkbook.AddErrorMsg('Cell %s: %s', [GetCellString(ARow, ACol), warning]);
exit;
end;
// Check for a DATE/TIME cell
// No idea how to apply the date/time formatsettings here...
if IsDateTimevalue(AText, FFormatSettings, dtValue, nf) then
begin
FWorksheet.WriteDateTime(cell, dtValue, nf);
exit;
end;
// Check for a BOOLEAN cell
if IsBoolValue(AText, HTMLParams.TrueText, HTMLParams.FalseText, boolValue) then
begin
FWorksheet.WriteBoolValue(cell, boolValue);
exit;
end;
// What is left is handled as a TEXT cell
FWorksheet.WriteUTF8Text(cell, AText);
end;
procedure TsHTMLReader.TagFoundHandler(NoCaseTag, ActualTag: string);
begin
if pos('<TABLE', NoCaseTag) = 1 then
begin
inc(FTableCounter);
if HTMLParams.TableIndex < 0 then // all tables
begin
FWorksheet := FWorkbook.AddWorksheet(Format('Table #%d', [FTableCounter+1]));
FInTable := true;
FCurrRow := -1;
FCurrCol := -1;
end else
if FTableCounter = HTMLParams.TableIndex then
begin
FWorksheet := FWorkbook.AddWorksheet(Format('Table #%d', [FTableCounter+1]));
FInTable := true;
FCurrRow := -1;
FCurrCol := -1;
end;
end else
if ((NoCaseTag = '<TR>') or (pos('<TR ', NoCaseTag) = 1)) and FInTable then
begin
inc(FCurrRow);
FCurrCol := -1;
end else
if ((NoCaseTag = '<TD>') or (pos('<TD ', NoCaseTag) = 1)) and FInTable then
begin
FInCell := true;
inc(FCurrCol);
FCellText := '';
end else
if ((NoCaseTag = '<TH>') or (pos('<TH ', NoCaseTag) = 1)) and FInTable then
begin
FInCell := true;
FCellText := '';
end else
if pos('<SPAN', NoCaseTag) = 1 then
begin
if FInCell then
FInSpan := true;
end else
if pos('<A', NoCaseTag) = 1 then
begin
if FInCell then
FInA := true
end else
if (pos('<H', NoCaseTag) = 1) and (NoCaseTag[3] in ['1', '2', '3', '4', '5', '6']) then
begin
if FInCell then
FInHeader := true;
end else
if ((NoCaseTag = '<BR>') or (pos('<BR ', NoCaseTag) = 1)) and FInCell then
FCellText := FCellText + LineEnding
else
case NoCaseTag of
'</TABLE>':
if FInTable then FInTable := false;
'</TD>', '</TH>':
if FInCell then
begin
ProcessCellValue(FCurrRow, FCurrCol, FCellText);
FInCell := false;
end;
'</A>':
if FInCell then FInA := false;
'</SPAN>':
if FInCell then FInSpan := false;
'<H1/>', '<H2/>', '<H3/>', '<H4/>', '<H5/>', '<H6/>':
if FinCell then FInHeader := false;
'<TR/>', '<TR />':
if FInTable then inc(FCurrRow);
'<TD/>', '<TD />':
if FInCell then inc(FCurrCol);
'<TH/>', '<TH />':
if FInCell then inc(FCurrCol);
end;
end;
procedure TsHTMLReader.TextFoundHandler(AText: String);
begin
if FInCell then
begin
AText := CleanHTMLString(AText);
if AText <> '' then
begin
if FCellText = '' then
FCellText := AText
else
FCellText := FCellText + ' ' + AText;
end;
end;
end;
{==============================================================================}
{ TsHTMLWriter }
{==============================================================================}
constructor TsHTMLWriter.Create(AWorkbook: TsWorkbook); constructor TsHTMLWriter.Create(AWorkbook: TsWorkbook);
begin begin
inherited Create(AWorkbook); inherited Create(AWorkbook);
@@ -493,7 +998,7 @@ begin
Unused(AStream); Unused(AStream);
Unused(ARow, ACol, ACell); Unused(ARow, ACol, ACell);
AppendToStream(AStream, AppendToStream(AStream,
'<div>' + IfThen(AValue, HTMLParams.TrueText, HTMLParams.FalseText) + '</div>'); '<div>' + StrUtils.IfThen(AValue, HTMLParams.TrueText, HTMLParams.FalseText) + '</div>');
end; end;
{ Write date/time values in the same way they are displayed in the sheet } { Write date/time values in the same way they are displayed in the sheet }
@@ -502,6 +1007,7 @@ procedure TsHTMLWriter.WriteDateTime(AStream: TStream; const ARow, ACol: Cardina
var var
s: String; s: String;
begin begin
Unused(AValue);
s := FWorksheet.ReadAsUTF8Text(ACell); s := FWorksheet.ReadAsUTF8Text(ACell);
AppendToStream(AStream, AppendToStream(AStream,
'<div>' + s + '</div>'); '<div>' + s + '</div>');
@@ -512,6 +1018,7 @@ procedure TsHTMLWriter.WriteError(AStream: TStream;
var var
s: String; s: String;
begin begin
Unused(AValue);
s := FWOrksheet.ReadAsUTF8Text(ACell); s := FWOrksheet.ReadAsUTF8Text(ACell);
AppendToStream(AStream, AppendToStream(AStream,
'<div>' + s + '</div>'); '<div>' + s + '</div>');
@@ -663,7 +1170,7 @@ procedure TsHTMLWriter.WriteNumber(AStream: TStream; const ARow, ACol: Cardinal;
var var
s: String; s: String;
begin begin
Unused(ARow, ACol); Unused(ARow, ACol, AValue);
s := FWorksheet.ReadAsUTF8Text(ACell, FWorkbook.FormatSettings); s := FWorksheet.ReadAsUTF8Text(ACell, FWorkbook.FormatSettings);
AppendToStream(AStream, AppendToStream(AStream,
'<div>' + s + '</div>'); '<div>' + s + '</div>');
@@ -873,7 +1380,8 @@ begin
end; end;
initialization initialization
RegisterSpreadFormat(nil, TsHTMLWriter, sfHTML); InitFormatSettings(HTMLParams.FormatSettings);
RegisterSpreadFormat(TsHTMLReader, TsHTMLWriter, sfHTML);
end. end.

View File

@@ -0,0 +1,400 @@
unit fpsHTMLUtils;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils;
type
THTMLEntity = record
E: String;
Ch: String;
N: Word;
end;
function IsHTMLEntity(AText: PChar; out AEntity: THTMLEntity): Boolean;
function CleanHTMLString(AText: String): String;
implementation
uses
Strings;
const
// http://unicode.e-workers.de/entities.php
HTMLEntities: array[0..250] of THTMLEntity = (
// A
(E: 'Acirc'; Ch: 'Â'; N: 194), // 0
(E: 'acirc'; Ch: 'â'; N: 226),
(E: 'acute'; Ch: '´'; N: 180),
(E: 'AElig'; Ch: 'Æ'; N: 198),
(E: 'aelig'; Ch: 'æ'; N: 230),
(E: 'Agrave'; Ch: 'À'; N: 192),
(E: 'agrave'; Ch: 'à'; N: 224),
(E: 'alefsym';Ch: 'ℵ'; N: 8501),
(E: 'Alpha'; Ch: 'Α'; N: 913),
(E: 'alpha'; Ch: 'α'; N: 945),
(E: 'amp'; Ch: '&'; N: 38), // 10
(E: 'and'; Ch: '∧'; N: 8743),
(E: 'ang'; Ch: '∠'; N: 8736),
(E: 'apos'; Ch: ''''; N: 39),
(E: 'Aring'; Ch: 'Å'; N: 197),
(E: 'aring'; Ch: 'å'; N: 229),
(E: 'asymp'; Ch: '≈'; N: 2248),
(E: 'Atilde'; Ch: 'Ã'; N: 195),
(E: 'atilde'; Ch: 'ã'; N: 227),
(E: 'Auml'; Ch: 'Ä'; N: 196),
(E: 'auml'; Ch: 'ä'; N: 228), // 20
// B
(E: 'bdquo'; Ch: '„'; N: 8222), // 21
(E: 'Beta'; Ch: 'Β'; N: 914),
(E: 'beta'; Ch: 'β'; N: 946),
(E: 'brvbar'; Ch: '¦'; N: 166),
(E: 'bull'; Ch: '•'; N: 8226),
// C
(E: 'cap'; Ch: '∩'; N: 8745), // 26
(E: 'Ccedil'; Ch: 'Ç'; N: 199),
(E: 'ccedil'; Ch: 'ç'; N: 231),
(E: 'cedil'; Ch: '¸'; N: 184),
(E: 'cent'; Ch: '¢'; N: 162), // 30
(E: 'Chi'; Ch: 'Χ'; N: 935),
(E: 'chi'; Ch: 'χ'; N: 967),
(E: 'circ'; Ch: 'ˆ'; N: 710),
(E: 'clubs'; Ch: '♣'; N: 9827),
(E: 'cong'; Ch: '≅'; N: 8773), // approximately equal
(E: 'copy'; Ch: '©'; N: 169),
(E: 'crarr'; Ch: '↵'; N: 8629), // carriage return
(E: 'cup'; Ch: '∪'; N: 8746),
(E: 'curren'; Ch: '¤'; N: 164),
// D
(E: 'Dagger'; Ch: '‡'; N: 8225), // 40
(E: 'dagger'; Ch: '†'; N: 8224),
(E: 'dArr'; Ch: '⇓'; N: 8659), // wide down-arrow
(E: 'darr'; Ch: '↓'; N: 8595), // narrow down-arrow
(E: 'deg'; Ch: '°'; N: 176),
(E: 'Delta'; Ch: 'Δ'; N: 916),
(E: 'delta'; Ch: 'δ'; N: 948),
(E: 'diams'; Ch: '♦'; N: 9830),
(E: 'divide'; Ch: '÷'; N: 247),
// E
(E: 'Eacute'; Ch: 'É'; N: 201), // 49
(E: 'eacute'; Ch: 'é'; N: 233), // 50
(E: 'Ecirc'; Ch: 'Ê'; N: 202),
(E: 'ecirc'; Ch: 'ê'; N: 234),
(E: 'Egrave'; Ch: 'È'; N: 200),
(E: 'egrave'; Ch: 'è'; N: 232),
(E: 'empty'; Ch: '∅'; N: 8709),
(E: 'emsp'; Ch: ' '; N: 8195), // Space character width of "m"
(E: 'ensp'; Ch: ' '; N: 8194), // Space character width of "n"
(E: 'Epsilon';Ch: 'Ε'; N: 917), // capital epsilon
(E: 'epsilon';Ch: 'ε'; N: 949),
(E: 'equiv'; Ch: '≡'; N: 8801), // 60
(E: 'Eta'; Ch: 'Η'; N: 919),
(E: 'eta'; Ch: 'η'; N: 951),
(E: 'ETH'; Ch: 'Ð'; N: 208),
(E: 'eth'; Ch: 'ð'; N: 240),
(E: 'Euml'; Ch: 'Ë'; N: 203),
(E: 'euml'; Ch: 'ë'; N: 235),
(E: 'euro'; Ch: '€'; N: 8364),
(E: 'exist'; Ch: '∃'; N: 8707),
// F
(E: 'fnof'; Ch: 'ƒ'; N: 402), // 70
(E: 'forall'; Ch: '∀'; N: 8704),
(E: 'frac12'; Ch: '½'; N: 189),
(E: 'frac14'; Ch: '¼'; N: 188),
(E: 'frac34'; Ch: '¾'; N: 190),
(E: 'frasl'; Ch: '⁄'; N: 8260),
// G
(E: 'Gamma'; Ch: 'Γ'; N: 915),
(E: 'gamma'; Ch: 'γ'; N: 947),
(E: 'ge'; Ch: '≥'; N: 8805),
(E: 'gt'; Ch: '>'; N: 62),
// H
(E: 'hArr'; Ch: '⇔'; N: 8660), // 80, wide horizontal double arrow
(E: 'harr'; Ch: '↔'; N: 8596), // narrow horizontal double arrow
(E: 'hearts'; Ch: '♥'; N: 9829),
(E: 'hellip'; Ch: '…'; N: 8230),
// I
(E: 'Iacute'; Ch: 'Í'; N: 205),
(E: 'iacute'; Ch: 'í'; N: 237),
(E: 'Icirc'; Ch: 'Î'; N: 206),
(E: 'icirc'; Ch: 'î'; N: 238),
(E: 'iexcl'; Ch: '¡'; N: 161),
(E: 'Igrave'; Ch: 'Ì'; N: 204),
(E: 'igrave'; Ch: 'ì'; N: 236), // 90
(E: 'image'; Ch: 'ℑ'; N: 2465), // I in factura
(E: 'infin'; Ch: '∞'; N: 8734),
(E: 'int'; Ch: '∫'; N: 8747),
(E: 'Iota'; Ch: 'Ι'; N: 921),
(E: 'iota'; Ch: 'ι'; N: 953),
(E: 'iquest'; Ch: '¿'; N: 191),
(E: 'isin'; Ch: '∈'; N: 8712),
(E: 'Iuml'; Ch: 'Ï'; N: 207),
(E: 'iuml'; Ch: 'ï'; N: 239),
// K
(E: 'Kappa'; Ch: 'Κ'; N: 922), // 100
(E: 'kappa'; Ch: 'κ'; N: 254),
// L
(E: 'Lambda'; Ch: 'Λ'; N: 923),
(E: 'lambda'; Ch: 'λ'; N: 955),
(E: 'lang'; Ch: '⟨'; N: 9001), // Left-pointing angle bracket
(E: 'laquo'; Ch: '«'; N: 171),
(E: 'lArr'; Ch: '⇐'; N: 8656), // Left-pointing wide arrow
(E: 'larr'; Ch: '←'; N: 8592),
(E: 'lceil'; Ch: '⌈'; N: 8968), // Left ceiling
(E: 'ldquo'; Ch: '“'; N: 8220),
(E: 'le'; Ch: '≤'; N: 8804), // 110
(E: 'lfloor'; Ch: '⌊'; N: 8970), // Left floor
(E: 'lowast'; Ch: '∗'; N: 8727), // Low asterisk
(E: 'loz'; Ch: '◊'; N: 9674),
(E: 'lrm'; Ch: '‎'; N: 8206), // Left-to-right mark
(E: 'lsaquo'; Ch: '‹'; N: 8249),
(E: 'lsquo'; Ch: '‘'; N: 8216),
(E: 'lt'; Ch: '<'; N: 60),
// M
(E: 'macr'; Ch: '¯'; N: 175),
(E: 'mdash'; Ch: '—'; N: 8212),
(E: 'micro'; Ch: 'µ'; N: 181), // 120
(E: 'middot'; Ch: '·'; N: 183),
(E: 'minus'; Ch: '−'; N: 8722),
(E: 'Mu'; Ch: 'Μ'; N: 924),
(E: 'mu'; Ch: 'μ'; N: 956),
// N
(E: 'nabla'; Ch: '∇'; N: 8711),
(E: 'nbsp'; Ch: ' '; N: 160), // 126
(E: 'ndash'; Ch: '–'; N: 8211),
(E: 'ne'; Ch: '≠'; N: 8800),
(E: 'ni'; Ch: '∋'; N: 8715),
(E: 'not'; Ch: '¬'; N: 172), // 130
(E: 'notin'; Ch: '∉'; N: 8713), // math: "not in"
(E: 'nsub'; Ch: '⊄'; N: 8836), // math: "not a subset of"
(E: 'Ntilde'; Ch: 'Ñ'; N: 209),
(E: 'ntilde'; Ch: 'ñ'; N: 241),
(E: 'Nu'; Ch: 'Ν'; N: 925),
(E: 'nu'; Ch: 'ν'; N: 957),
// O
(E: 'Oacute'; Ch: 'Ó'; N: 211),
(E: 'oacute'; Ch: 'ó'; N: 243),
(E: 'Ocirc'; Ch: 'Ô'; N: 212),
(E: 'ocirc'; Ch: 'ô'; N: 244),
(E: 'OElig'; Ch: 'Œ'; N: 338),
(E: 'oelig'; Ch: 'œ'; N: 339),
(E: 'Ograve'; Ch: 'Ò'; N: 210),
(E: 'ograve'; Ch: 'ò'; N: 242),
(E: 'oline'; Ch: '‾'; N: 8254),
(E: 'Omega'; Ch: 'Ω'; N: 937),
(E: 'omega'; Ch: 'ω'; N: 969),
(E: 'Omicron';Ch: 'Ο'; N: 927),
(E: 'omicron';Ch: 'ο'; N: 959),
(E: 'oplus'; Ch: '⊕'; N: 8853), // Circled plus
(E: 'or'; Ch: '∨'; N: 8744),
(E: 'ordf'; Ch: 'ª'; N: 170),
(E: 'ordm'; Ch: 'º'; N: 186),
(E: 'Oslash'; Ch: 'Ø'; N: 216),
(E: 'oslash'; Ch: 'ø'; N: 248),
(E: 'Otilde'; Ch: 'Õ'; N: 213),
(E: 'otilde'; Ch: 'õ'; N: 245),
(E: 'otimes'; Ch: '⊗'; N: 8855), // Circled times
(E: 'Ouml'; Ch: 'Ö'; N: 214),
(E: 'ouml'; Ch: 'ö'; N: 246),
// P
(E: 'para'; Ch: '¶'; N: 182),
(E: 'part'; Ch: '∂'; N: 8706),
(E: 'permil'; Ch: '‰'; N: 8240),
(E: 'perp'; Ch: '⊥'; N: 8869),
(E: 'Phi'; Ch: 'Φ'; N: 934),
(E: 'phi'; Ch: 'φ'; N: 966),
(E: 'Pi'; Ch: 'Π'; N: 928),
(E: 'pi'; Ch: 'π'; N: 960), // lower-case pi
(E: 'piv'; Ch: 'ϖ'; N: 982),
(E: 'plusmn'; Ch: '±'; N: 177),
(E: 'pound'; Ch: '£'; N: 163),
(E: 'Prime'; Ch: '″'; N: 8243),
(E: 'prime'; Ch: '′'; N: 8242),
(E: 'prod'; Ch: '∏'; N: 8719),
(E: 'prop'; Ch: '∝'; N: 8733),
(E: 'Psi'; Ch: 'Ψ'; N: 936),
(E: 'psi'; Ch: 'ψ'; N: 968),
// Q
(E: 'quot'; Ch: '"'; N: 34),
// R
(E: 'radic'; Ch: '√'; N: 8730),
(E: 'rang'; Ch: '⟩'; N: 9002), // right-pointing angle bracket
(E: 'raquo'; Ch: '»'; N: 187),
(E: 'rArr'; Ch: '⇒'; N: 8658),
(E: 'rarr'; Ch: '→'; N: 8594),
(E: 'rceil'; Ch: '⌉'; N: 8969), // right ceiling
(E: 'rdquo'; Ch: '”'; N: 8221),
(E: 'real'; Ch: 'ℜ'; N: 8476), // R in factura
(E: 'reg'; Ch: '®'; N: 174),
(E: 'rfloor'; Ch: '⌋'; N: 8971), // Right floor
(E: 'Rho'; Ch: 'Ρ'; N: 929),
(E: 'rho'; Ch: 'ρ'; N: 961),
(E: 'rlm'; Ch: ''; N: 8207), // right-to-left mark
(E: 'rsaquo'; Ch: '›'; N: 8250),
(E: 'rsquo'; Ch: '’'; N: 8217),
// S
(E: 'sbquo'; Ch: '‚'; N: 8218),
(E: 'Scaron'; Ch: 'Š'; N: 352),
(E: 'scaron'; Ch: 'š'; N: 353),
(E: 'sdot'; Ch: '⋅'; N: 8901), // math: dot operator
(E: 'sect'; Ch: '§'; N: 167),
(E: 'shy'; Ch: ''; N: 173), // conditional hyphen
(E: 'Sigma'; Ch: 'Σ'; N: 931),
(E: 'sigma'; Ch: 'σ'; N: 963),
(E: 'sigmaf'; Ch: 'ς'; N: 962),
(E: 'sim'; Ch: '∼'; N: 8764), // similar
(E: 'spades'; Ch: '♠'; N: 9824),
(E: 'sub'; Ch: '⊂'; N: 8834),
(E: 'sube'; Ch: '⊆'; N: 8838),
(E: 'sum'; Ch: '∑'; N: 8721),
(E: 'sup'; Ch: '⊃'; N: 8835),
(E: 'sup1'; Ch: '¹'; N: 185),
(E: 'sup2'; Ch: '²'; N: 178),
(E: 'sup3'; Ch: '³'; N: 179),
(E: 'supe'; Ch: '⊇'; N: 8839),
(E: 'szlig'; Ch: 'ß'; N: 223),
//T
(E: 'Tau'; Ch: 'Τ'; N: 932),
(E: 'tau'; Ch: 'τ'; N: 964),
(E: 'there4'; Ch: '∴'; N: 8756),
(E: 'Theta'; Ch: 'Θ'; N: 920),
(E: 'theta'; Ch: 'θ'; N: 952),
(E: 'thetasym';Ch: 'ϑ'; N: 977),
(E: 'thinsp'; Ch: ' '; N: 8201), // thin space
(E: 'THORN'; Ch: 'Þ'; N: 222),
(E: 'thorn'; Ch: 'þ'; N: 254),
(E: 'tilde'; Ch: '˜'; N: 732),
(E: 'times'; Ch: '×'; N: 215),
(E: 'trade'; Ch: '™'; N: 8482),
// U
(E: 'Uacute'; Ch: 'Ú'; N: 218),
(E: 'uacute'; Ch: 'ú'; N: 250),
(E: 'uArr'; Ch: '⇑'; N: 8657), // wide up-arrow
(E: 'uarr'; Ch: '↑'; N: 8593),
(E: 'Ucirc'; Ch: 'Û'; N: 219),
(E: 'ucirc'; Ch: 'û'; N: 251),
(E: 'Ugrave'; Ch: 'Ù'; N: 217),
(E: 'ugrave'; Ch: 'ù'; N: 249),
(E: 'uml'; Ch: '¨'; N: 168),
(E: 'upsih'; Ch: 'ϒ'; N: 978),
(E: 'Upsilon';Ch: 'Υ'; N: 933),
(E: 'upsilon';Ch: 'υ'; N: 965),
(E: 'Uuml'; Ch: 'Ü'; N: 220),
(E: 'uuml'; Ch: 'ü'; N: 252),
// W
(E: 'weierp'; Ch: '℘'; N: 8472), // Script Capital P; Weierstrass Elliptic Function
// X
(E: 'Xi'; Ch: 'Ξ'; N: 926),
(E: 'xi'; Ch: 'ξ'; N: 958),
// Y
(E: 'Yacute'; Ch: 'Ý'; N: 221),
(E: 'yacute'; Ch: 'ý'; N: 253),
(E: 'yen'; Ch: '¥'; N: 165),
(E: 'Yuml'; Ch: 'Ÿ'; N: 376),
(E: 'yuml'; Ch: 'ÿ'; N: 255),
// Z
(E: 'Zeta'; Ch: 'Ζ'; N: 918),
(E: 'zeta'; Ch: 'ζ'; N: 950),
(E: 'zwj'; Ch: ''; N: 8205), // Zero-width joiner
(E: 'zwnj'; Ch: ''; N: 8204) // Zero-width non-joiner
);
function IsHTMLEntity(AText: PChar; out AEntity: THTMLEntity): Boolean;
function Compare(s: String): Boolean;
var
j: Integer;
begin
Result := false;
for j:=1 to Length(s) do
if s[j] <> PChar(AText)[j-1] then
exit;
if PChar(AText)[Length(s)] <> ';' then
exit;
Result := true;
end;
var
j, k: Integer;
equ: Boolean;
ch1, ch2: Char;
P: PChar;
begin
Result := false;
for k:=0 to High(HTMLEntities) do
begin
equ := Compare(HTMLEntities[k].E);
if not equ then
begin
P := AText;
ch1 := P^;
if ch1 = '#' then
begin
inc(P);
ch2 := P^;
if ch1 = 'x' then
equ := Compare(Format('#x%x', [HTMLEntities[k].N]))
else
equ := Compare(Format('#%d', [HTMLEntities[k].N]));
end;
end;
if equ then
begin
AEntity := HTMLEntities[k];
Result := true;
exit;
end;
end;
end;
function CleanHTMLString(AText: String): String;
var
len: Integer;
ent: THTMLEntity;
P: PChar;
ch: Char;
begin
Result := '';
// Remove leading and trailing spaces and line endings coming from formatted
// source lines
while (Length(AText) > 0) and (AText[1] in [#9, #10, #13, ' ']) do
Delete(AText, 1,1);
while (Length(AText) > 0) and (AText[Length(AText)] in [#9, #10, #13, ' ']) do
Delete(AText, Length(AText), 1);
if AText = '' then
exit;
// Replace HTML entities by their counter part UTF8 characters
len := Length(AText);
P := @AText[1];
while (P^ <> #0) do begin
ch := P^;
case ch of
'&': begin
inc(P);
if (P <> nil) and IsHTMLEntity(P, ent) then
begin
Result := Result + ent.Ch;
inc(P, Length(ent.E));
end else
begin
Result := Result + '&';
Continue;
end;
end;
else Result := Result + ch;
end;
inc(P);
end;
end;
end.

View File

@@ -212,6 +212,9 @@ function ConvertFloatToStr(AValue: Double; AParams: TsNumFormatParams;
AFormatSettings: TFormatSettings): String; AFormatSettings: TFormatSettings): String;
function CountDecs(AFormatString: String; ADecChars: TsDecsChars = ['0']): Byte; function CountDecs(AFormatString: String; ADecChars: TsDecsChars = ['0']): Byte;
function IsBoolValue(const AText, ATrueText, AFalseText: String;
out AValue: Boolean): Boolean;
function IsCurrencyFormat(AFormat: TsNumberFormat): Boolean; overload; function IsCurrencyFormat(AFormat: TsNumberFormat): Boolean; overload;
function IsCurrencyFormat(ANumFormat: TsNumFormatParams): Boolean; overload; function IsCurrencyFormat(ANumFormat: TsNumFormatParams): Boolean; overload;
@@ -219,6 +222,9 @@ function IsDateTimeFormat(AFormat: TsNumberFormat): Boolean; overload;
function IsDateTimeFormat(AFormatStr: String): Boolean; overload; function IsDateTimeFormat(AFormatStr: String): Boolean; overload;
function IsDateTimeFormat(ANumFormat: TsNumFormatParams): Boolean; overload; function IsDateTimeFormat(ANumFormat: TsNumFormatParams): Boolean; overload;
function IsDateTimeValue(AText: String; const AFormatSettings: TFormatSettings;
out ADateTime: TDateTime; out ANumFormat: TsNumberFormat): Boolean;
function IsDateFormat(ANumFormat: TsNumFormatParams): Boolean; function IsDateFormat(ANumFormat: TsNumFormatParams): Boolean;
function IsTimeFormat(AFormat: TsNumberFormat): Boolean; overload; function IsTimeFormat(AFormat: TsNumberFormat): Boolean; overload;
@@ -226,6 +232,11 @@ function IsTimeFormat(AFormatStr: String): Boolean; overload;
function IsTimeFormat(ANumFormat: TsNumFormatParams): Boolean; overload; function IsTimeFormat(ANumFormat: TsNumFormatParams): Boolean; overload;
function IsLongTimeFormat(AFormatStr: String; ATimeSeparator: char): Boolean; overload; function IsLongTimeFormat(AFormatStr: String; ATimeSeparator: char): Boolean; overload;
function IsNumberValue(AText: String; AutoDetectNumberFormat: Boolean;
const AFormatSettings: TFormatSettings; out ANumber: Double;
out ANumFormat: TsNumberFormat; out ADecimals: Integer;
out ACurrencySymbol, AWarning: String): Boolean;
function IsTimeIntervalFormat(ANumFormat: TsNumFormatParams): Boolean; function IsTimeIntervalFormat(ANumFormat: TsNumFormatParams): Boolean;
function MakeLongDateFormat(ADateFormat: String): String; function MakeLongDateFormat(ADateFormat: String): String;
@@ -233,12 +244,15 @@ function MakeShortDateFormat(ADateFormat: String): String;
procedure MakeTimeIntervalMask(Src: String; var Dest: String); procedure MakeTimeIntervalMask(Src: String; var Dest: String);
function StripAMPM(const ATimeFormatString: String): String; function StripAMPM(const ATimeFormatString: String): String;
procedure InitFormatSettings(out AFormatSettings: TFormatSettings);
procedure ReplaceFormatSettings(var AFormatSettings: TFormatSettings;
const ADefaultFormats: TFormatSettings);
implementation implementation
uses uses
StrUtils, Math, StrUtils, Math,
fpsUtils, fpsNumFormatParser; fpsUtils, fpsNumFormatParser, fpsCurrency;
const const
{@@ Array of format strings identifying the order of number and {@@ Array of format strings identifying the order of number and
@@ -1408,6 +1422,26 @@ begin
end; end;
end; end;
{@@ ----------------------------------------------------------------------------
Checks whether the specified text corresponds to a boolean value. For this,
it must match the specified TRUE and FALSE text phrases.
-------------------------------------------------------------------------------}
function IsBoolValue(const AText, ATrueText, AFalseText: String;
out AValue: Boolean): Boolean;
begin
if SameText(AText, ATrueText) then
begin
AValue := true;
Result := true;
end else
if SameText(AText, AFalseText) then
begin
AValue := false;
Result := true;
end else
Result := false;
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Checks whether the given number format code is for currency, Checks whether the given number format code is for currency,
i.e. requires a currency symbol. i.e. requires a currency symbol.
@@ -1479,6 +1513,55 @@ begin
(ANumFormat.Sections[0].Kind * [nfkDate, nfkTime] <> []); (ANumFormat.Sections[0].Kind * [nfkDate, nfkTime] <> []);
end; end;
{@@ ----------------------------------------------------------------------------
Checks whether the specified text corresponds to a date/time value and returns
true, its numerical value and its built-in numberformat if it is.
-------------------------------------------------------------------------------}
function IsDateTimeValue(AText: String; const AFormatSettings: TFormatSettings;
out ADateTime: TDateTime; out ANumFormat: TsNumberFormat): Boolean;
{ Test whether the text is formatted according to a built-in date/time format.
Converts the obtained date/time value back to a string and compares. }
function TestFormat(lNumFmt: TsNumberFormat): Boolean;
var
fmt: string;
begin
fmt := BuildDateTimeFormatString(lNumFmt, AFormatSettings);
Result := FormatDateTime(fmt, ADateTime, AFormatSettings) = AText;
if Result then ANumFormat := lNumFmt;
end;
begin
Result := TryStrToDateTime(AText, ADateTime, AFormatSettings);
if Result then
begin
ANumFormat := nfCustom;
if abs(ADateTime) > 1 then // this is most probably a date
begin
if TestFormat(nfShortDateTime) then
exit;
if TestFormat(nfLongDate) then
exit;
if TestFormat(nfShortDate) then
exit;
if TestFormat(nfMonthYear) then
exit;
if TestFormat(nfDayMonth) then
exit;
end else
begin // this case is time-only
if TestFormat(nfLongTimeAM) then
exit;
if TestFormat(nfLongTime) then
exit;
if TestFormat(nfShortTimeAM) then
exit;
if TestFormat(nfShortTime) then
exit;
end;
end;
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Checks whether the specified number format parameters apply to a date value. Checks whether the specified number format parameters apply to a date value.
@@ -1549,6 +1632,102 @@ begin
Result := (n=2); Result := (n=2);
end; end;
{@@ ----------------------------------------------------------------------------
Checks whether the specified text corresponds to a numerical value. If it is
then the function result is TRUE, and the number value and its formatting
parameters are returned.
-------------------------------------------------------------------------------}
function IsNumberValue(AText: String; AutoDetectNumberFormat: Boolean;
const AFormatSettings: TFormatSettings;
out ANumber: Double; out ANumFormat: TsNumberFormat; out ADecimals: Integer;
out ACurrencySymbol, AWarning: String): Boolean;
var
p: Integer;
DecSep, ThousSep: Char;
begin
Result := false;
AWarning := '';
// To detect whether the text is a currency value we look for the currency
// string. If we find it, we delete it and convert the remaining string to
// a number.
ACurrencySymbol := AFormatSettings.CurrencyString;
if RemoveCurrencySymbol(ACurrencySymbol, AText) then
begin
if IsNegative(AText) then
begin
if AText = '' then
exit;
AText := '-' + AText;
end;
end else
ACurrencySymbol := '';
if AutoDetectNumberFormat then
Result := TryStrToFloatAuto(AText, ANumber, DecSep, ThousSep, AWarning)
else begin
Result := TryStrToFloat(AText, ANumber, AFormatSettings);
if Result then
begin
if pos(AFormatSettings.DecimalSeparator, AText) = 0
then DecSep := #0
else DecSep := AFormatSettings.DecimalSeparator;
if pos(AFormatSettings.ThousandSeparator, AText) = 0
then ThousSep := #0
else ThousSep := AFormatSettings.ThousandSeparator;
end;
end;
// Try to determine the number format
if Result then
begin
if ThousSep <> #0 then
ANumFormat := nfFixedTh
else
ANumFormat := nfGeneral;
// count number of decimal places and try to catch special formats
ADecimals := 0;
if DecSep <> #0 then
begin
// Go to the decimal separator and search towards the end of the string
p := pos(DecSep, AText) + 1;
while (p <= Length(AText)) do begin
// exponential format
if AText[p] in ['+', '-', 'E', 'e'] then
begin
ANumFormat := nfExp;
break;
end else
// percent format
if AText[p] = '%' then
begin
ANumFormat := nfPercentage;
break;
end else
begin
inc(p);
inc(ADecimals);
end;
end;
if (ADecimals > 0) and (ADecimals < 9) and (ANumFormat = nfGeneral) then
// "no formatting" assumed if there are "many" decimals
ANumFormat := nfFixed;
end else
begin
p := Length(AText);
while (p > 0) do begin
case AText[p] of
'%' : ANumFormat := nfPercentage;
'e', 'E': ANumFormat := nfExp;
else dec(p);
end;
break;
end;
end;
end else
ACurrencySymbol := '';
end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------
Checks whether the specified number format parameters is a time interval Checks whether the specified number format parameters is a time interval
format. format.
@@ -1684,6 +1863,99 @@ begin
end; end;
end; end;
{@@ ----------------------------------------------------------------------------
Initializes the FormatSettings of file a import/export parameters record to
default values which can be replaced by the FormatSettings of the
workbook's FormatSettings
-------------------------------------------------------------------------------}
procedure InitFormatSettings(out AFormatSettings: TFormatSettings);
var
i: Integer;
begin
with AFormatSettings do
begin
CurrencyFormat := Byte(-1);
NegCurrFormat := Byte(-1);
ThousandSeparator := #0;
DecimalSeparator := #0;
CurrencyDecimals := Byte(-1);
DateSeparator := #0;
TimeSeparator := #0;
ListSeparator := #0;
CurrencyString := '';
ShortDateFormat := '';
LongDateFormat := '';
TimeAMString := '';
TimePMString := '';
ShortTimeFormat := '';
LongTimeFormat := '';
for i:=1 to 12 do
begin
ShortMonthNames[i] := '';
LongMonthNames[i] := '';
end;
for i:=1 to 7 do
begin
ShortDayNames[i] := '';
LongDayNames[i] := '';
end;
TwoDigitYearCenturyWindow := Word(-1);
end;
end;
{@@ ----------------------------------------------------------------------------
Replaces in AFormatSettings all members marked as having default values (#0,
-1, '') by the corresponding values of the ADefaultFormats record
-------------------------------------------------------------------------------}
procedure ReplaceFormatSettings(var AFormatSettings: TFormatSettings;
const ADefaultFormats: TFormatSettings);
var
i: Integer;
begin
if AFormatSettings.CurrencyFormat = Byte(-1) then
AFormatSettings.CurrencyFormat := ADefaultFormats.CurrencyFormat;
if AFormatSettings.NegCurrFormat = Byte(-1) then
AFormatSettings.NegCurrFormat := ADefaultFormats.NegCurrFormat;
if AFormatSettings.ThousandSeparator = #0 then
AFormatSettings.ThousandSeparator := ADefaultFormats.ThousandSeparator;
if AFormatSettings.DecimalSeparator = #0 then
AFormatSettings.DecimalSeparator := ADefaultFormats.DecimalSeparator;
if AFormatSettings.CurrencyDecimals = Byte(-1) then
AFormatSettings.CurrencyDecimals := ADefaultFormats.CurrencyDecimals;
if AFormatSettings.DateSeparator = #0 then
AFormatSettings.DateSeparator := ADefaultFormats.DateSeparator;
if AFormatSettings.TimeSeparator = #0 then
AFormatSettings.TimeSeparator := ADefaultFormats.TimeSeparator;
if AFormatSettings.ListSeparator = #0 then
AFormatSettings.ListSeparator := ADefaultFormats.ListSeparator;
if AFormatSettings.CurrencyString = '' then
AFormatSettings.CurrencyString := ADefaultFormats.CurrencyString;
if AFormatSettings.ShortDateFormat = '' then
AFormatSettings.ShortDateFormat := ADefaultFormats.ShortDateFormat;
if AFormatSettings.LongDateFormat = '' then
AFormatSettings.LongDateFormat := ADefaultFormats.LongDateFormat;
if AFormatSettings.ShortTimeFormat = '' then
AFormatSettings.ShortTimeFormat := ADefaultFormats.ShortTimeFormat;
if AFormatSettings.LongTimeFormat = '' then
AFormatSettings.LongTimeFormat := ADefaultFormats.LongTimeFormat;
for i:=1 to 12 do
begin
if AFormatSettings.ShortMonthNames[i] = '' then
AFormatSettings.ShortMonthNames[i] := ADefaultFormats.ShortMonthNames[i];
if AFormatSettings.LongMonthNames[i] = '' then
AFormatSettings.LongMonthNames[i] := ADefaultFormats.LongMonthNames[i];
end;
for i:=1 to 7 do
begin
if AFormatSettings.ShortDayNames[i] = '' then
AFormatSettings.ShortDayNames[i] := ADefaultFormats.ShortDayNames[i];
if AFormatSettings.LongDayNames[i] = '' then
AFormatSettings.LongDayNames[i] := ADefaultFormats.LongDayNames[i];
end;
if AFormatSettings.TwoDigitYearCenturyWindow = Word(-1) then
AFormatSettings.TwoDigitYearCenturyWindow := ADefaultFormats.TwoDigitYearCenturyWindow;
end;
{==============================================================================} {==============================================================================}
{ TsNumFormatParams } { TsNumFormatParams }

View File

@@ -1253,7 +1253,6 @@ end;
procedure TsNumFormatParser.ScanFormat; procedure TsNumFormatParser.ScanFormat;
var var
done: Boolean; done: Boolean;
s: String;
n: Integer; n: Integer;
uch: Cardinal; uch: Cardinal;
begin begin

View File

@@ -29,7 +29,7 @@
This package is all you need if you don't want graphical components (like grids and charts)."/> This package is all you need if you don't want graphical components (like grids and charts)."/>
<License Value="LGPL with static linking exception. This is the same license as is used in the LCL (Lazarus Component Library)."/> <License Value="LGPL with static linking exception. This is the same license as is used in the LCL (Lazarus Component Library)."/>
<Version Major="1" Minor="7"/> <Version Major="1" Minor="7"/>
<Files Count="36"> <Files Count="37">
<Item1> <Item1>
<Filename Value="fpolestorage.pas"/> <Filename Value="fpolestorage.pas"/>
<UnitName Value="fpolestorage"/> <UnitName Value="fpolestorage"/>
@@ -174,6 +174,10 @@ This package is all you need if you don't want graphical components (like grids
<Filename Value="fpshtml.pas"/> <Filename Value="fpshtml.pas"/>
<UnitName Value="fpsHTML"/> <UnitName Value="fpsHTML"/>
</Item36> </Item36>
<Item37>
<Filename Value="fpshtmlutils.pas"/>
<UnitName Value="fpshtmlutils"/>
</Item37>
</Files> </Files>
<RequiredPkgs Count="2"> <RequiredPkgs Count="2">
<Item1> <Item1>

View File

@@ -13,7 +13,8 @@ uses
uvirtuallayer_ole_helpers, uvirtuallayer_ole_types, uvirtuallayer_stream, uvirtuallayer_ole_helpers, uvirtuallayer_ole_types, uvirtuallayer_stream,
fpolebasic, wikitable, fpsNumFormatParser, fpsfunc, fpsRPN, fpsStrings, fpolebasic, wikitable, fpsNumFormatParser, fpsfunc, fpsRPN, fpsStrings,
fpscsv, fpsCsvDocument, fpspatches, fpsTypes, xlsEscher, fpsReaderWriter, fpscsv, fpsCsvDocument, fpspatches, fpsTypes, xlsEscher, fpsReaderWriter,
fpsNumFormat, fpsclasses, fpsHeaderFooterParser, fpsPalette, fpsHTML; fpsNumFormat, fpsclasses, fpsHeaderFooterParser, fpsPalette, fpsHTML,
fpshtmlutils;
implementation implementation

View File

@@ -155,6 +155,9 @@
<OtherUnitFiles Value=".."/> <OtherUnitFiles Value=".."/>
<UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/>
</SearchPaths> </SearchPaths>
<Other>
<CustomOptions Value="-d-MDelphi"/>
</Other>
</CompilerOptions> </CompilerOptions>
<Debugging> <Debugging>
<Exceptions Count="6"> <Exceptions Count="6">

View File

@@ -519,7 +519,7 @@ end;
procedure TsSpreadBIFF5Reader.ReadRSTRING(AStream: TStream); procedure TsSpreadBIFF5Reader.ReadRSTRING(AStream: TStream);
var var
L: Word; L: Word;
B, F: Byte; B: Byte;
ARow, ACol: Cardinal; ARow, ACol: Cardinal;
XF: Word; XF: Word;
ansistr: ansistring; ansistr: ansistring;
@@ -629,7 +629,7 @@ procedure TsSpreadBIFF5Reader.ReadXF(AStream: TStream);
var var
rec: TBIFF5_XFRecord; rec: TBIFF5_XFRecord;
fmt: TsCellFormat; fmt: TsCellFormat;
i, cidx: Integer; cidx: Integer;
nfparams: TsNumFormatParams; nfparams: TsNumFormatParams;
nfs: String; nfs: String;
b: Byte; b: Byte;

View File

@@ -557,31 +557,14 @@ function TsSpreadBIFF8Reader.ReadUnformattedWideString(const AStream: TStream;
var var
flags: Byte; flags: Byte;
DecomprStrValue: WideString; DecomprStrValue: WideString;
AnsiStrValue: ansistring;
//RunsCounter: Word;
//AsianPhoneticBytes: DWord;
i: Integer; i: Integer;
j: SizeUInt;
len: SizeInt; len: SizeInt;
recType: Word; recType: Word;
recSize: Word; {%H-}recSize: Word;
C: WideChar; C: WideChar;
begin begin
flags := AStream.ReadByte; flags := AStream.ReadByte;
dec(PendingRecordSize); dec(PendingRecordSize);
{
if StringFlags and 4 = 4 then begin
//Asian phonetics
//Read Asian phonetics Length (not used)
AsianPhoneticBytes := DWordLEtoN(AStream.ReadDWord);
dec(PendingRecordSize,4);
end;
if StringFlags and 8 = 8 then begin
//Rich string
RunsCounter := WordLEtoN(AStream.ReadWord);
dec(PendingRecordSize,2);
end;
}
if flags and 1 = 1 Then begin if flags and 1 = 1 Then begin
//String is WideStringLE //String is WideStringLE
if (ALength * SizeOf(WideChar)) > PendingRecordSize then begin if (ALength * SizeOf(WideChar)) > PendingRecordSize then begin
@@ -619,34 +602,6 @@ begin
end; end;
Result := DecomprStrValue; Result := DecomprStrValue;
end; end;
{
if StringFlags and 8 = 8 then begin
// Rich string (This only occurs in BIFF8)
SetLength(ARichTextRuns, RunsCounter);
for j := 0 to RunsCounter - 1 do begin
if (PendingRecordSize <= 0) then begin
// A CONTINUE may happened here
RecordType := WordLEToN(AStream.ReadWord);
RecordSize := WordLEToN(AStream.ReadWord);
if RecordType <> INT_EXCEL_ID_CONTINUE then begin
Raise Exception.Create('[TsSpreadBIFF8Reader.ReadWideString] Expected CONTINUE record not found.');
end else begin
PendingRecordSize := RecordSize;
end;
end;
ARichTextRuns[j].FirstIndex := WordLEToN(AStream.ReadWord);
ARichTextRuns[j].FontIndex := WordLEToN(AStream.ReadWord);
dec(PendingRecordSize, 2*2);
end;
end;
if StringFlags and 4 = 4 then begin
//Asian phonetics
//Read Asian phonetics, discarded as not used.
SetLength(AnsiStrValue, AsianPhoneticBytes);
AStream.ReadBuffer(AnsiStrValue[1], AsianPhoneticBytes);
dec(PendingRecordSize, AsianPhoneticBytes);
end;
}
end; end;
function TsSpreadBIFF8Reader.ReadWideString(const AStream: TStream; function TsSpreadBIFF8Reader.ReadWideString(const AStream: TStream;
@@ -1369,7 +1324,7 @@ var
cell: PCell; cell: PCell;
ms: TMemoryStream; ms: TMemoryStream;
rtfRuns: TsRichTextFormattingRuns; rtfRuns: TsRichTextFormattingRuns;
j, n: Integer; n: Integer;
begin begin
rec.Row := 0; // to silence the compiler... rec.Row := 0; // to silence the compiler...
@@ -1450,16 +1405,6 @@ begin
end; end;
procedure TsSpreadBIFF8Reader.ReadXF(const AStream: TStream); procedure TsSpreadBIFF8Reader.ReadXF(const AStream: TStream);
(*
function FixLineStyle(dw: DWord): TsLineStyle;
{ Not all line styles defined in BIFF8 are supported by fpspreadsheet. }
begin
case dw of
$01..$07: result := TsLineStyle(dw-1);
else Result := lsDashed;
end;
end;
*)
var var
rec: TBIFF8_XFRecord; rec: TBIFF8_XFRecord;
fmt: TsCellFormat; fmt: TsCellFormat;
@@ -1469,9 +1414,7 @@ var
fs: TsFillStyle; fs: TsFillStyle;
nfs: String; nfs: String;
nfParams: TsNumFormatParams; nfParams: TsNumFormatParams;
i: Integer;
iclr: Integer; iclr: Integer;
fnt: TsFont;
begin begin
InitFormatRecord(fmt); InitFormatRecord(fmt);
fmt.ID := FCellFormatList.Count; fmt.ID := FCellFormatList.Count;

View File

@@ -325,6 +325,7 @@ type
// Adjusts Excel float (date, date/time, time) with the file's base date to get a TDateTime // Adjusts Excel float (date, date/time, time) with the file's base date to get a TDateTime
function ConvertExcelDateTimeToDateTime function ConvertExcelDateTimeToDateTime
(const AExcelDateNum: Double; ADateMode: TDateMode): TDateTime; (const AExcelDateNum: Double; ADateMode: TDateMode): TDateTime;
// Adjusts TDateTime with the file's base date to get // Adjusts TDateTime with the file's base date to get
// an Excel float value representing a time/date/datetime // an Excel float value representing a time/date/datetime
function ConvertDateTimeToExcelDateTime function ConvertDateTimeToExcelDateTime
@@ -332,6 +333,7 @@ type
// Converts the error byte read from cells or formulas to fps error value // Converts the error byte read from cells or formulas to fps error value
function ConvertFromExcelError(AValue: Byte): TsErrorValue; function ConvertFromExcelError(AValue: Byte): TsErrorValue;
// Converts an fps error value to the byte code needed in xls files // Converts an fps error value to the byte code needed in xls files
function ConvertToExcelError(AValue: TsErrorValue): byte; function ConvertToExcelError(AValue: TsErrorValue): byte;
@@ -665,6 +667,16 @@ begin
case ADateMode of case ADateMode of
dm1900: dm1900:
begin begin
{
Result := AExcelDateNum + DATEMODE_1900_BASE - 1.0;
// Excel and Lotus 1-2-3 incorrectly assume that 1900 was a leap year
// Therefore all dates before March 01 are off by 1.
// The old fps implementation corrected only Feb 29, but all days are
// wrong!
if AExcelDateNum < 61 then
Result := Result + 1.0;
}
// Check for Lotus 1-2-3 bug with 1900 leap year // Check for Lotus 1-2-3 bug with 1900 leap year
if AExcelDateNum=61.0 then if AExcelDateNum=61.0 then
// 29 feb does not exist, change to 28 // 29 feb does not exist, change to 28
@@ -693,9 +705,12 @@ begin
begin begin
case ADateMode of case ADateMode of
dm1900: dm1900:
result:=ADateTime-DATEMODE_1900_BASE+1.0; begin
Result := ADateTime - DATEMODE_1900_BASE + 1.0;
// if Result < 61 then Result := Result - 1.0;
end;
dm1904: dm1904:
result:=ADateTime-DATEMODE_1904_BASE; Result := ADateTime - DATEMODE_1904_BASE;
else else
raise Exception.CreateFmt('ConvertDateTimeToExcelDateTime: unknown datemode %d. Please correct fpspreadsheet source code. ', [ADateMode]); raise Exception.CreateFmt('ConvertDateTimeToExcelDateTime: unknown datemode %d. Please correct fpspreadsheet source code. ', [ADateMode]);
end; end;
@@ -2271,11 +2286,13 @@ end;
procedure TsSpreadBIFFReader.ReadWorkbookGlobals(AStream: TStream); procedure TsSpreadBIFFReader.ReadWorkbookGlobals(AStream: TStream);
begin begin
// To be overridden by BIFF5 and BIFF8 // To be overridden by BIFF5 and BIFF8
Unused(AStream);
end; end;
procedure TsSpreadBIFFReader.ReadWorksheet(AStream: TStream); procedure TsSpreadBIFFReader.ReadWorksheet(AStream: TStream);
begin begin
// To be overridden by BIFF5 and BIFF8 // To be overridden by BIFF5 and BIFF8
Unused(AStream);
end; end;
{@@ ---------------------------------------------------------------------------- {@@ ----------------------------------------------------------------------------