{ This file is part of the Free Component Library Object model for DTD. Copyright (c) 2010 by Sergei Gorelkin, sergei_gorelkin@mail.ru See the file COPYING.FPC, included in this distribution, for details about the copyright. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. **********************************************************************} unit dtdmodel; {$ifdef fpc} {$MODE objfpc}{$H+} {$endif} interface uses Classes, SysUtils, xmlutils; type TCPType = (ctName, ctChoice, ctSeq); TCPQuant = (cqOnce, cqZeroOrOnce, cqZeroOrMore, cqOnceOrMore); TContentParticle = class(TObject) private FParent: TContentParticle; FChildren: TFPList; FIndex: Integer; FDef: TObject; FCPType: TCPType; FCPQuant: TCPQuant; function GetChildCount: Integer; function GetChild(Index: Integer): TContentParticle; public destructor Destroy; override; function Add: TContentParticle; function IsRequired: Boolean; function FindFirst(aDef: TObject): TContentParticle; function FindNext(aDef: TObject; ChildIdx: Integer): TContentParticle; function MoreRequired(ChildIdx: Integer): Boolean; property ChildCount: Integer read GetChildCount; property Children[Index: Integer]: TContentParticle read GetChild; property Def: TObject read FDef write FDef; property CPType: TCPType read FCPType write FCPType; property CPQuant: TCPQuant read FCPQuant write FCPQuant; end; TDTDObject = class(TObject) private FExternallyDeclared: Boolean; public property ExternallyDeclared: Boolean read FExternallyDeclared write FExternallyDeclared; end; TAttrDefault = ( adImplied, adDefault, adRequired, adFixed ); TAttributeDef = class(TDTDObject) private FData: PNodeData; FDataType: TAttrDataType; FDefault: TAttrDefault; FTag: Cardinal; FIsNamespaceDecl: Boolean; FEnumeration: array of WideString; public constructor Create(aName: PHashItem; aColonPos: Integer); destructor Destroy; override; function AddEnumToken(Buf: PWideChar; Len: Integer): Boolean; function HasEnumToken(const aValue: WideString): Boolean; property Data: PNodeData read FData; property Default: TAttrDefault read FDefault write FDefault; property DataType: TAttrDataType read FDataType write FDataType; property Tag: Cardinal read FTag write FTag; property IsNamespaceDecl: Boolean read FIsNamespaceDecl; end; TElementContentType = ( ctUndeclared, ctAny, ctEmpty, ctMixed, ctChildren ); TElementDecl = class(TDTDObject) private FAttrDefs: TFPList; FNeedsDefaultPass: Boolean; function GetAttrDefCount: Integer; function AttrDefByIndex(index: Integer): TAttributeDef; public ContentType: TElementContentType; IDAttr: TAttributeDef; NotationAttr: TAttributeDef; RootCP: TContentParticle; destructor Destroy; override; function GetAttrDef(aName: PHashItem): TAttributeDef; procedure AddAttrDef(aDef: TAttributeDef); property AttrDefCount: Integer read GetAttrDefCount; property AttrDefs[index: Integer]: TAttributeDef read AttrDefByIndex; property NeedsDefaultPass: Boolean read FNeedsDefaultPass; end; TEntityDecl = class(TDTDObject) public FName: WideString; // TODO: change to PHashItem FInputEncoding: WideString; FXMLEncoding: WideString; FPublicID: WideString; FSystemID: WideString; FNotationName: WideString; FURI: WideString; FReplacementText: WideString; FXMLVersion: TXMLVersion; FPrefetched: Boolean; FResolved: Boolean; FOnStack: Boolean; FBetweenDecls: Boolean; FIsPE: Boolean; FStartLocation: TLocation; FCharCount: Cardinal; end; TNotationDecl = class(TDTDObject) public FName: WideString; FPublicID: WideString; FSystemID: WideString; end; TDTDModel = class private FRefCount: Integer; FNameTable: THashTable; FEntities: THashTable; FNotations: THashTable; function GetEntities: THashTable; function GetNotations: THashTable; public FName: WideString; FSystemID: WideString; FPublicID: WideString; FInternalSubset: WideString; constructor Create(aNameTable: THashTable); destructor Destroy; override; function Reference: TDTDModel; procedure Release; property Entities: THashTable read GetEntities; property Notations: THashTable read GetNotations; end; implementation { TDTDModel } function TDTDModel.GetEntities: THashTable; begin if FEntities = nil then FEntities := THashTable.Create(256, True); Result := FEntities; end; function TDTDModel.GetNotations: THashTable; begin if FNotations = nil then FNotations := THashTable.Create(256, True); Result := FNotations; end; constructor TDTDModel.Create(aNameTable: THashTable); begin FNameTable := aNameTable; FRefCount := 1; end; destructor TDTDModel.Destroy; begin FEntities.Free; FNotations.Free; inherited Destroy; end; function TDTDModel.Reference: TDTDModel; begin Inc(FRefCount); Result := Self; end; procedure TDTDModel.Release; begin if Assigned(Self) then begin Dec(FRefCount); if FRefCount = 0 then self.Destroy; end; end; { TContentParticle } function TContentParticle.Add: TContentParticle; begin if FChildren = nil then FChildren := TFPList.Create; Result := TContentParticle.Create; Result.FParent := Self; Result.FIndex := FChildren.Add(Result); end; destructor TContentParticle.Destroy; var I: Integer; begin if Assigned(FChildren) then for I := FChildren.Count-1 downto 0 do TObject(FChildren[I]).Free; FChildren.Free; inherited Destroy; end; function TContentParticle.GetChild(Index: Integer): TContentParticle; begin Result := TContentParticle(FChildren[Index]); end; function TContentParticle.GetChildCount: Integer; begin if Assigned(FChildren) then Result := FChildren.Count else Result := 0; end; function TContentParticle.IsRequired: Boolean; var I: Integer; begin Result := (CPQuant = cqOnce) or (CPQuant = cqOnceOrMore); // do not return True if all children are optional if (CPType <> ctName) and Result then begin for I := 0 to ChildCount-1 do begin Result := Children[I].IsRequired; if Result then Exit; end; end; end; function TContentParticle.MoreRequired(ChildIdx: Integer): Boolean; var I: Integer; begin Result := False; if CPType = ctSeq then begin for I := ChildIdx + 1 to ChildCount-1 do begin Result := Children[I].IsRequired; if Result then Exit; end; end; if Assigned(FParent) then Result := FParent.MoreRequired(FIndex); end; function TContentParticle.FindFirst(aDef: TObject): TContentParticle; var I: Integer; begin Result := nil; case CPType of ctSeq: for I := 0 to ChildCount-1 do with Children[I] do begin Result := FindFirst(aDef); if Assigned(Result) or IsRequired then Exit; end; ctChoice: for I := 0 to ChildCount-1 do with Children[I] do begin Result := FindFirst(aDef); if Assigned(Result) then Exit; end; else // ctName if aDef = Self.Def then Result := Self end; end; function TContentParticle.FindNext(aDef: TObject; ChildIdx: Integer): TContentParticle; var I: Integer; begin Result := nil; if CPType = ctSeq then // search sequence to its end begin for I := ChildIdx + 1 to ChildCount-1 do with Children[I] do begin Result := FindFirst(aDef); if (Result <> nil) or IsRequired then Exit; end; end; if (CPQuant = cqZeroOrMore) or (CPQuant = cqOnceOrMore) then Result := FindFirst(aDef); if (Result = nil) and Assigned(FParent) then Result := FParent.FindNext(aDef, FIndex); end; { TElementDecl } function TElementDecl.GetAttrDefCount: Integer; begin if Assigned(FAttrDefs) then Result := FAttrDefs.Count else Result := 0; end; function TElementDecl.AttrDefByIndex(index: Integer): TAttributeDef; begin if Assigned(FAttrDefs) then Result := TAttributeDef(FAttrDefs[index]) else Result := nil; end; destructor TElementDecl.Destroy; var i: Integer; begin RootCP.Free; if Assigned(FAttrDefs) then begin for i := FAttrDefs.Count-1 downto 0 do TObject(FAttrDefs.List^[i]).Free; FAttrDefs.Free; end; inherited Destroy; end; function TElementDecl.GetAttrDef(aName: PHashItem): TAttributeDef; var i: Integer; begin if Assigned(FAttrDefs) then begin for i := 0 to FAttrDefs.Count-1 do begin Result := TAttributeDef(FAttrDefs.List^[i]); if Result.FData^.FQName = aName then Exit; end; end; Result := nil; end; procedure TElementDecl.AddAttrDef(aDef: TAttributeDef); begin if FAttrDefs = nil then FAttrDefs := TFPList.Create; FAttrDefs.Add(aDef); if aDef.Default in [adRequired, adDefault, adFixed] then FNeedsDefaultPass := True; end; { TAttributeDef } constructor TAttributeDef.Create(aName: PHashItem; aColonPos: Integer); begin New(FData); FillChar(FData^, sizeof(TNodeData), 0); FData^.FIsDefault := True; FData^.FQName := aName; FData^.FColonPos := aColonPos; FData^.FTypeInfo := Self; FIsNamespaceDecl := ((Length(aName^.Key) = 5) or (aColonPos = 6)) and (Pos(WideString('xmlns'), aName^.Key) = 1); end; destructor TAttributeDef.Destroy; var curr, tmp: PNodeData; begin curr := FData; while Assigned(curr) do begin tmp := curr^.FNext; Dispose(curr); curr := tmp; end; inherited Destroy; end; function TAttributeDef.AddEnumToken(Buf: PWideChar; Len: Integer): Boolean; var I, L: Integer; begin // TODO: this implementaion is the slowest possible... Result := False; L := Length(FEnumeration); for I := 0 to L-1 do begin if (Len = Length(FEnumeration[i])) and CompareMem(Pointer(FEnumeration[i]), Buf, Len*sizeof(WideChar)) then Exit; end; SetLength(FEnumeration, L+1); SetString(FEnumeration[L], Buf, Len); Result := True; end; function TAttributeDef.HasEnumToken(const aValue: WideString): Boolean; var I: Integer; begin Result := True; if Length(FEnumeration) = 0 then Exit; for I := 0 to Length(FEnumeration)-1 do begin if FEnumeration[I] = aValue then Exit; end; Result := False; end; end.