Skip to content

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:

var
  Root: ISVGRoot;
begin
  Root := SVG2Image1.SVGRoot;

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:

Elements := Root.FindElementsByTag('rect');

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:

Elements := Root.FindElementsByClass('valve');

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:

Elements := Root.FindElementsByAttribute('data-tag', 'PT-102');

Pass False as the third parameter to find every element that has the attribute, whatever its value. This collects a whole set at once:

Elements := Root.FindElementsByAttribute('data-tag', '', False);

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.