* Patch from Sergei Gorelkin:

src/xmlread.pp, src/dom.pp
  * Improvements to attribute processing: attributes are now validated as
    they come. This enables reporting of the corresponding validation
    errors at correct positions (previously everything was reported at the
    end of element start-tag).
  * Search for a declaration for attribute, not for an attribute
    corresponding to the declaration. This reduces number of lookups
    (because unspecified attributes are not searched) and obsoletes the
    need in FDeclared field on every attribute.

  tests/domunit.pp, tests/testgen.pp:

  * Various improvements required to support converting of the
    DOM level 3 XPath module.

git-svn-id: trunk@12026 -
This commit is contained in:
michael 2008-11-04 18:33:05 +00:00
parent e5920bc2b8
commit 67f56b7adf
4 changed files with 173 additions and 115 deletions

View File

@ -501,8 +501,7 @@ type
protected protected
FName: DOMString; FName: DOMString;
FOwnerElement: TDOMElement; FOwnerElement: TDOMElement;
// TODO: following 2 - replace with a link to AttDecl ?? // TODO: replace with a link to AttDecl ??
FDeclared: Boolean;
FDataType: TAttrDataType; FDataType: TAttrDataType;
function GetNodeValue: DOMString; override; function GetNodeValue: DOMString; override;
function GetNodeType: Integer; override; function GetNodeType: Integer; override;
@ -2016,7 +2015,6 @@ begin
// Cloned attribute is always specified and carries its children // Cloned attribute is always specified and carries its children
Result := ACloneOwner.CreateAttribute(FName); Result := ACloneOwner.CreateAttribute(FName);
TDOMAttr(Result).FDataType := FDataType; TDOMAttr(Result).FDataType := FDataType;
// Declared = ?
CloneChildren(Result, ACloneOwner); CloneChildren(Result, ACloneOwner);
end; end;

View File

