Source code for svgen.element

"""
svgen - Common interfaces and assets for SVG elements.
"""

# built-in
from io import StringIO
import os
from typing import Dict, List, TextIO, TypeVar, cast
from xml.etree import ElementTree as et

# internal
from svgen.attribute import (
    Attribute,
    AttributeValue,
    SimpleAttribute,
    attributes,
)
from svgen.attribute.style import Style

INDENT: int = 2
T = TypeVar("T", bound="Element")


[docs] class Element: """A basic interface for an SVG element.""" def __init__( self, tag: str = None, text: str = "", attrib: List[Attribute] = None, children: List["Element"] = None, allow_no_end_tag: bool = True, class_str: str = None, preformatted: bool = False, **extra, ) -> None: """Construct a new SVG element.""" if tag is None: tag = type(self).__name__ tag = tag[0].lower() + tag[1:] if attrib is None: attrib = [] if children is None: children = [] self.tag = tag self.text = text self.allow_no_end_tag = allow_no_end_tag self.attributes: Dict[str, Attribute] = {} self.booleans: set[str] = set() self.preformatted = preformatted for attr in attrib + attributes(extra): self.add_attribute(attr) # Set a 'class' attribute. if class_str: self["class"] = class_str self.children: List[Element] = children
[docs] def add_class(self: T, *data: str) -> T: """Add a class string.""" raw = self["class"] classes = set(raw.split()) for item in data: classes.add(item) self["class"] = " ".join(classes) return self
def __setitem__(self, tag: str, value: AttributeValue) -> None: """Allow adding attributes dict-set style.""" self.add_attribute(SimpleAttribute(tag, value), strict=False) def __getitem__(self, tag: str) -> str: """Get an attibute as a string.""" if tag not in self.attributes: self[tag] = "" return self.attributes[tag].value @property def xml(self) -> et.Element: """Get this element as an xml element.""" elem = et.Element( self.tag, {x.key: x.value for x in self.attributes.values()} ) elem.text = self.text # Add children. elem.extend(child.xml for child in self.children) return elem @property def style(self) -> Style: """Get the style attribute for this element.""" if "style" not in self.attributes: self.add_attribute(Style()) return cast(Style, self.attributes["style"])
[docs] def add_attribute(self: T, attr: Attribute, strict: bool = True) -> T: """Add an attribute to this element.""" assert not strict or ( attr.key not in self.attributes ), f"This '{self.tag}' element already has attribute '{attr.key}'!" self.attributes[attr.key] = attr return self
[docs] def closing(self, indent: int = 0) -> str: """Create a string to close this element.""" close_tag = f"</{self.tag}>" if not self.text and not self.children: return " />" if self.allow_no_end_tag else ">" + close_tag return ( close_tag if self.preformatted else (" " * (indent * INDENT)) + close_tag )
def _write_text( self, output: TextIO, indent_str: str, newlines: bool = True ) -> None: """Write the inner-text section of this element.""" if not newlines or self.preformatted: output.write(self.text) return # Increase indent one level. indent_str += " " * INDENT for line in self.text.splitlines(): output.write(indent_str + line) output.write(os.linesep)
[docs] def encode( self, output: TextIO, quote: str = '"', indent: int = 0, newlines: bool = True, ) -> None: """Encode this element to a string stream.""" # Indent will matter if we want lines. indent_str = " " * (indent * INDENT) if newlines and not self.preformatted: output.write(indent_str) attr_strs = [x.encode(quote) for x in self.attributes.values()] + list( self.booleans ) attrs = " ".join(x for x in attr_strs if x) output.write(f"<{self.tag}") if attrs: output.write(f" {attrs}") if self.text or self.children: # Close the opening tag. output.write(">") if newlines: output.write(os.linesep) # Write content, if any. if self.text: self._write_text(output, indent_str, newlines=newlines) # Write children. for child in self.children: if child: child.encode(output, quote, indent + 1, newlines) # Close this element tag. output.write(self.closing(indent)) if newlines: output.write(os.linesep)
[docs] def encode_str( self, quote: str = '"', indent: int = 0, newlines: bool = True, ) -> str: """Encode this element to a string.""" with StringIO() as output: self.encode(output, quote, indent, newlines) return output.getvalue()