Part 1: Refactor CPS Delta code to utility class 88/141288/11
authorArpit Singh <AS00745003@techmahindra.com>
Thu, 26 Jun 2025 14:47:11 +0000 (20:17 +0530)
committerArpit Singh <AS00745003@techmahindra.com>
Fri, 11 Jul 2025 04:53:00 +0000 (10:23 +0530)
 - Refactoring Delta code to separate utility class for a more granular
   and manageble code.
 - Patch 1 refactors code for 'update' action as the code is shared
   between old and new delta report formats.

Issue-ID: CPS-2838
Change-Id: I74425817f1b21a1b369986f65f12dba8a501f3ea
Signed-off-by: Arpit Singh <AS00745003@techmahindra.com>
cps-service/src/main/java/org/onap/cps/impl/CpsDeltaServiceImpl.java
cps-service/src/main/java/org/onap/cps/utils/deltareport/DeltaReportHelper.java [new file with mode: 0644]
cps-service/src/test/groovy/org/onap/cps/impl/CpsDeltaServiceImplSpec.groovy

index c902c00..1a5cdab 100644 (file)
@@ -27,11 +27,9 @@ import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.stream.Collectors;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -43,11 +41,11 @@ import org.onap.cps.api.model.Anchor;
 import org.onap.cps.api.model.DataNode;
 import org.onap.cps.api.model.DeltaReport;
 import org.onap.cps.api.parameters.FetchDescendantsOption;
-import org.onap.cps.cpspath.parser.CpsPathQuery;
 import org.onap.cps.cpspath.parser.CpsPathUtil;
 import org.onap.cps.utils.DataMapUtils;
 import org.onap.cps.utils.DataMapper;
 import org.onap.cps.utils.JsonObjectMapper;
+import org.onap.cps.utils.deltareport.DeltaReportHelper;
 import org.springframework.stereotype.Service;
 
 @Slf4j
