|
@@ -296,6 +296,19 @@ type
|
|
|
);
|
|
|
|
|
|
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
|
|
|
private
|
|
@@ -322,6 +335,15 @@ type
|
|
|
FDTDStartPos: PWideChar;
|
|
|
FIntSubset: TWideCharBuf;
|
|
|
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;
|
|
|
FValidate: Boolean; // parsing options, copy of FCtrl.Options
|
|
@@ -402,6 +424,9 @@ type
|
|
|
procedure ParseNotationDecl;
|
|
|
function ResolveEntity(const SystemID, PublicID: WideString; out Source: TXMLCharSource): Boolean;
|
|
|
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 PopVC;
|
|
@@ -1338,7 +1363,12 @@ begin
|
|
|
end;
|
|
|
end;
|
|
|
|
|
|
+const
|
|
|
+ PrefixDefault: array[0..4] of WideChar = ('x','m','l','n','s');
|
|
|
+
|
|
|
constructor TXMLReader.Create;
|
|
|
+var
|
|
|
+ b: TBinding;
|
|
|
begin
|
|
|
inherited Create;
|
|
|
BufAllocate(FName, 128);
|
|
@@ -1346,6 +1376,20 @@ begin
|
|
|
FIDRefs := 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
|
|
|
FNamePages := @NamePages;
|
|
|
SetLength(FValidator, 16);
|
|
@@ -1365,6 +1409,8 @@ begin
|
|
|
end;
|
|
|
|
|
|
destructor TXMLReader.Destroy;
|
|
|
+var
|
|
|
+ I: Integer;
|
|
|
begin
|
|
|
if Assigned(FEntityValue.Buffer) then
|
|
|
FreeMem(FEntityValue.Buffer);
|
|
@@ -1376,6 +1422,11 @@ begin
|
|
|
FPEMap.Free;
|
|
|
ClearRefs(FNotationRefs);
|
|
|
ClearRefs(FIDRefs);
|
|
|
+ FNsAttHash.Free;
|
|
|
+ for I := FBindings.Count-1 downto 0 do
|
|
|
+ TObject(FBindings.List^[I]).Free;
|
|
|
+ FPrefixes.Free;
|
|
|
+ FBindings.Free;
|
|
|
FNotationRefs.Free;
|
|
|
FIDRefs.Free;
|
|
|
inherited Destroy;
|
|
@@ -2869,6 +2920,8 @@ begin
|
|
|
if Assigned(ElDef) and Assigned(ElDef.FAttributes) then
|
|
|
ProcessDefaultAttributes(NewElem, ElDef.FAttributes);
|
|
|
PushVC(ElDef); // this increases FNesting
|
|
|
+ if FNamespaces then
|
|
|
+ ProcessNamespaceAtts(NewElem);
|
|
|
|
|
|
// SAX: ContentHandler.StartElement(...)
|
|
|
// SAX: ContentHandler.StartPrefixMapping(...)
|
|
@@ -2914,6 +2967,8 @@ begin
|
|
|
if FValidate and FValidator[FNesting].Incomplete then
|
|
|
ValidationError('Element ''%s'' is missing required sub-elements', [ElName^.Key], ErrOffset);
|
|
|
|
|
|
+ if FNamespaces then
|
|
|
+ EndNamespaceScope(FBindingStack[FNesting]);
|
|
|
PopVC;
|
|
|
end;
|
|
|
|
|
@@ -3040,6 +3095,153 @@ begin
|
|
|
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]
|
|
|
SysIdOptional: Boolean): Boolean;
|
|
|
begin
|