在Groovy中为XML文件中的每一行返回XPath

在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

我想解析一个XML文件并创建一个新的CSV,其中每个XML标记都位于它自己的XPath旁边

这是在SoapUI中。我尝试过使用XMLSlurper,但是我不能完全理解它的逻辑,并且我试图查看日志中发生了什么的尝试也没有起作用

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]