@@ -60,6 +58,7 @@ public class CpsDeltaServiceImpl implements CpsDeltaService {
     private final DataNodeFactory dataNodeFactory;
     private final DataMapper dataMapper;
     private final JsonObjectMapper jsonObjectMapper;
+    private final DeltaReportHelper deltaReportHelper;
 
     @Override
     @Timed(value = "cps.delta.service.get.delta",
@@ -99,7 +98,7 @@ public class CpsDeltaServiceImpl implements CpsDeltaService {
         return getDeltaReports(sourceDataNodesRebuilt, targetDataNodes, groupDataNodes);
     }
 
-    private static List<DeltaReport> getDeltaReports(final Collection<DataNode> sourceDataNodes,
+    private List<DeltaReport> getDeltaReports(final Collection<DataNode> sourceDataNodes,
                                               final Collection<DataNode> targetDataNodes,
                                               final boolean groupDataNodes) {
 
@@ -127,7 +126,7 @@ public class CpsDeltaServiceImpl implements CpsDeltaService {
         return xpathToDataNode;
     }
 
-    private static List<DeltaReport> getRemovedAndUpdatedDeltaReports(
+    private List<DeltaReport> getRemovedAndUpdatedDeltaReports(
                                                                 final Map<String, DataNode> xpathToSourceDataNodes,
                                                                 final Map<String, DataNode> xpathToTargetDataNodes) {
         final List<DeltaReport> removedAndUpdatedDeltaReportEntries = new ArrayList<>();
@@ -139,7 +138,7 @@ public class CpsDeltaServiceImpl implements CpsDeltaService {
             if (targetDataNode == null) {
                 deltaReports = getDeltaReportsForRemove(xpath, sourceDataNode);
             } else {
-                deltaReports = getDeltaReportsForUpdates(xpath, sourceDataNode, targetDataNode);
+                deltaReports = deltaReportHelper.createDeltaReportsForUpdates(xpath, sourceDataNode, targetDataNode);
             }
             removedAndUpdatedDeltaReportEntries.addAll(deltaReports);
         }
@@ -155,132 +154,6 @@ public class CpsDeltaServiceImpl implements CpsDeltaService {
         return deltaReportEntriesForRemove;
     }
 
-    private static List<DeltaReport> getDeltaReportsForUpdates(final String xpath, final DataNode sourceDataNode,
-                                                               final DataNode targetDataNode) {
-        final List<DeltaReport> deltaReportEntriesForUpdates = new ArrayList<>();
-        final Map<Map<String, Serializable>, Map<String, Serializable>> updatedSourceDataToTargetData =
-                getUpdatedSourceAndTargetDataNode(sourceDataNode, targetDataNode);
-        if (!updatedSourceDataToTargetData.isEmpty()) {
-            addUpdatedDataToDeltaReport(xpath, updatedSourceDataToTargetData, deltaReportEntriesForUpdates);
-        }
-        return deltaReportEntriesForUpdates;
-    }
-
-    private static Map<Map<String, Serializable>, Map<String, Serializable>> getUpdatedSourceAndTargetDataNode(
-                                                            final DataNode sourceDataNode,
-                                                            final DataNode targetDataNode) {
-        final Map<String, Serializable> updatedLeavesInSourceData = new HashMap<>();
-        final Map<String, Serializable> updatedLeavesInTargetData = new HashMap<>();
-        processSourceAndTargetDataNode(sourceDataNode, targetDataNode,
-            updatedLeavesInSourceData, updatedLeavesInTargetData);
-        processUniqueDataInTargetDataNode(sourceDataNode, targetDataNode,
-            updatedLeavesInSourceData, updatedLeavesInTargetData);
-        final Map<String, Serializable> updatedSourceData =
-            getUpdatedNodeData(sourceDataNode, updatedLeavesInSourceData);
-        final Map<String, Serializable> updatedTargetData =
-            getUpdatedNodeData(targetDataNode, updatedLeavesInTargetData);
-        if (updatedSourceData.isEmpty() && updatedTargetData.isEmpty()) {
-            return Collections.emptyMap();
-        }
-        return Collections.singletonMap(updatedSourceData, updatedTargetData);
-    }
-
-    private static void processSourceAndTargetDataNode(
-                                                            final DataNode sourceDataNode,
-                                                            final DataNode targetDataNode,
-                                                            final Map<String, Serializable> sourceDataInDeltaReport,
-                                                            final Map<String, Serializable> targetDataInDeltaReport) {
-        final Map<String, Serializable> leavesOfSourceDataNode = sourceDataNode.getLeaves();
-        final Map<String, Serializable> leavesOfTargetDataNode = targetDataNode.getLeaves();
-        for (final Map.Entry<String, Serializable> entry: leavesOfSourceDataNode.entrySet()) {
-            final String key = entry.getKey();
-            final Serializable sourceLeaf = entry.getValue();
-            final Serializable targetLeaf = leavesOfTargetDataNode.get(key);
-            compareLeaves(key, sourceLeaf, targetLeaf, sourceDataInDeltaReport, targetDataInDeltaReport);
-        }
-    }
-
-    private static void processUniqueDataInTargetDataNode(
-                                                            final DataNode sourceDataNode,
-                                                            final DataNode targetDataNode,
-                                                            final Map<String, Serializable> sourceDataInDeltaReport,
-                                                            final Map<String, Serializable> targetDataInDeltaReport) {
-        final Map<String, Serializable> leavesOfSourceDataNode = sourceDataNode.getLeaves();
-        final Map<String, Serializable> uniqueLeavesOfTargetDataNode =
-                new HashMap<>(targetDataNode.getLeaves());
-        uniqueLeavesOfTargetDataNode.keySet().removeAll(leavesOfSourceDataNode.keySet());
-        for (final Map.Entry<String, Serializable> entry: uniqueLeavesOfTargetDataNode.entrySet()) {
-            final String key = entry.getKey();
-            final Serializable targetLeaf = entry.getValue();
-            final Serializable sourceLeaf = leavesOfSourceDataNode.get(key);
-            compareLeaves(key, sourceLeaf, targetLeaf, sourceDataInDeltaReport, targetDataInDeltaReport);
-        }
-    }
-
-    private static void compareLeaves(final String key,
-                                      final Serializable sourceLeaf,
-                                      final Serializable targetLeaf,
-                                      final Map<String, Serializable> sourceDataInDeltaReport,
-                                      final Map<String, Serializable> targetDataInDeltaReport) {
-        if (sourceLeaf != null && targetLeaf != null) {
-            if (!Objects.equals(sourceLeaf, targetLeaf)) {
-                sourceDataInDeltaReport.put(key, sourceLeaf);
-                targetDataInDeltaReport.put(key, targetLeaf);
-            }
-        } else if (sourceLeaf == null) {
-            targetDataInDeltaReport.put(key, targetLeaf);
-        } else {
-            sourceDataInDeltaReport.put(key, sourceLeaf);
-        }
-    }
-
-    private static Map<String, Serializable> getUpdatedNodeData(final DataNode dataNode,
-                                                                final Map<String, Serializable> updatedLeaves) {
-        final Map<String, Serializable> updatedSourceData = new HashMap<>();
-        if (!updatedLeaves.isEmpty()) {
-            final String xpath = dataNode.getXpath();
-            if (CpsPathUtil.isPathToListElement(xpath)) {
-                addKeyLeavesToUpdatedData(xpath, updatedLeaves);
-            }
-            final Collection<DataNode> updatedDataNode = buildUpdatedDataNode(dataNode, updatedLeaves);
-            updatedSourceData.putAll(getCondensedDataForDeltaReport(updatedDataNode));
-        }
-        return updatedSourceData;
-    }
-
-    private static void addKeyLeavesToUpdatedData(final String xpath,
-                                                  final Map<String, Serializable> updatedLeaves) {
-        final Map<String, Serializable> keyLeaves = new HashMap<>();
-        final List<CpsPathQuery.LeafCondition> leafConditions = CpsPathUtil.getCpsPathQuery(xpath).getLeafConditions();
-        for (final CpsPathQuery.LeafCondition leafCondition: leafConditions) {
-            final String leafName = leafCondition.name();
-            final Serializable leafValue = (Serializable) leafCondition.value();
-            keyLeaves.put(leafName, leafValue);
-        }
-        updatedLeaves.putAll(keyLeaves);
-    }
-
-    private static Collection<DataNode> buildUpdatedDataNode(final DataNode dataNode,
-                                                             final Map<String, Serializable> updatedLeaves) {
-        final DataNode updatedDataNode = new DataNodeBuilder()
-            .withXpath(dataNode.getXpath())
-            .withModuleNamePrefix(dataNode.getModuleNamePrefix())
-            .withLeaves(updatedLeaves)
-            .build();
-        return Collections.singletonList(updatedDataNode);
-    }
-
-    private static void addUpdatedDataToDeltaReport(final String xpath,
-                        final Map<Map<String, Serializable>, Map<String, Serializable>> updatedSourceDataToTargetData,
-                        final List<DeltaReport> deltaReportEntriesForUpdates) {
-        for (final Map.Entry<Map<String, Serializable>, Map<String, Serializable>> entry:
-            updatedSourceDataToTargetData.entrySet()) {
-            final DeltaReport updatedDataForDeltaReport = new DeltaReportBuilder().actionReplace().withXpath(xpath)
-                .withSourceData(entry.getKey()).withTargetData(entry.getValue()).build();
-            deltaReportEntriesForUpdates.add(updatedDataForDeltaReport);
-        }
-    }
-
     private static List<DeltaReport> getAddedDeltaReports(final Map<String, DataNode> xpathToSourceDataNodes,
                                                           final Map<String, DataNode> xpathToTargetDataNodes) {
 
@@ -323,7 +196,7 @@ public class CpsDeltaServiceImpl implements CpsDeltaService {
         }
     }
 
-    private static List<DeltaReport> getCondensedDeltaReports(final Collection<DataNode> sourceDataNodes,
+    private List<DeltaReport> getCondensedDeltaReports(final Collection<DataNode> sourceDataNodes,
                                                               final Collection<DataNode> targetDataNodes) {
 
         final List<DeltaReport> deltaReportEntries = new ArrayList<>();
@@ -348,21 +221,23 @@ public class CpsDeltaServiceImpl implements CpsDeltaService {
         return deltaReportEntriesForRemove;
     }
 
-    private static List<DeltaReport> getCondensedUpdatedDeltaReports(final Collection<DataNode> sourceDataNodes,
+    private List<DeltaReport> getCondensedUpdatedDeltaReports(final Collection<DataNode> sourceDataNodes,
                                                                    final Map<String, DataNode> xpathToTargetDataNodes) {
         final List<DeltaReport> deltaReportEntriesForUpdates = new ArrayList<>();
         for (final DataNode sourceDataNode : sourceDataNodes) {
             final String xpath = sourceDataNode.getXpath();
             if (xpathToTargetDataNodes.containsKey(xpath)) {
                 final DataNode targetDataNode = xpathToTargetDataNodes.get(xpath);
-                deltaReportEntriesForUpdates.addAll(getDeltaReportsForUpdates(xpath, sourceDataNode, targetDataNode));
+                final List<DeltaReport> updatedDataForDeltaReport =
+                        deltaReportHelper.createDeltaReportsForUpdates(xpath, sourceDataNode, targetDataNode);
+                deltaReportEntriesForUpdates.addAll(updatedDataForDeltaReport);
                 getCondensedDeltaReportsForChildDataNodes(sourceDataNode, targetDataNode, deltaReportEntriesForUpdates);
             }
         }
         return deltaReportEntriesForUpdates;
     }
 
-    private static void getCondensedDeltaReportsForChildDataNodes(final DataNode sourceDataNode,
+    private void getCondensedDeltaReportsForChildDataNodes(final DataNode sourceDataNode,
                                                                   final DataNode targetDataNode,
                                                                   final List<DeltaReport> deltaReportEntries) {
         final Collection<DataNode> childrenOfSourceDataNodes = sourceDataNode.getChildDataNodes();
diff --git a/cps-service/src/main/java/org/onap/cps/utils/deltareport/DeltaReportHelper.java b/cps-service/src/main/java/org/onap/cps/utils/deltareport/DeltaReportHelper.java
new file mode 100644 (file)
index 0000000..0112718
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ *  ============LICENSE_START=======================================================
+ *  Copyright (C) 2025 TechMahindra Ltd.
+ *  ================================================================================
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  SPDX-License-Identifier: Apache-2.0
+ *  ============LICENSE_END=========================================================
+ */
+
+package org.onap.cps.utils.deltareport;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import lombok.extern.slf4j.Slf4j;
+import org.onap.cps.api.model.DataNode;
+import org.onap.cps.api.model.DeltaReport;
+import org.onap.cps.cpspath.parser.CpsPathQuery;
+import org.onap.cps.cpspath.parser.CpsPathUtil;
+import org.onap.cps.impl.DataNodeBuilder;
+import org.onap.cps.impl.DeltaReportBuilder;
+import org.onap.cps.utils.DataMapUtils;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+public class DeltaReportHelper {
+
+    /**
+     * Get delta report entries for updates.
+     *
+     * @param xpath          the xpath of the data node
+     * @param sourceDataNode the source data node
+     * @param targetDataNode the target data node
+     * @return               a list of delta report entries for updates
+     */
+    public List<DeltaReport> createDeltaReportsForUpdates(final String xpath, final DataNode sourceDataNode,
+                                                          final DataNode targetDataNode) {
+        final List<DeltaReport> deltaReportEntriesForUpdates = new ArrayList<>();
+        final Map<Map<String, Serializable>, Map<String, Serializable>> updatedSourceDataToTargetData =
+            getUpdatedSourceAndTargetDataNode(sourceDataNode, targetDataNode);
+        if (!updatedSourceDataToTargetData.isEmpty()) {
+            addUpdatedDataToDeltaReport(xpath, updatedSourceDataToTargetData, deltaReportEntriesForUpdates);
+        }
+        return deltaReportEntriesForUpdates;
+    }
+
+    private static Map<Map<String, Serializable>, Map<String, Serializable>> getUpdatedSourceAndTargetDataNode(
+        final DataNode sourceDataNode,
+        final DataNode targetDataNode) {
+        final Map<String, Serializable> updatedLeavesInSourceData = new HashMap<>();
+        final Map<String, Serializable> updatedLeavesInTargetData = new HashMap<>();
+        processSourceAndTargetDataNode(sourceDataNode, targetDataNode,
+            updatedLeavesInSourceData, updatedLeavesInTargetData);
+        processUniqueDataInTargetDataNode(sourceDataNode, targetDataNode, updatedLeavesInTargetData);
+        final Map<String, Serializable> updatedSourceData =
+            getUpdatedNodeData(sourceDataNode, updatedLeavesInSourceData);
+        final Map<String, Serializable> updatedTargetData =
+            getUpdatedNodeData(targetDataNode, updatedLeavesInTargetData);
+        if (updatedSourceData.isEmpty() && updatedTargetData.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        return Collections.singletonMap(updatedSourceData, updatedTargetData);
+    }
+
+    private static void addUpdatedDataToDeltaReport(final String xpath,
+                                                    final Map<Map<String, Serializable>,
+                                                        Map<String, Serializable>> updatedSourceDataToTargetData,
+                                                    final List<DeltaReport> deltaReportEntriesForUpdates) {
+        for (final Map.Entry<Map<String, Serializable>, Map<String, Serializable>> entry:
+            updatedSourceDataToTargetData.entrySet()) {
+            final DeltaReport updatedDataForDeltaReport = new DeltaReportBuilder().actionReplace().withXpath(xpath)
+                .withSourceData(entry.getKey()).withTargetData(entry.getValue()).build();
+            deltaReportEntriesForUpdates.add(updatedDataForDeltaReport);
+        }
+    }
+
+    private static void processSourceAndTargetDataNode(
+        final DataNode sourceDataNode,
+        final DataNode targetDataNode,
+        final Map<String, Serializable> sourceDataInDeltaReport,
+        final Map<String, Serializable> targetDataInDeltaReport) {
+        final Map<String, Serializable> leavesOfSourceDataNode = sourceDataNode.getLeaves();
+        final Map<String, Serializable> leavesOfTargetDataNode = targetDataNode.getLeaves();
+        for (final Map.Entry<String, Serializable> entry: leavesOfSourceDataNode.entrySet()) {
+            final String key = entry.getKey();
+            final Serializable sourceLeaf = entry.getValue();
+            final Serializable targetLeaf = leavesOfTargetDataNode.get(key);
+            compareLeaves(key, sourceLeaf, targetLeaf, sourceDataInDeltaReport, targetDataInDeltaReport);
+        }
+    }
+
+    private static void processUniqueDataInTargetDataNode(
+        final DataNode sourceDataNode,
+        final DataNode targetDataNode,
+        final Map<String, Serializable> targetDataInDeltaReport) {
+        final Map<String, Serializable> leavesOfSourceDataNode = sourceDataNode.getLeaves();
+        final Map<String, Serializable> uniqueLeavesOfTargetDataNode =
+            new HashMap<>(targetDataNode.getLeaves());
+        uniqueLeavesOfTargetDataNode.keySet().removeAll(leavesOfSourceDataNode.keySet());
+        targetDataInDeltaReport.putAll(uniqueLeavesOfTargetDataNode);
+    }
+
+    private static void compareLeaves(final String key,
+                                      final Serializable sourceLeaf,
+                                      final Serializable targetLeaf,
+                                      final Map<String, Serializable> sourceDataInDeltaReport,
+                                      final Map<String, Serializable> targetDataInDeltaReport) {
+        if (targetLeaf != null) {
+            if (!Objects.equals(sourceLeaf, targetLeaf)) {
+                sourceDataInDeltaReport.put(key, sourceLeaf);
+                targetDataInDeltaReport.put(key, targetLeaf);
+            }
+        } else {
+            sourceDataInDeltaReport.put(key, sourceLeaf);
+        }
+    }
+
+    private static Map<String, Serializable> getUpdatedNodeData(final DataNode dataNode,
+                                                                final Map<String, Serializable> updatedLeaves) {
+        final Map<String, Serializable> updatedSourceData = new HashMap<>();
+        if (!updatedLeaves.isEmpty()) {
+            final String xpath = dataNode.getXpath();
+            if (CpsPathUtil.isPathToListElement(xpath)) {
+                addKeyLeavesToUpdatedData(xpath, updatedLeaves);
+            }
+            final Collection<DataNode> updatedDataNode = buildUpdatedDataNode(dataNode, updatedLeaves);
+            updatedSourceData.putAll(getCondensedDataForDeltaReport(updatedDataNode));
+        }
+        return updatedSourceData;
+    }
+
+    private static void addKeyLeavesToUpdatedData(final String xpath,
+                                                  final Map<String, Serializable> updatedLeaves) {
+        final Map<String, Serializable> keyLeaves = new HashMap<>();
+        final List<CpsPathQuery.LeafCondition> leafConditions = CpsPathUtil.getCpsPathQuery(xpath).getLeafConditions();
+        for (final CpsPathQuery.LeafCondition leafCondition: leafConditions) {
+            final String leafName = leafCondition.name();
+            final Serializable leafValue = (Serializable) leafCondition.value();
+            keyLeaves.put(leafName, leafValue);
+        }
+        updatedLeaves.putAll(keyLeaves);
+    }
+
+    private static Collection<DataNode> buildUpdatedDataNode(final DataNode dataNode,
+                                                             final Map<String, Serializable> updatedLeaves) {
+        final DataNode updatedDataNode = new DataNodeBuilder()
+            .withXpath(dataNode.getXpath())
+            .withModuleNamePrefix(dataNode.getModuleNamePrefix())
+            .withLeaves(updatedLeaves)
+            .build();
+        return Collections.singletonList(updatedDataNode);
+    }
+
+    private static Map<String, Serializable> getCondensedDataForDeltaReport(final Collection<DataNode> dataNodes) {
+        final DataNode containerNode = new DataNodeBuilder().withChildDataNodes(dataNodes).build();
+        final Map<String, Object> condensedData = DataMapUtils.toDataMap(containerNode);
+        return condensedData.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey,
+            entry -> (Serializable) entry.getValue()));
+    }
+}
index 82fc893..97618de 100644 (file)
@@ -36,6 +36,7 @@ import org.onap.cps.utils.JsonObjectMapper
 import org.onap.cps.utils.PrefixResolver
 import org.onap.cps.utils.YangParser
 import org.onap.cps.utils.YangParserHelper
+import org.onap.cps.utils.deltareport.DeltaReportHelper
 import org.onap.cps.yang.TimedYangTextSchemaSourceSetBuilder
 import org.onap.cps.yang.YangTextSchemaSourceSet
 import org.onap.cps.yang.YangTextSchemaSourceSetBuilder
@@ -58,7 +59,8 @@ class CpsDeltaServiceImplSpec extends Specification {
     def mockPrefixResolver = Mock(PrefixResolver)
     def dataMapper = new DataMapper(mockCpsAnchorService, mockPrefixResolver)
     def jsonObjectMapper = new JsonObjectMapper(new ObjectMapper())
-    def objectUnderTest = new CpsDeltaServiceImpl(mockCpsAnchorService, mockCpsDataService, dataNodeFactory, dataMapper, jsonObjectMapper)
+    def deltaReportHelper = new DeltaReportHelper()
+    def objectUnderTest = new CpsDeltaServiceImpl(mockCpsAnchorService, mockCpsDataService, dataNodeFactory, dataMapper, jsonObjectMapper, deltaReportHelper)
 
     static def bookstoreDataNodeWithParentXpath = [new DataNode(xpath: '/bookstore', leaves: ['bookstore-name': 'Easons'])]
     static def bookstoreDataNodeWithChildXpath = [new DataNode(xpath: '/bookstore/categories[@code=\'02\']', leaves: ['code': '02', 'name': 'Kids'])]