Blog

Aufmotzen der Scala XML-Bibliothek

Age Mooij

Aktualisiert Oktober 23, 2025
4 Minuten

Anfang dieser Woche bin ich auf eine fehlende Funktion in der Scala xml-Bibliothek gestoßen und habe diese Funktion schließlich selbst hinzugefügt, was sich als ziemlich einfach herausstellte. Ich habe versucht, den Textinhalt eines Elements in einem Stück XML mit Hilfe des Handys und der Methoden von scala.xml.NodeSeq zu extrahieren. Diese Methoden ermöglichen es Ihnen, Unterelemente aus einem XML-Knoten auf eine Weise zu extrahieren, die XPath sehr ähnlich ist, etwa so: [scala] val xml = <a><b><c>text</c></b></a> val c1 = xml "b" "c" val c2 = xml "c" val text = c2.text [/scala] Das Problem, auf das ich gestoßen bin, trat auf, als ich versuchte, mit diesen Methoden ein Element zu extrahieren, wenn eines seiner Attribute einen bestimmten Wert hatte.

Ausgehend von meinen Erfahrungen mit "echtem" XPath habe ich versucht, Folgendes zu tun: [scala] val xml = <a><b id="b1"/><b id="b2"/></a> val b2 = xml "b[@id == b2]" [/scala] Aber das hat nicht funktioniert und führte zu einer leeren NodeSeq. Ich habe ein paar Syntaxvarianten ausprobiert, aber nichts schien zu funktionieren. Als ich mir den Scala-Quellcode ansah, war es ziemlich einfach zu erkennen, dass eine Unterstützung für diese Art von Dingen einfach nicht vorhanden war. Es gibt ein Paket mit dem Namen scala.xml.path, aber es enthält nur eine Quelldatei und soweit ich feststellen konnte, sind dieses Paket und sein Inhalt weder nützlich noch werden sie irgendwo anders im Scala-Quellcode verwendet.Nachdem ich den Quellcode für die and-Methoden gesehen hatte, dachte ich, dass es ziemlich einfach sein sollte, Unterstützung für das Extrahieren von Knoten auf der Basis von Attributwerten hinzuzufügen, und das war es auch. Der Scala 2.7.5 Quellcode für diese Methoden war etwas klobig und sehr imperativ (viele while-Schleifen und verschachtelte ifs), aber der 2.8 Code sah viel besser aus, so dass ich mich entschied, meine Implementierung auf den dortigen Ansatz zu stützen.Hier ist also ohne weiteres der Code für meine RichNodeSeq Implementierung. Den vollständigen Code und die dazugehörigen Unit-Tests finden Sie auf GitHub. [scala] import scala.xml. object RichNodeSeq { val MatchNodeByAttributeValueRegExp = """^(.)[@(.)==(.*)]$""".r def apply(nodeSeq: NodeSeq): RichNodeSeq = { new RichNodeSeq(nodeSeq) } } class RichNodeSeq(nodeSeq: NodeSeq) extends NodeSeq { def theSeq = nodeSeq.theSeq import RichNodeSeq. override def (that: String): RichNodeSeq = { def filterChildNodes(cond: (Node) => Boolean) = RichNodeSeq(NodeSeq fromSeq (this flatMap (.child) filter cond)) that match { case MatchNodeByAttributeValueRegExp(element, attribute, value) => filterChildNodes( isElementWithAttributeValue(, element.trim, attribute.trim, value.trim)) Fall => RichNodeSeq(super.(that)) } } override def (that: String): RichNodeSeq = { def filterChildNodes(cond: (Node) => Boolean) = RichNodeSeq(NodeSeq fromSeq (this flatMap (.descendant_orself) filter cond)) that match { case MatchNodeByAttributeValueRegExp(element, attribute, value) => filterChildNodes( isElementWithAttributeValue(, element.trim, attribute.trim, value.trim)) Fall _ => RichNodeSeq(super.(that)) } } private def isElementWithAttributeValue( Knoten: Knoten, elementName: String, attributeName: String, attributeValue: String): Boolean = { (node.label == elementName) .&&( node.attribute(attributeName) match { case Some(Attribute) => attributes(0) == attributeValue Fall Keine => false } ) } } [/scala] Vielleicht bemerken Sie die seltsame Verwendung von geschweiften Klammern hier und da, vor allem im letzten Teil. Dies sollte eigentlich nicht notwendig sein, aber ohne die geschweiften Klammern und die Punkte konnte das Scala Eclipse Plugin den Code nicht korrekt analysieren. Sie können diese Funktionalität nutzen, indem Sie eine beliebige vorhandene Instanz von NodeSeq in eine Instanz von RichNodeSeq einwickeln. Ich habe mit einer impliziten Konvertierung herumgespielt, so dass Sie nur eine Import-Anweisung benötigen würden, aber (AFAIK) hätte ich die Methoden umbenennen müssen, damit das funktioniert. Hier ist ein Beispiel: [scala] val xml = <a><b id="b1"/><b id="b2"/></a> val b2 = RichNodeSeq(xml) "b[@id == b2]" [/scala] Wie funktioniert das Ganze also? Der grundlegende Ansatz besteht darin, NodeSeq zu erweitern und die beiden XPath-ähnlichen Methoden zu überschreiben. Ich verwende einen Extraktor für reguläre Ausdrücke, um zu prüfen, ob das Argument der Methoden einen Attributwertausdruck enthält. Wenn nicht, übergebe ich einfach an die Superklasse. Wenn das Argument einen Attributwertausdruck enthält, verwende ich den extrahierten Elementnamen, den Attributnamen und den Attributwert, um einen Abgleich mit den Kindern und/oder den Nachkommen der aktuellen NodeSeq durchzuführen. Die isElementWithAttributeValue-Methode stellt dann einfach fest, ob der Elementname übereinstimmt, ob das Element irgendwelche Attribute hat und wenn ja, ob das Attribut mit dem Attributwert übereinstimmt. Durch die verschachtelte Musterübereinstimmung im booleschen Ausdruck sieht es etwas schwieriger aus, als es sein sollte. Ich denke, ich werde das später in eine andere Methode auslagern.Wie bereits erwähnt, sind die Quellen und die Unit-Tests auf GitHub verfügbar, also nutzen Sie diesen Code, wenn Sie ihn für nützlich halten. Und wenn Sie der Meinung sind, dass ich es völlig falsch (oder völlig richtig) gemacht habe, zögern Sie nicht, einen Kommentar zu hinterlassen.

Verfasst von

Age Mooij

Contact

Let’s discuss how we can support your journey.