Warning: file_get_contents(/data/phpspider/zhask/data//catemap/0/xml/13.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 获取两个XML树之间差异的算法(JS或伪代码)_Javascript_Xml_Typescript_Algorithm_Pseudocode - Fatal编程技术网

Javascript 获取两个XML树之间差异的算法(JS或伪代码)

Javascript 获取两个XML树之间差异的算法(JS或伪代码),javascript,xml,typescript,algorithm,pseudocode,Javascript,Xml,Typescript,Algorithm,Pseudocode,因此,我试图找出一种方法来获得两个XML树(下面的示例)之间的差异,但什么都想不出来。我需要的结果是一个差异数组,数组中的每个元素都包含已更改的节点、如何更改(添加、删除)以及节点的路径 编辑:忘了提到,XML的顺序不重要。我尝试使用npm/dom compare,但是它没有给出期望的结果(下面的例子),因为它不希望看到新的标记(dir-photos),但是没有提供任何关于它的信息,因为它发现了一个意外的标记 一, 二, 我的XML源将只包含和标记 例如,在上面的两个XML文档上,比较(

因此,我试图找出一种方法来获得两个XML树(下面的示例)之间的差异,但什么都想不出来。我需要的结果是一个差异数组,数组中的每个元素都包含已更改的节点、如何更改(添加、删除)以及节点的路径

编辑:忘了提到,XML的顺序不重要。我尝试使用npm/dom compare,但是它没有给出期望的结果(下面的例子),因为它不希望看到新的标记(dir-photos),但是没有提供任何关于它的信息,因为它发现了一个意外的标记

一,


二,


我的XML源将只包含和标记

例如,在上面的两个XML文档上,比较(1,2)应该导致:(出于我的目的,没有“更改”的更改,例如,如果文件名更改,则它是一个新文件,旧文件被视为已删除而不是移动,如果文件更改,则不包括目录)

二,

{dir:[
{
名称:'rootDir',
目录:[
{
姓名:'childDir',
文件:[
{name:'hello.jpg'},
{name:'有趣的大脑'}
]
},
{
姓名:‘照片’,
文件:[
{name:'me.dng'}
]
}
],
文件:[
{name:'img.png'}
]
}
] }
但是,这会导致额外的复杂性,因为生成的格式使用数组和对象,这至少会增加计算如何区分两者的工作量。它也可能会慢一些,因为显然您必须首先解析XML字符串,更不用说添加第三方库了

寻找任何建议或伪代码算法,我可以用来解决这个问题。应该注意,我使用的是Typescript,目标是ES6/Node.js


干杯。

我根据您对问题的描述创建了一个简单的解决方案。它可能不是真正的最优,但它完成了工作(希望如此)。看看这是不是你需要的

我们将使用这个包来处理XML

TL;DR:获取完整代码

所以,要解决这个问题,我们将经历两个步骤

步骤1:创建XML文件的映射

让我们定义一个名为“map”的数据结构(应该选择一个更具描述性的名称,但想不起来)。这张地图将是一张地图

我们的映射由键值对组成

  • 关键是路径。我们的映射将包含XML结构中的所有现有路径
  • 该值是另一个字典:
    • 键是元素的名称
    • 值是元素的标记
因此,您提供的两个示例XML结构的映射如下所示:

// recursive function to build map
function buildMap(element, path, map) {
  map[path] = {}
  // const childElements = element.childNodes.filter(childNode => childNode.type === 'element');
  for (const childNode of element.childNodes) {
    // skip text (because the xml-parse package also returns the unnecessary texts in an XML structure, e.g. line breaks)
    if (childNode.type === 'text') continue;

    // process child element
    // add child element's name to indicate that this path has a child with this name
    // use child element's type (dir/file) as the value
    map[path][childNode.attributes.name] = childNode.tagName;

    // if child element is dir, process it recursively
    if (childNode.tagName === 'dir') buildMap(childNode, `${path}/${childNode.attributes.name}`, map);
  }
}
旧地图:

{
   "/rootDir":{
      "childDir":"dir",
      "linux.txt":"file",
      "img.png":"file"
   },
   "/rootDir/childDir":{
      "hello.jpg":"file"
   }
}
新地图:

{
   "/rootDir":{
      "childDir":"dir",
      "photos":"dir",
      "img.png":"file"
   },
   "/rootDir/childDir":{
      "hello.jpg":"file",
      "interesting.brain":"file"
   },
   "/rootDir/photos":{
      "me.dng":"file"
   }
}
从XML结构构建映射的递归函数如下所示:

// recursive function to build map
function buildMap(element, path, map) {
  map[path] = {}
  // const childElements = element.childNodes.filter(childNode => childNode.type === 'element');
  for (const childNode of element.childNodes) {
    // skip text (because the xml-parse package also returns the unnecessary texts in an XML structure, e.g. line breaks)
    if (childNode.type === 'text') continue;

    // process child element
    // add child element's name to indicate that this path has a child with this name
    // use child element's type (dir/file) as the value
    map[path][childNode.attributes.name] = childNode.tagName;

    // if child element is dir, process it recursively
    if (childNode.tagName === 'dir') buildMap(childNode, `${path}/${childNode.attributes.name}`, map);
  }
}
步骤2:获取两个贴图之间的差异

现在,我们将从地图中导出更改

基本上,我们要做的是遍历旧地图的路径,获得每条路径中的子对象集(来自两个地图),并比较两组子对象以获得我们需要的更改

此步骤的功能如下所示:

// function to get the differences between two maps
function diffMaps(oldMap, newMap) {
  const changes = [];
  // traverse each path of the old map
  for (const key of Object.keys(oldMap)) {
    // get children in this path for both old map and new map
    const oldChildren = oldMap[key];
    const newChildren = newMap[key];
    changes.push(...diffChildren(key, oldChildren, newChildren));
  }
  return changes;
}

// function to get the differences between the children of two maps
function diffChildren(path, oldChildren, newChildren) {
  const changes = [];
  // traverse each child of the old children
  for (const key of Object.keys(oldChildren)) {
    // if new children also have that child ==> no change ==> remove that child from new children and continue
    if (newChildren[key]) {
      // the reason for deleting is that after we have deleted all the keys that are present in old children, the remaining keys in new children will be the newly added ones.
      delete newChildren[key];
      continue;
    }

    // new children don't have that child ==> deleted ==> add to changes
    const type = oldChildren[key];
    changes.push({
      node: type === 'dir' ? `<dir name="${key}">` : `<file name="${key}"/>`,
      path: path,
      change: 'deleted'
    });
  }

  // traverse each child of the new children and add them to changes
  for (const key of Object.keys(newChildren)) {
    const type = newChildren[key];
    changes.push({
      node: type === 'dir' ? `<dir name="${key}">` : `<file name="${key}"/>`,
      path: path,
      change: 'added'
    });
  }

  return changes;
}
//获取两个映射之间差异的函数
函数diffMaps(oldMap、newMap){
常数变化=[];
//遍历旧地图的每条路径
for(对象的常量键。键(oldMap)){
//在此路径中获取旧地图和新地图的子项
const oldChildren=oldMap[key];
const newChildren=newMap[key];
push(…diffChildren(key、oldChildren、newChildren));
}
回报变化;
}
//函数以获取两个贴图的子级之间的差异
函数diffChildren(路径、旧子对象、新子对象){
常数变化=[];
//遍历老孩子的每个孩子
for(对象的常量键。键(旧子级)){
//如果新子级也有该子级==>无更改==>从新子级中删除该子级并继续
if(新子项[键]){
//删除的原因是,我们删除了旧子系统中存在的所有密钥后,新子系统中剩余的密钥将是新添加的密钥。
删除新的子项[key];
继续;
}
//新子项没有该子项==>已删除==>添加到更改
const type=oldChildren[key];
改变。推({
节点:类型=='dir'?`:````,
路径:路径,
更改:“已删除”
});
}
//遍历新子项的每个子项并将它们添加到更改中
for(对象的常量键。键(新子项)){
const type=newChildren[key];
改变。推({
节点:类型=='dir'?`:````,
路径:路径,
更改:“添加”
});
}
回报变化;
}
最后:测试

现在我们有了必要的函数,只需插入数据并运行:)

const oldXmlString=String.raw`
`.trim();
const newXmlString=String.raw`
`.trim();
const oldXml=xml.parse(oldXmlString);
const newXml=xml.parse(newXmlString);
const oldRoot=oldXml[0];
const newRoot=newXml[0];
//将路径作为键,子节点的名称作为值进行映射
constoldmap={};
const newMap={};
buildMap(oldRoot,`/${oldRoot.attributes.name}`,oldMap);
buildMap(newRoot,`/${newRoot.attributes.name}`,newMap);
const diffs=diffMaps(oldMap、newMap);
控制台日志(差异);
输出:

[ { node: '<file name="linux.txt"/>',
    path: '/rootDir',
    change: 'deleted' },
  { node: '<dir name="photos">',
    path: '/rootDir',
    change: 'added' },
  { node: '<file name="interesting.brain"/>',
    path: '/rootDir/childDir',
    change: 'added' } ]
[{节点:“”,
路径:'/rootDir',
更改:“已删除”},
{节点:“”,
路径:'/rootDir',
更改:'添加'},
{节点:“”,
路径:'/rootD
[ { node: '<file name="linux.txt"/>',
    path: '/rootDir',
    change: 'deleted' },
  { node: '<dir name="photos">',
    path: '/rootDir',
    change: 'added' },
  { node: '<file name="interesting.brain"/>',
    path: '/rootDir/childDir',
    change: 'added' } ]