diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index d6dbfcf..0000000 --- a/.gitmodules +++ /dev/null @@ -1,6 +0,0 @@ -[submodule "vendor/x2utils"] - path = vendor/x2utils - url = https://github.com/MvRens/x2utils.git -[submodule "vendor/x2log"] - path = vendor/x2log - url = https://github.com/MvRens/x2log.git diff --git a/Forms/MainFrm.dfm b/Forms/MainFrm.dfm index adf8064..f9c6125 100644 --- a/Forms/MainFrm.dfm +++ b/Forms/MainFrm.dfm @@ -63,15 +63,12 @@ object MainForm: TMainForm Top = 68 Width = 416 Height = 93 - ActivePage = spFolder + ActivePage = spFile Anchors = [akLeft, akTop, akRight] Style = tsButtons TabOrder = 2 object spFile: TTabSheet TabVisible = False - DesignSize = ( - 408 - 83) object lblFile: TLabel Left = 4 Top = 7 @@ -79,15 +76,21 @@ object MainForm: TMainForm Height = 13 Caption = 'Output file:' end - object feFile: TJvFilenameEdit - Left = 89 - Top = 3 - Width = 316 + object feFile: TEdit + Left = 65 + Top = 4 + Width = 269 Height = 21 - DialogOptions = [ofHideReadOnly, ofCreatePrompt] - Anchors = [akLeft, akTop, akRight] TabOrder = 0 - Text = '' + end + object ButtonOutputFileBrowseButton: TButton + Left = 340 + Top = 4 + Width = 65 + Height = 21 + Caption = 'Browse' + TabOrder = 1 + OnClick = ButtonOutputFileBrowseButtonClick end end object spFolder: TTabSheet @@ -201,15 +204,21 @@ object MainForm: TMainForm TabOrder = 1 OnClick = btnHintsClick end - object feSchema: TJvFilenameEdit - Left = 104 + object feSchema: TEdit + Left = 72 Top = 8 - Width = 316 + Width = 289 Height = 21 - OnAfterDialog = feSchemaAfterDialog - DialogOptions = [ofHideReadOnly, ofFileMustExist] TabOrder = 4 - Text = '' + end + object SchmeFileBrowseButton: TButton + Left = 367 + Top = 8 + Width = 65 + Height = 21 + Caption = 'Browse' + TabOrder = 5 + OnClick = SchmeFileBrowseButtonClick end object dlgSchema: TOpenDialog Filter = 'W3C XML Schema files (*.xsd)|*.xsd|All files (*.*)|*.*' diff --git a/Forms/MainFrm.pas b/Forms/MainFrm.pas index 5bbfecc..142899c 100644 --- a/Forms/MainFrm.pas +++ b/Forms/MainFrm.pas @@ -14,7 +14,7 @@ uses XMLDOM, XMLIntf, DataBindingHintsXML, - XMLDataBindingGenerator, JvExMask, JvToolEdit + XMLDataBindingGenerator { cxButtonEdit, cxContainer, @@ -51,9 +51,11 @@ type edtFolderPrefix: TEdit; edtFolderPostfix: TEdit; deFolder: TEdit; - feSchema: TJvFilenameEdit; deFolderPropertiesButton: TButton; - feFile: TJvFilenameEdit; + feSchema: TEdit; + SchmeFileBrowseButton: TButton; + feFile: TEdit; + ButtonOutputFileBrowseButton: TButton; procedure btnCloseClick(Sender: TObject); procedure btnGenerateClick(Sender: TObject); @@ -64,8 +66,8 @@ type procedure feSchemaPropertiesButtonClick(Sender: TObject; AButtonIndex: Integer); procedure feSchemaPropertiesChange(Sender: TObject); procedure btnHintsClick(Sender: TObject); - procedure feSchemaAfterDialog(Sender: TObject; var AName: string; - var AAction: Boolean); + procedure SchmeFileBrowseButtonClick(Sender: TObject); + procedure ButtonOutputFileBrowseButtonClick(Sender: TObject); private function CheckValidSchemaFile: Boolean; function CheckReadOnly(const AFileName: String): Boolean; @@ -176,11 +178,11 @@ begin if rbFile.Checked then begin - if not CheckReadOnly(feFile.FileName) then + if not CheckReadOnly(feFile.Text) then Exit; generator.OutputType := otSingle; - generator.OutputPath := feFile.FileName; + generator.OutputPath := feFile.Text; end else if rbFolder.Checked then begin generator.OutputType := otMultiple; @@ -190,9 +192,9 @@ begin generator.HasChecksEmpty := cbHasChecksEmpty.Checked; generator.HasGenerateGetOptionalOrDefault := cbGenerateGetOptionalOrDefault.Checked; generator.OnGetFileName := GetFileName; - generator.Execute(feSchema.Filename); + generator.Execute(feSchema.Text); - SaveSettings(feSchema.FileName); + SaveSettings(feSchema.Text); ShowMessage('The data binding has been generated.'); finally @@ -235,12 +237,6 @@ begin end; -procedure TMainForm.feSchemaAfterDialog(Sender: TObject; var AName: string; - var AAction: Boolean); -begin - feFile.FileName := ChangeFileExt(AName, '.pas'); -end; - procedure TMainForm.feSchemaPropertiesButtonClick(Sender: TObject; AButtonIndex: Integer); begin if dlgSchema.Execute then @@ -337,9 +333,17 @@ begin end; +procedure TMainForm.SchmeFileBrowseButtonClick(Sender: TObject); +begin + if dlgSchema.Execute then + begin + feSchema.Text := dlgSchema.FileName; + end; +end; + function TMainForm.CheckValidSchemaFile: Boolean; begin - Result := FileExists(feSchema.FileName); + Result := FileExists(feSchema.Text); if not Result then begin @@ -392,6 +396,14 @@ begin end; +procedure TMainForm.ButtonOutputFileBrowseButtonClick(Sender: TObject); +begin + if dlgOutputFile.Execute then + begin + feFile.Text := dlgOutputFile.FileName; + end; +end; + { THintsDelphiXMLDataBindingGenerator } procedure THintsDelphiXMLDataBindingGenerator.GenerateDataBinding; begin diff --git a/README.md b/README.md index 0f040f8..afacc18 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,6 @@ - Influence the generator by using a Hints file ## Dependencies - - JEDI Component Library (jcl /jvcl) - - x2utils (https://github.com/MvRens/x2utils.git) (git submodule) - - x2log (https://github.com/MvRens/x2log.git) (git submodule) - +* Works up from Delphi XE2 Documentation is available on [ReadTheDocs](https://x2xmldatabinding.readthedocs.io/). \ No newline at end of file diff --git a/Units/DelphiXMLDataBindingGenerator.pas b/Units/DelphiXMLDataBindingGenerator.pas index 17c5171..ac629c2 100644 --- a/Units/DelphiXMLDataBindingGenerator.pas +++ b/Units/DelphiXMLDataBindingGenerator.pas @@ -84,9 +84,7 @@ uses StrUtils, SysUtils, - X2UtNamedFormat, - - X2Log.Global; + X2UtNamedFormat; const VariantText = 'Variant'; @@ -937,8 +935,6 @@ var member: TDelphiXMLMember; begin - TX2GlobalLog.Verbose('WriteSchemaInterfaceProperties: ' + AItem.Name); - if ASection = dxsForward then Exit; @@ -1812,7 +1808,8 @@ begin if OutputType = otMultiple then begin path := IncludeTrailingPathDelimiter(Result); - fileName := ASchemaName + '.pas'; + fileName := ASchemaName.Replace('-', '_'); + fileName := fileName + '.pas'; if Assigned(FOnGetFileName) then FOnGetFileName(Self, ASchemaName, path, fileName); diff --git a/Units/X2UtNamedFormat.pas b/Units/X2UtNamedFormat.pas new file mode 100644 index 0000000..ac67997 --- /dev/null +++ b/Units/X2UtNamedFormat.pas @@ -0,0 +1,248 @@ +{ + :: X2UtNamedFormat implements Format-style functionality using named + :: instead of indexed parameters. + :: + :: Last changed: $Date$ + :: Revision: $Rev$ + :: Author: $Author$ +} +unit X2UtNamedFormat; + +interface +uses + Classes, + SysUtils; + + +type + TNamedFormatStringList = class(TStringList) + public + procedure AddLn(); + + function Format(AParams: array of const): String; + end; + + + { + AFormat uses the same format strings as SysUtils.Format, where each + format specifier may use a named instead of a numeric index, surrounded by + <>, eg: + + %:s %:.2d + + + AParams contains alternating the parameter name and it's value. + + Note: NamedFormat works by mapping names to indices and passing the result + to SysUtils.Format. Unnamed or existing indexed specifiers will therefore + be affected by named specifiers! It is strongly recommended to name all + specifiers. + } + function NamedFormat(const AFormat: String; AParams: array of const; AFormatSettings: TFormatSettings): String; overload; + function NamedFormat(const AFormat: String; AParams: array of const): String; overload; + + +implementation +uses + Windows; + +type + TProtectedMemoryStream = class(TMemoryStream); + + +const + SpecifierChar = '%'; + SpecifierNameStart = '<'; + SpecifierNameEnd = '>'; + ValidNameChars = ['A'..'Z', 'a'..'z', '0'..'9', '_']; + + +procedure StreamWriteChar(const AStream: TStream; const AValue: Char); +begin + AStream.WriteBuffer(AValue, SizeOf(Char)); +end; + + +procedure StreamWriteString(const AStream: TStream; const AValue: String); +begin + AStream.WriteBuffer(PChar(AValue)^, Length(AValue) * SizeOf(Char)); +end; + + +function FindNameEnd(const APosition: PChar; const AEnd: PChar): PChar; +var + position: PChar; + +begin + Result := nil; + position := APosition; + + while position < AEnd do + begin + if position^ = SpecifierNameEnd then + begin + Result := position; + break; + end; + + if not CharInSet(position^, ValidNameChars) then + break; + + Inc(position); + end; +end; + + +function NamedFormat(const AFormat: String; AParams: array of const): String; overload; +var + formatSettings: TFormatSettings; + +begin + formatSettings := TFormatSettings.Create; + Result := NamedFormat(AFormat, AParams, formatSettings); +end; + + +function NamedFormat(const AFormat: string; AParams: array of const; AFormatSettings: TFormatSettings): String; +var + currentPos: PChar; + formatEnd: PChar; + formatStream: TMemoryStream; + formatString: String; + name: String; + nameEnd: PChar; + nameStart: PChar; + param: TVarRec; + paramIndex: Integer; + paramNames: TStringList; + paramValues: array of TVarRec; + specifierIndex: Integer; + errorMsg: String; + +begin + if Length(AParams) mod 2 = 1 then + raise Exception.Create('AParams must contains a multiple of 2 number of items'); + + currentPos := PChar(AFormat); + SetLength(paramValues, 0); + + formatEnd := currentPos; + Inc(formatEnd, Length(AFormat)); + + paramNames := TStringList.Create(); + try + paramNames.CaseSensitive := False; + + formatStream := TMemoryStream.Create(); + try + { Most likely scenario; the names are longer than the replacement + indexes. } + TProtectedMemoryStream(formatStream).Capacity := Length(AFormat) * SizeOf(Char); + + while currentPos < formatEnd do + begin + { Search for % } + if currentPos^ = SpecifierChar then + begin + StreamWriteChar(formatStream, currentPos^); + Inc(currentPos); + + { Check if this is indeed a named specifier } + if (currentPos < formatEnd) and (currentPos^ = SpecifierNameStart) then + begin + Inc(currentPos); + + nameStart := currentPos; + nameEnd := FindNameEnd(currentPos, formatEnd); + + if Assigned(nameEnd) then + begin + SetString(name, nameStart, nameEnd - nameStart); + + specifierIndex := paramNames.IndexOf(name); + if specifierIndex = -1 then + specifierIndex := paramNames.Add(name); + + StreamWriteString(formatStream, IntToStr(specifierIndex)); + + currentPos := nameEnd; + end; + end else + StreamWriteChar(formatStream, currentPos^); + end else + StreamWriteChar(formatStream, currentPos^); + + Inc(currentPos); + end; + + SetString(formatString, PChar(formatStream.Memory), formatStream.Size div SizeOf(Char)); + finally + FreeAndNil(formatStream); + end; + + SetLength(paramValues, paramNames.Count); + paramIndex := 0; + + while paramIndex < High(AParams) do + begin + param := AParams[paramIndex]; + + case param.VType of + vtChar: name := string(param.VChar); + vtString: name := string(param.VString^); + vtPChar: name := string(param.VPChar); + vtAnsiString: name := string(PChar(param.VAnsiString)); + vtWideChar: name := string(param.VWideChar); + vtWideString: name := string(WideString(param.VWideString)); + vtUnicodeString: name := string(UnicodeString(param.VUnicodeString)); + else + raise Exception.CreateFmt('Parameter name at index %d is not a string value', + [paramIndex div 2]); + end; + + Inc(paramIndex); + + specifierIndex := paramNames.IndexOf(name); + if specifierIndex > -1 then + paramValues[specifierIndex] := AParams[paramIndex]; + + Inc(paramIndex); + end; + + try + + Result := Format(formatString, paramValues, AFormatSettings); + except + on E:EConvertError do + begin + errorMsg := E.Message; + + { Translate specifiers in error messages back to names } + for paramIndex := 0 to Pred(paramNames.Count) do + errorMsg := StringReplace(errorMsg, SpecifierChar + IntToStr(paramIndex) + ':', + SpecifierChar + SpecifierNameStart + + paramNames[paramIndex] + SpecifierNameEnd + ':', + [rfReplaceAll]); + + raise EConvertError.Create(errorMsg); + end; + end; + finally + FreeAndNil(paramNames); + end; +end; + + +{ TNamedFormatStringList } +procedure TNamedFormatStringList.AddLn; +begin + Add(''); +end; + + +function TNamedFormatStringList.Format(AParams: array of const): String; +begin + Result := NamedFormat(Text, AParams); +end; + +end. diff --git a/Units/X2UtTempFile.pas b/Units/X2UtTempFile.pas new file mode 100644 index 0000000..95ae37e --- /dev/null +++ b/Units/X2UtTempFile.pas @@ -0,0 +1,145 @@ +{: Provides temporary file functions. + + Last changed: $Date$ + Revision: $Rev$ + Author: $Author$ +} +unit X2UtTempFile; + +interface + function GetAppDataPath(): String; + + function GetTempFile(const APrefix: String): String; overload; + function GetTempFile(const APath, AFileName, AExtension: String): String; overload; + function GetTempFile(const APath, AFileName: String): String; overload; + function GetTempAppDataFile(const ASubPath, AFileName, AExtension: String): String; overload; + function GetTempAppDataFile(const ASubPath, AFileName: String): String; overload; + + function IsValidFileChar(const AChar: Char): Boolean; + function CheckValidFileName(var AFileName: String; const AReplacement: Char = #0): Boolean; + +implementation +uses + ShlObj, + SysUtils, + Windows; + +function GetAppDataPath(): String; +var + path: array[0..MAX_PATH] of Char; + +begin + FillChar(path, SizeOf(path), #0); + if not SHGetSpecialFolderPath(0, @path, CSIDL_APPDATA, True) then + begin + FillChar(path, SizeOf(path), #0); + GetTempPath(SizeOf(path), @path); + end; + + Result := path; + if Length(Result) > 0 then + Result := IncludeTrailingPathDelimiter(Result); +end; + + +function GetTempFile(const APrefix: String): String; overload; +var + tempPath: array[0..MAX_PATH] of Char; + tempFile: array[0..MAX_PATH] of Char; + +begin + FillChar(tempPath, SizeOf(tempPath), #0); + FillChar(tempFile, SizeOf(tempFile), #0); + + Windows.GetTempPath(SizeOf(tempPath), @tempPath); + Windows.GetTempFileName(@tempPath, PChar(APrefix), 0, @tempFile); + + Result := String(tempFile); +end; + + +function GetTempFile(const APath, AFileName, AExtension: String): String; overload; +var + iCounter: Integer; + sBase: String; + sExtension: String; + +begin + iCounter := 0; + sBase := IncludeTrailingPathDelimiter(APath); + + if not ForceDirectories(sBase) then + begin + Result := ''; + exit; + end; + + sExtension := AExtension; + if (Length(sExtension) > 0) and (AnsiPos('.', sExtension) = 0) then + sExtension := '.' + sExtension; + + sBase := sBase + AFileName; + Result := sBase + sExtension; + + while FileExists(Result) do + begin + Inc(iCounter); + Result := Format('%s(%d)%s', [sBase, iCounter, sExtension]); + end; +end; + + +function GetTempFile(const APath, AFileName: String): String; overload; +var + sExt: String; + +begin + sExt := ExtractFileExt(AFileName); + Result := GetTempFile(APath, Copy(AFileName, 1, Length(AFileName) - Length(sExt)), + sExt); +end; + + +function GetTempAppDataFile(const ASubPath, AFileName, AExtension: String): String; overload; +begin + Result := GetTempFile(GetAppDataPath + ASubPath, AFileName, AExtension); +end; + + +function GetTempAppDataFile(const ASubPath, AFileName: String): String; overload; +var + sExt: String; + +begin + sExt := ExtractFileExt(AFileName); + Result := GetTempAppDataFile(ASubPath, Copy(AFileName, 1, + Length(AFileName) - Length(sExt)), + sExt); +end; + + +function IsValidFileChar(const AChar: Char): Boolean; +begin + Result := not CharInSet(AChar, ['\', '/', ':', '*', '?', '"', '<', '>', '|']); +end; + + +function CheckValidFileName(var AFileName: String; const AReplacement: Char): Boolean; +var + iPos: Integer; + +begin + Result := True; + + for iPos := Length(AFileName) downto 1 do + if not IsValidFileChar(AFileName[iPos]) then + begin + Result := False; + if AReplacement = #0 then + Delete(AFileName, iPos, 1) + else + AFileName[iPos] := AReplacement; + end; +end; + +end. diff --git a/Units/XMLDataBindingGenerator.pas b/Units/XMLDataBindingGenerator.pas index 62f2b87..048fec7 100644 --- a/Units/XMLDataBindingGenerator.pas +++ b/Units/XMLDataBindingGenerator.pas @@ -489,6 +489,7 @@ function TXMLDataBindingGenerator.LoadSchema(const AStream: TStream; const ASche begin location := ADocRefs[refIndex].SchemaLocation; schemaName := ChangeFileExt(ExtractFileName(location), ''); + schemaName := schemaName.Replace('./', ''); // fix explizit current dir refSchema := FindSchema(schemaName); if not Assigned(refSchema) then diff --git a/Units/XMLDataBindingUtils.pas b/Units/XMLDataBindingUtils.pas new file mode 100644 index 0000000..07b5660 --- /dev/null +++ b/Units/XMLDataBindingUtils.pas @@ -0,0 +1,1331 @@ +{ + Helpers functions for the X2Software XML Data Binding +} +unit XMLDataBindingUtils; + +interface +uses + Classes, + SysUtils, + XMLDoc, + xmldom, + XMLIntf; + + +type + EBase64Error = class(Exception); + EXSDValidationError = class(Exception); + + TXMLDateTimeFormat = (xdtDateTime, xdtDate, xdtTime); + TXMLTimeFragment = (xtfMilliseconds, xtfTimezone); + TXMLTimeFragments = set of TXMLTimeFragment; + TDateConvert = (dcToUtc, dcToLocal); + + IXSDValidate = interface + ['{3BFDC851-7459-403B-87B3-A52E9E85BC8C}'] + procedure XSDValidate; + end; + + IXSDValidateStrictResult = interface + ['{F10E1CB2-ECDF-4215-AF2C-28B5C6C51A90}'] + procedure MissingElement(AParent: IXMLNode; const AName: string); + procedure MissingAttribute(AParent: IXMLNode; const AName: string); + end; + + IXSDValidateStrict = interface + ['{82C3B08E-F327-4D38-9FE2-F99925E7E401}'] + procedure XSDValidateStrict(AResult: IXSDValidateStrictResult); + end; + + + TX2XMLNode = class(TXMLNode) + private + function GetChildNodesNS(const ANodeName, ANamespaceURI: DOMString): IXMLNode; + protected + property ChildNodesNS[const ANodeName, ANamespaceURI: DOMString]: IXMLNode read GetChildNodesNS; + end; + + + TX2XMLNodeCollection = class(TXMLNodeCollection) + private + function GetChildNodesNS(const ANodeName, ANamespaceURI: DOMString): IXMLNode; + protected + property ChildNodesNS[const ANodeName, ANamespaceURI: DOMString]: IXMLNode read GetChildNodesNS; + end; + + + TXMLNodeCollectionEnumerator = class(TInterfacedObject) + private + FNodeCollection: IXMLNodeCollection; + FIndex: Integer; + public + constructor Create(ANodeCollection: IXMLNodeCollection); + + function GetCurrent: IXMLNode; + function MoveNext: Boolean; virtual; + + property Current: IXMLNode read GetCurrent; + end; + + + +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 GetNodeIsNil(ANode: IXMLNode): Boolean; + procedure SetNodeIsNil(ANode: IXMLNode; ASetNil: Boolean); + + procedure XSDValidate(AParent: IXMLNode; ARecurse: Boolean = True; AValidateParent: Boolean = True); + procedure XSDValidateStrict(AParent: IXMLNode; ARecurse: Boolean = True; AValidateParent: Boolean = True); overload; + procedure XSDValidateStrict(AResult: IXSDValidateStrictResult; AParent: IXMLNode; ARecurse: Boolean = True; AValidateParent: Boolean = True); overload; + procedure ValidateRequiredElements(AResult: IXSDValidateStrictResult; AParent: IXMLNode; ANodes: array of string); + procedure ValidateRequiredAttributes(AResult: IXSDValidateStrictResult; AParent: IXMLNode; ANodes: array of string); + procedure CreateRequiredElements(AParent: IXMLNode; ANodes: array of string); overload; + procedure CreateRequiredElements(AParent: IXMLNode; ANodes: array of string; Namespaces: array of string); overload; + procedure CreateRequiredAttributes(AParent: IXMLNode; ANodes: array of string); + procedure SortChildNodes(AParent: IXMLNode; ASortOrder: array of string); + + function IsValidXMLChar(AChar: WideChar): Boolean; + function GetValidXMLText(AText: WideString): WideString; + + { Now wraps the JclMime implementation: + Lightening fast Mime (Base64) Encoding and Decoding routines. + Coded by Ralf Junker (ralfjunker@gmx.de).} + function Base64Encode(AValue: String): string; + function Base64Decode(AValue: String): string; + function Base64EncodeFromStream(AStream: TStream): string; + function Base64EncodeFromFile(const AFileName: string): string; + procedure Base64DecodeToStream(AValue: string; AStream: TStream); + procedure Base64DecodeToFile(AValue: string; const AFileName: string); + +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'; + XMLIsNilAttributeNS = 'xsi:nil'; + + Base64ValidChars = ['A'..'Z', 'a'..'z', '0'..'9', '+', '/']; + Base64LookupTable = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + + 'abcdefghijklmnopqrstuvwxyz' + + '0123456789+/'; + Base64Padding = '='; + + +implementation +uses + DateUtils, + Math, + Types, + Windows; + + +type + PSortNodeInfo = ^TSortNodeInfo; + TSortNodeInfo = record + Node: IXMLNode; + SortIndex: Integer; + OriginalIndex: Integer; + end; + + + TXSDValidateStrictResult = class(TInterfacedPersistent, IXSDValidateStrictResult) + private + FMissingElements: TStrings; + FMissingAttributes: TStrings; + + function GetMissingAttributes: TStrings; + function GetMissingElements: TStrings; + protected + function GetNodeTree(AParent: IXMLNode; const AName: string): string; + + property MissingElements: TStrings read GetMissingElements; + property MissingAttributes: TStrings read GetMissingAttributes; + public + destructor Destroy; override; + + procedure RaiseResult; + + { IXSDValidateStrictResult } + procedure MissingElement(AParent: IXMLNode; const AName: string); + procedure MissingAttribute(AParent: IXMLNode; const AName: string); + end; + + + function MimeEncodeString(const S: AnsiString): AnsiString; forward; + function MimeDecodeString(const S: AnsiString): AnsiString; forward; + procedure MimeEncodeStream(const InputStream: TStream; const OutputStream: TStream); forward; + procedure MimeDecodeStream(const InputStream: TStream; const OutputStream: TStream); forward; + + + +{ TX2XMLNode } +function TX2XMLNode.GetChildNodesNS(const ANodeName, ANamespaceURI: DOMString): IXMLNode; +begin + Result := ChildNodes.FindNode(ANodeName, ANamespaceURI); + if (not Assigned(Result)) and (doNodeAutoCreate in OwnerDocument.Options) then + Result := AddChild(ANodeName, ANamespaceURI); +end; + + + +{ TX2XMLNodeCollection } +function TX2XMLNodeCollection.GetChildNodesNS(const ANodeName, ANamespaceURI: DOMString): IXMLNode; +begin + Result := ChildNodes.FindNode(ANodeName, ANamespaceURI); + if (not Assigned(Result)) and (doNodeAutoCreate in OwnerDocument.Options) then + Result := AddChild(ANodeName, ANamespaceURI); +end; + + + +{ TXMLNodeCollectionEnumerator } +constructor TXMLNodeCollectionEnumerator.Create(ANodeCollection: IXMLNodeCollection); +begin + inherited Create; + + FNodeCollection := ANodeCollection; + FIndex := -1; +end; + + +function TXMLNodeCollectionEnumerator.GetCurrent: IXMLNode; +begin + if (FIndex >= 0) and (FIndex < FNodeCollection.Count) then + Result := FNodeCollection.Nodes[FIndex] + else + Result := nil; +end; + + +function TXMLNodeCollectionEnumerator.MoveNext: Boolean; +begin + Inc(FIndex); + Result := (FIndex < FNodeCollection.Count); +end; + + +function InDSTSpan(ADate: TDateTime; ATimeZoneInfo: TTimeZoneInformation): boolean; +var + lowerDayLight: TDateTime; + upperDayLight: TDateTime; + day: TDateTime; + days: Integer; + + function GetDay(AYear, AMonth, ADay, ADayOfWeek: Integer): TDateTime; + var + I, Counter : Integer; + begin + Result := 0; + Counter := 0; + + days := DaysInAMonth(AYear, AMonth); + for I := 1 to days do + begin + Result := EncodeDate(AYear, AMonth, I); + // Delphi DayOfWeek 1 = Sunday + // TimeZoneInfo.wDayOfWeek 0 = Sunday + if DayOfWeek(Result) -1 = ADayOfWeek then + begin + inc(Counter); + if (counter = ADay) or ((Counter < Aday) and (I >= days - 6)) then + break; + end; + end; + end; + +begin + with ATimeZoneInfo.DaylightDate do + begin + day := GetDay(wYear + YearOf(ADate), wMonth, wDay, wDayOfWeek); + lowerDayLight := day + EncodeTime(wHour, wMinute, wSecond, wMilliseconds); + end; + + with ATimeZoneInfo.StandardDate do + begin + day := GetDay(wYear + YearOf(ADate), wMonth, wDay, wDayOfWeek); + upperDayLight := day + EncodeTime(wHour, wMinute, wSecond, wMilliseconds); + end; + + Result := (ADate >= lowerDayLight) and (ADate <= upperDayLight); +end; + + +function ConvertDate(ADate: TDateTime; ADateconvert: TDateConvert): TDateTime; +var + timeZone: TTimeZoneInformation; + timeZoneID: Cardinal; + localOffset: Integer; + +begin + FillChar(timeZone, SizeOf(TTimeZoneInformation), #0); + timeZoneID := GetTimeZoneInformation(timeZone); + + if timeZoneID in [TIME_ZONE_ID_STANDARD, TIME_ZONE_ID_DAYLIGHT] then + localOffset := -timeZone.Bias - IfThen(InDSTSpan(ADate, timeZone), timeZone.DaylightBias, timeZone.StandardBias) + else + localOffset := 0; + + if ADateconvert = dcToUtc then + localOffset := localOffset * -1; + + Result := IncMinute(ADate, localOffset); +end; + + +function DateTimeToXML(ADate: TDateTime; AFormat: TXMLDateTimeFormat; ATimeFragments: TXMLTimeFragments): string; +var + formatSettings: TFormatSettings; + utcDate: TDateTime; + offsetMinutes: Integer; + +begin + formatSettings := TFormatSettings.Create;; + 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 + utcDate := ConvertDate(ADate, dcToUtc); + offsetMinutes := MinutesBetween(ADate, utcDate); + + if offsetMinutes = 0 then + Result := Result + XMLTimezoneZulu + else + Result := Result + Format(XMLTimezoneFormat, + [XMLTimezoneSigns[offsetMinutes > 0], offsetMinutes div 60, offsetMinutes mod 60]); + 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; + endPos: 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+) } + Delete(time, 1, 1); + endPos := 1; + + while (endPos <= Length(time)) and (CharInSet(time[endPos], ['0'..'9'])) do + Inc(endPos); + + Dec(endPos); + + if (endPos = 0) or (not TryStrToInt(Copy(time, 1, Min(endPos, 3)), msec)) then + msec := 0; + + if endPos > 0 then + Delete(time, 1, endPos); + end; + end; + + Result := Result + EncodeTime(hour, minute, second, msec); + + if Length(time) > 0 then + begin + hasTimezone := False; + + if time[1] = XMLTimezoneZulu then + begin + { Zulu time } + hasTimezone := True; + end else if CharInSet(time[1], [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; + + Result := IncMinute(Result, - xmlOffset); + end; + end; + + if hasTimezone then + Result := ConvertDate(Result, dcToLocal); + 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; +begin + Result := string(MimeEncodeString(AnsiString(AValue))); +end; + + +function Base64Decode(AValue: String): String; +begin + Result := string(MimeDecodeString(AnsiString(AValue))); +end; + + +function Base64EncodeFromStream(AStream: TStream): string; +var + output: TStringStream; + +begin + output := TStringStream.Create(''); + try + MimeEncodeStream(AStream, output); + Result := output.DataString; + finally + FreeAndNil(output); + end; +end; + + +function Base64EncodeFromFile(const AFileName: string): string; +var + input: TFileStream; + +begin + input := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite); + try + Result := Base64EncodeFromStream(input); + finally + FreeAndNil(input); + end; +end; + + +procedure Base64DecodeToStream(AValue: String; AStream: TStream); +var + input: TStringStream; + +begin + input := TStringStream.Create(AValue); + try + MimeDecodeStream(input, AStream); + finally + FreeAndNil(input); + end; +end; + + +procedure Base64DecodeToFile(AValue: String; const AFileName: String); +var + output: TFileStream; + +begin + output := TFileStream.Create(AFileName, fmCreate or fmShareDenyWrite); + try + Base64DecodeToStream(AValue, output); + finally + FreeAndNil(output); + end; +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); +var + documentElement: IXMLNode; + +begin + if ASetNil then + begin + ANode.ChildNodes.Clear; + + documentElement := ANode.OwnerDocument.DocumentElement; + if not documentElement.HasAttribute('xmlns:xsi') then + documentElement.SetAttributeNS('xmlns:xsi', '', XMLSchemaInstanceURI); + + ANode.SetAttributeNS(XMLIsNilAttributeNS, XMLSchemaInstanceURI, BoolToXML(True)); + end else + ANode.AttributeNodes.Delete(XMLIsNilAttribute, XMLSchemaInstanceURI); +end; + + +function DoSortNodes(Item1, Item2: Pointer): Integer; +var + nodeInfo1: PSortNodeInfo; + nodeInfo2: PSortNodeInfo; + +begin + nodeInfo1 := Item1; + nodeInfo2 := Item2; + + if (nodeInfo1^.SortIndex > -1) and (nodeInfo2^.SortIndex = -1) then + Result := GreaterThanValue + + else if (nodeInfo1^.SortIndex = -1) and (nodeInfo2^.SortIndex > -1) then + Result := LessThanValue + + else if (nodeInfo1^.SortIndex = nodeInfo2^.SortIndex) then + Result := CompareValue(nodeInfo1^.OriginalIndex, nodeInfo2^.OriginalIndex) + + else + Result := CompareValue(nodeInfo1^.SortIndex, nodeInfo2^.SortIndex); +end; + + +procedure XSDValidate(AParent: IXMLNode; ARecurse, AValidateParent: Boolean); +var + validate: IXSDValidate; + childIndex: Integer; + +begin + if AValidateParent and Supports(AParent, IXSDValidate, validate) then + validate.XSDValidate; + + if ARecurse then + begin + for childIndex := 0 to Pred(AParent.ChildNodes.Count) do + XSDValidate(AParent.ChildNodes[childIndex], ARecurse, True); + end; +end; + + +procedure XSDValidateStrict(AParent: IXMLNode; ARecurse: Boolean; AValidateParent: Boolean); +var + result: TXSDValidateStrictResult; + +begin + result := TXSDValidateStrictResult.Create; + try + XSDValidateStrict(result, AParent, ARecurse, AValidateParent); + + result.RaiseResult; + finally + FreeAndNil(result); + end; +end; + + +procedure XSDValidateStrict(AResult: IXSDValidateStrictResult; AParent: IXMLNode; ARecurse: Boolean; AValidateParent: Boolean); +var + validate: IXSDValidateStrict; + childIndex: Integer; + +begin + if AValidateParent and Supports(AParent, IXSDValidateStrict, validate) then + validate.XSDValidateStrict(AResult); + + if ARecurse then + begin + for childIndex := 0 to Pred(AParent.ChildNodes.Count) do + XSDValidateStrict(AResult, AParent.ChildNodes[childIndex], ARecurse, True); + end; +end; + + +procedure ValidateRequiredElements(AResult: IXSDValidateStrictResult; AParent: IXMLNode; ANodes: array of string); +var + nodeIndex: Integer; + +begin + for nodeIndex := Low(ANodes) to High(ANodes) do + begin + if not Assigned(AParent.ChildNodes.FindNode(ANodes[nodeIndex])) then + AResult.MissingElement(AParent, ANodes[nodeIndex]); + end; +end; + + +procedure ValidateRequiredAttributes(AResult: IXSDValidateStrictResult; AParent: IXMLNode; ANodes: array of string); +var + nodeIndex: Integer; + +begin + for nodeIndex := Low(ANodes) to High(ANodes) do + begin + if not Assigned(AParent.AttributeNodes.FindNode(ANodes[nodeIndex])) then + AResult.MissingAttribute(AParent, ANodes[nodeIndex]); + end; +end; + + +procedure CreateRequiredElements(AParent: IXMLNode; ANodes: array of string); overload; +var + nodeIndex: Integer; + node: IXMLNode; + +begin + for nodeIndex := Low(ANodes) to High(ANodes) do + begin + if not Assigned(AParent.ChildNodes.FindNode(ANodes[nodeIndex])) then + begin + node := AParent.OwnerDocument.CreateElement(ANodes[nodeIndex], AParent.NamespaceURI); + AParent.ChildNodes.Add(node); + end; + end; +end; + + +procedure CreateRequiredElements(AParent: IXMLNode; ANodes: array of string; Namespaces: array of string); +var + nodeIndex: Integer; + node: IXMLNode; + +begin + for nodeIndex := Low(ANodes) to High(ANodes) do + begin + if not Assigned(AParent.ChildNodes.FindNode(ANodes[nodeIndex], Namespaces[nodeIndex])) then + begin + node := AParent.OwnerDocument.CreateElement(ANodes[nodeIndex], Namespaces[nodeIndex]); + AParent.ChildNodes.Add(node); + end; + end; +end; + + +procedure CreateRequiredAttributes(AParent: IXMLNode; ANodes: array of string); +var + nodeIndex: Integer; + +begin + for nodeIndex := Low(ANodes) to High(ANodes) do + begin + if not Assigned(AParent.AttributeNodes.FindNode(ANodes[nodeIndex])) then + AParent.Attributes[ANodes[nodeIndex]] := ''; + end; +end; + + +procedure SortChildNodes(AParent: IXMLNode; ASortOrder: array of string); +var + sortList: TList; + nodeInfo: PSortNodeInfo; + childIndex: Integer; + sortIndex: Integer; + node: IXMLNode; + +begin + sortList := TList.Create; + try + { Build a list of the child nodes, with their original index and the + index in the ASortOrder array. } + for childIndex := 0 to Pred(AParent.ChildNodes.Count) do + begin + New(nodeInfo); + nodeInfo^.Node := AParent.ChildNodes[childIndex]; + nodeInfo^.OriginalIndex := childIndex; + + for sortIndex := Low(ASortOrder) to High(ASortOrder) do + begin + if ASortOrder[sortIndex] = nodeInfo^.Node.NodeName then + begin + nodeInfo^.SortIndex := sortIndex; + Break; + end; + end; + + sortList.Add(nodeInfo); + end; + + sortList.Sort(DoSortNodes); + + { Rebuild the ChildNodes list } + for childIndex := 0 to Pred(sortList.Count) do + begin + node := PSortNodeInfo(sortList[childIndex])^.Node; + + AParent.ChildNodes.Remove(node); + AParent.ChildNodes.Insert(childIndex, node); + end; + finally + for sortIndex := 0 to Pred(sortList.Count) do + Dispose(PSortNodeInfo(sortList[sortIndex])); + + FreeAndNil(sortList); + end; +end; + + +function IsValidXMLChar(AChar: WideChar): Boolean; +begin + Result := (Ord(AChar) in [9, 10, 13]) or + (Ord(AChar) >= 32); +end; + + +function GetValidXMLText(AText: WideString): WideString; +var + validText: WideString; + sourcePos: Integer; + destPos: Integer; + +begin + SetLength(validText, Length(AText)); + destPos := 0; + + for sourcePos := 1 to Length(AText) do + begin + if IsValidXMLChar(AText[sourcePos]) then + begin + Inc(destPos); + validText[destPos] := AText[sourcePos]; + end; + end; + + SetLength(validText, destPos); + Result := validText; +end; + + +{ --- JclMime implementation from here. } +type + {$IFDEF WIN64} + SizeInt = NativeInt; + TJclAddr = UInt64; + {$ELSE} + SizeInt = Integer; + TJclAddr = Cardinal; + {$ENDIF} + + PByte4 = ^TByte4; + TByte4 = packed record + B1: Byte; + B2: Byte; + B3: Byte; + B4: Byte; + end; + + PByte3 = ^TByte3; + TByte3 = packed record + B1: Byte; + B2: Byte; + B3: Byte; + end; + + +const + MIME_ENCODED_LINE_BREAK = 76; + MIME_DECODED_LINE_BREAK = MIME_ENCODED_LINE_BREAK div 4 * 3; + MIME_BUFFER_SIZE = MIME_DECODED_LINE_BREAK * 3 * 4 * 4; + + MIME_ENCODE_TABLE: array [0..63] of Byte = ( + 065, 066, 067, 068, 069, 070, 071, 072, // 00 - 07 + 073, 074, 075, 076, 077, 078, 079, 080, // 08 - 15 + 081, 082, 083, 084, 085, 086, 087, 088, // 16 - 23 + 089, 090, 097, 098, 099, 100, 101, 102, // 24 - 31 + 103, 104, 105, 106, 107, 108, 109, 110, // 32 - 39 + 111, 112, 113, 114, 115, 116, 117, 118, // 40 - 47 + 119, 120, 121, 122, 048, 049, 050, 051, // 48 - 55 + 052, 053, 054, 055, 056, 057, 043, 047); // 56 - 63 + + MIME_PAD_CHAR = Byte('='); + + MIME_DECODE_TABLE: array [Byte] of Byte = ( + 255, 255, 255, 255, 255, 255, 255, 255, // 0 - 7 + 255, 255, 255, 255, 255, 255, 255, 255, // 8 - 15 + 255, 255, 255, 255, 255, 255, 255, 255, // 16 - 23 + 255, 255, 255, 255, 255, 255, 255, 255, // 24 - 31 + 255, 255, 255, 255, 255, 255, 255, 255, // 32 - 39 + 255, 255, 255, 062, 255, 255, 255, 063, // 40 - 47 + 052, 053, 054, 055, 056, 057, 058, 059, // 48 - 55 + 060, 061, 255, 255, 255, 255, 255, 255, // 56 - 63 + 255, 000, 001, 002, 003, 004, 005, 006, // 64 - 71 + 007, 008, 009, 010, 011, 012, 013, 014, // 72 - 79 + 015, 016, 017, 018, 019, 020, 021, 022, // 80 - 87 + 023, 024, 025, 255, 255, 255, 255, 255, // 88 - 95 + 255, 026, 027, 028, 029, 030, 031, 032, // 96 - 103 + 033, 034, 035, 036, 037, 038, 039, 040, // 104 - 111 + 041, 042, 043, 044, 045, 046, 047, 048, // 112 - 119 + 049, 050, 051, 255, 255, 255, 255, 255, // 120 - 127 + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255); + + +function MimeEncodedSize(const InputSize: SizeInt): SizeInt; +begin + if InputSize > 0 then + Result := (InputSize + 2) div 3 * 4 + (InputSize - 1) div MIME_DECODED_LINE_BREAK * 2 + else + Result := InputSize; +end; + + +procedure MimeEncodeFullLines(const InputBuffer; const InputByteCount: SizeInt; out OutputBuffer); +var + B: Cardinal; + InnerLimit, OuterLimit: TJclAddr; + InPtr: PByte3; + OutPtr: PByte4; +begin + { Do we have enough input to encode a full line? } + if InputByteCount < MIME_DECODED_LINE_BREAK then + Exit; + + InPtr := @InputBuffer; + OutPtr := @OutputBuffer; + + InnerLimit := TJclAddr(InPtr); + Inc(InnerLimit, MIME_DECODED_LINE_BREAK); + + OuterLimit := TJclAddr(InPtr); + Inc(OuterLimit, InputByteCount); + + { Multiple line loop. } + repeat + { Single line loop. } + repeat + { Read 3 bytes from InputBuffer. } + B := InPtr^.B1; + B := B shl 8; + B := B or InPtr^.B2; + B := B shl 8; + B := B or InPtr^.B3; + Inc(InPtr); + { Write 4 bytes to OutputBuffer (in reverse order). } + OutPtr^.B4 := MIME_ENCODE_TABLE[B and $3F]; + B := B shr 6; + OutPtr^.B3 := MIME_ENCODE_TABLE[B and $3F]; + B := B shr 6; + OutPtr^.B2 := MIME_ENCODE_TABLE[B and $3F]; + B := B shr 6; + OutPtr^.B1 := MIME_ENCODE_TABLE[B]; + Inc(OutPtr); + until TJclAddr(InPtr) >= InnerLimit; + + { Write line break (CRLF). } + OutPtr^.B1 := 13; + OutPtr^.B2 := 10; + Inc(TJclAddr(OutPtr), 2); + + Inc(InnerLimit, MIME_DECODED_LINE_BREAK); + until InnerLimit > OuterLimit; +end; + + +procedure MimeEncodeNoCRLF(const InputBuffer; const InputByteCount: SizeInt; out OutputBuffer); +var + B: Cardinal; + InnerLimit, OuterLimit: SizeInt; + InPtr: PByte3; + OutPtr: PByte4; +begin + if InputByteCount = 0 then + Exit; + + InPtr := @InputBuffer; + OutPtr := @OutputBuffer; + + OuterLimit := InputByteCount div 3 * 3; + + InnerLimit := TJclAddr(InPtr); + Inc(InnerLimit, OuterLimit); + + { Last line loop. } + while TJclAddr(InPtr) < TJclAddr(InnerLimit) do + begin + { Read 3 bytes from InputBuffer. } + B := InPtr^.B1; + B := B shl 8; + B := B or InPtr^.B2; + B := B shl 8; + B := B or InPtr^.B3; + Inc(InPtr); + { Write 4 bytes to OutputBuffer (in reverse order). } + OutPtr^.B4 := MIME_ENCODE_TABLE[B and $3F]; + B := B shr 6; + OutPtr^.B3 := MIME_ENCODE_TABLE[B and $3F]; + B := B shr 6; + OutPtr^.B2 := MIME_ENCODE_TABLE[B and $3F]; + B := B shr 6; + OutPtr^.B1 := MIME_ENCODE_TABLE[B]; + Inc(OutPtr); + end; + + { End of data & padding. } + case InputByteCount - OuterLimit of + 1: + begin + B := InPtr^.B1; + B := B shl 4; + OutPtr.B2 := MIME_ENCODE_TABLE[B and $3F]; + B := B shr 6; + OutPtr.B1 := MIME_ENCODE_TABLE[B]; + OutPtr.B3 := MIME_PAD_CHAR; { Pad remaining 2 bytes. } + OutPtr.B4 := MIME_PAD_CHAR; + end; + 2: + begin + B := InPtr^.B1; + B := B shl 8; + B := B or InPtr^.B2; + B := B shl 2; + OutPtr.B3 := MIME_ENCODE_TABLE[B and $3F]; + B := B shr 6; + OutPtr.B2 := MIME_ENCODE_TABLE[B and $3F]; + B := B shr 6; + OutPtr.B1 := MIME_ENCODE_TABLE[B]; + OutPtr.B4 := MIME_PAD_CHAR; { Pad remaining byte. } + end; + end; +end; + + +procedure MimeEncode(const InputBuffer; const InputByteCount: SizeInt; out OutputBuffer); +var + IDelta, ODelta: SizeInt; + I, O: PByte; +begin + MimeEncodeFullLines(InputBuffer, InputByteCount, OutputBuffer); + IDelta := InputByteCount div MIME_DECODED_LINE_BREAK; // Number of lines processed so far. + ODelta := IDelta * (MIME_ENCODED_LINE_BREAK + 2); + IDelta := IDelta * MIME_DECODED_LINE_BREAK; + I := @InputBuffer; + Inc(I, IDelta); + O := @OutputBuffer; + Inc(O, ODelta); + MimeEncodeNoCRLF(I^, InputByteCount - IDelta, O^); +end; + + +function MimeDecodePartial(const InputBuffer; const InputByteCount: SizeInt; out OutputBuffer; + var ByteBuffer: Cardinal; var ByteBufferSpace: Cardinal): SizeInt; +var + LByteBuffer, LByteBufferSpace, C: Cardinal; + InPtr, OuterLimit: PByte; + OutPtr: PByte3; +begin + if InputByteCount > 0 then + begin + InPtr := @InputBuffer; + OuterLimit := Pointer(TJclAddr(InPtr) + TJclAddr(InputByteCount)); + OutPtr := @OutputBuffer; + LByteBuffer := ByteBuffer; + LByteBufferSpace := ByteBufferSpace; + while InPtr <> OuterLimit do + begin + { Read from InputBuffer. } + C := MIME_DECODE_TABLE[InPtr^]; + Inc(InPtr); + if C = $FF then + Continue; + LByteBuffer := LByteBuffer shl 6; + LByteBuffer := LByteBuffer or C; + Dec(LByteBufferSpace); + { Have we read 4 bytes from InputBuffer? } + if LByteBufferSpace <> 0 then + Continue; + + { Write 3 bytes to OutputBuffer (in reverse order). } + OutPtr^.B3 := Byte(LByteBuffer); + LByteBuffer := LByteBuffer shr 8; + OutPtr^.B2 := Byte(LByteBuffer); + LByteBuffer := LByteBuffer shr 8; + OutPtr^.B1 := Byte(LByteBuffer); + LByteBuffer := 0; + Inc(OutPtr); + LByteBufferSpace := 4; + end; + ByteBuffer := LByteBuffer; + ByteBufferSpace := LByteBufferSpace; + Result := SizeInt(TJclAddr(OutPtr) - TJclAddr(@OutputBuffer)); + end + else + Result := 0; +end; + + +function MimeDecodePartialEnd(out OutputBuffer; const ByteBuffer: Cardinal; + const ByteBufferSpace: Cardinal): SizeInt; +var + LByteBuffer: Cardinal; +begin + case ByteBufferSpace of + 1: + begin + LByteBuffer := ByteBuffer shr 2; + PByte3(@OutputBuffer)^.B2 := Byte(LByteBuffer); + LByteBuffer := LByteBuffer shr 8; + PByte3(@OutputBuffer)^.B1 := Byte(LByteBuffer); + Result := 2; + end; + 2: + begin + LByteBuffer := ByteBuffer shr 4; + PByte3(@OutputBuffer)^.B1 := Byte(LByteBuffer); + Result := 1; + end; + else + Result := 0; + end; +end; + + +function MimeEncodeString(const S: AnsiString): AnsiString; +var + L: SizeInt; +begin + if S <> '' then + begin + L := Length(S); + SetLength(Result, MimeEncodedSize(L)); + MimeEncode(PAnsiChar(S)^, L, PAnsiChar(Result)^); + end + else + Result := ''; +end; + + +function MimeDecodedSize(const InputSize: SizeInt): SizeInt; +begin + Result := (InputSize + 3) div 4 * 3; +end; + + +function MimeDecodeString(const S: AnsiString): AnsiString; +var + ByteBuffer, ByteBufferSpace: Cardinal; + L: SizeInt; + P, R: PAnsiChar; +begin + if S <> '' then + begin + L := Length(S); + SetLength(Result, MimeDecodedSize(L)); + ByteBuffer := 0; + ByteBufferSpace := 4; + P := PAnsiChar(S); + R := PAnsiChar(Result); + L := MimeDecodePartial(P^, L, R^, ByteBuffer, ByteBufferSpace); + Inc(R, L); + Inc(L, MimeDecodePartialEnd(R^, ByteBuffer, ByteBufferSpace)); + SetLength(Result, L); + end + else + Result := ''; +end; + + +procedure MimeEncodeStream(const InputStream: TStream; const OutputStream: TStream); +var + InputBuffer: array [0..MIME_BUFFER_SIZE - 1] of Byte; + OutputBuffer: array [0..(MIME_BUFFER_SIZE + 2) div 3 * 4 + MIME_BUFFER_SIZE div MIME_DECODED_LINE_BREAK * 2 - 1] of Byte; + BytesRead: SizeInt; + IDelta, ODelta: SizeInt; + I, O: PByte; +begin + InputBuffer[0] := 0; + BytesRead := InputStream.Read(InputBuffer, SizeOf(InputBuffer)); + + while BytesRead = Length(InputBuffer) do + begin + MimeEncodeFullLines(InputBuffer, Length(InputBuffer), OutputBuffer); + OutputStream.Write(OutputBuffer, Length(OutputBuffer)); + BytesRead := InputStream.Read(InputBuffer, Length(InputBuffer)); + end; + + MimeEncodeFullLines(InputBuffer, BytesRead, OutputBuffer); + + IDelta := BytesRead div MIME_DECODED_LINE_BREAK; // Number of lines processed. + ODelta := IDelta * (MIME_ENCODED_LINE_BREAK + 2); + IDelta := IDelta * MIME_DECODED_LINE_BREAK; + + I := @InputBuffer; + Inc(I, IDelta); + O := @OutputBuffer; + Inc(O, ODelta); + + MimeEncodeNoCRLF(I^, BytesRead - IDelta, O^); + + OutputStream.Write(OutputBuffer, MimeEncodedSize(BytesRead)); +end; + + +procedure MimeDecodeStream(const InputStream: TStream; const OutputStream: TStream); +var + ByteBuffer, ByteBufferSpace: Cardinal; + InputBuffer: array [0..MIME_BUFFER_SIZE - 1] of Byte; + OutputBuffer: array [0..(MIME_BUFFER_SIZE + 3) div 4 * 3 - 1] of Byte; + BytesRead: SizeInt; +begin + ByteBuffer := 0; + ByteBufferSpace := 4; + InputBuffer[0] := 0; + BytesRead := InputStream.Read(InputBuffer, SizeOf(InputBuffer)); + + while BytesRead > 0 do + begin + OutputStream.Write(OutputBuffer, MimeDecodePartial(InputBuffer, BytesRead, OutputBuffer, ByteBuffer, ByteBufferSpace)); + BytesRead := InputStream.Read(InputBuffer, Length(InputBuffer)); + end; + OutputStream.Write(OutputBuffer, MimeDecodePartialEnd(OutputBuffer, ByteBuffer, ByteBufferSpace)); +end; + + +{ TXSDValidateStrictResult } +destructor TXSDValidateStrictResult.Destroy; +begin + FreeAndNil(FMissingAttributes); + FreeAndNil(FMissingElements); + + inherited Destroy; +end; + + +procedure TXSDValidateStrictResult.MissingElement(AParent: IXMLNode; const AName: string); +begin + MissingElements.Add(GetNodeTree(AParent, AName)); +end; + + +procedure TXSDValidateStrictResult.MissingAttribute(AParent: IXMLNode; const AName: string); +begin + MissingAttributes.Add(GetNodeTree(AParent, AName)); +end; + + +procedure TXSDValidateStrictResult.RaiseResult; +var + msg: string; + + procedure AddList(AList: TStrings; const ATitle: string); + var + itemIndex: Integer; + + begin + if not Assigned(AList) then + exit; + + msg := msg + ATitle + #13#10; + for itemIndex := 0 to Pred(AList.Count) do + msg := msg + '- ' + AList[itemIndex] + #13#10; + + msg := msg + #13#10; + end; + +begin + msg := ''; + AddList(FMissingElements, 'Missing elements:'); + AddList(FMissingAttributes, 'Missing attributes:'); + + if Length(msg) > 0 then + raise EXSDValidationError.Create('XSD validation failed.'#13#10 + Trim(msg)); +end; + + +function TXSDValidateStrictResult.GetMissingElements: TStrings; +begin + if not Assigned(FMissingElements) then + FMissingElements := TStringList.Create; + + Result := FMissingElements; +end; + + +function TXSDValidateStrictResult.GetNodeTree(AParent: IXMLNode; const AName: string): string; + + + function GetNodeIndex(ANodeCollection: IXMLNodeCollection; ANode: IXMLNode): string; + var + nodeIndex: Integer; + begin + Result := '?'; + + for nodeIndex := 0 to Pred(ANodeCollection.Count) do + if ANodeCollection[nodeIndex] = ANode then + begin + Result := IntToStr(nodeIndex); + break; + end; + end; + + +var + node: IXMLNode; + nodeCollection: IXMLNodeCollection; + +begin + Result := ''; + + node := AParent; + while Assigned(node) and Assigned(node.ParentNode) do + begin + if Length(Result) > 0 then + Result := '.' + Result; + + if Supports(node.ParentNode, IXMLNodeCollection, nodeCollection) then + Result := Result + '[' + GetNodeIndex(nodeCollection, node) + ']'; + + + Result := node.NodeName + Result; + node := node.ParentNode; + end; + + if Length(Result) > 0 then + Result := Result + '.'; + + Result := Result + AName; +end; + + +function TXSDValidateStrictResult.GetMissingAttributes: TStrings; +begin + if not Assigned(FMissingAttributes) then + FMissingAttributes := TStringList.Create; + + Result := FMissingAttributes; +end; + +end. + diff --git a/X2XMLDataBinding.dpr b/X2XMLDataBinding.dpr index dd03bb1..549f8e7 100644 --- a/X2XMLDataBinding.dpr +++ b/X2XMLDataBinding.dpr @@ -11,7 +11,10 @@ uses DelphiXMLDataBindingResources in 'Units\DelphiXMLDataBindingResources.pas', DataBindingSettingsXML in 'Units\DataBindingSettingsXML.pas', DataBindingHintsXML in 'Units\DataBindingHintsXML.pas', - MSXML2_TLB in 'Units\MSXML2_TLB.pas'; + MSXML2_TLB in 'Units\MSXML2_TLB.pas', + XMLDataBindingUtils in 'Units\XMLDataBindingUtils.pas', + X2UtNamedFormat in 'Units\X2UtNamedFormat.pas', + X2UtTempFile in 'Units\X2UtTempFile.pas'; {$R *.res} diff --git a/X2XMLDataBinding.dproj b/X2XMLDataBinding.dproj index 3644cb8..13767bb 100644 --- a/X2XMLDataBinding.dproj +++ b/X2XMLDataBinding.dproj @@ -52,7 +52,6 @@ 1043 $(BDS)\bin\default_app.manifest X2XMLDataBinding - vendor\x2utils;vendor\x2log;$(DCC_UnitSearchPath) Data.Win;Datasnap.Win;Web.Win;Soap.Win;Bde;$(DCC_Namespace) @@ -142,6 +141,429 @@ True True + + + + X2XMLDataBinding.exe + true + + + + + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 0 + + + + + classes + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\armeabi + 1 + + + + + library\lib\mips + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + + + res\values + 1 + + + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + 1 + + + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\ + 1 + + + + + Contents + 1 + + + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + + + + + + + 12 @@ -160,6 +582,9 @@ + + + Cfg_2 Base @@ -170,4 +595,5 @@ + diff --git a/vendor/x2log b/vendor/x2log deleted file mode 160000 index 8b17a99..0000000 --- a/vendor/x2log +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8b17a99c33453d0cf6c94d26af735beb3bb8666c diff --git a/vendor/x2utils b/vendor/x2utils deleted file mode 160000 index 1e7a087..0000000 --- a/vendor/x2utils +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1e7a0873558140b506c01af5c50c73cfe940466e