lazarus/components/lazutils/laz2_dom.pas
2016-06-18 16:40:04 +00:00

3739 lines
104 KiB
ObjectPascal

{
**********************************************************************
This file is part of LazUtils.
It is copied from FCL unit dom svn revision 15251 and adapted to use
UTF8 instead of widestrings by Mattias Gaertner.
See the file COPYING.FPC, included in this distribution,
for details about the license.
**********************************************************************
Implementation of DOM interfaces
Copyright (c) 1999-2000 by Sebastian Guenther, sg@freepascal.org
Modified in 2006 by Sergei Gorelkin, sergei_gorelkin@mail.ru
}
{
This unit provides classes which implement the interfaces defined in the
DOM (Document Object Model) specification.
The current state is:
DOM Levels 1 and 2 - Completely implemented
DOM Level 3 - Partially implemented
Specification used for this implementation:
"Document Object Model (DOM) Level 2 Specification Version 1.0
W3C Recommendation 11 November, 2000"
http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113
}
unit Laz2_DOM;
{$ifdef fpc}
{$MODE objfpc}{$H+}
{$endif}
interface
uses
SysUtils, Classes, laz2_xmlutils;
// -------------------------------------------------------
// DOMException
// -------------------------------------------------------
const
// DOM Level 1 exception codes:
INDEX_SIZE_ERR = 1; // index or size is negative, or greater than the allowed value
DOMSTRING_SIZE_ERR = 2; // Specified range of text does not fit into a DOMString
HIERARCHY_REQUEST_ERR = 3; // node is inserted somewhere it does not belong
WRONG_DOCUMENT_ERR = 4; // node is used in a different document than the one that created it (that does not support it)
INVALID_CHARACTER_ERR = 5; // invalid or illegal character is specified, such as in a name
NO_DATA_ALLOWED_ERR = 6; // data is specified for a node which does not support data
NO_MODIFICATION_ALLOWED_ERR = 7; // an attempt is made to modify an object where modifications are not allowed
NOT_FOUND_ERR = 8; // an attempt is made to reference a node in a context where it does not exist
NOT_SUPPORTED_ERR = 9; // implementation does not support the type of object requested
INUSE_ATTRIBUTE_ERR = 10; // an attempt is made to add an attribute that is already in use elsewhere
// DOM Level 2 exception codes:
INVALID_STATE_ERR = 11; // an attempt is made to use an object that is not, or is no longer, usable
SYNTAX_ERR = 12; // invalid or illegal string specified
INVALID_MODIFICATION_ERR = 13; // an attempt is made to modify the type of the underlying object
NAMESPACE_ERR = 14; // an attempt is made to create or change an object in a way which is incorrect with regard to namespaces
INVALID_ACCESS_ERR = 15; // parameter or operation is not supported by the underlying object
// -------------------------------------------------------
// Node
// -------------------------------------------------------
const
ELEMENT_NODE = 1;
ATTRIBUTE_NODE = 2;
TEXT_NODE = 3;
CDATA_SECTION_NODE = 4;
ENTITY_REFERENCE_NODE = 5;
ENTITY_NODE = 6;
PROCESSING_INSTRUCTION_NODE = 7;
COMMENT_NODE = 8;
DOCUMENT_NODE = 9;
DOCUMENT_TYPE_NODE = 10;
DOCUMENT_FRAGMENT_NODE = 11;
NOTATION_NODE = 12;
type
TDOMDocument = class;
TDOMNodeList = class;
TDOMNamedNodeMap = class;
TDOMNode = class;
TDOMAttr = class;
TDOMElement = class;
TDOMText = class;
TDOMComment = class;
TDOMCDATASection = class;
TDOMDocumentType = class;
TDOMEntityReference = class;
TDOMProcessingInstruction = class;
TDOMAttrDef = class;
TNodePool = class;
PNodePoolArray = ^TNodePoolArray;
TNodePoolArray = array[0..MaxInt div sizeof(Pointer)-1] of TNodePool;
{$ifndef fpc}
TFPList = TList;
{$endif}
// -------------------------------------------------------
// DOMString
// -------------------------------------------------------
TSetOfChar = set of Char;
DOMString = AnsiString;
DOMPChar = PChar;
DOMChar = Char;
PDOMString = ^DOMString;
EDOMError = class(Exception)
public
Code: Integer;
constructor Create(ACode: Integer; const ASituation: String);
end;
EDOMIndexSize = class(EDOMError)
public
constructor Create(const ASituation: String);
end;
EDOMHierarchyRequest = class(EDOMError)
public
constructor Create(const ASituation: String);
end;
EDOMWrongDocument = class(EDOMError)
public
constructor Create(const ASituation: String);
end;
EDOMNotFound = class(EDOMError)
public
constructor Create(const ASituation: String);
end;
EDOMNotSupported = class(EDOMError)
public
constructor Create(const ASituation: String);
end;
EDOMInUseAttribute = class(EDOMError)
public
constructor Create(const ASituation: String);
end;
EDOMInvalidState = class(EDOMError)
public
constructor Create(const ASituation: String);
end;
EDOMSyntax = class(EDOMError)
public
constructor Create(const ASituation: String);
end;
EDOMInvalidModification = class(EDOMError)
public
constructor Create(const ASituation: String);
end;
EDOMNamespace = class(EDOMError)
public
constructor Create(const ASituation: String);
end;
EDOMInvalidAccess = class(EDOMError)
public
constructor Create(const ASituation: String);
end;
{ NodeType, NodeName and NodeValue had been moved from fields to functions.
This lowers memory usage and also obsoletes most constructors,
at a slight performance penalty. However, NodeName and NodeValue are
accessible via fields using specialized properties of descendant classes,
e.g. TDOMElement.TagName, TDOMCharacterData.Data etc.}
TNodeFlagEnum = (
nfReadonly,
nfRecycled,
nfLevel2,
nfIgnorableWS,
nfSpecified,
nfDestroying
);
TNodeFlags = set of TNodeFlagEnum;
{ TDOMNodeEnumerator }
TDOMNodeEnumerator = class
private
FNode: TDOMNode;
FCurrent: TDOMNode;
public
constructor Create(Node: TDOMNode);
function MoveNext: boolean;
property Current: TDOMNode read FCurrent;
end;
{ TDOMNodeAllChildEnumerator }
TDOMNodeAllChildEnumerator = class
private
FNode: TDOMNode;
FCurrent: TDOMNode;
FEnd: TDOMNode;
public
constructor Create(Node: TDOMNode);
function MoveNext: boolean;
property Current: TDOMNode read FCurrent;
function GetEnumerator: TDOMNodeAllChildEnumerator; // including grand children
end;
{ TDOMNode }
TDOMNode = class
protected
FPool: TObject;
FFlags: TNodeFlags;
FParentNode: TDOMNode;
FPreviousSibling, FNextSibling: TDOMNode;
FOwnerDocument: TDOMDocument;
function GetNodeName: DOMString; virtual; abstract;
function GetNodeValue: DOMString; virtual;
procedure SetNodeValue(const {%H-}AValue: DOMString); virtual;
function GetFirstChild: TDOMNode; virtual;
function GetLastChild: TDOMNode; virtual;
function GetAttributes: TDOMNamedNodeMap; virtual;
function GetRevision: Integer;
function GetNodeType: Integer; virtual; abstract;
function GetTextContent: DOMString; virtual;
procedure SetTextContent(const AValue: DOMString); virtual;
function GetLocalName: DOMString; virtual;
function GetNamespaceURI: DOMString; virtual;
function GetPrefix: DOMString; virtual;
procedure SetPrefix(const {%H-}Value: DOMString); virtual;
function GetOwnerDocument: TDOMDocument; virtual;
function GetBaseURI: DOMString;
procedure SetReadOnly(Value: Boolean);
procedure Changing;
public
constructor Create(AOwner: TDOMDocument);
destructor Destroy; override;
procedure FreeInstance; override;
function GetChildNodes: TDOMNodeList;
function GetChildCount: SizeInt; virtual;
property NodeName: DOMString read GetNodeName;
property NodeValue: DOMString read GetNodeValue write SetNodeValue;
property NodeType: Integer read GetNodeType;
property ParentNode: TDOMNode read FParentNode;
property FirstChild: TDOMNode read GetFirstChild;
property LastChild: TDOMNode read GetLastChild;
property ChildNodes: TDOMNodeList read GetChildNodes;
property PreviousSibling: TDOMNode read FPreviousSibling;
property NextSibling: TDOMNode read FNextSibling;
property Attributes: TDOMNamedNodeMap read GetAttributes;
property OwnerDocument: TDOMDocument read GetOwnerDocument;
function GetEnumerator: TDOMNodeEnumerator; // all children excluding grand children
function GetEnumeratorAllChildren: TDOMNodeAllChildEnumerator; // all children including grand children
function GetNextNode: TDOMNode; // first child, then next sibling, then next sibling of parent, ...
function GetNextNodeSkipChildren: TDOMNode; // first next sibling, then next sibling of parent, ...
function GetPreviousNode: TDOMNode; // the reverse of GetNextNode
function GetLastLeaf: TDOMNode; // get last child of last child of ...
function GetLevel: SizeInt; // root node has 0
function InsertBefore({%H-}NewChild, {%H-}RefChild: TDOMNode): TDOMNode; virtual;
function ReplaceChild({%H-}NewChild, {%H-}OldChild: TDOMNode): TDOMNode; virtual;
function DetachChild({%H-}OldChild: TDOMNode): TDOMNode; virtual;
function RemoveChild(OldChild: TDOMNode): TDOMNode;
function AppendChild(NewChild: TDOMNode): TDOMNode;
function HasChildNodes: Boolean; virtual;
function CloneNode(deep: Boolean): TDOMNode; overload;
// DOM level 2
function IsSupported(const Feature, Version: DOMString): Boolean;
function HasAttributes: Boolean; virtual;
procedure Normalize; virtual;
property NamespaceURI: DOMString read GetNamespaceURI;
property LocalName: DOMString read GetLocalName;
property Prefix: DOMString read GetPrefix write SetPrefix;
// DOM level 3
property TextContent: DOMString read GetTextContent write SetTextContent;
function LookupPrefix(const nsURI: DOMString): DOMString;
function LookupNamespaceURI(const APrefix: DOMString): DOMString;
function IsDefaultNamespace(const nsURI: DOMString): Boolean;
property baseURI: DOMString read GetBaseURI;
// Extensions to DOM interface:
function CloneNode({%H-}deep: Boolean; {%H-}ACloneOwner: TDOMDocument): TDOMNode; overload; virtual;
function FindNode(const {%H-}ANodeName: DOMString): TDOMNode; virtual;
function CompareName(const name: DOMString): Integer; virtual;
property Flags: TNodeFlags read FFlags;
end;
TDOMNodeClass = class of TDOMNode;
{ The following class is an implementation specific extension, it is just an
extended implementation of TDOMNode, the generic DOM::Node interface
implementation. (Its main purpose is to save memory in a big node tree) }
{ TDOMNode_WithChildren }
TDOMNode_WithChildren = class(TDOMNode)
protected
FFirstChild, FLastChild: TDOMNode;
FChildNodes: TDOMNodeList;
function GetFirstChild: TDOMNode; override;
function GetLastChild: TDOMNode; override;
procedure CloneChildren(ACopy: TDOMNode; ACloneOwner: TDOMDocument);
procedure FreeChildren;
function GetTextContent: DOMString; override;
procedure SetTextContent(const AValue: DOMString); override;
public
destructor Destroy; override;
function InsertBefore(NewChild, RefChild: TDOMNode): TDOMNode; override;
function ReplaceChild(NewChild, OldChild: TDOMNode): TDOMNode; override;
function DetachChild(OldChild: TDOMNode): TDOMNode; override;
function HasChildNodes: Boolean; override;
function GetChildCount: SizeInt; override;
function FindNode(const ANodeName: DOMString): TDOMNode; override;
procedure InternalAppend(NewChild: TDOMNode);
end;
// -------------------------------------------------------
// NodeList
// -------------------------------------------------------
TFilterResult = (frFalse, frNorecurseFalse, frTrue, frNorecurseTrue);
TDOMNodeList = class(TObject)
protected
FNode: TDOMNode;
FRevision: Integer;
FList: TFPList;
function GetCount: LongWord;
function GetItem(index: LongWord): TDOMNode;
function NodeFilter({%H-}aNode: TDOMNode): TFilterResult; virtual;
// now deprecated in favor of NodeFilter
procedure BuildList; virtual;
public
constructor Create(ANode: TDOMNode);
destructor Destroy; override;
property Item[index: LongWord]: TDOMNode read GetItem; default;
property Count: LongWord read GetCount;
property Length: LongWord read GetCount;
end;
{ an extension to DOM interface, used to build recursive lists of elements }
TDOMElementList = class(TDOMNodeList)
protected
filter: DOMString;
FNSIndexFilter: Integer;
localNameFilter: DOMString;
FMatchNS: Boolean;
FMatchAnyNS: Boolean;
UseFilter: Boolean;
function NodeFilter(aNode: TDOMNode): TFilterResult; override;
public
constructor Create(ANode: TDOMNode; const AFilter: DOMString); overload;
constructor Create(ANode: TDOMNode; const nsURI, localName: DOMString); overload;
end;
// -------------------------------------------------------
// NamedNodeMap
// -------------------------------------------------------
{ TDOMNamedNodeMap }
TDOMNamedNodeMap = class(TObject)
protected
FOwner: TDOMNode;
FNodeType: Integer;
FSortedList: TFPList; // list of TDOMNode sorted via CompareName
FPosList: TFPList; // list of TDOMNode not sorted
function GetPosItem(index: LongWord): TDOMNode;
function GetSortedItem(index: LongWord): TDOMNode;
function GetLength: LongWord;
function FindSorted(const name: DOMString; out Index: LongWord): Boolean;
function DeleteSorted(index: LongWord): TDOMNode;
procedure RestoreDefault(const name: DOMString);
function InternalRemove(const name: DOMString): TDOMNode;
function ValidateInsert(arg: TDOMNode): Integer;
public
constructor Create(AOwner: TDOMNode; ANodeType: Integer);
destructor Destroy; override;
function GetNamedItem(const name: DOMString): TDOMNode;
function SetNamedItem(arg: TDOMNode): TDOMNode;
function RemoveNamedItem(const name: DOMString): TDOMNode;
// Introduced in DOM Level 2:
function getNamedItemNS(const {%H-}namespaceURI, {%H-}localName: DOMString): TDOMNode; virtual;
function setNamedItemNS(arg: TDOMNode): TDOMNode; virtual;
function removeNamedItemNS(const {%H-}namespaceURI,{%H-}localName: DOMString): TDOMNode; virtual;
property Item[index: LongWord]: TDOMNode read GetPosItem; default;
property SortedItem[index: LongWord]: TDOMNode read GetSortedItem;
property Length: LongWord read GetLength;
end;
// -------------------------------------------------------
// CharacterData
// -------------------------------------------------------
TDOMCharacterData = class(TDOMNode)
private
FNodeValue: DOMString;
protected
function GetLength: LongWord;
function GetNodeValue: DOMString; override;
procedure SetNodeValue(const AValue: DOMString); override;
public
property Data: DOMString read FNodeValue write SetNodeValue;
property Length: LongWord read GetLength;
function SubstringData(offset, count: LongWord): DOMString;
procedure AppendData(const arg: DOMString);
procedure InsertData(offset: LongWord; const arg: DOMString);
procedure DeleteData(offset, count: LongWord);
procedure ReplaceData(offset, count: LongWord; const arg: DOMString);
end;
// -------------------------------------------------------
// DOMImplementation
// -------------------------------------------------------
TDOMImplementation = class
public
function HasFeature(const feature, version: DOMString): Boolean;
// Introduced in DOM Level 2:
function CreateDocumentType(const QualifiedName, PublicID,
SystemID: DOMString): TDOMDocumentType;
function CreateDocument(const NamespaceURI, QualifiedName: DOMString;
doctype: TDOMDocumentType): TDOMDocument;
end;
// -------------------------------------------------------
// DocumentFragment
// -------------------------------------------------------
TDOMDocumentFragment = class(TDOMNode_WithChildren)
protected
function GetNodeType: Integer; override;
function GetNodeName: DOMString; override;
public
function CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode; overload; override;
end;
// -------------------------------------------------------
// Document
// -------------------------------------------------------
// TODO: to be replaced by more suitable container
TNamespaces = array of DOMString;
TDOMDocument = class(TDOMNode_WithChildren)
protected
FIDList: THashTable;
FRevision: Integer;
FXML11: Boolean;
FImplementation: TDOMImplementation;
FNamespaces: TNamespaces;
FNames: THashTable;
FEmptyNode: TDOMElement;
FNodeLists: THashTable;
FMaxPoolSize: Integer;
FPools: PNodePoolArray;
FDocumentURI: DOMString;
function GetDocumentElement: TDOMElement;
function GetDocType: TDOMDocumentType;
function GetNodeType: Integer; override;
function GetNodeName: DOMString; override;
function GetTextContent: DOMString; override;
function GetOwnerDocument: TDOMDocument; override;
procedure SetTextContent(const {%H-}value: DOMString); override;
procedure RemoveID(Elem: TDOMElement);
function GetChildNodeList(aNode: TDOMNode): TDOMNodeList;
function GetElementList(aNode: TDOMNode; const nsURI, aLocalName: DOMString; UseNS: Boolean): TDOMNodeList;
procedure NodeListDestroyed(aList: TDOMNodeList);
function Alloc(AClass: TDOMNodeClass): TDOMNode;
public
function IndexOfNS(const nsURI: DOMString; AddIfAbsent: Boolean = False): Integer;
function InsertBefore(NewChild, RefChild: TDOMNode): TDOMNode; override;
function ReplaceChild(NewChild, OldChild: TDOMNode): TDOMNode; override;
property DocType: TDOMDocumentType read GetDocType;
property Impl: TDOMImplementation read FImplementation;
property DocumentElement: TDOMElement read GetDocumentElement;
function CreateElement(const tagName: DOMString): TDOMElement; virtual;
function CreateElementBuf(Buf: DOMPChar; Length: Integer): TDOMElement;
function CreateDocumentFragment: TDOMDocumentFragment;
function CreateTextNode(const data: DOMString): TDOMText;
function CreateTextNodeBuf(Buf: DOMPChar; Length: Integer; IgnWS: Boolean): TDOMText;
function CreateComment(const data: DOMString): TDOMComment;
function CreateCommentBuf(Buf: DOMPChar; Length: Integer): TDOMComment;
function CreateCDATASection(const {%H-}data: DOMString): TDOMCDATASection;
virtual;
function CreateProcessingInstruction(const {%H-}target, {%H-}data: DOMString):
TDOMProcessingInstruction; virtual;
function CreateAttribute(const name: DOMString): TDOMAttr;
function CreateAttributeBuf(Buf: DOMPChar; Length: Integer): TDOMAttr;
function CreateAttributeDef(Buf: DOMPChar; Length: Integer): TDOMAttrDef;
function CreateEntityReference(const {%H-}name: DOMString): TDOMEntityReference;
virtual;
function GetElementsByTagName(const tagname: DOMString): TDOMNodeList;
// DOM level 2 methods
function ImportNode(ImportedNode: TDOMNode; Deep: Boolean): TDOMNode;
function CreateElementNS(const nsURI, QualifiedName: DOMString): TDOMElement;
function CreateAttributeNS(const nsURI, QualifiedName: DOMString): TDOMAttr;
function GetElementsByTagNameNS(const nsURI, alocalName: DOMString): TDOMNodeList;
function GetElementById(const ElementID: DOMString): TDOMElement;
// DOM level 3:
property documentURI: DOMString read FDocumentURI write FDocumentURI;
// Extensions to DOM interface:
constructor Create;
destructor Destroy; override;
function AddID(Attr: TDOMAttr): Boolean;
property Names: THashTable read FNames;
end;
TXMLDocument = class(TDOMDocument)
private
FXMLVersion: DOMString;
procedure SetXMLVersion(const aValue: DOMString);
public
// These fields are extensions to the DOM interface:
Encoding, StylesheetType, StylesheetHRef: DOMString;
function CreateCDATASection(const data: DOMString): TDOMCDATASection; override;
function CreateProcessingInstruction(const target, data: DOMString):
TDOMProcessingInstruction; override;
function CreateEntityReference(const name: DOMString): TDOMEntityReference; override;
property XMLVersion: DOMString read FXMLVersion write SetXMLVersion;
end;
// This limits number of namespaces per document to 65535,
// and prefix length to 65535, too.
// I believe that higher values may only be found in deliberately malformed documents.
TNamespaceInfo = packed record
NSIndex: Word;
PrefixLen: Word;
QName: PHashItem;
end;
// -------------------------------------------------------
// Attr
// -------------------------------------------------------
TAttrDataType = (
dtCdata,
dtId,
dtIdRef,
dtIdRefs,
dtEntity,
dtEntities,
dtNmToken,
dtNmTokens,
dtNotation
);
TDOMNode_NS = class(TDOMNode_WithChildren)
protected
FNSI: TNamespaceInfo;
function GetNodeName: DOMString; override;
function GetLocalName: DOMString; override;
function GetNamespaceURI: DOMString; override;
function GetPrefix: DOMString; override;
procedure SetPrefix(const Value: DOMString); override;
public
{ Used by parser }
procedure SetNSI(const nsUri: DOMString; ColonPos: Integer);
function CompareName(const AName: DOMString): Integer; override;
property NSI: TNamespaceInfo read FNSI;
end;
TDOMAttr = class(TDOMNode_NS)
protected
FOwnerElement: TDOMElement;
FDataType: TAttrDataType;
function GetNodeValue: DOMString; override;
function GetNodeType: Integer; override;
function GetSpecified: Boolean;
function GetIsID: Boolean;
procedure SetNodeValue(const AValue: DOMString); override;
public
destructor Destroy; override;
function CloneNode({%H-}deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode; overload; override;
property Name: DOMString read GetNodeName;
property Specified: Boolean read GetSpecified;
property Value: DOMString read GetNodeValue write SetNodeValue;
property OwnerElement: TDOMElement read FOwnerElement;
property IsID: Boolean read GetIsID;
// extensions
// TODO: this is to be replaced with DOM 3 TypeInfo
property DataType: TAttrDataType read FDataType write FDataType;
end;
// -------------------------------------------------------
// Element
// -------------------------------------------------------
TDOMElement = class(TDOMNode_NS)
protected
FAttributes: TDOMNamedNodeMap;
function GetNodeType: Integer; override;
function GetAttributes: TDOMNamedNodeMap; override;
procedure AttachDefaultAttrs;
function InternalLookupPrefix(const nsURI: DOMString; Original: TDOMElement): DOMString;
procedure RestoreDefaultAttr(AttrDef: TDOMAttr);
public
destructor Destroy; override;
function CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode; overload; override;
function IsEmpty: Boolean; virtual;
procedure Normalize; override;
property TagName: DOMString read GetNodeName;
function GetAttribute(const name: DOMString): DOMString;
procedure SetAttribute(const name, value: DOMString);
procedure RemoveAttribute(const name: DOMString);
function GetAttributeNode(const name: DOMString): TDOMAttr;
function SetAttributeNode(NewAttr: TDOMAttr): TDOMAttr;
function RemoveAttributeNode(OldAttr: TDOMAttr): TDOMAttr;
function GetElementsByTagName(const name: DOMString): TDOMNodeList;
// Introduced in DOM Level 2:
function GetAttributeNS(const nsURI, aLocalName: DOMString): DOMString;
procedure SetAttributeNS(const nsURI, qualifiedName, value: DOMString);
procedure RemoveAttributeNS(const nsURI, aLocalName: DOMString);
function GetAttributeNodeNS(const nsURI, aLocalName: DOMString): TDOMAttr;
function SetAttributeNodeNS(newAttr: TDOMAttr): TDOMAttr;
function GetElementsByTagNameNS(const nsURI, aLocalName: DOMString): TDOMNodeList;
function hasAttribute(const name: DOMString): Boolean;
function hasAttributeNS(const nsURI, aLocalName: DOMString): Boolean;
function HasAttributes: Boolean; override;
// extension
property AttribStrings[const Name: DOMString]: DOMString
read GetAttribute write SetAttribute; default;
end;
// -------------------------------------------------------
// Text
// -------------------------------------------------------
TDOMText = class(TDOMCharacterData)
protected
function GetNodeType: Integer; override;
function GetNodeName: DOMString; override;
procedure SetNodeValue(const aValue: DOMString); override;
public
function CloneNode({%H-}deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode; overload; override;
function SplitText(offset: LongWord): TDOMText;
function IsElementContentWhitespace: Boolean;
end;
// -------------------------------------------------------
// Comment
// -------------------------------------------------------
TDOMComment = class(TDOMCharacterData)
protected
function GetNodeType: Integer; override;
function GetNodeName: DOMString; override;
public
function CloneNode({%H-}deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode; overload; override;
end;
// -------------------------------------------------------
// CDATASection
// -------------------------------------------------------
TDOMCDATASection = class(TDOMText)
protected
function GetNodeType: Integer; override;
function GetNodeName: DOMString; override;
public
function CloneNode({%H-}deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode; overload; override;
end;
// -------------------------------------------------------
// DocumentType
// -------------------------------------------------------
TDOMDocumentType = class(TDOMNode)
protected
FName: DOMString;
FPublicID: DOMString;
FSystemID: DOMString;
FInternalSubset: DOMString;
FEntities, FNotations: TDOMNamedNodeMap;
function GetEntities: TDOMNamedNodeMap;
function GetNotations: TDOMNamedNodeMap;
function GetNodeType: Integer; override;
function GetNodeName: DOMString; override;
public
destructor Destroy; override;
property Name: DOMString read FName;
property Entities: TDOMNamedNodeMap read GetEntities;
property Notations: TDOMNamedNodeMap read GetNotations;
// Introduced in DOM Level 2:
property PublicID: DOMString read FPublicID;
property SystemID: DOMString read FSystemID;
property InternalSubset: DOMString read FInternalSubset;
end;
// -------------------------------------------------------
// Notation
// -------------------------------------------------------
TDOMNotation = class(TDOMNode)
protected
FName: DOMString;
FPublicID, FSystemID: DOMString;
function GetNodeType: Integer; override;
function GetNodeName: DOMString; override;
public
function CloneNode({%H-}deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode; overload; override;
property PublicID: DOMString read FPublicID;
property SystemID: DOMString read FSystemID;
end;
// -------------------------------------------------------
// Entity
// -------------------------------------------------------
TDOMEntity = class(TDOMNode_WithChildren)
protected
FName: DOMString;
FPublicID, FSystemID, FNotationName: DOMString;
function GetNodeType: Integer; override;
function GetNodeName: DOMString; override;
public
function CloneNode(deep: Boolean; aCloneOwner: TDOMDocument): TDOMNode; override;
property PublicID: DOMString read FPublicID;
property SystemID: DOMString read FSystemID;
property NotationName: DOMString read FNotationName;
end;
// -------------------------------------------------------
// EntityReference
// -------------------------------------------------------
TDOMEntityReference = class(TDOMNode_WithChildren)
protected
FName: DOMString;
function GetNodeType: Integer; override;
function GetNodeName: DOMString; override;
public
function CloneNode({%H-}deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode; overload; override;
end;
// -------------------------------------------------------
// ProcessingInstruction
// -------------------------------------------------------
TDOMProcessingInstruction = class(TDOMNode)
private
FTarget: DOMString;
FNodeValue: DOMString;
protected
function GetNodeType: Integer; override;
function GetNodeName: DOMString; override;
function GetNodeValue: DOMString; override;
procedure SetNodeValue(const AValue: DOMString); override;
public
function CloneNode({%H-}deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode; overload; override;
property Target: DOMString read FTarget;
property Data: DOMString read FNodeValue write SetNodeValue;
end;
// Attribute declaration - Attr descendant which carries rudimentary type info
// must be severely improved while developing Level 3
TAttrDefault = (
adImplied,
adDefault,
adRequired,
adFixed
);
TDOMAttrDef = class(TDOMAttr)
protected
FExternallyDeclared: Boolean;
FDefault: TAttrDefault;
FTag: Cardinal;
FEnumeration: array of DOMString;
public
function AddEnumToken(Buf: DOMPChar; Len: Integer): Boolean;
function HasEnumToken(const aValue: DOMString): Boolean;
function CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode; overload; override;
property Default: TAttrDefault read FDefault write FDefault;
property ExternallyDeclared: Boolean read FExternallyDeclared write FExternallyDeclared;
property Tag: Cardinal read FTag write FTag;
end;
// TNodePool - custom memory management for TDOMNode's
// One pool manages objects of the same InstanceSize (may be of various classes)
PExtent = ^TExtent;
TExtent = record
Next: PExtent;
// following: array of TDOMNode instances
end;
TNodePool = class(TObject)
private
FCurrExtent: PExtent;
FCurrExtentSize: Integer;
FElementSize: Integer;
FCurrBlock: TDOMNode;
FFirstFree: TDOMNode;
procedure AddExtent(AElemCount: Integer);
public
constructor Create(AElementSize: Integer; AElementCount: Integer = 32);
destructor Destroy; override;
function AllocNode(AClass: TDOMNodeClass): TDOMNode;
procedure FreeNode(ANode: TDOMNode);
end;
// URIs of predefined namespaces
const
stduri_xml: DOMString = 'http://www.w3.org/XML/1998/namespace';
stduri_xmlns: DOMString = 'http://www.w3.org/2000/xmlns/';
function StrToXMLValue(const s: string): string; // removes #0, encodes <>&'"
function XMLValueToStr(const s: string): string; // reverse of StrToXMLValue (except for invalid #0)
function EncodeLesserAndGreaterThan(const s: string): string;
// =======================================================
// =======================================================
implementation
function StrToXMLValue(const s: string): string;
function Convert(Dst: PChar; out NewLen: PtrUInt): boolean;
var
h: PChar;
l: Integer;
NewLength: Integer;
Src: PChar;
i: Integer;
begin
Result:=false;
NewLength:=0;
Src:=PChar(s);
repeat
case Src^ of
#0:
if Src-PChar(s)=length(s) then
break
else begin
h:=''; l:=0;
end;
'&': begin h:='&amp;'; l:=5; end;
'<': begin h:='&lt;'#0; l:=4; end;
'>': begin h:='&gt;'#0; l:=4; end;
'"': begin h:='&quot;'#0; l:=6; end;
'''': begin h:='&apos;'#0; l:=6; end;
else
if Dst<>nil then begin
Dst^:=Src^;
inc(Dst);
end else
inc(NewLength);
inc(Src);
continue;
end;
Result:=true;
if l>0 then begin
if Dst<>nil then begin
for i:=1 to l do begin
Dst^:=h^;
inc(Dst);
inc(h);
end;
end else
inc(NewLength,l);
end;
inc(Src);
until false;
NewLen:=NewLength;
end;
var
NewLen: PtrUInt;
begin
Result:=s;
if Result='' then exit;
if not Convert(nil,NewLen) then exit;
SetLength(Result,NewLen);
if NewLen=0 then exit;
Convert(PChar(Result),NewLen);
end;
function XMLValueToStr(const s: string): string;
// convert &amp &quot &apos &lt &gt
var
Src: PChar;
Dst: PChar;
begin
if Pos('&',s)<1 then exit(s);
SetLength(Result,length(s));
Src:=PChar(s);
Dst:=PChar(Result);
repeat
case Src^ of
#0:
if Src-PChar(s)=length(s) then
break
else
inc(Src);
'&':
begin
inc(Src);
case Src^ of
'a':
if (Src[1]='m') and (Src[2]='p') then begin
inc(Src,3);
if Src^=';' then inc(Src);
Dst^:='&';
inc(Dst);
continue;
end else if (Src[1]='p') and (Src[2]='o') and (Src[3]='s') then begin
inc(Src,4);
if Src^=';' then inc(Src);
Dst^:='''';
inc(Dst);
continue;
end;
'q':
if (Src[1]='u') and (Src[2]='o') and (Src[3]='t') then begin
inc(Src,4);
if Src^=';' then inc(Src);
Dst^:='"';
inc(Dst);
continue;
end;
'l':
if (Src[1]='t') then begin
inc(Src,2);
if Src^=';' then inc(Src);
Dst^:='<';
inc(Dst);
continue;
end;
'g':
if (Src[1]='t') then begin
inc(Src,2);
if Src^=';' then inc(Src);
Dst^:='>';
inc(Dst);
continue;
end;
end;
Dst^:='&';
inc(Dst);
end;
else
Dst^:=Src^;
inc(Src);
inc(Dst);
end;
until false;
SetLength(Result,Dst-PChar(Result));
end;
function EncodeLesserAndGreaterThan(const s: string): string;
function Convert(Dst: PChar; out NewLen: PtrUInt): boolean;
var
h: PChar;
l: Integer;
NewLength: Integer;
Src: PChar;
i: Integer;
begin
Result:=false;
NewLength:=0;
Src:=PChar(s);
repeat
case Src^ of
#0:
if Src-PChar(s)=length(s) then
break
else begin
h:=''; l:=0;
end;
'<': begin h:='&lt;'#0; l:=4; end;
'>': begin h:='&gt;'#0; l:=4; end;
else
if Dst<>nil then begin
Dst^:=Src^;
inc(Dst);
end else
inc(NewLength);
inc(Src);
continue;
end;
Result:=true;
if l>0 then begin
if Dst<>nil then begin
for i:=1 to l do begin
Dst^:=h^;
inc(Dst);
inc(h);
end;
end else
inc(NewLength,l);
end;
inc(Src);
until false;
NewLen:=NewLength;
end;
var
NewLen: PtrUInt;
begin
Result:=s;
if Result='' then exit;
if not Convert(nil,NewLen) then exit;
SetLength(Result,NewLen);
if NewLen=0 then exit;
Convert(PChar(Result),NewLen);
end;
{ a namespace-enabled NamedNodeMap }
type
TAttributeMap = class(TDOMNamedNodeMap)
private
function FindNS(nsIndex: Integer; const aLocalName: DOMString;
out SortedIndex: LongWord): Boolean;
function InternalRemoveNS(const nsURI, aLocalName: DOMString): TDOMNode;
public
function getNamedItemNS(const namespaceURI, localName: DOMString): TDOMNode; override;
function setNamedItemNS(arg: TDOMNode): TDOMNode; override;
function removeNamedItemNS(const namespaceURI,localName: DOMString): TDOMNode; override;
end;
{ TDOMNodeAllChildEnumerator }
constructor TDOMNodeAllChildEnumerator.Create(Node: TDOMNode);
begin
FNode:=Node;
FEnd:=Node.GetNextNodeSkipChildren;
end;
function TDOMNodeAllChildEnumerator.MoveNext: boolean;
begin
if FCurrent=nil then
FCurrent:=FNode.GetNextNode
else
FCurrent:=FCurrent.GetNextNode;
Result:=FCurrent<>FEnd;
end;
function TDOMNodeAllChildEnumerator.GetEnumerator: TDOMNodeAllChildEnumerator;
begin
Result:=Self;
end;
{ TDOMNodeEnumerator }
constructor TDOMNodeEnumerator.Create(Node: TDOMNode);
begin
FNode:=Node;
end;
function TDOMNodeEnumerator.MoveNext: boolean;
begin
if FCurrent=nil then
FCurrent:=FNode.FirstChild
else
FCurrent:=FCurrent.NextSibling;
Result:=FCurrent<>nil;
end;
// -------------------------------------------------------
// DOM Exception
// -------------------------------------------------------
constructor EDOMError.Create(ACode: Integer; const ASituation: String);
begin
Code := ACode;
inherited Create(Self.ClassName + ' in ' + ASituation);
end;
constructor EDOMIndexSize.Create(const ASituation: String); // 1
begin
inherited Create(INDEX_SIZE_ERR, ASituation);
end;
constructor EDOMHierarchyRequest.Create(const ASituation: String); // 3
begin
inherited Create(HIERARCHY_REQUEST_ERR, ASituation);
end;
constructor EDOMWrongDocument.Create(const ASituation: String); // 4
begin
inherited Create(WRONG_DOCUMENT_ERR, ASituation);
end;
constructor EDOMNotFound.Create(const ASituation: String); // 8
begin
inherited Create(NOT_FOUND_ERR, ASituation);
end;
constructor EDOMNotSupported.Create(const ASituation: String); // 9
begin
inherited Create(NOT_SUPPORTED_ERR, ASituation);
end;
constructor EDOMInUseAttribute.Create(const ASituation: String); // 10
begin
inherited Create(INUSE_ATTRIBUTE_ERR, ASituation);
end;
constructor EDOMInvalidState.Create(const ASituation: String); // 11
begin
inherited Create(INVALID_STATE_ERR, ASituation);
end;
constructor EDOMSyntax.Create(const ASituation: String); // 12
begin
inherited Create(SYNTAX_ERR, ASituation);
end;
constructor EDOMInvalidModification.Create(const ASituation: String); // 13
begin
inherited Create(INVALID_MODIFICATION_ERR, ASituation);
end;
constructor EDOMNamespace.Create(const ASituation: String); // 14
begin
inherited Create(NAMESPACE_ERR, ASituation);
end;
constructor EDOMInvalidAccess.Create(const ASituation: String); // 15
begin
inherited Create(INVALID_ACCESS_ERR, ASituation);
end;
// -------------------------------------------------------
// Node
// -------------------------------------------------------
constructor TDOMNode.Create(AOwner: TDOMDocument);
begin
FOwnerDocument := AOwner;
inherited Create;
end;
destructor TDOMNode.Destroy;
begin
if Assigned(FParentNode) then
FParentNode.DetachChild(Self);
inherited Destroy;
end;
procedure TDOMNode.FreeInstance;
begin
if Assigned(FPool) then
begin
CleanupInstance;
TNodePool(FPool).FreeNode(Self);
end
else
inherited FreeInstance;
end;
function TDOMNode.GetNodeValue: DOMString;
begin
Result := '';
end;
procedure TDOMNode.SetNodeValue(const AValue: DOMString);
begin
// do nothing
end;
function TDOMNode.GetChildNodes: TDOMNodeList;
begin
Result := FOwnerDocument.GetChildNodeList(Self);
end;
function TDOMNode.GetChildCount: SizeInt;
begin
Result:=0;
end;
function TDOMNode.GetEnumerator: TDOMNodeEnumerator;
begin
Result:=TDOMNodeEnumerator.Create(Self);
end;
function TDOMNode.GetEnumeratorAllChildren: TDOMNodeAllChildEnumerator;
begin
Result:=TDOMNodeAllChildEnumerator.Create(Self);
end;
function TDOMNode.GetNextNode: TDOMNode;
begin
Result:=FirstChild;
if Result=nil then
Result:=GetNextNodeSkipChildren;
end;
function TDOMNode.GetNextNodeSkipChildren: TDOMNode;
var
Node: TDOMNode;
begin
Result:=Self;
repeat
Node:=Result.NextSibling;
if Node<>nil then exit(Node);
Result:=Result.ParentNode;
until Result=nil;
Result:=nil;
end;
function TDOMNode.GetPreviousNode: TDOMNode;
var
Node: TDOMNode;
begin
Result:=PreviousSibling;
if Result=nil then
exit(ParentNode);
Node:=Result.GetLastLeaf;
if Node<>nil then
Result:=Node;
end;
function TDOMNode.GetLastLeaf: TDOMNode;
var
Node: TDOMNode;
begin
Result:=LastChild;
if Result=nil then exit;
repeat
Node:=Result.LastChild;
if Node=nil then exit;
Result:=Node;
until false;
end;
function TDOMNode.GetLevel: SizeInt;
var
Node: TDOMNode;
begin
Result:=0;
Node:=ParentNode;
while Node<>nil do begin
inc(Result);
Node:=Node.ParentNode;
end;
end;
function TDOMNode.GetFirstChild: TDOMNode;
begin
Result := nil;
end;
function TDOMNode.GetLastChild: TDOMNode;
begin
Result := nil;
end;
function TDOMNode.GetAttributes: TDOMNamedNodeMap;
begin
Result := nil;
end;
function TDOMNode.InsertBefore(NewChild, RefChild: TDOMNode): TDOMNode;
begin
Changing; // merely to comply with core3/nodeinsertbefore14
raise EDOMHierarchyRequest.Create('Node.InsertBefore');
Result:=nil;
end;
function TDOMNode.ReplaceChild(NewChild, OldChild: TDOMNode): TDOMNode;
begin
Changing; // merely to comply with core3/nodereplacechild21
raise EDOMHierarchyRequest.Create('Node.ReplaceChild');
Result:=nil;
end;
function TDOMNode.DetachChild(OldChild: TDOMNode): TDOMNode;
begin
// OldChild isn't in our child list
raise EDOMNotFound.Create('Node.RemoveChild');
Result:=nil;
end;
function TDOMNode.RemoveChild(OldChild: TDOMNode): TDOMNode;
begin
Result := DetachChild(OldChild);
end;
function TDOMNode.AppendChild(NewChild: TDOMNode): TDOMNode;
begin
Result := InsertBefore(NewChild, nil);
end;
function TDOMNode.HasChildNodes: Boolean;
begin
Result := False;
end;
function TDOMNode.CloneNode(deep: Boolean): TDOMNode;
begin
Result := CloneNode(deep, FOwnerDocument);
end;
function TDOMNode.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
begin
// !! CreateFmt() does not set Code property !!
raise EDOMNotSupported.Create(Format('Cloning/importing of %s is not supported', [ClassName]));
Result:=nil;
end;
function TDOMNode.FindNode(const ANodeName: DOMString): TDOMNode;
begin
// FIX: we have no children, hence cannot find anything
Result := nil;
end;
function TDOMNode.GetRevision: Integer;
begin
Result := FOwnerDocument.FRevision;
end;
function TDOMNode.IsSupported(const Feature, Version: DOMString): Boolean;
begin
Result := FOwnerDocument.Impl.HasFeature(Feature, Version);
end;
function TDOMNode.HasAttributes: Boolean;
begin
Result := False;
end;
procedure TDOMNode.Normalize;
var
Child, tmp: TDOMNode;
Txt: TDOMText;
begin
Child := FirstChild;
Txt := nil;
while Assigned(Child) do
begin
if Child.NodeType = TEXT_NODE then
begin
tmp := Child.NextSibling;
if TDOMText(Child).Data <> '' then
begin
if Assigned(Txt) then
begin
Txt.AppendData(TDOMText(Child).Data);
// TODO: maybe should be smarter
Exclude(Txt.FFlags, nfIgnorableWS);
end
else
begin
Txt := TDOMText(Child);
Child := Child.NextSibling;
Continue;
end;
end;
Child.Free;
Child := tmp;
end
else
begin
Child.Normalize; // should be recursive!
Child := Child.NextSibling;
Txt := nil;
end;
end;
end;
function TDOMNode.GetTextContent: DOMString;
begin
Result := NodeValue;
end;
procedure TDOMNode.SetTextContent(const AValue: DOMString);
begin
SetNodeValue(AValue);
end;
function TDOMNode.GetNamespaceURI: DOMString;
begin
Result := '';
end;
function TDOMNode.GetLocalName: DOMString;
begin
Result := '';
end;
function TDOMNode.GetPrefix: DOMString;
begin
Result := '';
end;
procedure TDOMNode.SetPrefix(const Value: DOMString);
begin
// do nothing, override for Elements and Attributes
end;
function TDOMNode.GetOwnerDocument: TDOMDocument;
begin
Result := FOwnerDocument;
end;
procedure TDOMNode.SetReadOnly(Value: Boolean);
var
child: TDOMNode;
attrs: TDOMNamedNodeMap;
I: Integer;
begin
if Value then
Include(FFlags, nfReadOnly)
else
Exclude(FFlags, nfReadOnly);
child := FirstChild;
while Assigned(child) do
begin
child.SetReadOnly(Value);
child := child.NextSibling;
end;
if HasAttributes then
begin
attrs := Attributes;
for I := 0 to attrs.Length-1 do
attrs[I].SetReadOnly(Value);
end;
end;
procedure TDOMNode.Changing;
begin
if (nfReadOnly in FFlags) and not (nfDestroying in FOwnerDocument.FFlags) then
raise EDOMError.Create(NO_MODIFICATION_ALLOWED_ERR, 'Node.CheckReadOnly');
end;
function CompareDOMStrings(const s1, s2: DOMPChar; l1, l2: integer): integer;
var i: integer;
begin
Result:=l1-l2;
i:=0;
while (i<l1) and (Result=0) do begin
Result:=ord(s1[i])-ord(s2[i]);
inc(i);
end;
end;
// generic version (slow)
function TDOMNode.CompareName(const name: DOMString): Integer;
var
SelfName: DOMString;
begin
SelfName := NodeName;
Result := CompareDOMStrings(DOMPChar(name), DOMPChar(SelfName), Length(name), Length(SelfName));
end;
// This will return nil for Entity, Notation, DocType and DocFragment's
function GetAncestorElement(n: TDOMNode): TDOMElement;
var
parent: TDOMNode;
begin
case n.nodeType of
DOCUMENT_NODE:
result := TDOMDocument(n).documentElement;
ATTRIBUTE_NODE:
result := TDOMAttr(n).OwnerElement;
else
parent := n.ParentNode;
while Assigned(parent) and (parent.NodeType <> ELEMENT_NODE) do
parent := parent.ParentNode;
Result := TDOMElement(parent);
end;
end;
// TODO: specs prescribe to return default namespace if APrefix=null,
// but we aren't able to distinguish null from an empty string.
// This breaks level3/nodelookupnamespaceuri08 which passes an empty string.
function TDOMNode.LookupNamespaceURI(const APrefix: DOMString): DOMString;
var
Attr: TDOMAttr;
Map: TDOMNamedNodeMap;
I: Integer;
begin
Result := '';
if Self = nil then
Exit;
if nodeType = ELEMENT_NODE then
begin
if (nfLevel2 in FFlags) and (TDOMElement(Self).Prefix = APrefix) then
begin
result := Self.NamespaceURI;
Exit;
end;
if HasAttributes then
begin
Map := Attributes;
for I := 0 to Map.Length-1 do
begin
Attr := TDOMAttr(Map[I]);
// should ignore level 1 atts here
if ((Attr.Prefix = 'xmlns') and (Attr.localName = APrefix)) or
((Attr.localName = 'xmlns') and (APrefix = '')) then
begin
result := Attr.NodeValue;
Exit;
end;
end
end;
end;
result := GetAncestorElement(Self).LookupNamespaceURI(APrefix);
end;
function TDOMNode.LookupPrefix(const nsURI: DOMString): DOMString;
begin
Result := '';
if (nsURI = '') or (Self = nil) then
Exit;
if nodeType = ELEMENT_NODE then
result := TDOMElement(Self).InternalLookupPrefix(nsURI, TDOMElement(Self))
else
result := GetAncestorElement(Self).LookupPrefix(nsURI);
end;
function TDOMNode.IsDefaultNamespace(const nsURI: DOMString): Boolean;
var
Attr: TDOMAttr;
Map: TDOMNamedNodeMap;
I: Integer;
begin
Result := False;
if Self = nil then
Exit;
if nodeType = ELEMENT_NODE then
begin
if TDOMElement(Self).FNSI.PrefixLen = 0 then
begin
result := (nsURI = namespaceURI);
Exit;
end
else if HasAttributes then
begin
Map := Attributes;
for I := 0 to Map.Length-1 do
begin
Attr := TDOMAttr(Map[I]);
if Attr.LocalName = 'xmlns' then
begin
result := (Attr.Value = nsURI);
Exit;
end;
end;
end;
end;
result := GetAncestorElement(Self).IsDefaultNamespace(nsURI);
end;
function TDOMNode.GetBaseURI: DOMString;
begin
case NodeType of
// !! Incomplete !!
DOCUMENT_NODE:
result := TDOMDocument(Self).FDocumentURI;
PROCESSING_INSTRUCTION_NODE:
if Assigned(ParentNode) then
result := ParentNode.GetBaseURI
else
result := OwnerDocument.DocumentURI;
else
result := '';
end;
end;
//------------------------------------------------------------------------------
type
TNodeTypeEnum = ELEMENT_NODE..NOTATION_NODE;
TNodeTypeSet = set of TNodeTypeEnum;
const
stdChildren = [TEXT_NODE, ENTITY_REFERENCE_NODE, PROCESSING_INSTRUCTION_NODE,
COMMENT_NODE, CDATA_SECTION_NODE, ELEMENT_NODE];
ValidChildren: array [TNodeTypeEnum] of TNodeTypeSet = (
stdChildren, { element }
[TEXT_NODE, ENTITY_REFERENCE_NODE], { attribute }
[], { text }
[], { cdata }
stdChildren, { ent ref }
stdChildren, { entity }
[], { pi }
[], { comment }
[ELEMENT_NODE, DOCUMENT_TYPE_NODE, PROCESSING_INSTRUCTION_NODE, COMMENT_NODE], { document }
[], { doctype }
stdChildren, { fragment }
[] { notation }
);
function TDOMNode_WithChildren.GetFirstChild: TDOMNode;
begin
Result := FFirstChild;
end;
function TDOMNode_WithChildren.GetLastChild: TDOMNode;
begin
Result := FLastChild;
end;
destructor TDOMNode_WithChildren.Destroy;
begin
FreeChildren;
FChildNodes.Free; // its destructor will zero the field
inherited Destroy;
end;
function TDOMNode_WithChildren.InsertBefore(NewChild, RefChild: TDOMNode):
TDOMNode;
var
Tmp: TDOMNode;
NewChildType: Integer;
begin
Result := NewChild;
NewChildType := NewChild.NodeType;
Changing;
if NewChild.FOwnerDocument <> FOwnerDocument then
begin
if (NewChildType <> DOCUMENT_TYPE_NODE) or
(NewChild.FOwnerDocument <> nil) then
raise EDOMWrongDocument.Create('NodeWC.InsertBefore');
end;
if Assigned(RefChild) and (RefChild.ParentNode <> Self) then
raise EDOMNotFound.Create('NodeWC.InsertBefore');
// TODO: skip checking Fragments as well? (Fragment itself cannot be in the tree)
if not (NewChildType in [TEXT_NODE, CDATA_SECTION_NODE, COMMENT_NODE, PROCESSING_INSTRUCTION_NODE]) and (NewChild.FirstChild <> nil) then
begin
Tmp := Self;
while Assigned(Tmp) do
begin
if Tmp = NewChild then
raise EDOMHierarchyRequest.Create('NodeWC.InsertBefore (cycle in tree)');
Tmp := Tmp.ParentNode;
end;
end;
if NewChild = RefChild then // inserting node before itself is a no-op
Exit;
Inc(FOwnerDocument.FRevision); // invalidate nodelists
if NewChildType = DOCUMENT_FRAGMENT_NODE then
begin
Tmp := NewChild.FirstChild;
if Assigned(Tmp) then
begin
while Assigned(Tmp) do
begin
if not (Tmp.NodeType in ValidChildren[NodeType]) then
raise EDOMHierarchyRequest.Create('NodeWC.InsertBefore');
Tmp := Tmp.NextSibling;
end;
while Assigned(TDOMDocumentFragment(NewChild).FFirstChild) do
InsertBefore(TDOMDocumentFragment(NewChild).FFirstChild, RefChild);
end;
Exit;
end;
if not (NewChildType in ValidChildren[NodeType]) then
raise EDOMHierarchyRequest.Create('NodeWC.InsertBefore');
if Assigned(NewChild.FParentNode) then
NewChild.FParentNode.DetachChild(NewChild);
NewChild.FNextSibling := RefChild;
if RefChild = nil then // append to the end
begin
if Assigned(FFirstChild) then
begin
FLastChild.FNextSibling := NewChild;
NewChild.FPreviousSibling := FLastChild;
end else
FFirstChild := NewChild;
FLastChild := NewChild;
end
else // insert before RefChild
begin
if RefChild = FFirstChild then
FFirstChild := NewChild
else
begin
RefChild.FPreviousSibling.FNextSibling := NewChild;
NewChild.FPreviousSibling := RefChild.FPreviousSibling;
end;
RefChild.FPreviousSibling := NewChild;
end;
NewChild.FParentNode := Self;
end;
function TDOMNode_WithChildren.ReplaceChild(NewChild, OldChild: TDOMNode):
TDOMNode;
begin
InsertBefore(NewChild, OldChild);
if Assigned(OldChild) then
RemoveChild(OldChild);
Result := OldChild;
end;
function TDOMNode_WithChildren.DetachChild(OldChild: TDOMNode): TDOMNode;
begin
Changing;
if OldChild.ParentNode <> Self then
raise EDOMNotFound.Create('NodeWC.RemoveChild');
Inc(FOwnerDocument.FRevision); // invalidate nodelists
if OldChild = FFirstChild then
FFirstChild := FFirstChild.FNextSibling
else
OldChild.FPreviousSibling.FNextSibling := OldChild.FNextSibling;
if OldChild = FLastChild then
FLastChild := FLastChild.FPreviousSibling
else
OldChild.FNextSibling.FPreviousSibling := OldChild.FPreviousSibling;
// Make sure removed child does not contain references to nowhere
OldChild.FPreviousSibling := nil;
OldChild.FNextSibling := nil;
OldChild.FParentNode := nil;
Result := OldChild;
end;
procedure TDOMNode_WithChildren.InternalAppend(NewChild: TDOMNode);
begin
if Assigned(FFirstChild) then
begin
FLastChild.FNextSibling := NewChild;
NewChild.FPreviousSibling := FLastChild;
end else
FFirstChild := NewChild;
FLastChild := NewChild;
NewChild.FParentNode := Self;
end;
function TDOMNode_WithChildren.HasChildNodes: Boolean;
begin
Result := Assigned(FFirstChild);
end;
function TDOMNode_WithChildren.GetChildCount: SizeInt;
var
Node: TDOMNode;
begin
if FFirstChild=nil then exit(0);
if FChildNodes<>nil then
Result:=FChildNodes.Count
else begin
Result:=0;
Node:=FFirstChild;
while Node<>nil do begin
inc(Result);
Node:=Node.NextSibling;
end;
end;
end;
function TDOMNode_WithChildren.FindNode(const ANodeName: DOMString): TDOMNode;
begin
Result := FFirstChild;
while Assigned(Result) do
begin
if Result.CompareName(ANodeName)=0 then
Exit;
Result := Result.NextSibling;
end;
end;
procedure TDOMNode_WithChildren.CloneChildren(ACopy: TDOMNode;
ACloneOwner: TDOMDocument);
var
node: TDOMNode;
begin
node := FirstChild;
while Assigned(node) do
begin
TDOMNode_WithChildren(ACopy).InternalAppend(node.CloneNode(True, ACloneOwner));
node := node.NextSibling;
end;
end;
procedure TDOMNode_WithChildren.FreeChildren;
var
child, next: TDOMNode;
begin
child := FFirstChild;
while Assigned(child) do
begin
next := child.NextSibling;
child.FParentNode := nil;
child.Destroy; // we know it's not nil, so save a call
child := next;
end;
FFirstChild := nil;
FLastChild := nil;
end;
function TDOMNode_WithChildren.GetTextContent: DOMString;
var
child: TDOMNode;
begin
Result := '';
child := FFirstChild;
// TODO: probably very slow, optimization needed
while Assigned(child) do
begin
case child.NodeType of
TEXT_NODE: if not (nfIgnorableWS in child.FFlags) then
Result := Result + TDOMText(child).Data;
COMMENT_NODE, PROCESSING_INSTRUCTION_NODE: ; // ignored
else
Result := Result + child.TextContent;
end;
child := child.NextSibling;
end;
end;
procedure TDOMNode_WithChildren.SetTextContent(const AValue: DOMString);
begin
Changing;
while Assigned(FFirstChild) do
DetachChild(FFirstChild);
if AValue <> '' then
AppendChild(FOwnerDocument.CreateTextNode(AValue));
end;
// -------------------------------------------------------
// NodeList
// -------------------------------------------------------
constructor TDOMNodeList.Create(ANode: TDOMNode);
begin
inherited Create;
FNode := ANode;
FRevision := ANode.GetRevision-1; // force BuildList at first access
FList := TFPList.Create;
end;
destructor TDOMNodeList.Destroy;
begin
if (FNode is TDOMNode_WithChildren) and
(TDOMNode_WithChildren(FNode).FChildNodes = Self) then
TDOMNode_WithChildren(FNode).FChildNodes := nil
else
FNode.FOwnerDocument.NodeListDestroyed(Self);
FList.Free;
inherited Destroy;
end;
function TDOMNodeList.NodeFilter(aNode: TDOMNode): TFilterResult;
begin
// accept all nodes but don't allow recursion
Result := frNorecurseTrue;
end;
procedure TDOMNodeList.BuildList;
var
current, next: TDOMNode;
res: TFilterResult;
begin
FList.Clear;
FRevision := FNode.GetRevision; // refresh
current := FNode.FirstChild;
while Assigned(current) do
begin
res := NodeFilter(current);
if res in [frTrue, frNorecurseTrue] then
FList.Add(current);
next := nil;
if res in [frTrue, frFalse] then
next := current.FirstChild;
if next = nil then
begin
while current <> FNode do
begin
next := current.NextSibling;
if Assigned(next) then
Break;
current := current.ParentNode;
end;
end;
current := next;
end;
end;
function TDOMNodeList.GetCount: LongWord;
begin
if FRevision <> FNode.GetRevision then
BuildList;
Result := FList.Count;
end;
function TDOMNodeList.GetItem(index: LongWord): TDOMNode;
begin
if FRevision <> FNode.GetRevision then
BuildList;
if index < LongWord(FList.Count) then
Result := TDOMNode(FList.List^[index])
else
Result := nil;
end;
{ TDOMElementList }
constructor TDOMElementList.Create(ANode: TDOMNode; const AFilter: DOMString);
begin
inherited Create(ANode);
filter := AFilter;
UseFilter := filter <> '*';
end;
constructor TDOMElementList.Create(ANode: TDOMNode; const nsURI, localName: DOMString);
begin
inherited Create(ANode);
localNameFilter := localName;
FMatchNS := True;
FMatchAnyNS := (nsURI = '*');
if not FMatchAnyNS then
FNSIndexFilter := ANode.FOwnerDocument.IndexOfNS(nsURI);
UseFilter := (localName <> '*');
end;
function TDOMElementList.NodeFilter(aNode: TDOMNode): TFilterResult;
var
I, L: Integer;
begin
Result := frFalse;
if aNode.NodeType = ELEMENT_NODE then with TDOMElement(aNode) do
begin
if FMatchNS then
begin
if (FMatchAnyNS or (FNSI.NSIndex = Word(FNSIndexFilter))) then
begin
I := FNSI.PrefixLen;
L := system.Length(FNSI.QName^.Key);
if (not UseFilter or ((L-I = system.Length(localNameFilter)) and
CompareMem(@FNSI.QName^.Key[I+1], DOMPChar(localNameFilter), system.Length(localNameFilter)*sizeof(DOMChar)))) then
Result := frTrue;
end;
end
else if (not UseFilter or (TagName = Filter)) then
Result := frTrue;
end;
end;
// -------------------------------------------------------
// NamedNodeMap
// -------------------------------------------------------
constructor TDOMNamedNodeMap.Create(AOwner: TDOMNode; ANodeType: Integer);
begin
inherited Create;
FOwner := AOwner;
FNodeType := ANodeType;
end;
destructor TDOMNamedNodeMap.Destroy;
var
I: Integer;
begin
FSortedList.Free;
if FPosList<>nil then begin
for I := FPosList.Count-1 downto 0 do
TDOMNode(FPosList[I]).Free;
FPosList.Free;
end;
inherited Destroy;
end;
function TDOMNamedNodeMap.GetSortedItem(index: LongWord): TDOMNode;
begin
Result := TDOMNode(FSortedList.List^[index]);
end;
function TDOMNamedNodeMap.GetPosItem(index: LongWord): TDOMNode;
begin
Result := TDOMNode(FPosList.List^[index]);
end;
function TDOMNamedNodeMap.GetLength: LongWord;
begin
if FPosList<>nil then
Result := FPosList.Count
else
Result := 0;
end;
function TDOMNamedNodeMap.FindSorted(const name: DOMString; out Index: LongWord): Boolean;
var
L, H, I, C: Integer;
begin
Result := False;
L := 0;
if FPosList<>nil then begin
H := FSortedList.Count - 1;
while L <= H do
begin
I := (L + H) shr 1;
C := TDOMNode(FSortedList.List^[I]).CompareName(name);
if C > 0 then L := I + 1 else
begin
H := I - 1;
if C = 0 then
begin
Result := True;
L := I;
end;
end;
end;
end;
Index := L;
end;
function TDOMNamedNodeMap.GetNamedItem(const name: DOMString): TDOMNode;
var
i: Cardinal;
begin
if FindSorted(name, i) then
Result := TDOMNode(FSortedList.List^[i])
else
Result := nil;
end;
// Note: this *may* raise NOT_SUPPORTED_ERR if the document is e.g. HTML.
// This isn't checked now.
function TDOMNamedNodeMap.GetNamedItemNS(const namespaceURI, localName: DOMString): TDOMNode;
begin
Result := nil;
end;
function TDOMNamedNodeMap.ValidateInsert(arg: TDOMNode): Integer;
var
AttrOwner: TDOMNode;
begin
Result := 0;
if nfReadOnly in FOwner.FFlags then
Result := NO_MODIFICATION_ALLOWED_ERR
else if arg.FOwnerDocument <> FOwner.FOwnerDocument then
Result := WRONG_DOCUMENT_ERR
else if arg.NodeType <> FNodeType then
Result := HIERARCHY_REQUEST_ERR
else if (FNodeType = ATTRIBUTE_NODE) then
begin
AttrOwner := TDOMAttr(arg).ownerElement;
if Assigned(AttrOwner) and (AttrOwner <> FOwner) then
Result := INUSE_ATTRIBUTE_ERR;
end;
end;
function TDOMNamedNodeMap.SetNamedItem(arg: TDOMNode): TDOMNode;
var
i: Cardinal;
Exists: Boolean;
res: Integer;
begin
res := ValidateInsert(arg);
if res <> 0 then
raise EDOMError.Create(res, 'NamedNodeMap.SetNamedItem');
if FNodeType = ATTRIBUTE_NODE then
begin
TDOMAttr(arg).FOwnerElement := TDOMElement(FOwner);
Exists := FindSorted(TDOMAttr(arg).Name, i); // optimization
end
else
Exists := FindSorted(arg.NodeName, i);
if Exists then
begin
Result := TDOMNode(FSortedList.List^[i]);
if (Result <> arg) then
begin
if (FNodeType = ATTRIBUTE_NODE) then
TDOMAttr(Result).FOwnerElement := nil;
FSortedList.List^[i] := arg;
i:=FPosList.IndexOf(Result);
FPosList.List^[i] := arg;
end;
exit;
end;
if FSortedList=nil then FSortedList:=TFPList.Create;
FSortedList.Insert(i, arg);
if FPosList=nil then FPosList:=TFPList.Create;
FPosList.Add(arg);
Result := nil;
end;
function TDOMNamedNodeMap.SetNamedItemNS(arg: TDOMNode): TDOMNode;
begin
{ Since the map contains only namespaceless nodes (all having empty
localName and namespaceURI properties), a namespaced arg won't match
any of them. Therefore, add it using nodeName as key.
Note: a namespaceless arg is another story, as it will match *any* node
in the map. This can be considered as a flaw in specs. }
Result := SetNamedItem(arg);
end;
function TDOMNamedNodeMap.DeleteSorted(index: LongWord): TDOMNode;
begin
Result := TDOMNode(FSortedList.List^[index]);
FSortedList.Delete(index);
FPosList.Remove(Result);
if FNodeType = ATTRIBUTE_NODE then
TDOMAttr(Result).FOwnerElement := nil;
end;
procedure TDOMNamedNodeMap.RestoreDefault(const name: DOMString);
var
eldef: TDOMElement;
attrdef: TDOMAttr;
begin
if FNodeType = ATTRIBUTE_NODE then
begin
if not Assigned(TDOMElement(FOwner).FNSI.QName) then // safeguard
Exit;
eldef := TDOMElement(TDOMElement(FOwner).FNSI.QName^.Data);
if Assigned(eldef) then
begin
// TODO: can be avoided by linking attributes directly to their defs
attrdef := eldef.GetAttributeNode(name);
if Assigned(attrdef) and (TDOMAttrDef(attrdef).FDefault in [adDefault, adFixed]) then
TDOMElement(FOwner).RestoreDefaultAttr(attrdef);
end;
end;
end;
function TDOMNamedNodeMap.InternalRemove(const name: DOMString): TDOMNode;
var
i: Cardinal;
begin
Result := nil;
if FindSorted(name, i) then
begin
Result := DeleteSorted(I);
RestoreDefault(name);
end;
end;
function TDOMNamedNodeMap.RemoveNamedItem(const name: DOMString): TDOMNode;
begin
if nfReadOnly in FOwner.FFlags then
raise EDOMError.Create(NO_MODIFICATION_ALLOWED_ERR, 'NamedNodeMap.RemoveNamedItem');
Result := InternalRemove(name);
if Result = nil then
raise EDOMNotFound.Create('NamedNodeMap.RemoveNamedItem');
end;
function TDOMNamedNodeMap.RemoveNamedItemNS(const namespaceURI, localName: DOMString): TDOMNode;
begin
// see comments to SetNamedItemNS. Related tests are written clever enough
// in the sense they don't expect NO_MODIFICATION_ERR in first place.
raise EDOMNotFound.Create('NamedNodeMap.RemoveNamedItemNS');
Result := nil;
end;
{ TAttributeMap }
// Since list is kept sorted by nodeName, we must use linear search here.
// This routine is not called while parsing, so parsing speed is not lowered.
function TAttributeMap.FindNS(nsIndex: Integer; const aLocalName: DOMString;
out SortedIndex: LongWord): Boolean;
var
I: Integer;
P: DOMPChar;
begin
if FSortedList<>nil then begin
for I := 0 to FSortedList.Count-1 do
begin
with TDOMAttr(FSortedList.List^[I]) do
begin
if nsIndex = FNSI.NSIndex then
begin
P := DOMPChar(FNSI.QName^.Key);
if FNSI.PrefixLen > 1 then
Inc(P, FNSI.PrefixLen);
if CompareDOMStrings(DOMPChar(aLocalName), P, System.Length(aLocalName), System.Length(FNSI.QName^.Key) - FNSI.PrefixLen) = 0 then
begin
SortedIndex := I;
Result := True;
Exit;
end;
end;
end;
end;
end;
SortedIndex := High(SortedIndex)-1;
Result := False;
end;
function TAttributeMap.InternalRemoveNS(const nsURI, aLocalName: DOMString): TDOMNode;
var
i: Cardinal;
nsIndex: Integer;
begin
Result := nil;
nsIndex := FOwner.FOwnerDocument.IndexOfNS(nsURI);
if (nsIndex >= 0) and FindNS(nsIndex, aLocalName, i) then
begin
Result := DeleteSorted(I);
RestoreDefault(TDOMAttr(Result).FNSI.QName^.Key);
end;
end;
function TAttributeMap.getNamedItemNS(const namespaceURI, localName: DOMString): TDOMNode;
var
nsIndex: Integer;
i: LongWord;
begin
nsIndex := FOwner.FOwnerDocument.IndexOfNS(namespaceURI);
if (nsIndex >= 0) and FindNS(nsIndex, localName, i) then
Result := TDOMNode(FSortedList.List^[i])
else
Result := nil;
end;
function TAttributeMap.setNamedItemNS(arg: TDOMNode): TDOMNode;
var
i: LongWord;
res: Integer;
Exists: Boolean;
begin
res := ValidateInsert(arg);
if res <> 0 then
raise EDOMError.Create(res, 'NamedNodeMap.SetNamedItemNS');
Result := nil;
with TDOMAttr(arg) do
begin
// calling LocalName is no good... but it is done once
if FindNS(FNSI.NSIndex, localName, i) then
begin
Result := TDOMNode(FSortedList.List^[i]);
FSortedList.Delete(i);
FPosList.Remove(Result);
end;
// Do a non-namespace search in order to keep the list sorted on nodeName
Exists := FindSorted(FNSI.QName^.Key, i);
if Exists and (Result = nil) then // case when arg has no namespace
begin
Result := TDOMNode(FSortedList.List^[i]);
FSortedList.List^[i] := arg;
i:=FPosList.IndexOf(Result);
FPosList.List^[i] := arg;
end
else begin
if FSortedList=nil then FSortedList:=TFPList.Create;
FSortedList.Insert(i, arg);
if FPosList=nil then FPosList:=TFPList.Create;
FPosList.Add(arg);
end;
end;
if Assigned(Result) then
TDOMAttr(Result).FOwnerElement := nil;
TDOMAttr(arg).FOwnerElement := TDOMElement(FOwner);
end;
function TAttributeMap.removeNamedItemNS(const namespaceURI,
localName: DOMString): TDOMNode;
begin
if nfReadOnly in FOwner.FFlags then
raise EDOMError.Create(NO_MODIFICATION_ALLOWED_ERR, 'NamedNodeMap.RemoveNamedItemNS');
Result := InternalRemoveNS(namespaceURI, localName);
if Result = nil then
raise EDOMNotFound.Create('NamedNodeMap.RemoveNamedItemNS');
end;
// -------------------------------------------------------
// CharacterData
// -------------------------------------------------------
function TDOMCharacterData.GetLength: LongWord;
begin
Result := system.Length(FNodeValue);
end;
function TDOMCharacterData.GetNodeValue: DOMString;
begin
Result := FNodeValue;
end;
procedure TDOMCharacterData.SetNodeValue(const AValue: DOMString);
begin
Changing;
FNodeValue := AValue;
end;
function TDOMCharacterData.SubstringData(offset, count: LongWord): DOMString;
begin
if offset > Length then
raise EDOMIndexSize.Create('CharacterData.SubstringData');
Result := Copy(FNodeValue, offset + 1, count);
end;
procedure TDOMCharacterData.AppendData(const arg: DOMString);
begin
Changing;
FNodeValue := FNodeValue + arg;
end;
procedure TDOMCharacterData.InsertData(offset: LongWord; const arg: DOMString);
begin
Changing;
if offset > Length then
raise EDOMIndexSize.Create('CharacterData.InsertData');
Insert(arg, FNodeValue, offset+1);
end;
procedure TDOMCharacterData.DeleteData(offset, count: LongWord);
begin
Changing;
if offset > Length then
raise EDOMIndexSize.Create('CharacterData.DeleteData');
Delete(FNodeValue, offset+1, count);
end;
procedure TDOMCharacterData.ReplaceData(offset, count: LongWord; const arg: DOMString);
begin
DeleteData(offset, count);
InsertData(offset, arg);
end;
// -------------------------------------------------------
// DocumentFragmet
// -------------------------------------------------------
function TDOMDocumentFragment.GetNodeType: Integer;
begin
Result := DOCUMENT_FRAGMENT_NODE;
end;
function TDOMDocumentFragment.GetNodeName: DOMString;
begin
Result := '#document-fragment';
end;
function TDOMDocumentFragment.CloneNode(deep: Boolean; aCloneOwner: TDOMDocument): TDOMNode;
begin
Result := aCloneOwner.CreateDocumentFragment;
if deep then
CloneChildren(Result, aCloneOwner);
end;
// -------------------------------------------------------
// DOMImplementation
// -------------------------------------------------------
{ if nsIdx = -1, checks only the name. Otherwise additionally checks if the prefix is
valid for standard namespace specified by nsIdx.
Non-negative return value is Pos(':', QName), negative is DOM error code. }
function CheckQName(const QName: DOMString; nsIdx: Integer; Xml11: Boolean): Integer;
var
I, L: Integer;
begin
if not IsXmlName(QName, Xml11) then
begin
Result := -INVALID_CHARACTER_ERR;
Exit;
end;
L := Length(QName);
Result := Pos(DOMChar(':'), QName);
if Result > 0 then
begin
for I := Result+1 to L-1 do // check for second colon (Use IndexWord?)
if QName[I] = ':' then
begin
Result := -NAMESPACE_ERR;
Exit;
end;
// Name validity has already been checked by IsXmlName() call above.
// So just check that colon isn't first or last char, and that it is follwed by NameStartChar.
if ((Result = 1) or (Result = L) or not IsXmlName(@QName[Result+1], 1, Xml11)) then
begin
Result := -NAMESPACE_ERR;
Exit;
end;
end;
if nsIdx < 0 then Exit;
// QName contains prefix, but no namespace
if ((nsIdx = 0) and (Result > 0)) or
// Bad usage of 'http://www.w3.org/2000/xmlns/'
((((L = 5) or (Result = 6)) and (Pos(DOMString('xmlns'), QName) = 1)) <> (nsIdx = 2)) or
// Bad usage of 'http://www.w3.org/XML/1998/namespace'
((Result = 4) and (Pos(DOMString('xml'), QName) = 1) and (nsIdx <> 1)) then
Result := -NAMESPACE_ERR;
end;
function TDOMImplementation.HasFeature(const feature, version: DOMString):
Boolean;
var
s: string;
begin
s := feature; // force Ansi, features do not contain non-ASCII chars
Result := (SameText(s, 'XML') and ((version = '') or (version = '1.0') or (version = '2.0'))) or
(SameText(s, 'Core') and ((version = '') or (version = '2.0')));
end;
function TDOMImplementation.CreateDocumentType(const QualifiedName, PublicID,
SystemID: DOMString): TDOMDocumentType;
var
res: Integer;
begin
res := CheckQName(QualifiedName, -1, False);
if res < 0 then
raise EDOMError.Create(-res, 'Implementation.CreateDocumentType');
Result := TDOMDocumentType.Create(nil);
Result.FName := QualifiedName;
// DOM does not restrict PublicID without SystemID (unlike XML spec)
Result.FPublicID := PublicID;
Result.FSystemID := SystemID;
end;
function TDOMImplementation.CreateDocument(const NamespaceURI,
QualifiedName: DOMString; doctype: TDOMDocumentType): TDOMDocument;
var
Root: TDOMNode;
begin
if Assigned(doctype) and Assigned(doctype.OwnerDocument) then
raise EDOMWrongDocument.Create('Implementation.CreateDocument');
Result := TXMLDocument.Create;
Result.FImplementation := Self;
try
if Assigned(doctype) then
begin
Doctype.FOwnerDocument := Result;
Result.AppendChild(doctype);
end;
Root := Result.CreateElementNS(NamespaceURI, QualifiedName);
Result.AppendChild(Root);
except
Result.Free;
raise;
end;
end;
// -------------------------------------------------------
// Document
// -------------------------------------------------------
constructor TDOMDocument.Create;
begin
inherited Create(nil);
FOwnerDocument := Self;
FMaxPoolSize := (TDOMAttr.InstanceSize + sizeof(Pointer)-1) and not (sizeof(Pointer)-1) + sizeof(Pointer);
FPools := AllocMem(FMaxPoolSize);
FNames := THashTable.Create(256, True);
SetLength(FNamespaces, 3);
// Namespace #0 should always be an empty string
FNamespaces[1] := stduri_xml;
FNamespaces[2] := stduri_xmlns;
FEmptyNode := TDOMElement.Create(Self);
FNodeLists := THashTable.Create(32, True);
end;
destructor TDOMDocument.Destroy;
var
i: Integer;
begin
Include(FFlags, nfDestroying);
FreeAndNil(FIDList); // set to nil before starting destroying children
FNodeLists.Free;
FEmptyNode.Free;
inherited Destroy;
for i := 0 to (FMaxPoolSize div sizeof(TNodePool))-1 do
FPools^[i].Free;
FreeMem(FPools);
FNames.Free; // free the nametable after inherited has destroyed the children
// (because children reference the nametable)
end;
function TDOMDocument.Alloc(AClass: TDOMNodeClass): TDOMNode;
var
pp: TNodePool;
size: Integer;
begin
size := (AClass.InstanceSize + sizeof(Pointer)-1) and not (sizeof(Pointer)-1);
if size > FMaxPoolSize then
begin
Result := TDOMNode(AClass.NewInstance);
Exit;
end;
pp := FPools^[size div sizeof(TNodePool)];
if pp = nil then
begin
pp := TNodePool.Create(size);
FPools^[size div sizeof(TNodePool)] := pp;
end;
Result := pp.AllocNode(AClass);
end;
function TDOMDocument.AddID(Attr: TDOMAttr): Boolean;
var
ID: DOMString;
Exists: Boolean;
p: PHashItem;
begin
if FIDList = nil then
FIDList := THashTable.Create(256, False);
ID := Attr.Value;
p := FIDList.FindOrAdd(DOMPChar(ID), Length(ID), Exists);
Result := not Exists;
if Result then
p^.Data := Attr.OwnerElement;
end;
// This shouldn't be called if document has no IDs,
// or when it is being destroyed
// TODO: This could be much faster if removing ID happens
// upon modification of corresponding attribute value.
procedure TDOMDocument.RemoveID(Elem: TDOMElement);
begin
FIDList.RemoveData(Elem);
end;
function TDOMDocument.GetNodeType: Integer;
begin
Result := DOCUMENT_NODE;
end;
function TDOMDocument.GetNodeName: DOMString;
begin
Result := '#document';
end;
function TDOMDocument.GetTextContent: DOMString;
begin
Result := '';
end;
procedure TDOMDocument.SetTextContent(const value: DOMString);
begin
// Document ignores setting TextContent
end;
function TDOMDocument.GetOwnerDocument: TDOMDocument;
begin
Result := nil;
end;
function TDOMDocument.InsertBefore(NewChild, RefChild: TDOMNode): TDOMNode;
var
nType: Integer;
begin
nType := NewChild.NodeType;
if ((nType = ELEMENT_NODE) and Assigned(DocumentElement)) or
((nType = DOCUMENT_TYPE_NODE) and Assigned(DocType)) then
raise EDOMHierarchyRequest.Create('Document.InsertBefore');
Result := inherited InsertBefore(NewChild, RefChild);
end;
function TDOMDocument.ReplaceChild(NewChild, OldChild: TDOMNode): TDOMNode;
var
nType: Integer;
begin
nType := NewChild.NodeType;
if ((nType = ELEMENT_NODE) and (OldChild = DocumentElement)) or // root can be replaced by another element
((nType = DOCUMENT_TYPE_NODE) and (OldChild = DocType)) then // and so can be DTD
begin
inherited InsertBefore(NewChild, OldChild);
Result := RemoveChild(OldChild);
end
else
Result := inherited ReplaceChild(NewChild, OldChild);
end;
function TDOMDocument.GetDocumentElement: TDOMElement;
var
node: TDOMNode;
begin
node := FFirstChild;
while Assigned(node) and (node.NodeType <> ELEMENT_NODE) do
node := node.NextSibling;
Result := TDOMElement(node);
end;
function TDOMDocument.GetDocType: TDOMDocumentType;
var
node: TDOMNode;
begin
node := FFirstChild;
while Assigned(node) and (node.NodeType <> DOCUMENT_TYPE_NODE) do
node := node.NextSibling;
Result := TDOMDocumentType(node);
end;
function TDOMDocument.CreateElement(const tagName: DOMString): TDOMElement;
begin
if not IsXmlName(tagName, FXML11) then
raise EDOMError.Create(INVALID_CHARACTER_ERR, 'DOMDocument.CreateElement');
TDOMNode(Result) := Alloc(TDOMElement);
Result.Create(Self);
Result.FNSI.QName := FNames.FindOrAdd(DOMPChar(tagName), Length(tagName));
Result.AttachDefaultAttrs;
end;
function TDOMDocument.CreateElementBuf(Buf: DOMPChar; Length: Integer): TDOMElement;
begin
TDOMNode(Result) := Alloc(TDOMElement);
Result.Create(Self);
Result.FNSI.QName := FNames.FindOrAdd(Buf, Length);
end;
function TDOMDocument.CreateDocumentFragment: TDOMDocumentFragment;
begin
TDOMNode(Result) := Alloc(TDOMDocumentFragment);
Result.Create(Self);
end;
function TDOMDocument.CreateTextNode(const data: DOMString): TDOMText;
begin
TDOMNode(Result) := Alloc(TDOMText);
Result.Create(Self);
Result.FNodeValue := data;
end;
function TDOMDocument.CreateTextNodeBuf(Buf: DOMPChar; Length: Integer; IgnWS: Boolean): TDOMText;
begin
TDOMNode(Result) := Alloc(TDOMText);
Result.Create(Self);
SetString(Result.FNodeValue, Buf, Length);
if IgnWS then
Include(Result.FFlags, nfIgnorableWS);
end;
function TDOMDocument.CreateComment(const data: DOMString): TDOMComment;
begin
TDOMNode(Result) := Alloc(TDOMComment);
Result.Create(Self);
Result.FNodeValue := data;
end;
function TDOMDocument.CreateCommentBuf(Buf: DOMPChar; Length: Integer): TDOMComment;
begin
TDOMNode(Result) := Alloc(TDOMComment);
Result.Create(Self);
SetString(Result.FNodeValue, Buf, Length);
end;
function TDOMDocument.CreateCDATASection(const data: DOMString):
TDOMCDATASection;
begin
raise EDOMNotSupported.Create('DOMDocument.CreateCDATASection');
Result:=nil;
end;
function TDOMDocument.CreateProcessingInstruction(const target,
data: DOMString): TDOMProcessingInstruction;
begin
raise EDOMNotSupported.Create('DOMDocument.CreateProcessingInstruction');
Result:=nil;
end;
function TDOMDocument.CreateAttribute(const name: DOMString): TDOMAttr;
begin
if not IsXmlName(name, FXML11) then
raise EDOMError.Create(INVALID_CHARACTER_ERR, 'DOMDocument.CreateAttribute');
TDOMNode(Result) := Alloc(TDOMAttr);
Result.Create(Self);
Result.FNSI.QName := FNames.FindOrAdd(DOMPChar(name), Length(name));
Include(Result.FFlags, nfSpecified);
end;
function TDOMDocument.CreateAttributeBuf(Buf: DOMPChar; Length: Integer): TDOMAttr;
begin
TDOMNode(Result) := Alloc(TDOMAttr);
Result.Create(Self);
Result.FNSI.QName := FNames.FindOrAdd(buf, Length);
Include(Result.FFlags, nfSpecified);
end;
function TDOMDocument.CreateAttributeDef(Buf: DOMPChar; Length: Integer): TDOMAttrDef;
begin
// not using custom allocation here
Result := TDOMAttrDef.Create(Self);
Result.FNSI.QName := FNames.FindOrAdd(Buf, Length);
end;
function TDOMDocument.CreateEntityReference(const name: DOMString):
TDOMEntityReference;
begin
raise EDOMNotSupported.Create('DOMDocument.CreateEntityReference');
Result:=nil;
end;
function TDOMDocument.GetChildNodeList(aNode: TDOMNode): TDOMNodeList;
begin
if not (aNode is TDOMNode_WithChildren) then
aNode := FEmptyNode;
Result := TDOMNode_WithChildren(aNode).FChildNodes;
if Result = nil then
begin
Result := TDOMNodeList.Create(aNode);
TDOMNode_WithChildren(aNode).FChildNodes := Result;
end;
end;
function TDOMDocument.GetElementList(aNode: TDOMNode; const nsURI, aLocalName: DOMString;
UseNS: Boolean): TDOMNodeList;
var
L: Integer;
Key, P: DOMPChar;
Item: PHashItem;
begin
L := (sizeof(Pointer) div sizeof(DOMChar)) + Length(aLocalName);
if UseNS then
Inc(L, Length(nsURI)+1);
GetMem(Key, L*sizeof(DOMChar));
try
// compose the key for hashing
P := Key;
PPointer(P)^ := aNode;
Inc(PPointer(P));
Move(DOMPChar(aLocalName)^, P^, Length(aLocalName)*sizeof(DOMChar));
if UseNS then
begin
Inc(P, Length(aLocalName));
P^ := #12; Inc(P); // separator -- diff ('foo','bar') from 'foobar'
Move(DOMPChar(nsURI)^, P^, Length(nsURI)*sizeof(DOMChar));
end;
// try finding in the hashtable
Item := FNodeLists.FindOrAdd(Key, L);
Result := TDOMNodeList(Item^.Data);
if Result = nil then
begin
if UseNS then
Result := TDOMElementList.Create(aNode, nsURI, aLocalName)
else
Result := TDOMElementList.Create(aNode, aLocalName);
Item^.Data := Result;
end;
finally
FreeMem(Key);
end;
end;
function TDOMDocument.GetElementsByTagName(const tagname: DOMString): TDOMNodeList;
begin
Result := GetElementList(Self, '', tagname, False);
end;
function TDOMDocument.GetElementsByTagNameNS(const nsURI, aLocalName: DOMString): TDOMNodeList;
begin
Result := GetElementList(Self, nsURI, aLocalName, True);
end;
{ This is linear hence slow. However:
- if user code frees each nodelist ASAP, there are only few items in the hashtable
- if user code does not free nodelists, this is not called at all.
}
procedure TDOMDocument.NodeListDestroyed(aList: TDOMNodeList);
begin
if not (nfDestroying in FFlags) then
FNodeLists.RemoveData(aList);
end;
function TDOMDocument.CreateAttributeNS(const nsURI,
QualifiedName: DOMString): TDOMAttr;
var
idx, PrefIdx: Integer;
begin
idx := IndexOfNS(nsURI, True);
PrefIdx := CheckQName(QualifiedName, idx, FXml11);
if PrefIdx < 0 then
raise EDOMError.Create(-PrefIdx, 'Document.CreateAttributeNS');
TDOMNode(Result) := Alloc(TDOMAttr);
Result.Create(Self);
Result.FNSI.QName := FNames.FindOrAdd(DOMPChar(QualifiedName), Length(QualifiedName));
Result.FNSI.NSIndex := Word(idx);
Result.FNSI.PrefixLen := Word(PrefIdx);
Include(Result.FFlags, nfLevel2);
Include(Result.FFlags, nfSpecified);
end;
function TDOMDocument.CreateElementNS(const nsURI,
QualifiedName: DOMString): TDOMElement;
var
idx, PrefIdx: Integer;
begin
idx := IndexOfNS(nsURI, True);
PrefIdx := CheckQName(QualifiedName, idx, FXml11);
if PrefIdx < 0 then
raise EDOMError.Create(-PrefIdx, 'Document.CreateElementNS');
TDOMNode(Result) := Alloc(TDOMElement);
Result.Create(Self);
Result.FNSI.QName := FNames.FindOrAdd(DOMPChar(QualifiedName), Length(QualifiedName));
Result.FNSI.NSIndex := Word(idx);
Result.FNSI.PrefixLen := Word(PrefIdx);
Include(Result.FFlags, nfLevel2);
Result.AttachDefaultAttrs;
end;
function TDOMDocument.GetElementById(const ElementID: DOMString): TDOMElement;
begin
Result := nil;
if Assigned(FIDList) then
Result := TDOMElement(FIDList.Get(DOMPChar(ElementID), Length(ElementID)));
end;
function TDOMDocument.ImportNode(ImportedNode: TDOMNode;
Deep: Boolean): TDOMNode;
begin
Result := ImportedNode.CloneNode(Deep, Self);
end;
function TDOMDocument.IndexOfNS(const nsURI: DOMString; AddIfAbsent: Boolean): Integer;
var
I: Integer;
begin
// TODO: elaborate implementation
for I := 0 to Length(FNamespaces)-1 do
if FNamespaces[I] = nsURI then
begin
Result := I;
Exit;
end;
if AddIfAbsent then
begin
Result := Length(FNamespaces);
SetLength(FNamespaces, Result+1);
FNamespaces[Result] := nsURI;
end
else
Result := -1;
end;
function TXMLDocument.CreateCDATASection(const data: DOMString):
TDOMCDATASection;
begin
TDOMNode(Result) := Alloc(TDOMCDATASection);
Result.Create(Self);
Result.FNodeValue := data;
end;
function TXMLDocument.CreateProcessingInstruction(const target,
data: DOMString): TDOMProcessingInstruction;
begin
if not IsXmlName(target, FXML11) then
raise EDOMError.Create(INVALID_CHARACTER_ERR, 'XMLDocument.CreateProcessingInstruction');
TDOMNode(Result) := Alloc(TDOMProcessingInstruction);
Result.Create(Self);
Result.FTarget := target;
Result.FNodeValue := data;
end;
function TXMLDocument.CreateEntityReference(const name: DOMString):
TDOMEntityReference;
var
dType: TDOMDocumentType;
ent: TDOMEntity;
begin
if not IsXmlName(name, FXML11) then
raise EDOMError.Create(INVALID_CHARACTER_ERR, 'XMLDocument.CreateEntityReference');
TDOMNode(Result) := Alloc(TDOMEntityReference);
Result.Create(Self);
Result.FName := name;
dType := DocType;
if Assigned(dType) then
begin
TDOMNode(ent) := dType.Entities.GetNamedItem(name);
if Assigned(ent) then
ent.CloneChildren(Result, Self);
end;
Result.SetReadOnly(True);
end;
procedure TXMLDocument.SetXMLVersion(const aValue: DOMString);
begin
FXMLVersion := aValue;
FXML11 := (aValue = '1.1');
end;
{ TDOMNode_NS }
function TDOMNode_NS.GetNodeName: DOMString;
begin
// Because FNSI.QName is not set by the TDOMNode itself, but is set by
// other classes/functions, it is necessary to check if FNSQ.QName is
// assigned.
if assigned(FNSI.QName) then
Result := FNSI.QName^.Key
else
Result := '';
end;
function TDOMNode_NS.GetLocalName: DOMString;
begin
if nfLevel2 in FFlags then
Result := Copy(FNSI.QName^.Key, FNSI.PrefixLen+1, MaxInt)
else
Result := '';
end;
function TDOMNode_NS.GetNamespaceURI: DOMString;
begin
Result := FOwnerDocument.FNamespaces[FNSI.NSIndex];
end;
function TDOMNode_NS.GetPrefix: DOMString;
begin
if FNSI.PrefixLen < 2 then
Result := ''
else
Result := Copy(FNSI.QName^.Key, 1, FNSI.PrefixLen-1);
end;
procedure TDOMNode_NS.SetPrefix(const Value: DOMString);
var
NewName: DOMString;
begin
Changing;
if not IsXmlName(Value, FOwnerDocument.FXml11) then
raise EDOMError.Create(INVALID_CHARACTER_ERR, 'Node.SetPrefix');
if (Pos(DOMChar(':'), Value) > 0) or not (nfLevel2 in FFlags) or
((Value = 'xml') and (FNSI.NSIndex <> 1)) or
((ClassType = TDOMAttr) and // BAD!
((Value = 'xmlns') and (FNSI.NSIndex <> 2)) or (FNSI.QName^.Key = 'xmlns')) then
raise EDOMNamespace.Create('Node.SetPrefix');
// TODO: rehash properly
NewName := Value + ':' + Copy(FNSI.QName^.Key, FNSI.PrefixLen+1, MaxInt);
FNSI.QName := FOwnerDocument.FNames.FindOrAdd(DOMPChar(NewName), Length(NewName));
FNSI.PrefixLen := Length(Value)+1;
end;
function TDOMNode_NS.CompareName(const AName: DOMString): Integer;
begin
Result := CompareDOMStrings(DOMPChar(AName), DOMPChar(NodeName), Length(AName), Length(NodeName));
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
// -------------------------------------------------------
function TDOMAttr.GetNodeType: Integer;
begin
Result := ATTRIBUTE_NODE;
end;
destructor TDOMAttr.Destroy;
begin
if Assigned(FOwnerElement) and not (nfDestroying in FOwnerElement.FFlags) then
// TODO: This may raise NOT_FOUND_ERR in case something's really wrong
FOwnerElement.RemoveAttributeNode(Self);
inherited Destroy;
end;
function TDOMAttr.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
begin
// Cloned attribute is always specified and carries its children
if nfLevel2 in FFlags then
Result := ACloneOwner.CreateAttributeNS(namespaceURI, NodeName)
else
Result := ACloneOwner.CreateAttribute(NodeName);
TDOMAttr(Result).FDataType := FDataType;
CloneChildren(Result, ACloneOwner);
end;
function TDOMAttr.GetNodeValue: DOMString;
begin
Result := GetTextContent;
if FDataType <> dtCdata then
NormalizeSpaces(Result);
end;
procedure TDOMAttr.SetNodeValue(const AValue: DOMString);
begin
SetTextContent(AValue);
Include(FFlags, nfSpecified);
end;
function TDOMAttr.GetSpecified: Boolean;
begin
Result := nfSpecified in FFlags;
end;
function TDOMAttr.GetIsID: Boolean;
begin
Result := FDataType = dtID;
end;
// -------------------------------------------------------
// Element
// -------------------------------------------------------
function TDOMElement.GetNodeType: Integer;
begin
Result := ELEMENT_NODE;
end;
destructor TDOMElement.Destroy;
begin
Include(FFlags, nfDestroying);
if Assigned(FOwnerDocument.FIDList) then
FOwnerDocument.RemoveID(Self);
FreeAndNil(FAttributes);
inherited Destroy;
end;
function TDOMElement.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
var
i: Integer;
Attr, AttrClone: TDOMAttr;
begin
if ACloneOwner <> FOwnerDocument then
begin
// Importing has to go the hard way...
if nfLevel2 in FFlags then
Result := ACloneOwner.CreateElementNS(NamespaceURI, NodeName)
else
Result := ACloneOwner.CreateElement(NodeName);
if Assigned(FAttributes) then
begin
for i := 0 to FAttributes.Length - 1 do
begin
Attr := TDOMAttr(FAttributes[i]);
// destroy defaulted attributes (if any), it is safe because caller had not seen them yet
if Attr.Specified then
TDOMElement(Result).SetAttributeNode(TDOMAttr(Attr.CloneNode(True, ACloneOwner))).Free;
end;
end;
end
else // Cloning may cheat a little bit.
begin
Result := FOwnerDocument.Alloc(TDOMElement);
TDOMElement(Result).Create(FOwnerDocument);
TDOMElement(Result).FNSI := FNSI;
if nfLevel2 in FFlags then
Include(Result.FFlags, nfLevel2);
if Assigned(FAttributes) then
begin
// clone all attributes, but preserve nfSpecified flag
for i := 0 to FAttributes.Length - 1 do
begin
Attr := TDOMAttr(FAttributes[i]);
AttrClone := TDOMAttr(Attr.CloneNode(True, ACloneOwner));
if not Attr.Specified then
Exclude(AttrClone.FFlags, nfSpecified);
TDOMElement(Result).SetAttributeNode(AttrClone);
end;
end;
end;
if deep then
CloneChildren(Result, ACloneOwner);
end;
function TDOMElement.IsEmpty: boolean;
begin
Result:=(FAttributes=nil) or (FAttributes.Length=0);
end;
procedure TDOMElement.AttachDefaultAttrs;
var
eldef: TDOMElement;
attrdef: TDOMAttrDef;
I: Integer;
begin
if not Assigned(FNSI.QName) then // safeguard
Exit;
eldef := TDOMElement(FNSI.QName^.Data);
if Assigned(eldef) and Assigned(eldef.FAttributes) then
begin
for I := 0 to eldef.FAttributes.Length-1 do
begin
attrdef := TDOMAttrDef(eldef.FAttributes[I]);
if attrdef.FDefault in [adDefault, adFixed] then
RestoreDefaultAttr(attrdef);
end;
end;
end;
function TDOMElement.InternalLookupPrefix(const nsURI: DOMString; Original: TDOMElement): DOMString;
var
I: Integer;
Attr: TDOMAttr;
begin
result := '';
if Self = nil then
Exit;
if (nfLevel2 in FFlags) and (namespaceURI = nsURI) and (FNSI.PrefixLen > 0) then
begin
Result := Prefix;
if Original.LookupNamespaceURI(result) = nsURI then
Exit;
end;
if Assigned(FAttributes) then
begin
for I := 0 to FAttributes.Length-1 do
begin
Attr := TDOMAttr(FAttributes[I]);
if (Attr.Prefix = 'xmlns') and (Attr.Value = nsURI) then
begin
result := Attr.LocalName;
if Original.LookupNamespaceURI(result) = nsURI then
Exit;
end;
end;
end;
result := GetAncestorElement(Self).InternalLookupPrefix(nsURI, Original);
end;
procedure TDOMElement.RestoreDefaultAttr(AttrDef: TDOMAttr);
var
Attr: TDOMAttr;
ColonPos: Integer;
AttrName, nsuri: DOMString;
begin
Attr := TDOMAttr(AttrDef.CloneNode(True));
AttrName := Attr.Name;
ColonPos := Pos(DOMChar(':'), AttrName);
if Pos(DOMString('xmlns'), AttrName) = 1 then
begin
if (Length(AttrName) = 5) or (ColonPos = 6) then
Attr.SetNSI(stduri_xmlns, ColonPos);
end
else if ColonPos > 0 then
begin
if (ColonPos = 4) and (Pos(DOMString('xml'), AttrName) = 1) then
Attr.SetNSI(stduri_xml, 4)
else
begin
nsuri := LookupNamespaceURI(Copy(AttrName, 1, ColonPos-1));
// TODO: what if prefix isn't defined?
Attr.SetNSI(nsuri, ColonPos);
end
end;
// TODO: this is cheat, should look at config['namespaces'] instead.
// revisit when it is implemented.
if nfLevel2 in FFlags then
Include(Attr.FFlags, nfLevel2);
// There should be no matching attribute at this point, so non-namespace method is ok
SetAttributeNode(Attr);
end;
procedure TDOMElement.Normalize;
var
I: Integer;
begin
if Assigned(FAttributes) then
for I := 0 to FAttributes.Length - 1 do
FAttributes[I].Normalize;
inherited Normalize;
end;
function TDOMElement.GetAttributes: TDOMNamedNodeMap;
begin
if FAttributes=nil then
FAttributes := TAttributeMap.Create(Self, ATTRIBUTE_NODE);
Result := FAttributes;
end;
function TDOMElement.GetAttribute(const name: DOMString): DOMString;
var
Attr: TDOMNode;
begin
SetLength(Result, 0);
if Assigned(FAttributes) then
begin
Attr := FAttributes.GetNamedItem(name);
if Assigned(Attr) then
Result := Attr.NodeValue;
end;
end;
function TDOMElement.GetAttributeNS(const nsURI, aLocalName: DOMString): DOMString;
var
Attr: TDOMNode;
begin
SetLength(Result, 0);
if Assigned(FAttributes) then
begin
Attr := FAttributes.GetNamedItemNS(nsURI, aLocalName);
if Assigned(Attr) then
Result := Attr.NodeValue;
end;
end;
procedure TDOMElement.SetAttribute(const name, value: DOMString);
var
I: Cardinal;
Attr: TDOMAttr;
begin
Changing;
if Attributes.FindSorted(name, I) then
Attr := Attributes.SortedItem[I] as TDOMAttr
else
begin
Attr := FOwnerDocument.CreateAttribute(name);
Attr.FOwnerElement := Self;
if FAttributes.FSortedList=nil then FAttributes.FSortedList:=TFPList.Create;
FAttributes.FSortedList.Insert(I, Attr);
if FAttributes.FPosList=nil then FAttributes.FPosList:=TFPList.Create;
FAttributes.FPosList.Add(Attr);
end;
Attr.NodeValue := value;
end;
procedure TDOMElement.RemoveAttribute(const name: DOMString);
begin
Changing;
// (note) NamedNodeMap.RemoveNamedItem can raise NOT_FOUND_ERR and we should not.
if Assigned(FAttributes) then
FAttributes.InternalRemove(name).Free;
end;
procedure TDOMElement.RemoveAttributeNS(const nsURI,
aLocalName: DOMString);
begin
Changing;
if Assigned(FAttributes) then
TAttributeMap(FAttributes).InternalRemoveNS(nsURI, aLocalName).Free;
end;
procedure TDOMElement.SetAttributeNS(const nsURI, qualifiedName,
value: DOMString);
var
I: Cardinal;
Attr: TDOMAttr;
idx, prefIdx: Integer;
begin
Changing;
idx := FOwnerDocument.IndexOfNS(nsURI, True);
prefIdx := CheckQName(qualifiedName, idx, FOwnerDocument.FXml11);
if prefIdx < 0 then
raise EDOMError.Create(-prefIdx, 'Element.SetAttributeNS');
if TAttributeMap(Attributes).FindNS(idx, Copy(qualifiedName, prefIdx+1, MaxInt), I) then
begin
Attr := TDOMAttr(FAttributes[I]);
// need to reinsert because the nodeName may change
FAttributes.FPosList.Remove(FAttributes.FSortedList.List^[i]);
FAttributes.FSortedList.Delete(I);
end
else
begin
TDOMNode(Attr) := FOwnerDocument.Alloc(TDOMAttr);
Attr.Create(FOwnerDocument);
Attr.FOwnerElement := Self;
Attr.FNSI.NSIndex := Word(idx);
Include(Attr.FFlags, nfLevel2);
end;
// keep list sorted by DOM Level 1 name
FAttributes.FindSorted(qualifiedName, I);
if FAttributes.FSortedList=nil then FAttributes.FSortedList:=TFPList.Create;
FAttributes.FSortedList.Insert(I, Attr);
if FAttributes.FPosList=nil then FAttributes.FPosList:=TFPList.Create;
FAttributes.FPosList.Add(Attr);
// TODO: rehash properly, same issue as with Node.SetPrefix()
Attr.FNSI.QName := FOwnerDocument.FNames.FindOrAdd(DOMPChar(qualifiedName), Length(qualifiedName));
Attr.FNSI.PrefixLen := Word(prefIdx);
attr.NodeValue := value;
end;
function TDOMElement.GetAttributeNode(const name: DOMString): TDOMAttr;
begin
if Assigned(FAttributes) then
Result := FAttributes.GetNamedItem(name) as TDOMAttr
else
Result := nil;
end;
function TDOMElement.GetAttributeNodeNS(const nsURI, aLocalName: DOMString): TDOMAttr;
begin
if Assigned(FAttributes) then
Result := FAttributes.GetNamedItemNS(nsURI, aLocalName) as TDOMAttr
else
Result := nil;
end;
function TDOMElement.SetAttributeNode(NewAttr: TDOMAttr): TDOMAttr;
begin
Result := Attributes.SetNamedItem(NewAttr) as TDOMAttr;
end;
function TDOMElement.SetAttributeNodeNS(NewAttr: TDOMAttr): TDOMAttr;
begin
Result := Attributes.SetNamedItemNS(NewAttr) as TDOMAttr;
end;
function TDOMElement.RemoveAttributeNode(OldAttr: TDOMAttr): TDOMAttr;
begin
Changing;
Result:=OldAttr;
if Assigned(FAttributes) and (FAttributes.FSortedList<>nil)
and (FAttributes.FSortedList.Remove(OldAttr) > -1)
then begin
FAttributes.FPosList.Remove(OldAttr);
if Assigned(OldAttr.FNSI.QName) then // safeguard
FAttributes.RestoreDefault(OldAttr.FNSI.QName^.Key);
Result.FOwnerElement := nil;
end
else
raise EDOMNotFound.Create('Element.RemoveAttributeNode');
end;
function TDOMElement.GetElementsByTagName(const name: DOMString): TDOMNodeList;
begin
Result := FOwnerDocument.GetElementList(Self, '', name, False);
end;
function TDOMElement.GetElementsByTagNameNS(const nsURI, aLocalName: DOMString): TDOMNodeList;
begin
Result := FOwnerDocument.GetElementList(Self, nsURI, aLocalName, True);
end;
function TDOMElement.hasAttribute(const name: DOMString): Boolean;
begin
Result := Assigned(FAttributes) and
Assigned(FAttributes.GetNamedItem(name));
end;
function TDOMElement.hasAttributeNS(const nsURI, aLocalName: DOMString): Boolean;
begin
Result := Assigned(FAttributes) and
Assigned(FAttributes.getNamedItemNS(nsURI, aLocalName));
end;
function TDOMElement.HasAttributes: Boolean;
begin
Result := Assigned(FAttributes) and (FAttributes.Length > 0);
end;
// -------------------------------------------------------
// Text
// -------------------------------------------------------
function TDOMText.GetNodeType: Integer;
begin
Result := TEXT_NODE;
end;
function TDOMText.GetNodeName: DOMString;
begin
Result := '#text';
end;
procedure TDOMText.SetNodeValue(const aValue: DOMString);
begin
inherited SetNodeValue(aValue);
// TODO: may analyze aValue, but this will slow things down...
Exclude(FFlags, nfIgnorableWS);
end;
function TDOMText.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
begin
Result := ACloneOwner.CreateTextNode(FNodeValue);
end;
function TDOMText.SplitText(offset: LongWord): TDOMText;
begin
Changing;
if offset > Length then
raise EDOMIndexSize.Create('Text.SplitText');
Result := TDOMText.Create(FOwnerDocument);
Result.FNodeValue := Copy(FNodeValue, offset + 1, Length);
Result.FFlags := FFlags * [nfIgnorableWS];
FNodeValue := Copy(FNodeValue, 1, offset);
if Assigned(FParentNode) then
FParentNode.InsertBefore(Result, FNextSibling);
end;
function TDOMText.IsElementContentWhitespace: Boolean;
begin
Result := nfIgnorableWS in FFlags;
end;
// -------------------------------------------------------
// Comment
// -------------------------------------------------------
function TDOMComment.GetNodeType: Integer;
begin
Result := COMMENT_NODE;
end;
function TDOMComment.GetNodeName: DOMString;
begin
Result := '#comment';
end;
function TDOMComment.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
begin
Result := ACloneOwner.CreateComment(FNodeValue);
end;
// -------------------------------------------------------
// CDATASection
// -------------------------------------------------------
function TDOMCDATASection.GetNodeType: Integer;
begin
Result := CDATA_SECTION_NODE;
end;
function TDOMCDATASection.GetNodeName: DOMString;
begin
Result := '#cdata-section';
end;
function TDOMCDATASection.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
begin
Result := ACloneOwner.CreateCDATASection(FNodeValue);
end;
// -------------------------------------------------------
// DocumentType
// -------------------------------------------------------
function TDOMDocumentType.GetNodeType: Integer;
begin
Result := DOCUMENT_TYPE_NODE;
end;
function TDOMDocumentType.GetNodeName: DOMString;
begin
Result := FName;
end;
destructor TDOMDocumentType.Destroy;
begin
FEntities.Free;
FNotations.Free;
inherited Destroy;
end;
function TDOMDocumentType.GetEntities: TDOMNamedNodeMap;
begin
if FEntities = nil then
FEntities := TDOMNamedNodeMap.Create(Self, ENTITY_NODE);
Result := FEntities;
end;
function TDOMDocumentType.GetNotations: TDOMNamedNodeMap;
begin
if FNotations = nil then
FNotations := TDOMNamedNodeMap.Create(Self, NOTATION_NODE);
Result := FNotations;
end;
// -------------------------------------------------------
// Notation
// -------------------------------------------------------
function TDOMNotation.GetNodeType: Integer;
begin
Result := NOTATION_NODE;
end;
function TDOMNotation.GetNodeName: DOMString;
begin
Result := FName;
end;
function TDOMNotation.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
begin
Result := ACloneOwner.Alloc(TDOMNotation);
TDOMNotation(Result).Create(ACloneOwner);
TDOMNotation(Result).FName := FName;
TDOMNotation(Result).FPublicID := PublicID;
TDOMNotation(Result).FSystemID := SystemID;
// notation cannot have children, ignore Deep
end;
// -------------------------------------------------------
// Entity
// -------------------------------------------------------
function TDOMEntity.GetNodeType: Integer;
begin
Result := ENTITY_NODE;
end;
function TDOMEntity.GetNodeName: DOMString;
begin
Result := FName;
end;
function TDOMEntity.CloneNode(deep: Boolean; aCloneOwner: TDOMDocument): TDOMNode;
begin
Result := aCloneOwner.Alloc(TDOMEntity);
TDOMEntity(Result).Create(aCloneOwner);
TDOMEntity(Result).FName := FName;
TDOMEntity(Result).FSystemID := FSystemID;
TDOMEntity(Result).FPublicID := FPublicID;
TDOMEntity(Result).FNotationName := FNotationName;
if deep then
CloneChildren(Result, aCloneOwner);
Result.SetReadOnly(True);
end;
// -------------------------------------------------------
// EntityReference
// -------------------------------------------------------
function TDOMEntityReference.GetNodeType: Integer;
begin
Result := ENTITY_REFERENCE_NODE;
end;
function TDOMEntityReference.GetNodeName: DOMString;
begin
Result := FName;
end;
function TDOMEntityReference.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
begin
Result := ACloneOwner.CreateEntityReference(FName);
end;
// -------------------------------------------------------
// ProcessingInstruction
// -------------------------------------------------------
function TDOMProcessingInstruction.CloneNode(deep: Boolean;
ACloneOwner: TDOMDocument): TDOMNode;
begin
Result := ACloneOwner.CreateProcessingInstruction(Target, Data);
end;
function TDOMProcessingInstruction.GetNodeType: Integer;
begin
Result := PROCESSING_INSTRUCTION_NODE;
end;
function TDOMProcessingInstruction.GetNodeName: DOMString;
begin
Result := FTarget;
end;
function TDOMProcessingInstruction.GetNodeValue: DOMString;
begin
Result := FNodeValue;
end;
procedure TDOMProcessingInstruction.SetNodeValue(const AValue: DOMString);
begin
Changing;
FNodeValue := AValue;
end;
{ TDOMAttrDef }
function TDOMAttrDef.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
begin
Result := inherited CloneNode(deep, ACloneOwner);
Exclude(Result.FFlags, nfSpecified);
end;
function TDOMAttrDef.AddEnumToken(Buf: DOMPChar; 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 CompareDomStrings(Buf, DOMPChar(FEnumeration[I]), Len, Length(FEnumeration[I])) = 0 then
Exit;
end;
SetLength(FEnumeration, L+1);
SetString(FEnumeration[L], Buf, Len);
Result := True;
end;
function TDOMAttrDef.HasEnumToken(const aValue: DOMString): 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;
{ TNodePool }
constructor TNodePool.Create(AElementSize: Integer; AElementCount: Integer);
begin
FElementSize := AElementSize;
AddExtent(AElementCount);
end;
destructor TNodePool.Destroy;
var
ext, next: PExtent;
ptr, ptr_end: PAnsiChar;
sz: Integer;
begin
ext := FCurrExtent;
ptr := PAnsiChar(FCurrBlock) + FElementSize;
sz := FCurrExtentSize;
while Assigned(ext) do
begin
// call destructors for everyone still there
ptr_end := PAnsiChar(ext) + sizeof(TExtent) + (sz - 1) * FElementSize;
while ptr <= ptr_end do
begin
if TDOMNode(ptr).FPool = Self then
TObject(ptr).Destroy;
Inc(ptr, FElementSize);
end;
// dispose the extent and pass to the next one
next := ext^.Next;
FreeMem(ext);
ext := next;
sz := sz div 2;
ptr := PAnsiChar(ext) + sizeof(TExtent);
end;
inherited Destroy;
end;
procedure TNodePool.AddExtent(AElemCount: Integer);
var
ext: PExtent;
begin
Assert((FCurrExtent = nil) or
(PAnsiChar(FCurrBlock) < PAnsiChar(FCurrExtent) + sizeof(TExtent)));
Assert(AElemCount > 0);
GetMem(ext, sizeof(TExtent) + AElemCount * FElementSize);
ext^.Next := FCurrExtent;
// point to the beginning of the last block of extent
FCurrBlock := TDOMNode(PAnsiChar(ext) + sizeof(TExtent) + (AElemCount - 1) * FElementSize);
FCurrExtent := ext;
FCurrExtentSize := AElemCount;
end;
function TNodePool.AllocNode(AClass: TDOMNodeClass): TDOMNode;
begin
if Assigned(FFirstFree) then
begin
Result := FFirstFree; // remove from free list
FFirstFree := TDOMNode(Result.FPool);
end
else
begin
if PAnsiChar(FCurrBlock) < PAnsiChar(FCurrExtent) + sizeof(TExtent) then
AddExtent(FCurrExtentSize * 2);
Result := FCurrBlock;
Dec(PAnsiChar(FCurrBlock), FElementSize);
end;
AClass.InitInstance(Result);
Result.FPool := Self; // mark as used
end;
procedure TNodePool.FreeNode(ANode: TDOMNode);
begin
ANode.FPool := FFirstFree;
FFirstFree := ANode;
end;
end.