x2xmldatabinding/Shared/XMLDataBindingUtils.pas

422 lines
11 KiB
ObjectPascal

{
Helpers functions for the X2Software XML Data Binding
Last changed: $Date$
Revision: $Rev$
URL: $URL$
}
unit XMLDataBindingUtils;
interface
uses
Classes,
SysUtils,
XMLIntf;
type
EBase64Error = class(Exception);
TXMLDateTimeFormat = (xdtDateTime, xdtDate, xdtTime);
TXMLTimeFragment = (xtfMilliseconds, xtfTimezone);
TXMLTimeFragments = set of TXMLTimeFragment;
const
AllTimeFragments = [Low(TXMLTimeFragment)..High(TXMLTimeFragment)];
function DateTimeToXML(ADate: TDateTime; AFormat: TXMLDateTimeFormat; ATimeFragments: TXMLTimeFragments = AllTimeFragments): string;
function XMLToDateTime(const ADate: string; AFormat: TXMLDateTimeFormat): TDateTime;
function BoolToXML(AValue: Boolean): WideString;
function XMLToBool(const AValue: WideString): Boolean;
function FloatToXML(AValue: Extended): WideString;
function XMLToFloat(const AValue: WideString): Extended;
function Base64Encode(AValue: String): String;
function Base64Decode(AValue: String): String;
function GetNodeIsNil(ANode: IXMLNode): Boolean;
procedure SetNodeIsNil(ANode: IXMLNode; ASetNil: Boolean);
const
XMLSchemaInstanceURI = 'http://www.w3.org/2001/XMLSchema-instance';
XMLDateFormat = 'yyyy"-"mm"-"dd';
XMLTimeFormat = 'hh":"nn":"ss';
XMLMsecsFormat = '"."zzz';
XMLTimezoneZulu = 'Z';
XMLTimezoneFormat = '%s%.2d:%.2d';
XMLDateTimeFormats: array[TXMLDateTimeFormat] of String =
(
XMLDateFormat + '"T"' + XMLTimeFormat,
XMLDateFormat,
XMLTimeFormat
);
XMLTimezoneSigns: array[Boolean] of Char = ('-', '+');
XMLBoolValues: array[Boolean] of String =
(
'false',
'true'
);
XMLIsNilAttribute = 'nil';
Base64ValidChars = ['A'..'Z', 'a'..'z', '0'..'9', '+', '/'];
Base64LookupTable = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +
'abcdefghijklmnopqrstuvwxyz' +
'0123456789+/';
Base64Padding = '=';
implementation
uses
DateUtils,
Windows;
function DateTimeToXML(ADate: TDateTime; AFormat: TXMLDateTimeFormat; ATimeFragments: TXMLTimeFragments): string;
var
formatSettings: TFormatSettings;
timeZone: TTimeZoneInformation;
timeOffset: Integer;
begin
GetLocaleFormatSettings(LOCALE_SYSTEM_DEFAULT, formatSettings);
Result := FormatDateTime(XMLDateTimeFormats[AFormat], ADate, formatSettings);
if AFormat in [xdtDateTime, xdtTime] then
begin
if xtfMilliseconds in ATimeFragments then
Result := Result + FormatDateTime(XMLMsecsFormat, ADate);
if xtfTimezone in ATimeFragments then
begin
FillChar(timeZone, SizeOf(TTimeZoneInformation), #0);
if GetTimeZoneInformation(timeZone) <> TIME_ZONE_ID_INVALID then
begin
timeOffset := -timeZone.Bias;
if timeOffset = 0 then
Result := Result + XMLTimezoneZulu
else
Result := Result + Format(XMLTimezoneFormat,
[XMLTimezoneSigns[timeOffset > 0],
Abs(timeZone.Bias div 60),
Abs(timeZone.Bias mod 60)]);
end;
end;
end;
end;
function XMLToDateTime(const ADate: string; AFormat: TXMLDateTimeFormat): TDateTime;
const
{ yyyy-mm-ddThh:nn:ss.zzz+xx:xx }
XMLTimeSeparatorPos = 11;
XMLTimeSeparator = 'T';
XMLMinTimeLength = 8;
var
date: string;
time: string;
year: Integer;
month: Integer;
day: Integer;
hour: Integer;
minute: Integer;
second: Integer;
msec: Integer;
hasTimezone: Boolean;
xmlOffset: Integer;
timeZone: TTimeZoneInformation;
localOffset: Integer;
begin
Result := 0;
date := '';
time := '';
case AFormat of
xdtDateTime:
begin
if (Length(ADate) < XMLTimeSeparatorPos) or
(ADate[XMLTimeSeparatorPos] <> XMLTimeSeparator) then
Exit;
date := ADate;
time := ADate;
SetLength(date, Pred(XMLTimeSeparatorPos));
Delete(time, 1, XMLTimeSeparatorPos);
end;
xdtDate:
begin
if Length(ADate) < Pred(XMLTimeSeparatorPos) then
Exit;
date := ADate;
end;
xdtTime:
begin
if Length(ADate) < XMLMinTimeLength then
Exit;
time := ADate;
end;
end;
if AFormat in [xdtDateTime, xdtDate] then
begin
{ Parse date (yyyy-mm-hh) }
if TryStrToInt(Copy(date, 1, 4), year) and
TryStrToInt(Copy(date, 6, 2), month) and
TryStrToInt(Copy(date, 9, 2), day) then
Result := EncodeDate(year, month, day);
end;
if AFormat in [xdtDateTime, xdtTime] then
begin
{ Parse time (hh:nn:ss) }
if TryStrToInt(Copy(time, 1, 2), hour) and
TryStrToInt(Copy(time, 4, 2), minute) and
TryStrToInt(Copy(time, 7, 2), second) then
begin
msec := 0;
Delete(time, 1, 8);
if Length(time) > 0 then
begin
if time[1] = '.' then
begin
{ Parse milliseconds (.zzz) }
if not TryStrToInt(Copy(time, 2, 3), msec) then
msec := 0;
Delete(time, 1, 4);
end;
end;
Result := Result + EncodeTime(hour, minute, second, msec);
if Length(time) > 0 then
begin
hasTimezone := False;
xmlOffset := 0;
if time[1] = XMLTimezoneZulu then
begin
{ Zulu time }
hasTimezone := True;
end else if time[1] in [XMLTimezoneSigns[False], XMLTimezoneSigns[True]] then
begin
{ Parse timezone ([+|-]xx:xx) }
if TryStrToInt(Copy(time, 2, 2), hour) and
TryStrToInt(Copy(time, 5, 2), minute) then
begin
xmlOffset := (hour * MinsPerHour) + minute;
hasTimezone := True;
if time[1] = XMLTimezoneSigns[False] then
xmlOffset := -xmlOffset;
end;
end;
if hasTimezone then
begin
FillChar(timeZone, SizeOf(TTimeZoneInformation), #0);
if GetTimeZoneInformation(timeZone) <> TIME_ZONE_ID_INVALID then
begin
localOffset := -timeZone.Bias;
Result := IncMinute(Result, localOffset - xmlOffset);
end;
end;
end;
end;
end;
end;
function BoolToXML(AValue: Boolean): WideString;
begin
Result := XMLBoolValues[AValue];
end;
function XMLToBool(const AValue: WideString): Boolean;
begin
Result := StrToBoolDef(AValue, False);
end;
function GetXMLFloatFormatSettings(): TFormatSettings;
begin
Result.DecimalSeparator := '.';
end;
function FloatToXML(AValue: Extended): WideString;
begin
Result := FloatToStr(AValue, GetXMLFloatFormatSettings());
end;
function XMLToFloat(const AValue: WideString): Extended;
begin
Result := StrToFloat(AValue, GetXMLFloatFormatSettings());
end;
function Base64Encode(AValue: String): String;
var
pos: Integer;
lookupIndex: array[0..3] of Byte;
padCount: Integer;
begin
Result := '';
if Length(AValue) = 0 then
exit;
padCount := 0;
{ At least 3 input bytes are required, and the input must be a multiple of 3 }
if Length(AValue) < 3 then
padCount := 3 - Length(AValue)
else if Length(AValue) mod 3 <> 0 then
padCount := 3 - (Length(AValue) mod 3);
if padCount > 0 then
AValue := AValue + StringOfChar(#0, padCount);
pos := 1;
{ Process in 3-byte blocks }
while pos <= Length(AValue) - 2 do
begin
{ Each 3 input bytes are converted into 4 index values
in the range of 0..63, by taking 6 bits each step.
6 high bytes of first char }
lookupIndex[0] := (Ord(AValue[pos]) shr 2) and $3F;
{ 2 low bytes of first char + 4 high bytes of second char }
lookupIndex[1] := ((Ord(AValue[pos]) shl 4) and $3F) or
(Ord(AValue[pos + 1]) shr 4);
{ 4 low bytes of second char + 2 high bytes of third char }
lookupIndex[2] :=((Ord(AValue[pos + 1]) shl 2) and $3F) or
(Ord(AValue[pos + 2]) shr 6);
{ 6 low bytes of third char }
lookupIndex[3] := Ord(AValue[pos + 2]) and $3F;
Result := Result + Base64LookupTable[lookupIndex[0] + 1] +
Base64LookupTable[lookupIndex[1] + 1] +
Base64LookupTable[lookupIndex[2] + 1] +
Base64LookupTable[lookupIndex[3] + 1];
Inc(pos, 3);
end;
{ Replace padding }
if padCount > 0 then
begin
for pos := Length(Result) downto Length(Result) - Pred(padCount) do
Result[pos] := Base64Padding;
end;
end;
function Base64LookupIndex(AChar: Char): Byte;
var
lookupIndex: Integer;
begin
Result := Ord(Base64Padding);
for lookupIndex := 1 to Length(Base64LookupTable) do
if Base64LookupTable[lookupIndex] = AChar then
begin
Result := Pred(lookupIndex);
break;
end;
end;
function Base64Decode(AValue: String): String;
var
pos: Integer;
padCount: Integer;
value: Byte;
begin
Result := '';
if Length(AValue) = 0 then
exit;
if Length(AValue) mod 4 <> 0 then
raise EBase64Error.Create('Value length must be a multiple of 4');
padCount := 0;
pos := Length(AValue);
{ Count padding chars }
while (pos > 0) and (AValue[pos] = Base64Padding) do
begin
Inc(padCount);
Dec(pos);
end;
Result := '';
pos := 1;
while pos <= Length(AValue) - 3 do
begin
value := (Base64LookupIndex(AValue[pos]) shl 2) or
(Base64LookupIndex(AValue[pos + 1]) shr 4);
Result := Result + Chr(value);
value := (Base64LookupIndex(AValue[pos + 1]) shl 4) or
(Base64LookupIndex(AValue[pos + 2]) shr 2);
Result := Result + Chr(value);
value := (Base64LookupIndex(AValue[pos + 2]) shl 6) or
(Base64LookupIndex(AValue[pos + 3]));
Result := Result + Chr(value);
Inc(pos, 4);
end;
{ Delete padding }
if padCount > 0 then
SetLength(Result, Length(Result) - padCount);
end;
function GetNodeIsNil(ANode: IXMLNode): Boolean;
begin
Result := ANode.HasAttribute(XMLIsNilAttribute, XMLSchemaInstanceURI) and
XMLToBool(ANode.GetAttributeNS(XMLIsNilAttribute, XMLSchemaInstanceURI));
end;
procedure SetNodeIsNil(ANode: IXMLNode; ASetNil: Boolean);
begin
if ASetNil then
begin
ANode.ChildNodes.Clear;
ANode.SetAttributeNS(XMLIsNilAttribute, XMLSchemaInstanceURI, BoolToXML(True));
end else
ANode.AttributeNodes.Delete(XMLIsNilAttribute, XMLSchemaInstanceURI);
end;
end.