Вы можете использовать jqgram, реализацию аппроксимации расстояния редактирования дерева PQ-Gram, чтобы конкретно решить эту проблему, но вам нужно будет запустить Node.js, если вы не хотите портировать на C #. Порт должен быть довольно простым, хотя ... алгоритм не так уж и сложен. Красота в простоте.
https://github.com/hoonto/jqgram
В этом примере показан пример DOM vs cheerio, который показывает, как обращаться с дочерними элементами и метками, чтобы сгенерировать приблизительное расстояние редактирования дерева. В результате вы получите число от нуля до единицы, и это ваше процентное равенство. Но обратите внимание, что нулевое значение не обязательно указывает на идентичные деревья, это только означает, что они очень похожи. Вы можете достаточно легко сравнить DOM с DOM или Cheerio против Cheerio - или использовать анализ HTML, который использует Cheerio, вместо того, чтобы беспокоиться об использовании всей библиотеки (Cheerio из коробки довольно быстро работает на jQuery- и DOM-стороне на стороне сервера реализация).
Очевидно, что это решение относится к Node.js и javascript для браузера, но я думаю, что эти проблемы могут быть проще, чем портирование на C # /. NET.
// This could probably be optimized significantly, but is a real-world
// example of how to use tree edit distance in the browser.
// For cheerio, you'll have to browserify,
// which requires some fiddling around
// due to cheerio's dynamically generated
// require's (good grief) that browserify
// does not see due to the static nature
// of its code analysis (dynamic off-line
// analysis is hard, but doable).
//
// Ultimately, the goal is to end up with
// something like this in the browser:
var cheerio = require('./lib/cheerio');
// The easy part, jqgram:
var jq = require("../jqgram").jqgram;
// Make a cheerio DOM:
var html = '<body><div id="a"><div class="c d"><span>Irrelevent text</span></div></div></body>';
var cheeriodom = cheerio.load(html, {
ignoreWhitespace: false,
lowerCaseTags: true
});
// For ease, lets assume you have jQuery laoded:
var realdom = $('body');
// The lfn and cfn functions allow you to specify
// how labels and children should be defined:
jq.distance({
root: cheeriodom,
lfn: function(node){
// We don't have to lowercase this because we already
// asked cheerio to do that for us above (lowerCaseTags).
return node.name;
},
cfn: function(node){
// Cheerio maintains attributes in the attribs array:
// We're going to put id's and classes in as children
// of nodes in our cheerio tree
var retarr = [];
if(!! node.attribs && !! node.attribs.class){
retarr = retarr.concat(node.attribs.class.split(' '));
}
if(!! node.attribs && !! node.attribs.id){
retarr.push(node.attribs.id);
}
retarr = retarr.concat(node.children);
return retarr;
}
},{
root: realdom,
lfn: function(node){
return node.nodeName.toLowerCase();
},
cfn: function(node){
var retarr = [];
if(!! node.attributes && !! node.attributes.class && !! node.attributes.class.nodeValue){
retarr = retarr.concat(node.attributes.class.nodeValue.split(' '));
}
if(!! node.attributes && !! node.attributes.id && !! node.attributes.id.nodeValue) {
retarr.push(node.attributes.id.nodeValue);
}
for(var i=0; i<node.children.length; ++i){
retarr.push(node.children[i]);
}
return retarr;
}
},{ p:2, q:3, depth:10 },
function(result) {
console.log(result.distance);
});