@ -318,6 +318,7 @@ type
FSaViolation: Boolean; FSaViolation: Boolean;
FDTDStartPos: PWideChar; FDTDStartPos: PWideChar;
FIntSubset: TWideCharBuf; FIntSubset: TWideCharBuf;
FAttrTag: Cardinal;
FColonPos: Integer; FColonPos: Integer;
FValidate: Boolean; // parsing options, copy of FCtrl.Options FValidate: Boolean; // parsing options, copy of FCtrl.Options
@ -340,6 +341,7 @@ type
procedure ParseQuantity(CP: TContentParticle); procedure ParseQuantity(CP: TContentParticle);
procedure StoreLocation(out Loc: TLocation); procedure StoreLocation(out Loc: TLocation);
function ValidateAttrSyntax(AttrDef: TDOMAttrDef; const aValue: WideString): Boolean; function ValidateAttrSyntax(AttrDef: TDOMAttrDef; const aValue: WideString): Boolean;
procedure ValidateAttrValue(Attr: TDOMAttr; const aValue: WideString);
procedure AddForwardRef(aList: TFPList; Buf: PWideChar; Length: Integer); procedure AddForwardRef(aList: TFPList; Buf: PWideChar; Length: Integer);
procedure ClearRefs(aList: TFPList); procedure ClearRefs(aList: TFPList);
procedure ValidateIdRefs; procedure ValidateIdRefs;
@ -425,6 +427,8 @@ type
// Attribute/Element declarations // Attribute/Element declarations
TDOMAttrDef = class(TDOMAttr) TDOMAttrDef = class(TDOMAttr)
private
FTag: Cardinal;
protected protected
FExternallyDeclared: Boolean; FExternallyDeclared: Boolean;
FDefault: TAttrDefault; FDefault: TAttrDefault;
@ -432,6 +436,8 @@ type
function AddEnumToken(Buf: DOMPChar; Len: Integer): Boolean; function AddEnumToken(Buf: DOMPChar; Len: Integer): Boolean;
function HasEnumToken(const aValue: WideString): Boolean; function HasEnumToken(const aValue: WideString): Boolean;
function Clone(AElement: TDOMElement): TDOMAttr; function Clone(AElement: TDOMElement): TDOMAttr;
public
property Tag: Cardinal read FTag write FTag;
end; end;
TDOMElementDef = class(TDOMElement) TDOMElementDef = class(TDOMElement)
@ -2625,6 +2631,8 @@ begin
NewElem := doc.CreateElementBuf(FName.Buffer, FName.Length); NewElem := doc.CreateElementBuf(FName.Buffer, FName.Length);
FCursor.AppendChild(NewElem); FCursor.AppendChild(NewElem);
// we're about to process a new set of attributes
Inc(FAttrTag);
// Find declaration for this element // Find declaration for this element
ElDef := nil; ElDef := nil;
@ -2697,10 +2705,49 @@ end;
procedure TXMLReader.ParseAttribute(Elem: TDOMElement; ElDef: TDOMElementDef); procedure TXMLReader.ParseAttribute(Elem: TDOMElement; ElDef: TDOMElementDef);
var var
attr: TDOMAttr; attr: TDOMAttr;
AttDef: TDOMAttrDef;
OldAttr: TDOMNode; OldAttr: TDOMNode;
procedure CheckValue;
var
AttValue, OldValue: WideString;
begin
if FStandalone and AttDef.FExternallyDeclared then
begin
OldValue := Attr.Value;
TDOMAttrDef(Attr).FDataType := AttDef.FDataType;
AttValue := Attr.Value;
if AttValue <> OldValue then
StandaloneError(-1);
end
else
begin
TDOMAttrDef(Attr).FDataType := AttDef.FDataType;
AttValue := Attr.Value;
end;
// TODO: what about normalization of AttDef.Value? (Currently it IS normalized)
if (AttDef.FDefault = adFixed) and (AttDef.Value <> AttValue) then
ValidationError('Value of attribute ''%s'' does not match its #FIXED default',[AttDef.Name], -1);
if not ValidateAttrSyntax(AttDef, AttValue) then
ValidationError('Attribute ''%s'' type mismatch', [AttDef.Name], -1);
ValidateAttrValue(Attr, AttValue);
end;
begin begin
CheckName; CheckName;
attr := doc.CreateAttributeBuf(FName.Buffer, FName.Length); attr := doc.CreateAttributeBuf(FName.Buffer, FName.Length);
if Assigned(ElDef) then
begin
AttDef := TDOMAttrDef(ElDef.GetAttributeNode(attr.Name));
if AttDef = nil then
ValidationError('Using undeclared attribute ''%s'' on element ''%s''',[attr.Name, Elem.TagName], FName.Length)
else
AttDef.Tag := FAttrTag; // indicates that this one is specified
end
else
AttDef := nil;
// !!cannot use TDOMElement.SetAttributeNode because it will free old attribute // !!cannot use TDOMElement.SetAttributeNode because it will free old attribute
OldAttr := Elem.Attributes.SetNamedItem(Attr); OldAttr := Elem.Attributes.SetNamedItem(Attr);
if Assigned(OldAttr) then if Assigned(OldAttr) then
@ -2711,6 +2758,9 @@ begin
ExpectEq; ExpectEq;
FCursor := attr; FCursor := attr;
ExpectAttValue; ExpectAttValue;
if Assigned(AttDef) and ((AttDef.FDataType <> dtCdata) or (AttDef.FDefault = adFixed)) then
CheckValue;
end; end;
procedure TXMLReader.AddForwardRef(aList: TFPList; Buf: PWideChar; Length: Integer); procedure TXMLReader.AddForwardRef(aList: TFPList; Buf: PWideChar; Length: Integer);
@ -2719,9 +2769,13 @@ var
begin begin
New(w); New(w);
SetString(w^.Value, Buf, Abs(Length)); SetString(w^.Value, Buf, Abs(Length));
StoreLocation(w^.Loc);
if Length > 0 then if Length > 0 then
begin
StoreLocation(w^.Loc);
Dec(w^.Loc.LinePos, Length); Dec(w^.Loc.LinePos, Length);
end
else
w^.Loc := FTokenStart;
aList.Add(w); aList.Add(w);
end; end;
@ -2752,9 +2806,7 @@ var
procedure DoDefaulting; procedure DoDefaulting;
var var
AttValue: WideString; I: Integer;
I, L, StartPos, EndPos: Integer;
Entity: TDOMEntity;
AttDef: TDOMAttrDef; AttDef: TDOMAttrDef;
begin begin
Map := ElDef.FAttributes; Map := ElDef.FAttributes;
@ -2763,96 +2815,25 @@ begin
begin begin
AttDef := Map[I] as TDOMAttrDef; AttDef := Map[I] as TDOMAttrDef;
Attr := Element.GetAttributeNode(AttDef.Name); if AttDef.Tag <> FAttrTag then // this one wasn't specified
if Attr = nil then
begin begin
// attribute needs defaulting
case AttDef.FDefault of case AttDef.FDefault of
adDefault, adFixed: begin adDefault, adFixed: begin
if FStandalone and AttDef.FExternallyDeclared then if FStandalone and AttDef.FExternallyDeclared then
StandaloneError; StandaloneError;
Attr := AttDef.Clone(Element); Attr := AttDef.Clone(Element);
Element.SetAttributeNode(Attr); Element.SetAttributeNode(Attr);
ValidateAttrValue(Attr, Attr.Value);
end; end;
adRequired: ValidationError('Required attribute ''%s'' of element ''%s'' is missing',[AttDef.Name, Element.TagName], 0) adRequired: ValidationError('Required attribute ''%s'' of element ''%s'' is missing',[AttDef.Name, Element.TagName], 0)
end; end;
end
else
begin
TDOMAttrDef(Attr).FDeclared := True;
// bypass heavyweight operations if possible
if (AttDef.DataType <> dtCdata) or (AttDef.FDefault = adFixed) then
begin
AttValue := Attr.Value; // unnormalized
// now assign DataType so that value is correctly normalized
TDOMAttrDef(Attr).FDataType := AttDef.FDataType;
if FStandalone and AttDef.FExternallyDeclared and (Attr.Value <> AttValue) then
StandaloneError;
AttValue := Attr.Value; // recalculate
// TODO: what about normalization of AttDef.Value? (Currently it IS normalized)
if (AttDef.FDefault = adFixed) and (AttDef.Value <> AttValue) then
ValidationError('Value of attribute ''%s'' does not match its #FIXED default',[AttDef.Name], 0);
if not ValidateAttrSyntax(AttDef, AttValue) then
ValidationError('Attribute ''%s'' type mismatch', [AttDef.Name], 0);
end; end;
end; end;
if Attr = nil then
Continue;
L := Length(AttValue);
case Attr.DataType of
dtId: if not Doc.AddID(Attr) then
ValidationError('The ID ''%s'' is not unique', [AttValue], 0);
dtIdRef, dtIdRefs: begin
StartPos := 1;
while StartPos <= L do
begin
EndPos := StartPos;
while (EndPos <= L) and (AttValue[EndPos] <> #32) do
Inc(EndPos);
// pass negative Length, so current location is not altered
AddForwardRef(FIDRefs, @AttValue[StartPos], StartPos-EndPos);
StartPos := EndPos + 1;
end;
end;
dtEntity, dtEntities: begin
StartPos := 1;
while StartPos <= L do
begin
EndPos := StartPos;
while (EndPos <= L) and (AttValue[EndPos] <> #32) do
Inc(EndPos);
Entity := TDOMEntity(FDocType.Entities.GetNamedItem(Copy(AttValue, StartPos, EndPos-StartPos)));
if (Entity = nil) or (Entity.NotationName = '') then
ValidationError('Attribute ''%s'' type mismatch', [Attr.Name], 0);
StartPos := EndPos + 1;
end;
end;
end;
end;
end;
procedure ReportUndeclared;
var
I: Integer;
begin
Map := Element.Attributes;
for I := 0 to Map.Length-1 do
begin
Attr := TDOMAttr(Map[I]);
if not TDOMAttrDef(Attr).FDeclared then
ValidationError('Using undeclared attribute ''%s'' on element ''%s''',[Attr.Name, Element.TagName], 0);
end;
end; end;
begin begin
if Assigned(ElDef) and Assigned(ElDef.FAttributes) then if Assigned(ElDef) and Assigned(ElDef.FAttributes) then
DoDefaulting; DoDefaulting;
// Now report undeclared attributes
if Assigned(FDocType) and Element.HasAttributes then
ReportUndeclared;
end; end;
function TXMLReader.ParseExternalID(out SysID, PubID: WideString; // [75] function TXMLReader.ParseExternalID(out SysID, PubID: WideString; // [75]
@ -2895,6 +2876,45 @@ begin
end; end;
end; end;
procedure TXMLReader.ValidateAttrValue(Attr: TDOMAttr; const aValue: WideString);
var
L, StartPos, EndPos: Integer;
Entity: TDOMEntity;
begin
L := Length(aValue);
case Attr.DataType of
dtId: if not Doc.AddID(Attr) then
ValidationError('The ID ''%s'' is not unique', [aValue], -1);
dtIdRef, dtIdRefs: begin
StartPos := 1;
while StartPos <= L do
begin
EndPos := StartPos;
while (EndPos <= L) and (aValue[EndPos] <> #32) do
Inc(EndPos);
// pass negative length, so uses FTokenStart as location
AddForwardRef(FIDRefs, @aValue[StartPos], StartPos-EndPos);
StartPos := EndPos + 1;
end;
end;
dtEntity, dtEntities: begin
StartPos := 1;
while StartPos <= L do
begin
EndPos := StartPos;
while (EndPos <= L) and (aValue[EndPos] <> #32) do
Inc(EndPos);
Entity := TDOMEntity(FDocType.Entities.GetNamedItem(Copy(aValue, StartPos, EndPos-StartPos)));
if (Entity = nil) or (Entity.NotationName = '') then
ValidationError('Attribute ''%s'' type mismatch', [Attr.Name], -1);
StartPos := EndPos + 1;
end;
end;
end;
end;
procedure TXMLReader.ValidateRoot; procedure TXMLReader.ValidateRoot;
begin begin
if Assigned(FDocType) then if Assigned(FDocType) then
@ -3068,7 +3088,6 @@ begin
Result := TDOMAttr.Create(FOwnerDocument); Result := TDOMAttr.Create(FOwnerDocument);
TDOMAttrEx(Result).FName := Self.FName; TDOMAttrEx(Result).FName := Self.FName;
TDOMAttrEx(Result).FDataType := FDataType; TDOMAttrEx(Result).FDataType := FDataType;
TDOMAttrEx(Result).FDeclared := True;
CloneChildren(Result, FOwnerDocument); CloneChildren(Result, FOwnerDocument);
end; end;

View File

@ -62,6 +62,7 @@ type
procedure _append(var coll: _collection; const Value: DOMString); procedure _append(var coll: _collection; const Value: DOMString);
procedure _assign(out rslt: _collection; const value: array of DOMString); procedure _assign(out rslt: _collection; const value: array of DOMString);
function IsSame(exp, act: TDOMNode): Boolean;
implementation implementation
@ -86,6 +87,11 @@ begin
rslt[I] := value[I]; rslt[I] := value[I];
end; end;
function IsSame(exp, act: TDOMNode): Boolean;
begin
Result := exp = act;
end;
procedure TDOMTestBase.SetUp; procedure TDOMTestBase.SetUp;
begin begin
FParser := TDOMParser.Create; FParser := TDOMParser.Create;

View File

@ -29,7 +29,7 @@ var
function PascalType(const s: WideString): string; function PascalType(const s: WideString): string;
begin begin
if (s = 'DOMString') or (s = 'boolean') or (s = 'DOMError') then if (s = 'DOMString') or (s = 'boolean') or (s = 'DOMError') or (s = 'double') then
result := s result := s
else if s = 'int' then else if s = 'int' then
result := 'Integer' result := 'Integer'
@ -39,7 +39,7 @@ begin
result := '_collection' result := '_collection'
else if s = 'List' then else if s = 'List' then
result := '_list' result := '_list'
else if Pos(WideString('DOM'), s) = 1 then else if (Pos(WideString('DOM'), s) = 1) or (Pos(WideString('XPath'), s) = 1) then
result := 'T' + s result := 'T' + s
else else
result := 'TDOM'+s; result := 'TDOM'+s;
@ -147,6 +147,11 @@ begin
else else
r := 'bad_condition(''contains intf=' + e['interface'] + ''')'; r := 'bad_condition(''contains intf=' + e['interface'] + ''')';
end end
else if e.TagName = 'same' then
begin
// maybe it would be sufficient to just compare pointers, but let's emit a helper for now
r := 'IsSame('+ e['expected'] + ', ' + e['actual'] + ')';
end
else if e.TagName = 'not' then else if e.TagName = 'not' then
begin begin
child := e.FirstChild; child := e.FirstChild;
@ -304,6 +309,10 @@ begin
s := node.TagName; s := node.TagName;
apinode := api.GetElementById(s); apinode := api.GetElementById(s);
// If not found by name only, try prepending the interface name.
// This enables support of same-named methods with different param lists on different objects
if (apinode = nil) and node.HasAttribute('interface') then
apinode := api.GetElementById(node['interface'] + '.' + s);
if assigned(apinode) then if assigned(apinode) then
begin begin
// handle most of DOM API in consistent way // handle most of DOM API in consistent way
@ -369,9 +378,15 @@ begin
// service (non-DOM) statements follow // service (non-DOM) statements follow
else if s = 'append' then else if s = 'append' then
rslt.Add(indent + '_append(' + node['collection'] + ', ' + node['item'] + ');') rslt.Add(indent + '_append(' + node['collection'] + ', ' + ReplaceQuotes(node['item']) + ');')
else if s = 'assign' then else if s = 'assign' then
begin
cond := TypeOfVar(node['var']);
if (cond = '_collection') or (cond = '_list') then
rslt.Add(indent + '_assign(' + node['var'] + ', ' + node['value'] + ');') rslt.Add(indent + '_assign(' + node['var'] + ', ' + node['value'] + ');')
else // emit an assignment operator. Force type for the case where they assign Document to Element.
rslt.Add(indent + node['var'] + ' := ' + TypeOfVar(node['var']) + '(' + ReplaceQuotes(node['value']) + ');');
end
else if s = 'increment' then else if s = 'increment' then
rslt.Add(indent + 'Inc(' + node['var'] + ', ' + node['value'] + ');') rslt.Add(indent + 'Inc(' + node['var'] + ', ' + node['value'] + ');')
else if s = 'decrement' then else if s = 'decrement' then
@ -433,6 +448,10 @@ begin
rslt.Add(indent + 'Load('+node['var']+', '''+ node['href']+''');') rslt.Add(indent + 'Load('+node['var']+', '''+ node['href']+''');')
else if s = 'implementationAttribute' then else if s = 'implementationAttribute' then
rslt.Add(indent + s + '[''' + node['name'] + '''] := ' + node['value'] + ';') rslt.Add(indent + s + '[''' + node['name'] + '''] := ' + node['value'] + ';')
else if s = 'createXPathEvaluator' then
rslt.Add(indent + node['var'] + ' := CreateXPathEvaluator(' + node['document'] + ');')
else if s = 'comment' then
rslt.Add(indent + '{ Source comment: ' + node.TextContent + ' }')
else else
begin begin
if not FailFlag then if not FailFlag then
@ -442,12 +461,44 @@ begin
end; end;
end; end;
procedure ConvertException(el: TDOMElement; const ExceptClass: string; indent: string);
var
excode: string;
begin
if not SuccessVarFlag then
rslt.Insert(2, ' success: Boolean;');
SuccessVarFlag := True;
rslt.Add(indent+'success := False;');
rslt.Add(indent+'try');
child := el.FirstChild;
while assigned(child) do
begin
if child.nodeType = ELEMENT_NODE then
begin
excode := child.nodeName;
subchild := child.FirstChild;
while Assigned(subchild) do
begin
if subchild.nodeType = ELEMENT_NODE then
ConvertStatement(TDOMElement(subchild), indent + ' ');
subchild := subchild.NextSibling;
end;
end;
child := child.NextSibling;
end;
rslt.Add(indent+'except');
rslt.Add(indent+' on E: Exception do');
rslt.Add(indent+' success := (E is ' + ExceptClass +') and (' + ExceptClass + '(E).Code = ' + excode + ');');
rslt.Add(indent+'end;');
rslt.Add(indent+'AssertTrue('''+el['id']+''', success);');
end;
procedure ConvertBlock(el: TDOMNode; indent: string); procedure ConvertBlock(el: TDOMNode; indent: string);
var var
curr: TDOMNode; curr: TDOMNode;
element: TDOMElement; element: TDOMElement;
List: TList; List: TList;
cond, excode: string; cond: string;
Frag: TDOMDocumentFragment; Frag: TDOMDocumentFragment;
I: Integer; I: Integer;
ElseNode: TDOMNode; ElseNode: TDOMNode;
@ -467,34 +518,9 @@ begin
element := TDOMElement(curr); element := TDOMElement(curr);
n := element.TagName; n := element.TagName;
if n = 'assertDOMException' then if n = 'assertDOMException' then
begin ConvertException(element, 'EDOMError', indent)
if not SuccessVarFlag then else if n = 'assertXPathException' then
rslt.Insert(2, ' success: Boolean;'); ConvertException(element, 'EXPathException', indent)
SuccessVarFlag := True;
rslt.Add(indent+'success := False;');
rslt.Add(indent+'try');
child := curr.FirstChild;
while assigned(child) do
begin
if child.nodeType = ELEMENT_NODE then
begin
excode := child.nodeName;
subchild := child.FirstChild;
while Assigned(subchild) do
begin
if subchild.nodeType = ELEMENT_NODE then
ConvertStatement(TDOMElement(subchild), indent + ' ');
subchild := subchild.NextSibling;
end;
end;
child := child.NextSibling;
end;
rslt.Add(indent+'except');
rslt.Add(indent+' on E: Exception do');
rslt.Add(indent+' success := (E is EDOMError) and (EDOMError(E).Code = ' + excode + ');');
rslt.Add(indent+'end;');
rslt.Add(indent+'AssertTrue('''+element['id']+''', success);');
end
else if n = 'try' then else if n = 'try' then
begin begin
GetChildElements(curr, List); GetChildElements(curr, List);
@ -658,7 +684,11 @@ begin
try try
if subvars.Count > 0 then if subvars.Count > 0 then
begin begin
TypedConsts.Add(' ' + Node['name'] + ': array[0..' + IntToStr(subvars.Count-1) + '] of DOMString = ('); if TDOMElement(subvars[0]).HasAttribute('type') then
hs := PascalType(TDOMElement(subvars[0]).GetAttribute('type'))
else
hs := 'DOMString';
TypedConsts.Add(' ' + Node['name'] + ': array[0..' + IntToStr(subvars.Count-1) + '] of ' + hs + ' = (');
for J := 0 to subvars.Count-1 do for J := 0 to subvars.Count-1 do
begin begin
hs := ' ' + ReplaceQuotes(subvars[J].TextContent); hs := ' ' + ReplaceQuotes(subvars[J].TextContent);
@ -817,7 +847,12 @@ begin
if root['name'] = 'attrname' then if root['name'] = 'attrname' then
root['name'] := 'attr_name'; root['name'] := 'attr_name';
sl.Add('procedure ' + class_name + '.' + root['name'] + ';'); sl.Add('procedure ' + class_name + '.' + root['name'] + ';');
try
ConvertTest(root, sl); ConvertTest(root, sl);
except
Writeln('An exception occured while converting '+root['name']);
raise;
end;
if sl.Count > 0 then if sl.Count > 0 then
begin begin
all.add(' procedure '+root['name']+';'); all.add(' procedure '+root['name']+';');