+ Processing of prefixed attributes and prefix bindings. This completes namespace support at

the XML reader side.

git-svn-id: trunk@13214 -
This commit is contained in:
sergei 2009-05-31 08:18:06 +00:00
parent 6452f5b692
commit b631754754
3 changed files with 299 additions and 0 deletions

View File

@ -522,6 +522,8 @@ type
function GetPrefix: DOMString; override; function GetPrefix: DOMString; override;
procedure SetPrefix(const Value: DOMString); override; procedure SetPrefix(const Value: DOMString); override;
public public
{ Used by parser }
procedure SetNSI(const nsUri: DOMString; ColonPos: Integer);
function CompareName(const AName: DOMString): Integer; override; function CompareName(const AName: DOMString): Integer; override;
property NSI: TNamespaceInfo read FNSI; property NSI: TNamespaceInfo read FNSI;
end; end;
@ -2324,6 +2326,13 @@ begin
Result := CompareDOMStrings(DOMPChar(AName), DOMPChar(NodeName), Length(AName), Length(NodeName)); Result := CompareDOMStrings(DOMPChar(AName), DOMPChar(NodeName), Length(AName), Length(NodeName));
end; end;
procedure TDOMNode_NS.SetNSI(const nsUri: DOMString; ColonPos: Integer);
begin
FNSI.NSIndex := FOwnerDocument.IndexOfNS(nsURI, True);
FNSI.PrefixLen := ColonPos;
Include(FFlags, nfLevel2);
end;
// ------------------------------------------------------- // -------------------------------------------------------
// Attr // Attr
// ------------------------------------------------------- // -------------------------------------------------------

View File

