| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253 |
- package svgparser
- import (
- "bufio"
- "encoding/xml"
- "errors"
- "fmt"
- "io"
- "golang.org/x/net/html/charset"
- )
- type Attr = xml.Attr
- type Node struct {
- Parent *Node
- Name xml.Name
- Attrs []Attr
- Children []any
- }
- func (n *Node) readFrom(r io.ReadSeeker) error {
- if n.Parent != nil {
- return errors.New("cannot read child node")
- }
- dec := xml.NewDecoder(r)
- dec.Strict = false
- dec.CharsetReader = charset.NewReaderLabel
- curNode := n
- for {
- // Save the current position to know where to read raw CData from.
- pos := dec.InputOffset()
- // Read raw token so decoder doesn't mess with attributes and namespaces.
- tok, err := dec.RawToken()
- if err == io.EOF {
- break
- }
- if err != nil {
- return err
- }
- switch t := tok.(type) {
- case xml.StartElement:
- // An element is opened, create a node for it
- el := &Node{
- Parent: curNode,
- Name: t.Name,
- Attrs: t.Attr,
- }
- // Append the node to the current node's children and make it current
- curNode.Children = append(curNode.Children, el)
- curNode = el
- case xml.EndElement:
- // If the current node has no parent, then we are at the root,
- // which can't be closed.
- if curNode.Parent == nil {
- return fmt.Errorf(
- "malformed XML: unexpected closing tag </%s> while no elements are opened",
- fullName(t.Name),
- )
- }
- // Closing tag name should match opened node name (which is current)
- if curNode.Name.Local != t.Name.Local || curNode.Name.Space != t.Name.Space {
- return fmt.Errorf(
- "malformed XML: unexpected closing tag </%s> for opened <%s> element",
- fullName(t.Name),
- fullName(curNode.Name),
- )
- }
- // The node is closed, return to its parent
- curNode = curNode.Parent
- case xml.CharData:
- // We want CData as is, so read it raw
- cdata, err := readRawCData(r, pos, dec.InputOffset()-pos)
- if err != nil {
- return err
- }
- curNode.Children = append(curNode.Children, cdata)
- case xml.Directive:
- curNode.Children = append(curNode.Children, t.Copy())
- case xml.Comment:
- curNode.Children = append(curNode.Children, t.Copy())
- case xml.ProcInst:
- curNode.Children = append(curNode.Children, t.Copy())
- }
- }
- return nil
- }
- func (n *Node) writeTo(w *bufio.Writer) error {
- if err := w.WriteByte('<'); err != nil {
- return err
- }
- if err := writeFullName(w, n.Name); err != nil {
- return err
- }
- n.writeAttrsTo(w)
- if len(n.Children) == 0 {
- if _, err := w.WriteString("/>"); err != nil {
- return err
- }
- return nil
- }
- if err := w.WriteByte('>'); err != nil {
- return err
- }
- if err := n.writeChildrenTo(w); err != nil {
- return err
- }
- if _, err := w.WriteString("</"); err != nil {
- return err
- }
- if err := writeFullName(w, n.Name); err != nil {
- return err
- }
- if err := w.WriteByte('>'); err != nil {
- return err
- }
- return nil
- }
- func (n *Node) writeAttrsTo(w *bufio.Writer) error {
- for _, attr := range n.Attrs {
- if err := w.WriteByte(' '); err != nil {
- return err
- }
- if err := writeFullName(w, attr.Name); err != nil {
- return err
- }
- if _, err := w.WriteString(`="`); err != nil {
- return err
- }
- if len(attr.Value) > 2 && attr.Value[0] == '&' && attr.Value[len(attr.Value)-1] == ';' {
- // Attribute value is an entity, write it as is
- if _, err := w.WriteString(attr.Value); err != nil {
- return err
- }
- } else {
- // Escape the attribute value
- if err := escapeString(w, attr.Value); err != nil {
- return err
- }
- }
- if err := w.WriteByte('"'); err != nil {
- return err
- }
- }
- return nil
- }
- func (n *Node) writeChildrenTo(w *bufio.Writer) error {
- for _, child := range n.Children {
- switch c := child.(type) {
- case *Node:
- if err := c.writeTo(w); err != nil {
- return err
- }
- case CData:
- if _, err := w.Write([]byte(c)); err != nil {
- return err
- }
- case Comment:
- if _, err := w.WriteString("<!--"); err != nil {
- return err
- }
- if _, err := w.Write([]byte(c)); err != nil {
- return err
- }
- if _, err := w.WriteString("-->"); err != nil {
- return err
- }
- case Directive:
- if _, err := w.WriteString("<!"); err != nil {
- return err
- }
- if _, err := w.Write([]byte(c)); err != nil {
- return err
- }
- if err := w.WriteByte('>'); err != nil {
- return err
- }
- case ProcInst:
- if _, err := w.WriteString("<?"); err != nil {
- return err
- }
- if _, err := w.WriteString(c.Target); err != nil {
- return err
- }
- if len(c.Inst) > 0 {
- if err := w.WriteByte(' '); err != nil {
- return err
- }
- if _, err := w.Write([]byte(c.Inst)); err != nil {
- return err
- }
- }
- if _, err := w.WriteString("?>"); err != nil {
- return err
- }
- default:
- return fmt.Errorf("unknown child type: %T", c)
- }
- }
- return nil
- }
- func fullName(name xml.Name) string {
- if len(name.Space) == 0 {
- return name.Local
- }
- return name.Space + ":" + name.Local
- }
- func writeFullName(w *bufio.Writer, name xml.Name) error {
- if len(name.Space) > 0 {
- if _, err := w.WriteString(name.Space); err != nil {
- return err
- }
- if err := w.WriteByte(':'); err != nil {
- return err
- }
- }
- if _, err := w.WriteString(name.Local); err != nil {
- return err
- }
- return nil
- }
|