I need your help because I don't find the solution in Java for my problem.
I stored in object LinkedHashMap<String, String>
this content:
TAG1.TAG2.TAG11 : value1
TAG1.TAG2.TAG12 : value2
TAG1.TAG2.TAG3.TAG131 : value3
TAG1.TAG2.TAG3.TAG132 : value4
TAG1.TAG2.TAG3.TAG133 : value5
TAG1.TAG2.TAG3.TAG134 : value6
TAG1.TAG4.TAG5.TAG21 : value7
TAG1.TAG4.TAG5.TAG22 : value8
TAG1.TAG4.TAG5.TAG23 : value9
TAG6 : value10
I need to display if a tag has 2 or more children, the list of child. Here is the expected result:
TAG1.TAG2
TAG11 : value1
TAG12 : value2
TAG1.TAG2.TAG3
TAG131 : value3
TAG132 : value4
TAG133 : value5
TAG134 : value6
TAG1.TAG4.TAG5
TAG21 : value7
TAG22 : value8
TAG23 : value9
TAG6 : value10
EDIT 14/06/2022 :
In fact, my original analyse is bad because initialy I have a XML file :
<TAG1>
<TAG2>
<TAG11>value1</TAG11>
<TAG12>value2</TAG12>
<TAG3>
<TAG131>value3</TAG131>
<TAG132>value4</TAG132>
<TAG133>value5</TAG133>
<TAG134>value6</TAG134>
</TAG3>
</TAG2>
<TAG4>
<TAG5>
<TAG21>value7</TAG21>
<TAG22>value8</TAG22>
<TAG23>value9</TAG23>
</TAG5>
</TAG4>
</TAG1>
<TAG6>value10</TAG6>
And I created a map to store it :
TAG1.TAG2.TAG11 : value1
TAG1.TAG2.TAG12 : value2
TAG1.TAG2.TAG3.TAG131 : value3
TAG1.TAG2.TAG3.TAG132 : value4
TAG1.TAG2.TAG3.TAG133 : value5
TAG1.TAG2.TAG3.TAG134 : value6
TAG1.TAG4.TAG5.TAG21 : value7
TAG1.TAG4.TAG5.TAG22 : value8
TAG1.TAG4.TAG5.TAG23 : value9
TAG6 : value10
But, today I have a this case :
<TAG1>
<TAG2>
<TAG11>value1</TAG11>
<TAG12>value2</TAG12>
<TAG3>
<TAG131>value3</TAG131>
<TAG132>value4</TAG132>
<TAG133>value5</TAG133>
<TAG134>value6</TAG134>
</TAG3>
<TAG3>
<TAG131>value11</TAG131>
<TAG132>value12</TAG132>
<TAG133>value13</TAG133>
<TAG134>value14</TAG134>
</TAG3>
</TAG2>
<TAG4>
<TAG5>
<TAG21>value7</TAG21>
<TAG22>value8</TAG22>
<TAG23>value9</TAG23>
</TAG5>
</TAG4>
</TAG1>
<TAG6>value10</TAG6>
But the Map object does not allow to store many keys (in the example many TAG3). Have you got an idea how I can resolve this problem ?
EDIT 15/06/2022 :
In fact the expected result needs to keep the original XML structure. Here the result of last sample :
TAG1.TAG2
TAG11 : value1
TAG12 : value2
TAG1.TAG2.TAG3
TAG131 : value3
TAG132 : value4
TAG133 : value5
TAG134 : value6
TAG1.TAG2.TAG3
TAG131 : value11
TAG132 : value12
TAG133 : value13
TAG134 : value14
TAG1.TAG4.TAG5
TAG21 : value7
TAG22 : value8
TAG23 : value9
TAG6 : value10
It's to display xml more human reader.
EDIT 04/07/2022 :
I detect a problem of inconsistent with "new TreeMap<>(Comparator.comparingInt(MyTag::getAppearanceOrder)". Indeed, some MyTag object are the same AppearanceOrder, so there is a problem of inconsistent ordering. Some value in Map are so removed. To resolve I used :
map.entrySet().stream().sorted(Map.Entry.comparingByKey(. . .))
And I store the result in Map with collect().
Below the working code :
public class Main {
public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {
//Accessing the xml file
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new File("data.xml"));
document.getDocumentElement().normalize();
Element root = document.getDocumentElement();
//Retrieving a List of records where each record contains: the original chain of tags, the numbered chain of tags and the value
String tagSep = ".";
List<Record> listRecords = new ArrayList<>();
visitXMLFile(listRecords, root.getChildNodes(), tagSep, "", "", new HashMap<>());
//Queue sorted by the numbered tag's length in descending order (from the longest to the shortest)
PriorityQueue<Record> queue = new PriorityQueue<>(Comparator.comparing(Record::getTagNumberedLen).reversed());
queue.addAll(listRecords);
//Using a set to have unique numbered tags (no duplicates) to group by in the resulting map
Set<MyTag> setMyTags = new HashSet<>();
//Checking for each numbered tag if its largest substring is equal to any other numbered tag's beginning:
// - if it does, then the substring is collected as a key to group by within the final map
//
// - if it doesn't, then another substring is generated from the previous substring until a matching value is found.
// If no value is found, then the numbered tag is collected entirely as a key for the resulting map.
while (!queue.isEmpty()) {
Record rec = queue.poll();
//This loop keeps creating substrings of the current numbered tag until:
// - the substring matches another numbered tag's beginning
// - or no more substrings can be generated
int lastIndexTagNum = rec.getTagNumbered().lastIndexOf(tagSep);
int lastIndexTag = rec.getTag().lastIndexOf(tagSep);
while (lastIndexTagNum > 0) {
//Checking if the substring matches the beginning of any numbered tag except the current one
String subStrTagNum = rec.getTagNumbered().substring(0, lastIndexTagNum);
if (listRecords.stream().anyMatch(r -> !r.getTagNumbered().equals(rec.getTagNumbered()) && r.getTagNumbered().startsWith(subStrTagNum + tagSep))) {
String subStrTag = rec.getTag().substring(0, lastIndexTag);
int appearanceOrder = listRecords.stream().filter(r -> r.getTagNumbered().startsWith(subStrTagNum + tagSep)).map(r -> r.getAppearanceOrder()).min(Comparator.naturalOrder()).orElse(0);
//If a match is found then the current substring is added to the set and the substring iteration is interrupted
setMyTags.add(new MyTag(subStrTag, subStrTagNum + tagSep, appearanceOrder));
break;
}
//Creating a new substring from the previous substring if no match has been found
lastIndexTagNum = rec.getTagNumbered().substring(0, lastIndexTagNum).lastIndexOf(tagSep);
lastIndexTag = rec.getTag().substring(0, lastIndexTag).lastIndexOf(tagSep);
}
//If no substrings of the current numbered tag matches the beginning of any other numbered tag,
//then the current numbered tag is collected as a key for the resulting map
if (lastIndexTagNum < 0) {
int appearanceOrder = listRecords.stream().filter(r -> r.getTagNumbered().startsWith(rec.getTagNumbered())).map(r -> r.getAppearanceOrder()).min(Comparator.naturalOrder()).orElse(0);
setMyTags.add(new MyTag(rec.getTag(), rec.getTagNumbered(), appearanceOrder));
}
}
//Creating a temporary resulting map (not sorted as the input)
Map<MyTag, List<String>> mapTemp = listRecords.stream()
.collect(Collectors.toMap(
rec -> {
//Looking for the longest numbered tag which matches the beginning of the current record's numbered tag.
//The reason why we need the longest match (i.e. the most accurate) is because some elements
//may share the same parents but be on different levels, for example the values 3, 4, 5 and 6
//have a key whose beginning matches both "TAG1.TAG2" and "TAG1.TAG2.TAG3", but only the longest
//match is actually the right one.
return setMyTags.stream().filter(mt -> rec.getTagNumbered().startsWith(mt.getTagNumbered())).max(Comparator.comparingInt(MyTag::getTagNumberedLen)).orElseThrow(() -> new RuntimeException("No key found"));
},
rec -> {
//Retrieving, like above, the numbered tag that will be used to map the current value
MyTag myTag = setMyTags.stream().filter(mt -> rec.getTagNumbered().startsWith(mt.getTagNumbered())).max(Comparator.comparingInt(MyTag::getTagNumberedLen)).orElseThrow(() -> new RuntimeException("No key found"));
//If the new numbered tag and the record's numbered tag are equal then a List with the current value is returned
if (myTag.getTagNumbered().equals(rec.getTagNumbered())) {
return new ArrayList<>(List.of(rec.getValue()));
} else { //If the new numbered tag is a substring of the record's numbered tag then the rest of the current (non-numbered) tag is added to the value
return new ArrayList<>(List.of(rec.getTag().substring(myTag.getTag().length() + 1) + " : " + rec.getValue()));
}
},
//Handling colliding cases by merging the lists together
(list1, list2) -> {
list1.addAll(list2);
return list1;
}
)
);
//Creating a TreeMap whose ordering is based on the insertion order of the input
Map<MyTag, List<String>> mapRes =
mapTemp.entrySet().stream()
.sorted(Map.Entry.comparingByKey(Comparator.comparingInt(MyTag::getAppearanceOrder)))
.collect(Collectors.toMap(
Map.Entry::getKey, Map.Entry::getValue, (e1, e2) -> e1, LinkedHashMap::new));
//Printing the resulting map
for (Map.Entry<MyTag, List<String>> entry : mapRes.entrySet()) {
System.out.println(entry.getKey());
for (String value : entry.getValue()) {
System.out.println("\t" + value);
}
}
}
private static void visitXMLFile(List<Record> listInput, NodeList nodeList, String tagSep, String tag, String tagNumbered, Map<String, Integer> mapTagOccurrence) {
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.hasChildNodes()) {
String newTag = tag.isEmpty() ? node.getNodeName() : tag + tagSep + node.getNodeName();
//Setting or incrementing the number of appearances of a tag chain
//(sometimes a same chain of tags can be repeated, ex: TAG1.TAG2.TAG3)
if (!mapTagOccurrence.containsKey(newTag)) {
mapTagOccurrence.put(newTag, 1);
} else {
mapTagOccurrence.computeIfPresent(newTag, (key, val) -> val + 1);
}
//Creating a numbered version of the tag where its number of appearances is added at the end.
//This is done to uniquely identify different groups of tag chain when these are repeated (ex: TAG1.TAG2.TAG3)
String newTagNum = tagNumbered.isEmpty() ? node.getNodeName() + mapTagOccurrence.get(newTag) : tagNumbered + tagSep + node.getNodeName() + mapTagOccurrence.get(newTag);
visitXMLFile(listInput, node.getChildNodes(), tagSep, newTag, newTagNum, mapTagOccurrence);
} else {
if (!node.getTextContent().trim().equals("")) {
int appearanceOrder = listInput.size() + 1;
listInput.add(new Record(tag, tagNumbered, node.getTextContent().trim(), appearanceOrder));
}
}
}
}
}
class MyTag {
//Tag chain for the user
private String tag;
//Unique tag chain for identification
private String tagNumbered;
private int appearanceOrder;
public MyTag(String tag, String tagNumbered, int appearanceOrder) {
this.tag = tag;
this.tagNumbered = tagNumbered;
this.appearanceOrder = appearanceOrder;
}
public String getTag() {
return tag;
}
public String getTagNumbered() {
return tagNumbered;
}
public int getTagNumberedLen() {
return tagNumbered == null ? 0 : tagNumbered.length();
}
public int getAppearanceOrder() {
return appearanceOrder;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyTag tagPair = (MyTag) o;
return Objects.equals(tagNumbered, tagPair.tagNumbered);
}
@Override
public int hashCode() {
return Objects.hash(tagNumbered);
}
@Override
public String toString() {
return tag;
}
}
class Record {
//Tag chain for the user
private String tag;
//Unique tag chain for identification
private String tagNumbered;
private String value;
private int appearanceOrder;
public Record(String tag, String tagNumbered, String value, int appearanceOrder) {
this.tag = tag;
this.tagNumbered = tagNumbered;
this.value = value;
this.appearanceOrder = appearanceOrder;
}
public String getTag() {
return tag;
}
public String getTagNumbered() {
return tagNumbered;
}
public int getTagNumberedLen() {
return tagNumbered == null ? 0 : tagNumbered.length();
}
public String getValue() {
return value;
}
public int getAppearanceOrder() {
return appearanceOrder;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Record record = (Record) o;
return Objects.equals(tagNumbered, record.tagNumbered);
}
@Override
public int hashCode() {
return Objects.hash(tagNumbered);
}
@Override
public String toString() {
return tag + " - " + tagNumbered + " - " + value;
}
}
At this point to answer your edited question, I had to use a List
instead of a Map
to store the input since multiple values share the same key and a Map<String, List<String>>
wouldn't maintain the insertion order. In fact, the values from 3 to 6 would be alternated with the values from 11 to 14.
Besides, since the same chain of tags can appear several times (ex: TAG1.TAG2.TAG3), I had to implement two custom classes: MyTag
and Record
.
The first class represents a custom tag made of two fields: tag
and tagNumbered
. The first field holds the tag chain that must be shown to the user, while the second is used as the actual identifier to group by in the stream operation. tagNumbered
is basically a copy of tag
where at the end of each nested tag is added its number of appearances.
Instead, the class Record
is used to represent a value accompanied by its tag chain and numbered tag chain.
So, the following XML is represented as follows by the respective classes:
<x>
<y>
<z>value1</z>
</y>
<y>
<z>value2</z>
</y>
</x>
Record:
Record1:
- tag: x.y.z
- tagNumbered: x1.y1.z1
- value: value1
Record2:
- tag: x.y.z
- tagNumbered: x1.y2.z1 //because y appears twice within x
- value: value2
MyTag (MyTag
is created from Record
):
MyTag1:
- tag: x.y.z
- tagNumbered: x1.y1.z1
MyTag2:
- tag: x.y.z
- tagNumbered: x1.y2.z1 //because y appears twice within x
Here is an XML sample based on your question's input, that I've used for the code below.
<root>
<TAG1>
<TAG2>
<TAG11>value1</TAG11>
<TAG12>value2</TAG12>
<TAG3>
<TAG131>value3</TAG131>
<TAG132>value4</TAG132>
<TAG133>value5</TAG133>
<TAG134>value6</TAG134>
</TAG3>
<TAG3>
<TAG131>value11</TAG131>
<TAG132>value12</TAG132>
<TAG133>value13</TAG133>
<TAG134>value14</TAG134>
</TAG3>
</TAG2>
<TAG4>
<TAG5>
<TAG21>value7</TAG21>
<TAG22>value8</TAG22>
<TAG23>value9</TAG23>
</TAG5>
</TAG4>
</TAG1>
<TAG6>value10</TAG6>
</root>
The first part of the problem consists in creating a List<Record>
while reading from the XML file which is achieved with the visitXMLFile
method.
After reading the records from the file, we need to create a Set
of unique numbered tag chains to identify each group of values. This is actually done with a Set<MyTag>
; however MyTag
's equals()
and hashCode()
are based exclusively on tagNumbered
.
After creating the Set
of unique numbered tags, we need to stream the input list of entries with a single operation: collect(Collectors.toMap())
. In this operation, each record is mapped to a MyTag
(i.e., a numbered tag) of the Set
previously created.
Finally, to maintain the original insertion order, the resulting Map
has been implemented as a TreeMap
initialized with a Comparator
defined on the order of the input list's records.
Here is an implementation with detailed comments explaining the whole logic step by step:
public class Main {
public static void main(String[] args) throws ParserConfigurationException, IOException, SAXException {
//Accessing the xml file
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new File("data.xml"));
document.getDocumentElement().normalize();
Element root = document.getDocumentElement();
//Retrieving a List of records where each record contains: the original chain of tags, the numbered chain of tags and the value
String tagSep = ".";
List<Record> listRecords = new ArrayList<>();
visitXMLFile(listRecords, root.getChildNodes(), tagSep, "", "", new HashMap<>());
//Queue sorted by the numbered tag's length in descending order (from the longest to the shortest)
PriorityQueue<Record> queue = new PriorityQueue<>(Comparator.comparing(Record::getTagNumberedLen).reversed());
queue.addAll(listRecords);
//Using a set to have unique numbered tags (no duplicates) to group by in the resulting map
Set<MyTag> setMyTags = new HashSet<>();
//Checking for each numbered tag if its largest substring is equal to any other numbered tag's beginning:
// - if it does, then the substring is collected as a key to group by within the final map
//
// - if it doesn't, then another substring is generated from the previous substring until a matching value is found.
// If no value is found, then the numbered tag is collected entirely as a key for the resulting map.
while (!queue.isEmpty()) {
Record rec = queue.poll();
//This loop keeps creating substrings of the current numbered tag until:
// - the substring matches another numbered tag's beginning
// - or no more substrings can be generated
int lastIndexTagNum = rec.getTagNumbered().lastIndexOf(tagSep);
int lastIndexTag = rec.getTag().lastIndexOf(tagSep);
while (lastIndexTagNum > 0) {
//Checking if the substring matches the beginning of any numbered tag except the current one
String subStrTagNum = rec.getTagNumbered().substring(0, lastIndexTagNum);
if (listRecords.stream().anyMatch(r -> !r.getTagNumbered().equals(rec.getTagNumbered()) && r.getTagNumbered().startsWith(subStrTagNum + tagSep))) {
String subStrTag = rec.getTag().substring(0, lastIndexTag);
int appearanceOrder = listRecords.stream().filter(r -> r.getTagNumbered().startsWith(subStrTagNum + tagSep)).map(r -> r.getAppearanceOrder()).min(Comparator.naturalOrder()).orElse(0);
//If a match is found then the current substring is added to the set and the substring iteration is interrupted
setMyTags.add(new MyTag(subStrTag, subStrTagNum + tagSep, appearanceOrder));
break;
}
//Creating a new substring from the previous substring if no match has been found
lastIndexTagNum = rec.getTagNumbered().substring(0, lastIndexTagNum).lastIndexOf(tagSep);
lastIndexTag = rec.getTag().substring(0, lastIndexTag).lastIndexOf(tagSep);
}
//If no substrings of the current numbered tag matches the beginning of any other numbered tag,
//then the current numbered tag is collected as a key for the resulting map
if (lastIndexTagNum < 0) {
int appearanceOrder = listRecords.stream().filter(r -> r.getTagNumbered().startsWith(rec.getTagNumbered())).map(r -> r.getAppearanceOrder()).min(Comparator.naturalOrder()).orElse(0);
setMyTags.add(new MyTag(rec.getTag(), rec.getTagNumbered(), appearanceOrder));
}
}
//Creating a temporary resulting map (not sorted as the input)
Map<MyTag, List<String>> mapTemp = listRecords.stream()
.collect(Collectors.toMap(
rec -> {
//Looking for the longest numbered tag which matches the beginning of the current record's numbered tag.
//The reason why we need the longest match (i.e. the most accurate) is because some elements
//may share the same parents but be on different levels, for example the values 3, 4, 5 and 6
//have a key whose beginning matches both "TAG1.TAG2" and "TAG1.TAG2.TAG3", but only the longest
//match is actually the right one.
return setMyTags.stream().filter(mt -> rec.getTagNumbered().startsWith(mt.getTagNumbered())).max(Comparator.comparingInt(MyTag::getTagNumberedLen)).orElseThrow(() -> new RuntimeException("No key found"));
},
rec -> {
//Retrieving, like above, the numbered tag that will be used to map the current value
MyTag myTag = setMyTags.stream().filter(mt -> rec.getTagNumbered().startsWith(mt.getTagNumbered())).max(Comparator.comparingInt(MyTag::getTagNumberedLen)).orElseThrow(() -> new RuntimeException("No key found"));
//If the new numbered tag and the record's numbered tag are equal then a List with the current value is returned
if (myTag.getTagNumbered().equals(rec.getTagNumbered())) {
return new ArrayList<>(List.of(rec.getValue()));
} else { //If the new numbered tag is a substring of the record's numbered tag then the rest of the current (non-numbered) tag is added to the value
return new ArrayList<>(List.of(rec.getTag().substring(myTag.getTag().length() + 1) + " : " + rec.getValue()));
}
},
//Handling colliding cases by merging the lists together
(list1, list2) -> {
list1.addAll(list2);
return list1;
}
)
);
//Creating a TreeMap whose ordering is based on the insertion order of the input
Map<MyTag, List<String>> mapRes = new TreeMap<>(Comparator.comparingInt(MyTag::getAppearanceOrder));
mapRes.putAll(mapTemp);
//Printing the resulting map
for (Map.Entry<MyTag, List<String>> entry : mapRes.entrySet()) {
System.out.println(entry.getKey());
for (String value : entry.getValue()) {
System.out.println("\t" + value);
}
}
}
private static void visitXMLFile(List<Record> listInput, NodeList nodeList, String tagSep, String tag, String tagNumbered, Map<String, Integer> mapTagOccurrence) {
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node.hasChildNodes()) {
String newTag = tag.isEmpty() ? node.getNodeName() : tag + tagSep + node.getNodeName();
//Setting or incrementing the number of appearances of a tag chain
//(sometimes a same chain of tags can be repeated, ex: TAG1.TAG2.TAG3)
if (!mapTagOccurrence.containsKey(newTag)) {
mapTagOccurrence.put(newTag, 1);
} else {
mapTagOccurrence.computeIfPresent(newTag, (key, val) -> val + 1);
}
//Creating a numbered version of the tag where its number of appearances is added at the end.
//This is done to uniquely identify different groups of tag chain when these are repeated (ex: TAG1.TAG2.TAG3)
String newTagNum = tagNumbered.isEmpty() ? node.getNodeName() + mapTagOccurrence.get(newTag) : tagNumbered + tagSep + node.getNodeName() + mapTagOccurrence.get(newTag);
visitXMLFile(listInput, node.getChildNodes(), tagSep, newTag, newTagNum, mapTagOccurrence);
} else {
if (!node.getTextContent().trim().equals("")) {
int appearanceOrder = listInput.size() + 1;
listInput.add(new Record(tag, tagNumbered, node.getTextContent().trim(), appearanceOrder));
}
}
}
}
}
class MyTag {
//Tag chain for the user
private String tag;
//Unique tag chain for identification
private String tagNumbered;
private int appearanceOrder;
public MyTag(String tag, String tagNumbered, int appearanceOrder) {
this.tag = tag;
this.tagNumbered = tagNumbered;
this.appearanceOrder = appearanceOrder;
}
public String getTag() {
return tag;
}
public String getTagNumbered() {
return tagNumbered;
}
public int getTagNumberedLen() {
return tagNumbered == null ? 0 : tagNumbered.length();
}
public int getAppearanceOrder() {
return appearanceOrder;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyTag tagPair = (MyTag) o;
return Objects.equals(tagNumbered, tagPair.tagNumbered);
}
@Override
public int hashCode() {
return Objects.hash(tagNumbered);
}
@Override
public String toString() {
return tag;
}
}
class Record {
//Tag chain for the user
private String tag;
//Unique tag chain for identification
private String tagNumbered;
private String value;
private int appearanceOrder;
public Record(String tag, String tagNumbered, String value, int appearanceOrder) {
this.tag = tag;
this.tagNumbered = tagNumbered;
this.value = value;
this.appearanceOrder = appearanceOrder;
}
public String getTag() {
return tag;
}
public String getTagNumbered() {
return tagNumbered;
}
public int getTagNumberedLen() {
return tagNumbered == null ? 0 : tagNumbered.length();
}
public String getValue() {
return value;
}
public int getAppearanceOrder() {
return appearanceOrder;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Record record = (Record) o;
return Objects.equals(tagNumbered, record.tagNumbered);
}
@Override
public int hashCode() {
return Objects.hash(tagNumbered);
}
@Override
public String toString() {
return tag + " - " + tagNumbered + " - " + value;
}
}
TAG1.TAG2
TAG11 : value1
TAG12 : value2
TAG1.TAG2.TAG3
TAG131 : value3
TAG132 : value4
TAG133 : value5
TAG134 : value6
TAG1.TAG2.TAG3
TAG131 : value11
TAG132 : value12
TAG133 : value13
TAG134 : value14
TAG1.TAG4.TAG5
TAG21 : value7
TAG22 : value8
TAG23 : value9
TAG6
value10