//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 7 Nov 08 Brian Frank Creation
//
**
** Models an XML element: its name, attributes, and children nodes.
**
@Js class XElem : XNode
{
//////////////////////////////////////////////////////////////////////////
// Construction
//////////////////////////////////////////////////////////////////////////
**
** Construct an element with unqualified local name and optional
** XML namespace. The XNs instance should be defined as an
** attribute on this or an ancestor element (see `XAttr.makeNs`).
**
new make(Str name, XNs? ns := null)
{
this.name = name
this.ns = ns
}
//////////////////////////////////////////////////////////////////////////
// Identity
//////////////////////////////////////////////////////////////////////////
**
** Return the `XNodeType.elem`. Note that during pull
** parsing XParser will return 'elemStart' and 'elemEnd'.
**
override XNodeType nodeType() { return XNodeType.elem }
**
** Unqualified local name of the element. If an XML namespace prefix
** was specified, then this is everything after the colon:
** <foo> => foo
** <x:foo> => foo
**
Str name
**
** The XML namespace which qualified this element's name.
** If the element name is unqualified return null.
**
XNs? ns
**
** If this element is qualified by an XML namespace then return
** the namespace's prefix. Otherwise return null. If the namespace
** is the default namespace then prefix is "".
**
Str? prefix()
{
return ns?.prefix
}
**
** If this element is qualified by an XML namespace then return
** the namespace's uri. Otherwise return null.
**
Uri? uri()
{
return ns?.uri
}
**
** Qualified name of the element. This is the full name including
** the XML namespace prefix:
** <foo> => foo
** <x:foo> => x:foo
**
Str qname()
{
if (ns == null || ns.isDefault) return name
return ns.prefix + ":" + name
}
**
** Line number of XML element in source file or zero if unknown.
**
Int line
**
** String representation is as a start tag.
**
override Str toStr()
{
s := StrBuf()
s.addChar('<').add(qname)
attrs.each |XAttr a| { s.addChar(' ').add(a) }
s.addChar('>')
return s.toStr
}
//////////////////////////////////////////////////////////////////////////
// Attributes
//////////////////////////////////////////////////////////////////////////
**
** Get this element's attributes as a readonly list.
**
XAttr[] attrs() { return attrList.ro }
**
** Iterate each attribute in the `attrs` list.
**
Void eachAttr(|XAttr attr, Int index| f)
{
attrList.each(f)
}
**
** Get an attribute by its non-qualified local name. If
** the attribute is not found and checked is false then
** return null otherwise throw XErr.
**
XAttr? attr(Str name, Bool checked := true)
{
attr := attrList.find |XAttr a->Bool| { return a.name == name }
if (attr != null || !checked) return attr
throw XErr("Missing attr '$name'", line)
}
**
** Get an attribute value by its non-qualified local name.
** If the attribute is not found and checked is false then
** return null otherwise throw XErr.
**
@Operator Str? get(Str name, Bool checked := true)
{
return attr(name, checked)?.val
}
**
** Add an attribute to this element. Return this.
** This method is a convenience for:
** add(XAttr(name, val, ns))
**
This addAttr(Str name, Str val, XNs? ns := null)
{
return add(XAttr(name, val, ns))
}
**
** Remove the attribute from this element. The attribute
** is matched by reference, so you must pass in the same XAttr
** contained by this element. Return the removed attribute
** or null if no match.
**
XAttr? removeAttr(XAttr attr)
{
if (attrList.isEmpty) return null
return attrList.removeSame(attr)
}
**
** Remove the attribute at the specified index into `attrs`.
** Return the removed attribute.
**
XAttr removeAttrAt(Int index)
{
return attrList.removeAt(index)
}
**
** Remove all the attributes. Return this.
**
This clearAttrs()
{
attrList = noAttrs
return this
}
//////////////////////////////////////////////////////////////////////////
// Children Nodes
//////////////////////////////////////////////////////////////////////////
**
** Get this element's children elements, text, and PIs
** as a readonly list.
**
XNode[] children() { return childList.ro }
**
** Iterate each child element, text, and PI node in
** the `children` list.
**
Void each(|XNode child, Int index| f)
{
childList.each(f)
}
**
** If child is a XAttr then add an attribute. Otherwise it must be
** a XElem, XText, or XPi and is added a child node. If the child node
** is already parented, then throw ArgErr. Return this.
**
@Operator This add(Obj child)
{
if (child is XAttr)
{
if (attrList.isRO) attrList = XAttr[,] { capacity=4 }
attrList.add(child)
}
else
{
node := (XNode)child
if (node.parent != null) throw ArgErr("Node already parented: $child")
node.parent = this
if (childList.isRO) childList = XNode[,] { capacity=4 }
childList.add(node)
}
return this
}
**
** Remove the child element, text, or PI from this element.
** The child is matched by reference, so you must pass in
** the same XNode contained by this element. Return the
** removed node or null if no match.
**
XNode? remove(XNode child)
{
if (childList.isEmpty) return null
if (childList.removeSame(child) !== child) return null
child.parent = null
return child
}
**
** Remove the child element, text, or PI at the specified
** index into `children`. Return the removed node.
**
XNode removeAt(Int index)
{
child := childList.removeAt(index)
child.parent = null
return child
}
**
** Get the children elements. If this element contains text
** or PI nodes, then they are excluded in the result.
**
XElem[] elems()
{
return childList.findType(XElem#)
}
**
** Find an element by its non-qualified local name. If there
** are multiple child elements with the name, then the first one
** is returned. If the element is not found and checked is false
** then return null otherwise throw XErr.
**
XElem? elem(Str name, Bool checked := true)
{
elem := childList.find |XNode n->Bool|
{
return n is XElem && ((XElem)n).name == name
}
if (elem != null || !checked) return elem
throw XErr("Missing element '$name'", line)
}
**
** Return this element's child text node. If this element contains
** multiple text nodes then return the first one. If this element
** does not contain a text node return null.
**
XText? text()
{
return childList.find |XNode n->Bool| { return n is XText }
}
//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////
**
** Make a shallow copy of this element.
**
This copy()
{
copy := XElem(name, ns)
copy.line = line
if (!attrList.isEmpty) copy.attrList = attrList.dup
if (!childList.isEmpty) copy.childList = childList.dup
return copy
}
//////////////////////////////////////////////////////////////////////////
// Write
//////////////////////////////////////////////////////////////////////////
**
** Write this node to the output stream.
**
override Void write(OutStream out) { doWrite(out, 0) }
internal Void doWrite(OutStream out, Int indent)
{
// start element tag
out.writeChar('<')
if (ns != null && !ns.isDefault) out.writeChars(ns.prefix).writeChar(':')
out.writeChars(name)
attrList.each |XAttr attr| { out.writeChar(' '); attr.write(out) }
// if empty element, then close element
if (childList.isEmpty) { out.writeChar('/').writeChar('>'); return }
// close start tag
out.writeChar('>')
// children elements
indent++
needIndent := childList.first isnot XText
childList.each |XNode node|
{
isText := node is XText
if (needIndent && !isText) { out.writeChar('\n').writeChars(Str.spaces(indent)) }
needIndent = !isText
if (node is XElem)
((XElem)node).doWrite(out, indent)
else
node.write(out)
}
indent--
// closing element tag
if (needIndent) out.writeChar('\n').writeChars(Str.spaces(indent))
out.writeChar('<').writeChar('/')
if (ns != null && !ns.isDefault) out.writeChars(ns.prefix).writeChar(':')
out.writeChars(name).writeChar('>')
}
//////////////////////////////////////////////////////////////////////////
// Fields
//////////////////////////////////////////////////////////////////////////
// Immutable empty lists
internal const static XElem[] noElems := XElem[,]
internal const static XNode[] noNodes := XNode[,]
internal const static XAttr[] noAttrs := XAttr[,]
internal XAttr[] attrList := noAttrs
internal XNode[] childList := noNodes
}