@ -297,6 +297,19 @@ type
TCheckNameFlags = set of (cnOptional, cnToken); TCheckNameFlags = set of (cnOptional, cnToken);
TBinding = class
public
uri: WideString;
next: TBinding;
prevPrefixBinding: TObject;
Prefix: PHashItem;
end;
TPrefixedAttr = record
Attr: TDOMAttr;
PrefixLen: Integer; // to avoid recalculation
end;
TXMLReader = class TXMLReader = class
private private
FSource: TXMLCharSource; FSource: TXMLCharSource;
@ -322,6 +335,15 @@ type
FDTDStartPos: PWideChar; FDTDStartPos: PWideChar;
FIntSubset: TWideCharBuf; FIntSubset: TWideCharBuf;
FAttrTag: Cardinal; FAttrTag: Cardinal;
FPrefixes: THashTable;
FBindings: TFPList;
FDefaultPrefix: THashItem;
FWorkAtts: array of TPrefixedAttr;
FBindingStack: array of TBinding;
FFreeBindings: TBinding;
FNsAttHash: TDblHashArray;
FStdPrefix_xml: PHashItem;
FStdPrefix_xmlns: PHashItem;
FColonPos: Integer; FColonPos: Integer;
FValidate: Boolean; // parsing options, copy of FCtrl.Options FValidate: Boolean; // parsing options, copy of FCtrl.Options
@ -402,6 +424,9 @@ type
procedure ParseNotationDecl; procedure ParseNotationDecl;
function ResolveEntity(const SystemID, PublicID: WideString; out Source: TXMLCharSource): Boolean; function ResolveEntity(const SystemID, PublicID: WideString; out Source: TXMLCharSource): Boolean;
procedure ProcessDefaultAttributes(Element: TDOMElement; Map: TDOMNamedNodeMap); procedure ProcessDefaultAttributes(Element: TDOMElement; Map: TDOMNamedNodeMap);
procedure ProcessNamespaceAtts(Element: TDOMElement);
procedure AddBinding(Attr: TDOMAttr; Prefix: PHashItem; var Chain: TBinding);
procedure EndNamespaceScope(var Chain: TBinding);
procedure PushVC(aElDef: TDOMElementDef); procedure PushVC(aElDef: TDOMElementDef);
procedure PopVC; procedure PopVC;
@ -1338,7 +1363,12 @@ begin
end; end;
end; end;
const
PrefixDefault: array[0..4] of WideChar = ('x','m','l','n','s');
constructor TXMLReader.Create; constructor TXMLReader.Create;
var
b: TBinding;
begin begin
inherited Create; inherited Create;
BufAllocate(FName, 128); BufAllocate(FName, 128);
@ -1346,6 +1376,20 @@ begin
FIDRefs := TFPList.Create; FIDRefs := TFPList.Create;
FNotationRefs := TFPList.Create; FNotationRefs := TFPList.Create;
FPrefixes := THashTable.Create(16, False);
FBindings := TFPList.Create;
FNsAttHash := TDblHashArray.Create;
SetLength(FWorkAtts, 16);
SetLength(FBindingStack, 16);
FStdPrefix_xml := FPrefixes.FindOrAdd(@PrefixDefault, 3);
FStdPrefix_xmlns := FPrefixes.FindOrAdd(@PrefixDefault, 5);
{ implicit binding for the 'xml' prefix }
b := TBinding.Create;
FBindings.Add(b);
FStdPrefix_xml^.Data := b;
b.uri := stduri_xml;
b.Prefix := FStdPrefix_xml;
// Set char rules to XML 1.0 // Set char rules to XML 1.0
FNamePages := @NamePages; FNamePages := @NamePages;
SetLength(FValidator, 16); SetLength(FValidator, 16);
@ -1365,6 +1409,8 @@ begin
end; end;
destructor TXMLReader.Destroy; destructor TXMLReader.Destroy;
var
I: Integer;
begin begin
if Assigned(FEntityValue.Buffer) then if Assigned(FEntityValue.Buffer) then
FreeMem(FEntityValue.Buffer); FreeMem(FEntityValue.Buffer);
@ -1376,6 +1422,11 @@ begin
FPEMap.Free; FPEMap.Free;
ClearRefs(FNotationRefs); ClearRefs(FNotationRefs);
ClearRefs(FIDRefs); ClearRefs(FIDRefs);
FNsAttHash.Free;
for I := FBindings.Count-1 downto 0 do
TObject(FBindings.List^[I]).Free;
FPrefixes.Free;
FBindings.Free;
FNotationRefs.Free; FNotationRefs.Free;
FIDRefs.Free; FIDRefs.Free;
inherited Destroy; inherited Destroy;
@ -2869,6 +2920,8 @@ begin
if Assigned(ElDef) and Assigned(ElDef.FAttributes) then if Assigned(ElDef) and Assigned(ElDef.FAttributes) then
ProcessDefaultAttributes(NewElem, ElDef.FAttributes); ProcessDefaultAttributes(NewElem, ElDef.FAttributes);
PushVC(ElDef); // this increases FNesting PushVC(ElDef); // this increases FNesting
if FNamespaces then
ProcessNamespaceAtts(NewElem);
// SAX: ContentHandler.StartElement(...) // SAX: ContentHandler.StartElement(...)
// SAX: ContentHandler.StartPrefixMapping(...) // SAX: ContentHandler.StartPrefixMapping(...)
@ -2914,6 +2967,8 @@ begin
if FValidate and FValidator[FNesting].Incomplete then if FValidate and FValidator[FNesting].Incomplete then
ValidationError('Element ''%s'' is missing required sub-elements', [ElName^.Key], ErrOffset); ValidationError('Element ''%s'' is missing required sub-elements', [ElName^.Key], ErrOffset);
if FNamespaces then
EndNamespaceScope(FBindingStack[FNesting]);
PopVC; PopVC;
end; end;
@ -3040,6 +3095,153 @@ begin
end; end;
end; end;
procedure TXMLReader.AddBinding(Attr: TDOMAttr; Prefix: PHashItem; var Chain: TBinding);
var
nsUri: DOMString;
b: TBinding;
begin
nsUri := Attr.NodeValue;
{ 'xml' is allowed to be bound to the correct namespace }
if ((nsUri = stduri_xml) <> (Prefix = FStdPrefix_xml)) or
(Prefix = FStdPrefix_xmlns) or
(nsUri = stduri_xmlns) then
begin
if (Prefix = FStdPrefix_xml) or (Prefix = FStdPrefix_xmlns) then
FatalError('Illegal usage of reserved prefix ''%s''', [Prefix^.Key])
else
FatalError('Illegal usage of reserved namespace URI ''%s''', [nsUri]);
end;
{ try reusing an existing binding }
b := FFreeBindings;
if Assigned(b) then
FFreeBindings := b.Next
else { no free bindings, create a new one }
begin
b := TBinding.Create;
FBindings.Add(b);
end;
b.uri := nsUri;
b.prefix := Prefix;
b.PrevPrefixBinding := Prefix^.Data;
if nsUri = '' then
begin
if (FXML11 or (Prefix = @FDefaultPrefix)) then // prefix being unbound
Prefix^.Data := nil
else
FatalError('Illegal undefining of namespace'); { position - ? }
end
else
Prefix^.Data := b;
b.Next := Chain;
Chain := b;
end;
procedure TXMLReader.EndNamespaceScope(var Chain: TBinding);
var
b: TBinding;
begin
while Assigned(Chain) do
begin
b := Chain;
Chain := b.next;
b.next := FFreeBindings;
FFreeBindings := b;
b.Prefix^.Data := b.prevPrefixBinding;
end;
end;
procedure TXMLReader.ProcessNamespaceAtts(Element: TDOMElement);
var
I, J: Integer;
Map: TDOMNamedNodeMap;
Prefix, AttrName: PHashItem;
Attr: TDOMAttr;
PrefixCount: Integer;
b: TBinding;
begin
if FNesting = Length(FBindingStack) then
SetLength(FBindingStack, FNesting * 2);
PrefixCount := 0;
if Element.HasAttributes then
begin
Map := Element.Attributes;
if Map.Length > LongWord(Length(FWorkAtts)) then
SetLength(FWorkAtts, Map.Length+10);
{ Pass 1, identify prefixed attrs and assign prefixes }
for I := 0 to Map.Length-1 do
begin
Attr := TDOMAttr(Map[I]);
AttrName := Attr.NSI.QName;
if Pos(WideString('xmlns'), AttrName^.Key) = 1 then
begin
{ this is a namespace declaration }
if Length(AttrName^.Key) = 5 then
begin
// TODO: check all consequences of having zero PrefixLength
Attr.SetNSI(stduri_xmlns, 0);
AddBinding(Attr, @FDefaultPrefix, FBindingStack[FNesting]);
end
else if AttrName^.Key[6] = ':' then
begin
Prefix := FPrefixes.FindOrAdd(@AttrName^.Key[7], Length(AttrName^.Key)-6);
Attr.SetNSI(stduri_xmlns, 6);
AddBinding(Attr, Prefix, FBindingStack[FNesting]);
end;
end
else
begin
J := Pos(WideChar(':'), AttrName^.Key);
if J > 1 then
begin
FWorkAtts[PrefixCount].Attr := Attr;
FWorkAtts[PrefixCount].PrefixLen := J;
Inc(PrefixCount);
end;
end;
end;
end;
{ Pass 2, now all bindings are known, handle remaining prefixed attributes }
if PrefixCount > 0 then
begin
FNsAttHash.Init(PrefixCount);
for I := 0 to PrefixCount-1 do
begin
AttrName := FWorkAtts[I].Attr.NSI.QName;
Prefix := FPrefixes.FindOrAdd(PWideChar(AttrName^.Key), FWorkAtts[I].PrefixLen-1);
b := TBinding(Prefix^.Data);
if b = nil then
FatalError('Unbound prefix "%s"', [Prefix^.Key]);
{ detect duplicates }
J := FWorkAtts[I].PrefixLen+1;
if FNsAttHash.Locate(@b.uri, @AttrName^.Key[J], Length(AttrName^.Key) - J) then
FatalError('Duplicate prefixed attribute');
// convert Attr into namespaced one (by hack for the time being)
FWorkAtts[I].Attr.SetNSI(b.uri, J-1);
end;
end;
{ Finally, expand the element name }
J := Pos(WideChar(':'), Element.NSI.QName^.Key);
if J > 1 then
begin
Prefix := FPrefixes.FindOrAdd(PWideChar(Element.NSI.QName^.Key), J-1);
if Prefix^.Data = nil then
FatalError('Unbound prefix "%s"', [Prefix^.Key]);
b := TBinding(Prefix^.Data);
end
else if Assigned(FDefaultPrefix.Data) then
b := TBinding(FDefaultPrefix.Data)
else
Exit;
// convert Element into namespaced one (by hack for the time being)
Element.SetNSI(b.uri, J);
end;
function TXMLReader.ParseExternalID(out SysID, PubID: WideString; // [75] function TXMLReader.ParseExternalID(out SysID, PubID: WideString; // [75]
SysIdOptional: Boolean): Boolean; SysIdOptional: Boolean): Boolean;
begin begin

