Skip to content

atteo/xml-combiner

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

31 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Build Status Coverage Status Maven Central Apache 2

Overview

XmlCombiner allows to combine multiple XML files into one.

For instance having two XML documents:

<config>
	<name>Alex</name>
</config>

and

<config>
	<surname>Murphy</surname>
</config>

then the result of merging those two documents will be:

<config>
	<name>Alex</name>
	<surname>Murphy</surname>
</config>

Attributes are merged in the same way:

<config name="Alex"/>

and

<config surname="Murphy"/>

results in

<config name="Alex" surname="Murphy"/>

Usage

import org.atteo.xmlcombiner.XmlCombiner;

// create combiner
XmlCombiner combiner = new XmlCombiner();

// combine files
combiner.combine(firstFile);
combiner.combine(secondFile);

// store the result
combiner.buildDocument(resultFile);

Maven dependency is:

<dependency>
    <groupId>org.atteo</groupId>
    <artifactId>xml-combiner</artifactId>
    <version>3.0.0</version>
</dependency>

Controlling the merging behavior

By default matching tags from two files are merged. That is, given two XML documents:

<config>
	<name>John</name>
	<surname>Murphy</surname>
</config>

and

<config>
	<name>Alex</name>
</config>

the result would be:

<config>
	<name>Alex</name>
	<surname>Murphy</surname>
</config>

Observe how the default behavior resulted in using the content of the <name> tag from the second file, ignoring the content from the first one.

If instead we would like for the children of the <config> tag from the second file to be appended to the children of the <config> tag from the first file then we can alter the way the combiner works using special 'combine.children' attribute.

Merging first file with 'combine.children' attribute set to 'append' value:

<config combine.children='append'>
    <name>John</name>
    <surname>Murphy</surname>
</config>

with the second file unchanged

<config>
    <name>Alex</name>
</config>

results in

<config>
    <name>John</name>
    <surname>Murphy</surname>
    <name>Alex</name>
</config>

In addition there is also 'combine.self' attribute which allows to control how the element itself is combined. Combining

<config>
    <name>John</name>
</config>

with

<config>
    <name combine.self='remove'/>
</config>

results in

<config>
</config>

Below you can find the table of all allowed values which link to their detailed description.

CombineChildren CombineSelf
merge (default) merge (default)
append defaults
overridable
overridable_by_tag
override
remove

Matching the elements

Matching the elements between the merged XML attributes based only on their tag names is usually insufficient. For instance let's analyze the following two files:

<config>
    <div id='title'>
		<h1>Title</h1>
	</div>
    <div id='button'>
		<button>OK</button>
	</div>
</config>

and

<config>
    <div id='button'>
        <button>Cancel</button>
    </div>
</config>

Here the intent is to merge 'div#button' elements between two files. So the key used to match the elements should consist of tag name and 'id' attribute.

Global keys

We can tell XmlCombiner which attributes to include in the key by listing their names in its constructor call as follows:

import org.atteo.xmlcombiner.XmlCombiner;

// create combiner
XmlCombiner combiner = new XmlCombiner("id");
...

Then the result will correctly be:

<config>
    <div id='title'>
		<h1>Title</h1>
	</div>
    <div id='button'>
		<button>Cancel</button>
	</div>
</config>

Local keys

Another option to specify the keys is within the XML document itself using 'combine.keys' attribute. For instance combining

<config combine.keys='id'>
    <service id='first' name='alpha'/>
    <service id='second' name='beta'/>
</config>

with

<config>
    <service id='second' name='theta'/>
</config>

will result in

<config>
    <service id='first' name='alpha'/>
    <service id='second' name='theta'/>
</config>

Multiple keys can be specified by separating them using comma, for example:

<config combine.keys='id,name'>
...

Artificial key

Sometimes the XML file format does not contain any usable keys. In this case you can specify an artificial key in 'combine.id' attribute. For instance combining

<config>
    <service combine.id='1' name='a'/>
    <service combine.id='2' name='b'/>
</config>

with

<config>
    <service combine.id='1' name='c'/>
    <service combine.id='3' name='d'/>
</config>

will result in

<config>
    <service name='c'/>
    <service name='b'/>
    <service name='d'/>
</config>

Notice how 'combine.id' attribute was removed from the final output.

Filtering

Filtering gives you the ability to further modify the resulting XML. For instance given two documents

<config>
    <element name='hydrogen' weight='1'/>
    <element name='helium' weight='2'/>
</config>

and

<config>
    <element name='hydrogen' weight='10'/>
    <element name='lithium' weight='20'/>
</config>

we want to get

<config>
    <element name='hydrogen' weight='11'/>
    <element name='helium' weight='2'/>
    <element name='lithium' weight='20'/>
</config>

That is, we want the weight to be the sum of the weights of the merged elements. To achieve that we can define the filter like this:

XmlCombiner.Filter weightFilter = new XmlCombiner.Filter() {
	@Override
	public void postProcess(Element recessive, Element dominant, Element result) {
		if (recessive == null || dominant == null) {
			return;
		}
		Attr recessiveNode = recessive.getAttributeNode("weight");
		Attr dominantNode = dominant.getAttributeNode("weight");
		if (recessiveNode == null || dominantNode == null) {
			return;
		}

		int recessiveWeight = Integer.parseInt(recessiveNode.getValue());
		int dominantWeight = Integer.parseInt(dominantNode.getValue());

		result.setAttribute("weight", Integer.toString(recessiveWeight + dominantWeight));
	}
};

XmlCombiner combiner = new XmlCombiner("name");
combiner.setFilter(weightFilter);

Alternatives

  • Plexus Utils Xpp3DomUtils - used by Maven to merge plugin configurations, not so straightforward to use outside Maven
  • EL4J XmlMerge - slightly different algorithm, it allows to specify merging behavior in separate file