0

I want to do the following:

I want to use a YAML file to define what values to change in an XML file. Theoretically a match made in heaven but for how the detail works. Below code is wrong on several levels but it displays what I need. The thing is there is no way to walk the YAML file leaf by leaf, which I'd need here. So I need to resort to some yucky recursive stuff to strip out the "path".

The second problem is, that even if I have the path in a form "parent1[0].parent2[0].@description" I can't go xmlIn."${pathVariable}" = value because that only works on direct children.

#!/usr/bin/env groovy

// Test for YAML

import groovy.util.* 
import groovy.text.*
import groovy.xml.*
@Grab('org.yaml:snakeyaml:1.18')

// YAML file
def yamlFile = '''"@description": "testParent1"

parent1[0]:
   parent2[0]:
      "@description": "testParent2"

'''

def xmlFile = '''
<root>
   <parent1 description="test0">
     <parent2 description="test1" />
   </parent1>
   <parent1>
      <parent2 description="test2" />
   </parent1>
</root>
'''


def config = new org.yaml.snakeyaml.Yaml().load(yamlFile)

def xmlIn = new XmlParser().parseText(xmlFile)

config.each {
   println "${it.key} = ${it.value}"
   xmlIn."${it.key}" = it.value
}
halfer
  • 19,824
  • 17
  • 99
  • 186
  • Oh and this get's close but doesn't quite do what I want. https://stackoverflow.com/questions/1307919/using-variables-in-xmlslurpers-gpath – OliverNZ May 01 '18 at 02:15

1 Answers1

0

The thing is there is no way to walk the YAML file leaf by leaf, which I'd need here.

Technically, there are no leafs because YAML represents a graph, not a tree. It may have cycles in it because of anchors & aliases. However, you can of course still implement a walker that visits every node yourself, as long as you integrate a check that detects cycles so you do not run into an endless loop.

But I think the more straightforward strategy here is to walk the event stream. Here's a way to do it, in Java (pardon me, I don't know Groovy; but I assume it is simple to translate):

DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
Document doc = dBuilder.parse(xmlFile);
XPathFactory xpf = XPathFactory.newInstance();
XPath xpath = xpf.newXPath();

List<String> pathComponents = new ArrayList<String>();
int pos = -1; // root event will be MappingStartEvent, which will lift this to 0.
for (Event e: new Yaml().parse(yamlFile)) {
    if (e instanceof MappingStartEvent) {
        if (pos == pathComponents.size()) {
            throw RuntimeException("Mapping as key not supported!");
        }
        pos++;
    } else if (e instanceof MappingEndEvent) {
        pos--;
        // pop recent key from list
        if (pos >= 0) {
            pathComponents.remove(pos);
        }
    } else if (e instanceof ScalarEvent) {
        if (pos == pathComponents.size()) {
            // this is a key, add it to the path
            pathComponents.add(((ScalarEvent) e).value);
        } else {
            // this is a replacement value, do the replacement.
            // last given key must specify an attribute.
            String lastKey = pathComponents.get(pathComponents.size() - 1);
            if (lastKey.charAt(0) != '@') {
                throw RuntimeException("can only change attributes!");
            }
            // attribute name not part of path; pop it
            pathComponents.remove(pos);

            Element userElement = (Element) xpath.evaluate("/" + String.join("/", pathComponents), doc,
            XPathConstants.NODE);
            userElement.setAttribute(lastKey.substring(1), ((ScalarEvent) e).value);
        }
    } else if (e instanceof SequenceStartEvent or e instanceof SequenceEndEvent) {
        throw RuntimeException("sequences not allowed!");
    } else if (e instanceof AliasEvent) {
        throw RuntimeException("aliases not allowed!");
    } else {
        // I skip checking for proper DocumentStartEvent / DocumentEndEvent here
    }
}

This code is untested!

There is a Java library named XModifier that looks like it makes the modifications easier, but I don't know it and it does not seem to be widespread, so be wary.

flyx
  • 35,506
  • 7
  • 89
  • 126
  • Thanks for that but I now realise (with your input), that this is hopeless. Just too much uglyness trying to make it work like I want. So I am devising a new solution after giving it a good go. – OliverNZ May 01 '18 at 22:06