docs: protected void mergeChildren(final List<Node> childrenNodes, final List<CSNodeWrapper> contentSpecNodes,
              final DataProviderFactory providerFactory, final CSNodeWrapper parentNode, final ContentSpecWrapper contentSpec,
              final Map<SpecNode, CSNodeWrapper> nodeMapping) throws Exception {
          final CSNodeProvider nodeProvider = providerFactory.getProvider(CSNodeProvider.class);
          final CSInfoNodeProvider nodeInfoProvider = providerFactory.getProvider(CSInfoNodeProvider.class);
  
          UpdateableCollectionWrapper<CSNodeWrapper> levelChildren;
          if (parentNode == null) {
              levelChildren = contentSpec.getChildren();
  
              // Check that the level container isn't null
              if (levelChildren == null) {
                  levelChildren = nodeProvider.newCSNodeCollection();
              }
          } else {
              levelChildren = parentNode.getChildren();
  
              // Check that the level container isn't null
              if (levelChildren == null) {
                  levelChildren = nodeProvider.newCSNodeCollection();
              }
          }
  
          // Update or create all of the children nodes that exist in the content spec
          CSNodeWrapper prevNode = null;
          for (final Node childNode : childrenNodes) {
              if (!isTransformableNode(childNode)) {
                  continue;
              }
  
              LOG.debug("Processing: {}", childNode.getText());
  
              // Find the Entity Node that matches the Content Spec node, if one exists
              CSNodeWrapper foundNodeEntity = findExistingNode(parentNode, childNode, contentSpecNodes);
  
              // If the node was not found create a new one
              boolean changed = false;
              boolean newNode = false;
              if (foundNodeEntity == null) {
                  LOG.debug("Creating a new node");
                  final CSNodeWrapper newCSNodeEntity = nodeProvider.newCSNode();
                  if (childNode instanceof SpecTopic) {
                      final SpecTopic specTopic = (SpecTopic) childNode;
                      mergeTopic(specTopic, newCSNodeEntity);
                      if (specTopic.getTopicType().equals(TopicType.INITIAL_CONTENT)) {
                          newCSNodeEntity.setNodeType(CommonConstants.CS_NODE_INITIAL_CONTENT_TOPIC);
                      } else {
                          newCSNodeEntity.setNodeType(CommonConstants.CS_NODE_TOPIC);
                      }
                  } else if (childNode instanceof Level) {
                      mergeLevel((Level) childNode, newCSNodeEntity);
                      newCSNodeEntity.setNodeType(((Level) childNode).getLevelType().getId());
                  } else if (childNode instanceof Comment) {
                      mergeComment((Comment) childNode, newCSNodeEntity);
                      newCSNodeEntity.setNodeType(CommonConstants.CS_NODE_COMMENT);
                  } else if (childNode instanceof CommonContent) {
                      mergeCommonContent((CommonContent) childNode, newCSNodeEntity);
                      newCSNodeEntity.setNodeType(CommonConstants.CS_NODE_COMMON_CONTENT);
                  } else if (childNode instanceof File) {
                      mergeFile((File) childNode, newCSNodeEntity);
                      newCSNodeEntity.setNodeType(CommonConstants.CS_NODE_FILE);
                  } else {
                      mergeMetaData((KeyValueNode<?>) childNode, newCSNodeEntity);
                      if (((KeyValueNode<?>) childNode).getValue() instanceof SpecTopic) {
                          newCSNodeEntity.setNodeType(CommonConstants.CS_NODE_META_DATA_TOPIC);
                      } else {
                          newCSNodeEntity.setNodeType(CommonConstants.CS_NODE_META_DATA);
                      }
                  }
  
                  // set the content spec for the new entity
                  foundNodeEntity = newCSNodeEntity;
                  newNode = true;
              } else {
                  LOG.debug("Found existing node {}", foundNodeEntity.getId());
  
                  // If the node was found remove it from the list of content spec nodes, so it can no longer be matched
                  contentSpecNodes.remove(foundNodeEntity);
  
                  if (childNode instanceof SpecTopic) {
                      final SpecTopic specTopic = (SpecTopic) childNode;
                      if (mergeTopic((SpecTopic) childNode, foundNodeEntity)) {
                          changed = true;
                      }
                      if (specTopic.getTopicType().equals(
                              TopicType.INITIAL_CONTENT) && (foundNodeEntity.getNodeType() == null || !foundNodeEntity.getNodeType().equals(
                              CommonConstants.CS_NODE_INITIAL_CONTENT_TOPIC))) {
                          foundNodeEntity.setNodeType(CommonConstants.CS_NODE_INITIAL_CONTENT_TOPIC);
                          changed = true;
                      } else if (!specTopic.getTopicType().equals(
                              TopicType.INITIAL_CONTENT) && (foundNodeEntity.getNodeType() == null || !foundNodeEntity.getNodeType().equals(
                              CommonConstants.CS_NODE_TOPIC))) {
                          foundNodeEntity.setNodeType(CommonConstants.CS_NODE_TOPIC);
                          changed = true;
                      }
                  } else if (childNode instanceof Level) {
                      if (mergeLevel((Level) childNode, foundNodeEntity)) {
                          changed = true;
                      }
                  } else if (childNode instanceof Comment) {
                      if (mergeComment((Comment) childNode, foundNodeEntity)) {
                          changed = true;
                      }
                  } else if (childNode instanceof CommonContent) {
                      if (mergeCommonContent((CommonContent) childNode, foundNodeEntity)) {
                          changed = true;
                      }
                  } else if (childNode instanceof File) {
                      if (mergeFile((File) childNode, foundNodeEntity)) {
                          changed = true;
                      }
                  } else {
                      if (mergeMetaData((KeyValueNode<?>) childNode, foundNodeEntity)) {
                          changed = true;
                      }
                  }
  
                  // Check if the parent node is different, if so then set it as moved
                  if (!doesParentMatch(parentNode, foundNodeEntity.getParent())) {
                      LOG.debug("Setting entity {} as moved", foundNodeEntity.getId());
                      changed = true;
                  }
              }
  
              // If the node is a level than merge the children nodes as well
              if (childNode instanceof Level) {
                  final Level level = (Level) childNode;
  
                  // Merge the info node if one exists
                  CSInfoNodeWrapper infoEntity = foundNodeEntity.getInfoTopicNode();
                  if (level.getInfoTopic() != null) {
                      final InfoTopic infoTopic = level.getInfoTopic();
                      boolean matches = doesInfoTopicMatch(infoTopic, infoEntity, foundNodeEntity);
                      if (!matches) {
                          // Create a new entity if needed
                          if (infoEntity == null) {
                              infoEntity = nodeInfoProvider.newCSNodeInfo(foundNodeEntity);
                              foundNodeEntity.setInfoTopicNode(infoEntity);
                              changed = true;
                          }
                      }
  
                      // Merge the changes
                      if (mergeLevelInfoTopic(infoTopic, infoEntity)) {
                          changed = true;
                      }
                  } else if (infoEntity != null) {
                      foundNodeEntity.setInfoTopicNode(null);
                      changed = true;
                  }
  
                  // Get the child nodes
                  final LinkedList<Node> children = new LinkedList<Node>(level.getChildNodes());
  
                  final ArrayList<CSNodeWrapper> currentChildren = new ArrayList<CSNodeWrapper>();
                  if (foundNodeEntity.getChildren() != null) {
                      currentChildren.addAll(foundNodeEntity.getChildren().getItems());
                  }
  
                  mergeChildren(getTransformableNodes(children), currentChildren, providerFactory, foundNodeEntity, contentSpec, nodeMapping);
              } else if (childNode instanceof FileList) {
                  final FileList fileList = (FileList) childNode;
  
                  // Get all the files from the list
                  final LinkedList<Node> children = new LinkedList<Node>();
                  if (fileList.getValue() != null) {
                      children.addAll(fileList.getValue());
                  }
  
                  final ArrayList<CSNodeWrapper> currentChildren = new ArrayList<CSNodeWrapper>();
                  if (foundNodeEntity.getChildren() != null) {
                      currentChildren.addAll(foundNodeEntity.getChildren().getItems());
                  }
  
                  mergeChildren(getTransformableNodes(children), currentChildren, providerFactory, foundNodeEntity, contentSpec, nodeMapping);
              }
  
              // Set up the next node relationship for the previous node
              if (prevNode != null && (prevNode.getNextNode() == null || (prevNode.getNextNode() != null && foundNodeEntity.getId() ==
                      null) || (prevNode.getNextNode() != null && !prevNode.getNextNode().getId().equals(
                      foundNodeEntity.getId())))) {
                  prevNode.setNextNode(foundNodeEntity);
                  changed = true;
  
                  // Add the previous node to the updated collection if it's not already there
                  if (parentNode == null) {
                      addContentSpecChild(contentSpec, levelChildren, prevNode);
                  } else {
                      addChild(parentNode, levelChildren, prevNode);
                  }
              }
  
              // The node has been updated or created so update it's state
              if (changed || newNode) {
                  if (parentNode == null) {
                      addContentSpecChild(contentSpec, levelChildren, foundNodeEntity);
                  } else {
                      addChild(parentNode, levelChildren, foundNodeEntity);
                  }
              }
  
              // Add the node to the mapping of nodes to entity nodes
              if (childNode instanceof SpecNode) {
                  nodeMapping.put((SpecNode) childNode, foundNodeEntity);
              }
  
              // Set the previous node to the current node since processing is done
              prevNode = foundNodeEntity;
          }
  
          // If there is a previous node then make sure it's next node id is null as it's the last in the linked list
          if (prevNode != null && prevNode.getNextNode() != null) {
              prevNode.setNextNode(null);
  
              // Add the previous node to the updated collection if it's not already there
              if (parentNode == null) {
                  addContentSpecChild(contentSpec, levelChildren, prevNode);
              } else {
                  addChild(parentNode, levelChildren, prevNode);
              }
          }
  
          // Set the nodes that are no longer used for removal
          if (!contentSpecNodes.isEmpty()) {
              for (final CSNodeWrapper childNode : contentSpecNodes) {
                  LOG.debug("Removing entity {} - {}", childNode.getId(), childNode.getTitle());
                  levelChildren.remove(childNode);
                  levelChildren.addRemoveItem(childNode);
  
                  // Remove any incoming relationships as well, since this node has been removed
                  if (childNode.getRelatedFromNodes() != null) {
                      final UpdateableCollectionWrapper<CSRelatedNodeWrapper> relatedNodes = childNode.getRelatedFromNodes();
                      for (final CSRelatedNodeWrapper relatedNode : relatedNodes.getItems()) {
                          relatedNodes.remove(relatedNode);
                          relatedNodes.addRemoveItem(relatedNode);
                      }
  
                      childNode.setRelatedFromNodes(relatedNodes);
                  }
              }
          }
  
          // Set the children so it'll be updated
          if (parentNode == null) {
              contentSpec.setChildren(levelChildren);
          } else {
              parentNode.setChildren(levelChildren);
          }
      }