Finding elements¶
Before an application can change a colour, move a needle or hide a layer, it
has to find the element. ISVGRoot finds elements by
id, by tag, by class and by attribute, and can walk the whole document.
All of these work on the document currently loaded in the component:
By id¶
FindElement returns the element with an id, or nil. The Element property
does the same and reads a little shorter:
var
Element: ISVGElement;
begin
Element := Root.FindElement('needle'); // or: Root.Element['needle']
if Assigned(Element) then
Element.Attributes['transform'] := 'rotate(45 50 50)';
This is the cheapest lookup. Ids are kept in an index, so the time it takes does not depend on the size of the drawing.
Repeated ids¶
Ids should be unique, but XML does not enforce it, and drawings exported by
other tools do repeat them. Loading such a drawing reports each repeated id
through ISVGRoot.OnLogMsg, and through the OnLog event of the components:
Duplicate element id "shape", lookup by id is ambiguous.
Use FindElements to get every element with it.
Which of the elements FindElement returns is then not defined.
FindElements returns all of them, in document order:
var
Elements: TSVGElementArray;
i: Integer;
begin
Elements := Root.FindElements('shape');
for i := 0 to Length(Elements) - 1 do
Elements[i].Attributes['opacity'] := '0.5';
An id used only once costs the same through FindElements as through
FindElement: it is still answered from the index.
By tag, class or attribute¶
Giving every interesting element its own id is awkward when there are many of
them, and hard to keep up when the drawing is redrawn. Three queries find
elements by what they are rather than by what they are called. Each returns a
TSVGElementArray in document order.
FindElementsByTag matches the element name. SVG names are lower case and are
matched exactly:
FindElementsByClass matches the class attribute. That attribute holds a
list separated by spaces, and an element matches when any entry is the name you
asked for, so class="valve open" is found both by valve and by open:
FindElementsByAttribute matches an attribute and its value. Custom
attributes work as well as the ones SVG defines, so a drawing can carry its own
markings:
Pass False as the third parameter to find every element that has the
attribute, whatever its value. This collects a whole set at once:
Values are compared as strings, exactly as written in the drawing. There is no
number parsing, so 1 does not match 1.0.
Ask your designers for classes
A class is usually the most practical way to prepare a drawing for an application. Drawing tools can set one, it survives the drawing being redrawn, and unlike an id it is meant to be shared by a group of elements.
Walking the whole document¶
To visit every element, for example to build a list of all tagged
instruments, use TraverseDepthFirst. It calls your method for each element
below the outer svg element, a parent before its children:
procedure TForm1.VisitElement(aElement: ISVGElement);
begin
if aElement.HasAttribute('data-tag') then
ListBox1.Items.Add(aElement.Attributes['data-tag']);
end;
procedure TForm1.SVG2Image1AfterParse(Sender: TObject);
begin
ListBox1.Clear;
SVG2Image1.SVGRoot.TraverseDepthFirst(VisitElement);
end;
Cost¶
Lookup by id uses an index. The other queries and TraverseDepthFirst walk
the document, so run them once after the drawing is loaded and keep the result,
rather than calling them for every update:
procedure TForm1.SVG2Image1AfterParse(Sender: TObject);
begin
// Collect once, and keep FValves for as long as this drawing is shown
FValves := SVG2Image1.SVGRoot.FindElementsByClass('valve');
end;
The elements in the array stay valid for as long as the document is loaded. After another document is loaded, collect them again.