Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Node support for io.writer #122

Merged
merged 2 commits into from
Jan 17, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 83 additions & 40 deletions node.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ type indentation struct {
level int
hasChild bool
indent string
w io.Writer
w io.Writer
}

func newIndentation(indent string, w io.Writer) *indentation {
Expand All @@ -168,101 +168,139 @@ func newIndentation(indent string, w io.Writer) *indentation {
}
}

func (i *indentation) NewLine() {
func (i *indentation) NewLine() (err error) {
if i == nil {
return
}
io.WriteString(i.w, "\n")
_, err = io.WriteString(i.w, "\n")
return
}

func (i *indentation) Open() {
func (i *indentation) Open() (err error) {
if i == nil {
return
}

io.WriteString(i.w, "\n")
io.WriteString(i.w, strings.Repeat(i.indent, i.level))
if err = i.writeIndent(); err != nil {
return
}

i.level++
i.hasChild = false
return
}

func (i *indentation) Close() {
func (i *indentation) Close() (err error) {
if i == nil {
return
}
i.level--
if i.hasChild {
io.WriteString(i.w, "\n")
io.WriteString(i.w, strings.Repeat(i.indent, i.level))
if err = i.writeIndent(); err != nil {
return
}
}
i.hasChild = true
return
}

func (i *indentation) writeIndent() (err error) {
_, err = io.WriteString(i.w, "\n")
if err != nil {
return
}
_, err = io.WriteString(i.w, strings.Repeat(i.indent, i.level))
return
}

func outputXML(w io.Writer, n *Node, preserveSpaces bool, config *outputConfiguration, indent *indentation) {
func outputXML(w io.Writer, n *Node, preserveSpaces bool, config *outputConfiguration, indent *indentation) (err error) {
preserveSpaces = calculatePreserveSpaces(n, preserveSpaces)
switch n.Type {
case TextNode:
io.WriteString(w, html.EscapeString(n.sanitizedData(preserveSpaces)))
_, err = io.WriteString(w, html.EscapeString(n.sanitizedData(preserveSpaces)))
return
case CharDataNode:
io.WriteString(w, "<![CDATA[")
io.WriteString(w, n.Data)
io.WriteString(w, "]]>")
_, err = fmt.Fprintf(w, "<![CDATA[%v]]>", n.Data)
return
case CommentNode:
if !config.skipComments {
io.WriteString(w, "<!--")
io.WriteString(w, n.Data)
io.WriteString(w, "-->")
_, err = fmt.Fprintf(w, "<!--%v-->", n.Data)
}
return
case NotationNode:
indent.NewLine()
fmt.Fprintf(w, "<!%s>", n.Data)
if err = indent.NewLine(); err != nil {
return
}
_, err = fmt.Fprintf(w, "<!%s>", n.Data)
return
case DeclarationNode:
io.WriteString(w, "<?" + n.Data)
_, err = io.WriteString(w, "<?"+n.Data)
if err != nil {
return
}
default:
indent.Open()
if err = indent.Open(); err != nil {
return
}
if n.Prefix == "" {
io.WriteString(w, "<" + n.Data)
_, err = io.WriteString(w, "<"+n.Data)
} else {
fmt.Fprintf(w, "<%s:%s", n.Prefix, n.Data)
_, err = fmt.Fprintf(w, "<%s:%s", n.Prefix, n.Data)
}
if err != nil {
return
}
}

for _, attr := range n.Attr {
if attr.Name.Space != "" {
fmt.Fprintf(w, ` %s:%s=`, attr.Name.Space, attr.Name.Local)
_, err = fmt.Fprintf(w, ` %s:%s=`, attr.Name.Space, attr.Name.Local)
} else {
fmt.Fprintf(w, ` %s=`, attr.Name.Local)
_, err = fmt.Fprintf(w, ` %s=`, attr.Name.Local)
}
if err != nil {
return
}

fmt.Fprintf(w, `"%v"`, html.EscapeString(attr.Value))
_, err = fmt.Fprintf(w, `"%v"`, html.EscapeString(attr.Value))
if err != nil {
return
}
}
if n.Type == DeclarationNode {
io.WriteString(w, "?>")
_, err = io.WriteString(w, "?>")
} else {
if n.FirstChild != nil || !config.emptyElementTagSupport {
io.WriteString(w, ">")
_, err = io.WriteString(w, ">")
} else {
io.WriteString(w, "/>")
indent.Close()
_, err = io.WriteString(w, "/>")
if err != nil {
return
}
err = indent.Close()
return
}
}
if err != nil {
return
}
for child := n.FirstChild; child != nil; child = child.NextSibling {
outputXML(w, child, preserveSpaces, config, indent)
err = outputXML(w, child, preserveSpaces, config, indent)
if err != nil {
return
}
}
if n.Type != DeclarationNode {
indent.Close()
if err = indent.Close(); err != nil {
return
}
if n.Prefix == "" {
fmt.Fprintf(w, "</%s>", n.Data)
_, err = fmt.Fprintf(w, "</%s>", n.Data)
} else {
fmt.Fprintf(w, "</%s:%s>", n.Prefix, n.Data)
_, err = fmt.Fprintf(w, "</%s:%s>", n.Prefix, n.Data)
}
}
return
}

// OutputXML returns the text that including tags name.
Expand All @@ -281,15 +319,15 @@ func (n *Node) OutputXMLWithOptions(opts ...OutputOption) string {
}

// Write writes xml to given writer.
func (n *Node) Write(writer io.Writer, self bool) {
func (n *Node) Write(writer io.Writer, self bool) error {
if self {
n.WriteWithOptions(writer, WithOutputSelf())
return n.WriteWithOptions(writer, WithOutputSelf())
}
n.WriteWithOptions(writer)
return n.WriteWithOptions(writer)
}

// WriteWithOptions writes xml with given options to given writer.
func (n *Node) WriteWithOptions(writer io.Writer, opts ...OutputOption) {
func (n *Node) WriteWithOptions(writer io.Writer, opts ...OutputOption) (err error) {
config := &outputConfiguration{}
// Set the options
for _, opt := range opts {
Expand All @@ -300,13 +338,18 @@ func (n *Node) WriteWithOptions(writer io.Writer, opts ...OutputOption) {
b := bufio.NewWriter(writer)
defer b.Flush()

ident := newIndentation(config.useIndentation, b)
if config.printSelf && n.Type != DocumentNode {
outputXML(b, n, preserveSpaces, config, newIndentation(config.useIndentation, b))
err = outputXML(b, n, preserveSpaces, config, ident)
} else {
for n := n.FirstChild; n != nil; n = n.NextSibling {
outputXML(b, n, preserveSpaces, config, newIndentation(config.useIndentation, b))
err = outputXML(b, n, preserveSpaces, config, ident)
if err != nil {
break
}
}
}
return
}

// AddAttr adds a new attribute specified by 'key' and 'val' to a node 'n'.
Expand Down
Loading