modificar - manejo de xml en c#
Scala XML Building: agregando hijos a los nodos existentes (9)
Tengo un nodo XML al que quiero agregar hijos con el tiempo:
val root: Node = <model></model>
Pero no puedo ver métodos como addChild () , ya que me gustaría escribir algo como:
def addToModel() = {
root.addChild(<subsection>content</subsection>)
}
Entonces, después de una sola llamada a este método, el xml raíz sería:
<model><subsection>content</subsection></model>
La única clase que puedo ver que tiene la capacidad de agregar un nodo es el NodeBuffer. ¿Me estoy perdiendo algo fundamental aquí?
Bueno comienza con esto:
def addChild(n: Node, newChild: Node) = n match {
case Elem(prefix, label, attribs, scope, child @ _*) =>
Elem(prefix, label, attribs, scope, child ++ newChild : _*)
case _ => error("Can only add children to elements!")
}
El método ++
funciona aquí porque child
es un Seq[Node]
, y newChild
es un Node
, que extiende NodeSeq
, lo que extiende a Seq[Node]
.
Ahora, esto no cambia nada, porque XML en Scala es inmutable. Producirá un nuevo nodo, con los cambios necesarios. El único costo es la creación de un nuevo objeto Elem
, así como la creación de una nueva Seq
de hijos. Los nodos hijos, ellos mismos, no se copian, solo se mencionan, lo que no causa problemas porque son inmutables.
Sin embargo, si está agregando hijos a un nodo en la jerarquía XML, las cosas se complican. Una forma sería usar cremalleras, como se describe en este blog .
Sin embargo, puede usar scala.xml.transform
, con una regla que cambiará un nodo específico para agregar el nuevo hijo. Primero, escribe una nueva clase de transformador:
class AddChildrenTo(label: String, newChild: Node) extends RewriteRule {
override def transform(n: Node) = n match {
case n @ Elem(_, `label`, _, _, _*) => addChild(n, newChild)
case other => other
}
}
Entonces, úsalo así:
val newXML = new RuleTransformer(new AddChildrenTo(parentName, newChild)).transform(oldXML).head
En Scala 2.7, reemplace la head
por la first
.
Ejemplo en Scala 2.7:
scala> val oldXML = <root><parent/></root>
oldXML: scala.xml.Elem = <root><parent></parent></root>
scala> val parentName = "parent"
parentName: java.lang.String = parent
scala> val newChild = <child/>
newChild: scala.xml.Elem = <child></child>
scala> val newXML = new RuleTransformer(new AddChildrenTo(parentName, newChild)).transform(oldXML).first
newXML: scala.xml.Node = <root><parent><child></child></parent></root>
Podría hacer que sea más complejo obtener el elemento correcto, si el padre no es suficiente. Sin embargo, si necesita agregar el hijo a un padre con un nombre común de un índice específico, entonces probablemente deba seguir el camino de las cremalleras.
Por ejemplo, si tiene <books><book/><book/></books>
y desea agregar <author/>
al segundo, sería difícil hacerlo con el transformador de reglas. Necesitaría una RewriteRule contra los books
, que luego obtendría su child
(que realmente debería haber sido nombrado children
), busque el book
en ellos, agregue el nuevo hijo a eso, y luego recomponga a los niños y construya el nuevo nodo . Se puede hacer, pero las cremalleras pueden ser más fáciles si tienes que hacer eso demasiado.
Como los XML
son immutable
, debe crear uno nuevo cada vez que desee agregar un nodo, puede usar la Pattern matching
para agregar su nuevo nodo:
var root: Node = <model></model>
def addToModel(newNode: Node) = root match {
//match all the node from your model
// and make a new one, appending old nodes and the new one
case <model>{oldNodes@_*}</model> => root = <model>{oldNodes}{newNode}</model>
}
addToModel(<subsection>content</subsection>)
Desde scala 2.10.0, el constructor de instancia de Elem ha cambiado, si desea utilizar una solución ingenua escrita por @Daniel C. Sobral, debería ser:
xmlSrc match {
case xml.Elem(prefix, label, attribs, scope, child @ _*) =>
xml.Elem(prefix, label, attribs, scope, child.isEmpty, child ++ ballot : _*)
case _ => throw new RuntimeException
}
Para mí, funciona muy bien.
En Scala los nodos XML son inmutables, pero pueden hacer esto:
var root = <model/>
def addToModel(child:Node) = {
root = root match {
case <model>{children@ _*}</model> => <model>{children ++ child}</model>
case other => other
}
}
addToModel(<subsection>content</subsection>)
Reescribe un nuevo xml, haciendo una copia del anterior y agregando su nodo como un niño.
Edit: Brian proporcionó más información y me di cuenta de una diferente para emparejar.
Para agregar un hijo a un nodo arbitrario en 2.8 puede hacer:
def add(n:Node,c:Node):Node = n match { case e:Elem => e.copy(child=e.child++c) }
Eso devolverá una nueva copia del nodo padre con el hijo agregado. Suponiendo que hayas apilado tus nodos hijos cuando estén disponibles:
scala> val stack = new Stack[Node]()
stack: scala.collection.mutable.Stack[scala.xml.Node] = Stack()
Una vez que haya calculado que ha terminado con la recuperación de hijos, puede hacer una llamada al padre para agregar a todos los niños de la pila de esta manera:
stack.foldRight(<parent/>:Node){(c:Node,n:Node) => add(n,c)}
No tengo idea de la implicación en el rendimiento de usar Stack
y foldRight
por lo que, dependiendo de cuántos niños haya apilado, es posible que tenga que hacer stack.clear
... Es posible que deba llamar a stack.clear
también. Esperemos que esto cuide la naturaleza inmutable de Node
pero también su proceso a medida que lo necesite.
En la forma habitual de Scala, todas las instancias de Node, Elem, etc. son inmutables. Puedes trabajar al revés:
scala> val child = <child>foo</child>
child: scala.xml.Elem = <child>foo</child>
scala> val root = <root>{child}</root>
root: scala.xml.Elem = <root><child>foo</child></root>
Consulte http://sites.google.com/site/burakemir/scalaxbook.docbk.html para obtener más información.
Estoy de acuerdo en que tienes que trabajar con XML "al revés". Tenga en cuenta que no tiene que tener todo el documento XML disponible cuando la información esté disponible, solo necesita redactar el XML cuando la aplicación necesita leerlo.
Mantenga su subsección como desee, cuando necesite el XML, envuélvalo todo junto.
val subsections : List[Elem]
def wrapInModel(f : => Elem) = {
<model>{f}</model>
}
wrapInModel(subsections)
o
def wrapInModel(f : => Elem) = {
<model>{f}</model>
}
wrapInModel(<subsection>content</subsection>)
Implemento mi método ''appendChild'' de la siguiente manera:
def appendChild(elem: Node, child: Node, names: String) = {
appendChild(elem, child, names.split("/"))
}
private def appendChild(elem: Node, child: Node, names: Array[String]) = {
var seq = elem.child.diff(elem / names.head)
if (names.length == 1)
for (re <- elem / names.head)
seq = seq ++ re.asInstanceOf[Elem].copy(child = re.child ++ child)
else
for (subElem <- elem / names.head)
seq = seq ++ appendChild(subElem, child, names.tail)
elem.asInstanceOf[Elem].copy(child = seq)
}
El método agrega niños a sus nodos de manera recursiva. En la declaración ''if'', simplemente llama al método ''copy'' de la clase Elem para producir nuevas instancias de niños afectados (estos pueden ser plurales). Luego, en la instrucción ''else'', las llamadas recursivas al método ''appendChild'' verifican que el XML resultante se reconstruya. Antes de ''if-else'' hay una secuencia que se construye a partir de niños no afectados. Al final, necesitamos copiar esta secuencia al elemento de origen.
val baz = <a><z x="1"/><b><z x="2"/><c><z x="3"/></c><z x="4"/></b></a>
println("Before: /n" + XmlPrettyPrinter.format(baz.toString()))
val res = appendChild(baz, <y x="5"/>, "b/c/z")
println("After: /n" + XmlPrettyPrinter.format(res.toString()))
Resultados:
Before:
<a>
<z x="1"/>
<b>
<z x="2"/>
<c>
<z x="3"/>
</c>
<z x="4"/>
</b>
</a>
After:
<a>
<z x="1"/>
<b>
<z x="2"/>
<z x="4"/>
<c>
<z x="3">
<y x="5"/>
</z>
</c>
</b>
</a>
su definición de raíz es en realidad un objeto Elem, una subclase de nodo, por lo que si elimina la escritura innecesaria de Nodos (que oculta su implementación), en realidad podría hacer un ++ en ella ya que la clase Elem tiene este método.
val root = <model/>
val myChild = <myChild/>
root.copy(child = root.child ++ myChild)
scala ev:
root: scala.xml.Elem = <model/>
myChild: scala.xml.Elem = <mychild/>
res2: scala.xml.Elem = <model><mychild/></model>
Ya que cada Elem y cada Nodo es un NodeSeq, puedes agregar estos de manera bastante efectiva incluso si lo que estás agregando es una secuencia desconocida:
val root = <model/>
//some node sequence of unknown subtype or structure
val children: scala.xml.NodeSeq = <node1><node2/></node1><node3/>
root.copy(child = root.child ++ children)
scala ev:
root: scala.xml.Elem = <model/>
children: scala.xml.NodeSeq = NodeSeq(<node1><node2/></node1>, <node3/>)
res6: scala.xml.Elem = <model><node1><node2/></node1><node3/></model>
Scales Xml permite cambios sencillos en el lugar mediante el plegado sobre XPaths, agregando hijos a un subnodo particular que se adapta a este enfoque.
Ver Transformaciones en el lugar para más detalles.