在Groovy中为XML文件中的每一行返回XPath
我想解析一个XML文件并创建一个新的CSV,其中每个XML标记都位于它自己的XPath旁边 这是在SoapUI中。我尝试过使用XMLSlurper,但是我不能完全理解它的逻辑,并且我试图查看日志中发生了什么的尝试也没有起作用在Groovy中为XML文件中的每一行返回XPath,xml,csv,xpath,groovy,soapui,Xml,Csv,Xpath,Groovy,Soapui,我想解析一个XML文件并创建一个新的CSV,其中每个XML标记都位于它自己的XPath旁边 这是在SoapUI中。我尝试过使用XMLSlurper,但是我不能完全理解它的逻辑,并且我试图查看日志中发生了什么的尝试也没有起作用 def String showTheXPath() { def input = "input.txt" def root = new XmlSlurper().parseText(input) def xpath1 = root.Vehicl
def String showTheXPath() {
def input = "input.txt"
def root = new XmlSlurper().parseText(input)
def xpath1 = root.Vehicle.Car.Prius.text();
def xpath2 = root.Vehicle.Boat.text();
log.info xpath1
}
理想情况下,此代码将返回如下所示的CSV文件,第一列中包含XML标记,第二列中包含每个标记的XPath:
<Vehicle> | Vehicle
<Car> | Vehicle/Car
<Prius>2018</Prius> | Vehicle/Car/Prius
<Bentley>2015</Bentley> | Vehicle/Car/Bentley
</Car> | Vehicle/Car
<Boat> | Vehicle/Boat
<Yacht>2011</Yacht> | Vehicle/Boat/Yacht
</Boat> | Vehicle/Boat
<Bicycle/> | Vehicle/Bicycle
</Vehicle> | Vehicle
|车辆
|车辆/汽车
2018年|车辆/汽车/普锐斯
2015年|车辆/汽车/宾利
|车辆/汽车
|车辆/船只
2011年|车辆/船只/游艇
|车辆/船只
|车辆/自行车
|车辆
在这里。深度优先遍历:
class XmlToPath {
static void main(String[] args) {
def input = """
<Vehicle>
<Car>
<Prius/>
</Car>
<Boat>
<Yacht/>
</Boat>
<Bicycle/>
</Vehicle>
"""
def root = new XmlParser().parseText(input)
traverse(root)
}
static void traverse(Node node) {
def path = path(node)
println(path)
def children = node.children()
if (children) {
children.each {
traverse(it)
}
println(path)
}
}
static String path(Node node) {
def parent = node.parent()
if (parent) {
"${path(parent)}/${node.name()}"
} else {
node.name()
}
}
}
下面是traverse
和path
版本,它在第一列打印一个完整的CSV,其中包含您想要实现的格式化XML:
static void traverse(Node node) {
def tags = path(node)
def path = tags.join("/")
def indent = ' ' * ((tags.size() - 1) * 2)
def nodeName = node.name()
def children = node.children()
if (children) {
println("$indent<$nodeName>|$path")
children.each {
traverse(it)
}
println("$indent</$nodeName>|$path")
} else {
println("$indent<$nodeName/>|$path")
}
}
static List<String> path(Node node) {
def parent = node.parent()
if (parent) {
path(parent).tap {
add(node.name())
}
} else {
[node.name()]
}
}
静态空心导线测量(节点){
def tags=路径(节点)
def path=tags.join(“/”)
def缩进=“”*((tags.size()-1)*2)
def nodeName=node.name()
def children=node.children()
if(儿童){
println($indent |$path)
每个孩子{
遍历(it)
}
println($indent |$path)
}否则{
println($indent |$path)
}
}
静态列表路径(节点){
def parent=node.parent()
如果(家长){
路径(父级)。点击{
添加(node.name())
}
}否则{
[node.name()]
}
}
使用格式化XML但不对齐管道的输出:
<Vehicle>|Vehicle
<Car>|Vehicle/Car
<Prius/>|Vehicle/Car/Prius
</Car>|Vehicle/Car
<Boat>|Vehicle/Boat
<Yacht/>|Vehicle/Boat/Yacht
</Boat>|Vehicle/Boat
<Bicycle/>|Vehicle/Bicycle
</Vehicle>|Vehicle
|车辆
|车辆/汽车
|车辆/汽车/普锐斯
|车辆/汽车
|车辆/船只
|车辆/船只/游艇
|车辆/船只
|车辆/自行车
|车辆
最后,这里是一个非常格式化的CSV版本。我希望您已经有了想法,可以根据您的需要/偏好调整解决方案:
class XmlToPath {
static void main(String[] args) {
def input = """
<Vehicle>
<Car>
<Prius/>
</Car>
<Boat>
<Yacht/>
</Boat>
<Bicycle/>
</Vehicle>
"""
def root = new XmlParser().parseText(input)
def printer = []
traverse(root, printer)
def width = printer.max { tagAndPath ->
tagAndPath[0].size()
}[0].size()
printer.each { tag, path ->
printf("%-${width}s | %s%n", tag, path)
}
}
static void traverse(Node node, List printer) {
def tags = path(node)
def path = tags.join("/")
def indent = ' ' * ((tags.size() - 1) * 2)
def nodeName = node.name()
def children = node.children()
if (children) {
printer << ["$indent<$nodeName>", path]
children.each {
traverse(it, printer)
}
printer << ["$indent</$nodeName>", path]
} else {
printer << ["$indent<$nodeName/>", path]
}
}
static List<String> path(Node node) {
def parent = node.parent()
if (parent) {
path(parent).with {
add(node.name())
it
}
} else {
[node.name()]
}
}
}
类XmlToPath{
静态void main(字符串[]参数){
def input=“”
"""
def root=new XmlParser().parseText(输入)
def打印机=[]
遍历(根,打印机)
def width=printer.max{tagAndPath->
tagAndPath[0]。大小()
}[0]。大小()
printer.each{标记,路径->
printf(“%-${width}s|%s%n”,标记,路径)
}
}
静态空洞遍历(节点、列表打印机){
def tags=路径(节点)
def path=tags.join(“/”)
def缩进=“”*((tags.size()-1)*2)
def nodeName=node.name()
def children=node.children()
if(儿童){
打印机我认为这可能会达到您的目的。这在某种程度上是受Dmitry Khamitovs建议的解决方案的启发。我当然复制/粘贴了其中的一些
请注意,为结束标记创建XPath是没有意义的,所以我在建议的解决方案中省略了这些
您应该能够将其复制/粘贴到SoapUI中的Groovy步骤中,并按原样运行它
我还修改了示例XML,使其包含多个元素,以说明对元素列表的处理
构建的xpath将始终返回一个索引,默认情况下为[1]。这是因为每个节点都可能是列表中的第一个元素。如果不事先查看XML或查看架构,就无法知道是否是这种情况。这将使解决方案复杂化,并且您只会得到一个稍微精简的xpath,最终将返回相同的结果
要获取CSV文件,您可能需要调整printStack。目前它只输出到日志
def input = """
<Vehicles>
<ns1:Cars xmlns:ns1="asdf" xmlns:ns2="ieurieur">
<ns2:Car>
<Prius/>
</ns2:Car>
<ns2:Car>
<Ka/>
</ns2:Car>
</ns1:Cars>
<Boat>
<Yacht/>
</Boat>
<Bicycle/>
</Vehicles>
"""
def root = new XmlParser().parseText(input)
java.util.Stack nodeStack = new java.util.Stack<String>()
traverse(root,1,nodeStack)
return
void traverse(Node node, Integer index, java.util.Stack stack) {
def nsposition = new Integer(node.name().toString().indexOf("}"))
nsposition++
def nodename = node.name().toString().substring(nsposition)
stack.push(nodename + "[" + index + "]")
def path = buildPath(stack)
printStack(nodename,stack)
def children = node.children()
def childCount = new java.util.HashMap<String,Integer>()
if (children) {
children.each {
def count = getIndex(it.name(),childCount)
childCount.put(it.name(),count)
traverse(it,count,stack)
}
}
stack.pop()
}
void printStack(def nodename, def stack) {
def indentation = ""
for (def x=0; x<stack.size(); x++) {
indentation += " "
}
def path = ""
for (def element : stack.elements()) {
path += "/${element}"
}
log.info "${indentation}<${nodename}>|${path}"
}
Integer getIndex(def name, def childCount) {
if (childCount.containsKey(name)) {
return childCount.get(name) + 1
}
else {
return 1
}
}
String buildPath(def stack) {
def result = ""
for (def element : stack.elements()) {
result += "/" + element
}
return result
}
def input=“”
"""
def root=new XmlParser().parseText(输入)
java.util.Stack nodeStack=new java.util.Stack()
遍历(根、1、节点堆栈)
返回
void遍历(节点,整数索引,java.util.Stack){
def nsposition=新整数(node.name().toString().indexOf(“}”))
位置++
def nodename=node.name().toString().substring(nsposition)
stack.push(节点名+“[”+索引+“]”)
def path=buildPath(堆栈)
打印堆栈(节点名称,堆栈)
def children=node.children()
def childCount=new java.util.HashMap()
if(儿童){
每个孩子{
def count=getIndex(it.name(),childCount)
childCount.put(it.name(),count)
遍历(it、计数、堆栈)
}
}
stack.pop()
}
无效打印堆栈(def节点名称、def堆栈){
def indentation=“”
对于(席夫X=0;XI可能会做某事……你能否解释一下你想要达到的目标是什么?重要的是要得到XPath的准确顺序,每个节点都出现在XML中,或者你会满意地得到XPath的任何顺序,而仅仅知道它们都在那里……而且标签里面有什么属性?你有名字吗?您的XML中有标记,或者如果它们存在,可以忽略它们吗?您好@Steen,谢谢您的提问!我需要XPath具有在未来项目中查找标记的正确顺序,并且XML确实有名称空间,应该将其排除在外!请帮助,给出我的答案和@Steen的注释,您可以提供一个更详细的示例吗您想要实现什么?@DmitryKhamitov我可以!CSV的目标是让一列返回具有适当缩进的XML文件,第二列返回XPath而不必担心重复标记。我将更新example@Helppls您可以重新检查我的答案。正如您所阐明的,无需索引重复标记。希望能有所帮助我喜欢这种方法。但是,它可能无法解决问题。例如:如果节点实际上只是节点列表中的一个,那么您必须在xpath中返回准确的节点号,如“Vahicle/Car[3]/Prius。您不需要”。@Steen感谢您的反馈。我建议OP给出一个更全面的示例
<Vehicle> | Vehicle
<Car> | Vehicle/Car
<Prius/> | Vehicle/Car/Prius
</Car> | Vehicle/Car
<Boat> | Vehicle/Boat
<Yacht/> | Vehicle/Boat/Yacht
</Boat> | Vehicle/Boat
<Bicycle/> | Vehicle/Bicycle
</Vehicle> | Vehicle
def input = """
<Vehicles>
<ns1:Cars xmlns:ns1="asdf" xmlns:ns2="ieurieur">
<ns2:Car>
<Prius/>
</ns2:Car>
<ns2:Car>
<Ka/>
</ns2:Car>
</ns1:Cars>
<Boat>
<Yacht/>
</Boat>
<Bicycle/>
</Vehicles>
"""
def root = new XmlParser().parseText(input)
java.util.Stack nodeStack = new java.util.Stack<String>()
traverse(root,1,nodeStack)
return
void traverse(Node node, Integer index, java.util.Stack stack) {
def nsposition = new Integer(node.name().toString().indexOf("}"))
nsposition++
def nodename = node.name().toString().substring(nsposition)
stack.push(nodename + "[" + index + "]")
def path = buildPath(stack)
printStack(nodename,stack)
def children = node.children()
def childCount = new java.util.HashMap<String,Integer>()
if (children) {
children.each {
def count = getIndex(it.name(),childCount)
childCount.put(it.name(),count)
traverse(it,count,stack)
}
}
stack.pop()
}
void printStack(def nodename, def stack) {
def indentation = ""
for (def x=0; x<stack.size(); x++) {
indentation += " "
}
def path = ""
for (def element : stack.elements()) {
path += "/${element}"
}
log.info "${indentation}<${nodename}>|${path}"
}
Integer getIndex(def name, def childCount) {
if (childCount.containsKey(name)) {
return childCount.get(name) + 1
}
else {
return 1
}
}
String buildPath(def stack) {
def result = ""
for (def element : stack.elements()) {
result += "/" + element
}
return result
}
Tue May 21 15:59:13 CEST 2019: INFO: <Vehicles>|/Vehicles[1]
Tue May 21 15:59:13 CEST 2019: INFO: <Cars>|/Vehicles[1]/Cars[1]
Tue May 21 15:59:13 CEST 2019: INFO: <Car>|/Vehicles[1]/Cars[1]/Car[1]
Tue May 21 15:59:13 CEST 2019: INFO: <Prius>|/Vehicles[1]/Cars[1]/Car[1]/Prius[1]
Tue May 21 15:59:13 CEST 2019: INFO: <Car>|/Vehicles[1]/Cars[1]/Car[2]
Tue May 21 15:59:13 CEST 2019: INFO: <Ka>|/Vehicles[1]/Cars[1]/Car[2]/Ka[1]
Tue May 21 15:59:13 CEST 2019: INFO: <Boat>|/Vehicles[1]/Boat[1]
Tue May 21 15:59:13 CEST 2019: INFO: <Yacht>|/Vehicles[1]/Boat[1]/Yacht[1]
Tue May 21 15:59:13 CEST 2019: INFO: <Bicycle>|/Vehicles[1]/Bicycle[1]