Вернуть XPath для каждой строки в XML-файле в Groovy - PullRequest
1 голос
/ 15 мая 2019

Я хочу проанализировать файл 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

Ответы [ 2 ]

1 голос
/ 18 мая 2019

Вот оно.Обход в глубину:

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()
        }
    }
}

Вывод, как и ожидалось:

Vehicle
Vehicle/Car
Vehicle/Car/Prius
Vehicle/Car
Vehicle/Boat
Vehicle/Boat/Yacht
Vehicle/Boat
Vehicle/Bicycle
Vehicle

А вот версия 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()]
    }
}

Вывод с форматированным 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()]
        }
    }
}

Вывод:

<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
0 голосов
/ 21 мая 2019

Я думаю, что это может сделать то, что вы намереваетесь сделать.Это несколько вдохновлено предложенным решением Дмитрия Хамитова.Я, конечно, скопировал / вставил некоторые из них.

Имейте в виду, что нет смысла делать xpaths для конечных тегов.Поэтому я исключил их из предложенного решения.

Вы должны быть в состоянии скопировать / вставить это в Groovy Step в SoapUI и запустить его как есть.

Я также изменилПример 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
}

Пример вывода из журнала:

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]
...