View File

@ -71,6 +71,29 @@ type
property Count: LongWord read FCount; property Count: LongWord read FCount;
end; end;
{ another hash, for detecting duplicate namespaced attributes without memory allocations }
PWideString = ^WideString;
PExpHashEntry = ^TExpHashEntry;
TExpHashEntry = record
rev: LongWord;
hash: LongWord;
uriPtr: PWideString;
lname: PWideChar;
lnameLen: Integer;
end;
TDblHashArray = class(TObject)
private
FSizeLog: Integer;
FRevision: LongWord;
FData: PExpHashEntry;
public
procedure Init(NumSlots: Integer);
function Locate(uri: PWideString; localName: PWideChar; localLength: Integer): Boolean;
destructor Destroy; override;
end;
{$i names.inc} {$i names.inc}
implementation implementation
@ -526,6 +549,71 @@ begin
end; end;
end; end;
{ TDblHashArray }
destructor TDblHashArray.Destroy;
begin
FreeMem(FData);
inherited Destroy;
end;
procedure TDblHashArray.Init(NumSlots: Integer);
var
i: Integer;
begin
if ((NumSlots * 2) shr FSizeLog) <> 0 then // need at least twice more entries, and no less than 8
begin
FSizeLog := 3;
while (NumSlots shr FSizeLog) <> 0 do
Inc(FSizeLog);
ReallocMem(FData, (1 shl FSizeLog) * sizeof(TExpHashEntry));
FRevision := 0;
end;
if FRevision = 0 then
begin
FRevision := $FFFFFFFF;
for i := (1 shl FSizeLog)-1 downto 0 do
FData[i].rev := FRevision;
end;
Dec(FRevision);
end;
function TDblHashArray.Locate(uri: PWideString; localName: PWideChar; localLength: Integer): Boolean;
var
step: Byte;
mask: LongWord;
idx: Integer;
HashValue: LongWord;
begin
HashValue := Hash(0, PWideChar(uri^), Length(uri^));
HashValue := Hash(HashValue, localName, localLength);
mask := (1 shl FSizeLog) - 1;
step := (HashValue and (not mask)) shr (FSizeLog-1) and (mask shr 2) or 1;
idx := HashValue and mask;
result := True;
while FData[idx].rev = FRevision do
begin
if (HashValue = FData[idx].hash) and (FData[idx].uriPtr^ = uri^) and
(FData[idx].lnameLen = localLength) and
CompareMem(FData[idx].lname, localName, localLength * sizeof(WideChar)) then
Exit;
if idx < step then
Inc(idx, (1 shl FSizeLog) - step)
else
Dec(idx, step);
end;
with FData[idx] do
begin
rev := FRevision;
hash := HashValue;
uriPtr := uri;
lname := localName;
lnameLen := localLength;
end;
result := False;
end;
initialization initialization
finalization finalization