XML字符串对比,按XML绝对路径(如root.xxx.xx.xx)忽略对比特定行,不改变原数据,完整渲染XML,仅在展示diff时,特定路径的行不高亮。
目前自己的实现
<!-- App.vue -->
<template>
<div>
<diff-viewer
:old-text="parseXmlStructure(oldXml)"
:new-text="parseXmlStructure(newXml)"
:ignore-paths="[
'root.company.gcc',
'root.company.employees.employee.salary',
]"
file-name="config.xml"
@diff-rendered="onDiffRendered"
/>
</div>
</template>
<script>
import DiffViewer from './components/diff2html.vue';
import vkbeautify from 'vkbeautify';
export default {
components: {
DiffViewer,
},
data() {
return {
oldXml: `<root>
<company id="123">
<gcc/>
<name>TechCorp International</name>
<foundedYear>1995</foundedYear>
<employees>
<employee id="1">
<name>John Smith</name>
<position>Senior Developer</position>
<department>Engineering</department>
<salary>85000</salary>
<contact>
<email>john.smith@techcorp.com</email>
<phone>212-555-0100</phone>
<address>
<street>123 Tech Avenue</street>
<city>San Francisco</city>
<state>CA</state>
<country>USA</country>
<zipCode>94105</zipCode>
</address>
</contact>
<projects>
<project>Cloud Migration</project>
<project>Mobile App</project>
</projects>
</employee>
<employee id="2">
<name>Sarah Johnson</name>
<position>Product Manager</position>
<department>Product</department>
<salary>95000</salary>
<contact>
<email>sarah.j@techcorp.com</email>
<phone>212-555-0101</phone>
<address>
<street>456 Innovation Drive</street>
<city>San Francisco</city>
<state>CA</state>
<country>USA</country>
<zipCode>94105</zipCode>
</address>
</contact>
<projects>
<project>User Analytics</project>
<project>Platform Redesign</project>
</projects>
</employee>
</employees>
<offices>
<office>
<location>San Francisco</location>
<employeeCount>150</employeeCount>
</office>
<office>
<location>New York</location>
<employeeCount>75</employeeCount>
</office>
</offices>
</company>
</root>`,
newXml: `<root>
<company id="123">
<name>TechCorp International</name>
<foundedYear>1995</foundedYear>
<employees>
<employee id="1">
<name>John Smith</name>
<position>Engineering Manager</position>
<department>Engineering</department>
<salary>95000</salary>
<contact>
<email>john.smith@techcorp.com</email>
<phone>212-555-0100</phone>
<address>
<street>123 Tech Avenue</street>
<city>San Jose</city>
<state>CA</state>
<country>USA</country>
<zipCode>95110</zipCode>
</address>
</contact>
<projects>
<project>Cloud Migration</project>
<project>Mobile App</project>
<project>AI Integration</project>
</projects>
</employee>
<employee id="2">
<name>Sarah Johnson</name>
<position>Director of Product</position>
<department>Product</department>
<salary>120000</salary>
<contact>
<email>sarah.johnson@techcorp.com</email>
<phone>212-555-0202</phone>
<address>
<street>456 Innovation Drive</street>
<city>San Jose</city>
<state>CA</state>
<country>USA</country>
<zipCode>95110</zipCode>
</address>
</contact>
<projects>
<project>User Analytics 2.0</project>
<project>Platform Redesign</project>
<project>Customer Portal</project>
</projects>
</employee>
</employees>
<offices>
<office>
<location>San Jose</location>
<employeeCount>200</employeeCount>
</office>
<office>
<location>New York</location>
<employeeCount>80</employeeCount>
</office>
</offices>
</company>
</root>`,
};
},
methods: {
onDiffRendered() {
console.log('Diff rendering completed');
},
parseXmlStructure(content) {
try {
// 移除多余的空白行
content = content.replace(/^\s*[\r\n]/gm, '');
// 格式化XML
return vkbeautify.xml(content, 2);
} catch (error) {
console.error('XML formatting error:', error);
return content;
}
},
},
};
</script>
<!-- DiffViewer.vue -->
<template>
<div class="diff-viewer">
<div v-if="loading" class="diff-loading">
<div class="loading-spinner"></div>
</div>
<div v-else-if="error" class="diff-error">
{{ error }}
<button @click="retry" class="retry-btn">重试</button>
</div>
<div
v-else
ref="diffContainer"
class="diff-container"
v-html="diffHtml"
></div>
<div class="diff-controls">
<label>
<input type="checkbox" v-model="sideBySide" @change="redraw" />
并排显示
</label>
<select v-model="matching" @change="redraw">
<option value="lines">按行匹配</option>
<option value="words">按词匹配</option>
<option value="none">禁用匹配</option>
</select>
</div>
</div>
</template>
<script>
import 'diff2html/bundles/css/diff2html.min.css';
import { createPatch } from 'diff';
import { parse, html } from 'diff2html';
export default {
name: 'DiffViewer',
props: {
oldText: {
type: String,
required: true,
},
newText: {
type: String,
required: true,
},
fileName: {
type: String,
default: 'file.txt',
},
ignorePaths: {
type: Array,
default: () => [],
},
},
data() {
return {
loading: false,
error: null,
sideBySide: true,
matching: 'lines',
diffHtml: '',
};
},
watch: {
oldText: {
handler: 'updateDiff',
immediate: true,
},
newText: {
handler: 'updateDiff',
immediate: true,
},
},
methods: {
async updateDiff() {
try {
this.loading = true;
this.error = null;
// 生成diff
const diffStr = createPatch(
this.fileName,
this.oldText,
this.newText,
'',
'',
{ context: 3 }
);
// 解析diff
const diffJson = parse(diffStr);
// 配置选项
const config = {
drawFileList: false,
matching: this.matching,
outputFormat: this.sideBySide ? 'side-by-side' : 'line-by-line',
renderNothingWhenEmpty: true,
};
// 生成 HTML
this.diffHtml = html(diffJson, config);
// 等待 DOM 更新后处理 XML diff
this.$nextTick(() => {
this.processXmlDiff();
this.$emit('diff-rendered');
});
} catch (err) {
console.error('Diff generation failed:', err);
this.error = '差异对比生成失败,请重试';
} finally {
this.loading = false;
}
},
retry() {
this.updateDiff();
},
redraw() {
this.updateDiff();
},
parseXmlLine(line) {
const result = {
openTags: [], // 此行打开的标签
closeTags: [], // 此行关闭的标签
selfClosing: [], // 自闭合标签
content: null, // 内容
isContentLine: false, // 是否是内容行
};
if (!line || !line.includes('<')) {
result.content = line ? line.trim() : '';
result.isContentLine = true;
return result;
}
const tagPattern = /<\/?([^\s>]+)[^>]*\/?>/g;
let match;
let lastIndex = 0;
while ((match = tagPattern.exec(line)) !== null) {
const fullTag = match[0];
const tagName = match[1];
if (match.index > lastIndex) {
const content = line.substring(lastIndex, match.index).trim();
if (content) {
result.content = content;
result.isContentLine = true;
}
}
if (fullTag.endsWith('/>')) {
result.selfClosing.push(tagName.replace(/\/$/, ''));
} else if (fullTag.startsWith('</')) {
result.closeTags.push(tagName);
} else {
result.openTags.push(tagName);
}
lastIndex = match.index + fullTag.length;
}
if (lastIndex < line.length) {
const content = line.substring(lastIndex).trim();
if (content) {
result.content = content;
result.isContentLine = true;
}
}
return result;
},
shouldIgnorePath(path) {
if (!path) return false;
return this.ignorePaths.some((ignorePath) => {
const pattern = ignorePath.replace(/\*/g, '[^.]+');
const regex = new RegExp(`^${pattern}$`);
return regex.test(path);
});
},
removeHighlightStyle(row) {
const lineNumCell = row.querySelector('.d2h-code-side-linenumber');
const codeCell = row.querySelector('.d2h-del, .d2h-ins');
if (!lineNumCell || !codeCell) return;
lineNumCell.classList.remove('d2h-del', 'd2h-ins', 'd2h-change');
lineNumCell.classList.add('d2h-cntx');
codeCell.classList.remove('d2h-del', 'd2h-ins', 'd2h-change');
codeCell.classList.add('d2h-cntx');
const prefix = codeCell.querySelector('.d2h-code-line-prefix');
if (prefix) {
prefix.textContent = ' ';
}
const container = codeCell.querySelector('.d2h-code-line-ctn');
if (container) {
const delTags = container.querySelectorAll('del');
const insTags = container.querySelectorAll('ins');
delTags.forEach((del) => {
const text = del.textContent;
del.replaceWith(text);
});
insTags.forEach((ins) => {
const text = ins.textContent;
ins.replaceWith(text);
});
}
},
processXmlDiff() {
const diffContainer = this.$refs.diffContainer;
if (!diffContainer) return;
const tables = diffContainer.querySelectorAll('.d2h-diff-table');
tables.forEach((table) => {
const rows = table.querySelectorAll('tr');
const currentPath = [];
rows.forEach((row) => {
const contentCell = row.querySelector('.d2h-code-line-ctn');
if (!contentCell) return;
const line = contentCell.textContent;
const xmlInfo = this.parseXmlLine(line);
// 更新当前路径
xmlInfo.openTags.forEach((tag) => currentPath.push(tag));
// 处理自闭合标签
xmlInfo.selfClosing.forEach((tag) => {
currentPath.push(tag);
if (this.shouldIgnorePath(currentPath.join('.'))) {
this.removeHighlightStyle(row);
}
currentPath.pop();
});
// 处理内容行
if (
xmlInfo.isContentLine &&
this.shouldIgnorePath(currentPath.join('.'))
) {
this.removeHighlightStyle(row);
}
// 处理关闭标签
xmlInfo.closeTags.forEach(() => currentPath.pop());
});
});
},
},
};
</script>
"diff": "^7.0.0",
"diff2html": "^3.4.48",
"vkbeautify": "^0.99.3"
### 回答
要实现 XML 字符串对比,并且按 XML 绝对路径忽略对比特定行,同时不改变原数据且完整渲染 XML,你可以在对比过程中自定义 diff 渲染逻辑。以下是一个可能的解决方案,使用 JavaScript 和一些 XML 处理库:
1. **解析 XML 字符串**:使用 XML 解析器(如 DOMParser)将 XML 字符串解析为 DOM 树。
2. **遍历 XML 树**:根据绝对路径忽略特定节点或属性。
3. **生成 diff**:使用 diff 库(如 `jsdiff`)对未忽略的部分进行 diff 对比。
4. **自定义渲染**:使用 diff2html 或其他 diff 渲染库,但修改其渲染逻辑,以忽略特定路径的高亮。
#### 具体步骤
1. **解析 XML**:
const parser = new DOMParser();
const xmlDoc1 = parser.parseFromString(xmlString1, "application/xml");
const xmlDoc2 = parser.parseFromString(xmlString2, "application/xml");
```
遍历 XML 并标记忽略路径:
function markIgnoredPaths(xmlDoc, paths) {
const walk = (node, path = []) => {
path.push(node.nodeName);
if (paths.includes(path.join('.'))) {
node._ignore = true; // 添加自定义属性标记忽略
}
node.childNodes.forEach(child => walk(child, path));
path.pop();
};
xmlDoc.documentElement.childNodes.forEach(child => walk(child));
}
const pathsToIgnore = ['root.xxx.xx.xx']; // 绝对路径数组
markIgnoredPaths(xmlDoc1, pathsToIgnore);
markIgnoredPaths(xmlDoc2, pathsToIgnore);
生成 diff:
const jsdiff = require('jsdiff');
function diffXmlStrings(xmlDoc1, xmlDoc2) {
const serializer = new XMLSerializer();
const xmlString1 = serializer.serializeToString(xmlDoc1);
const xmlString2 = serializer.serializeToString(xmlDoc2);
return jsdiff.diffLines(xmlString1, xmlString2);
}
const diffs = diffXmlStrings(xmlDoc1, xmlDoc2);
自定义渲染:
function renderDiff(diffs) {
const diffHtml = Diff2Html.getPrettyHtml(diffs, {
drawFileList: false,
matching: 'words',
renderNothingWhenEmpty: true,
showNonMatching: true,
customInlineDiffRenderer: (part, type) => {
if (part.originalElement && part.originalElement._ignore) {
return `<span>${part.text}</span>`; // 不高亮忽略部分
}
return Diff2Html.getInlineDiffLineHtml(part, type);
}
});
document.getElementById('diff-output').innerHTML = diffHtml;
}
renderDiff(diffs);
这种方法通过解析 XML,标记需要忽略的路径,生成 diff,并在渲染时自定义忽略部分的高亮,从而实现了你的需求。
请问如何使用git diff 对比两个文件的差异呢? 我从1.txt 复制内容到2.txt 并修改了内容信息, 我想要使用git diff 它们,输出差异的位置信息。 但是我执行如下的命令: 没有得到任何响应内容。
模板中{{index}}显示的是12、16,按照手册中的说法index是数组对应的下标。但是面对这样的data数据,我想显示1、2、3.....这样的序号。该怎么处理?
打开网址1:域名/article/359.html ,正常的内容页。 打开网址2:域名//////article/359.html ,还是正常的内容页。 正常来说 应该是 404页面或者自动跳到只有一个 /开头的该页面。 APACHE规则如下: NGINX规则如下: 伪静态处理时: 请问如果修改规则? 打开网址2,跳转到404错误或者301跳转到网址1
求一个数学问题�� 已知或者能够计算出来的相关条件: 1.ABCD是梯形,F是梯形内一点(不是中心点) 2.OC、OE、OF、CF、FE、OM、MC的长度均已知 3.OC、OE的夹角α为60°,OF是α的角度平分线 求 AB、CD的长度
你可以用 git diff 来比较项目中任意两个版本的差异。 $ git diff master..test 上面这条命令只显示两个分支间的差异,如果你想找出‘master’,‘test’的共有 父分支和'test'分支之间的差异,你用3个‘.'来取代前面的两个'.' 。 $ git diff master...test git diff 是一个难以置信的有用的工具,可以找出你项目上任意两点间
怎么求b相对于a点的弧度,js中通过鼠标点来求.
请问上面这段代码,我想封装成Promise 这种 直接调用this.home_barlist1().then 该怎么改呢? 我改成下面这样 好像不行
我想匹配一段字符串中所有的input 并使用replace进行替换,如果input里面有类似data-* 这种自定义属性的就跳过 不知道这种正则该怎么写,我也阅读了文档并使用google。都没找到 比如 <input type='text' /> 这种Input就匹配,<input data-xxx /> 带有自定义属性的input 正则则不匹配