应使用 xml.etree.ElementTree 解析后修改文本和属性值,避免字符串替换破坏结构;遍历用 tree.iter(),修改后调用 tree.write() 保存,注意编码和声明。

用 Python 的 xml.etree.ElementTree 安全修改 XML 内容
直接字符串替换 XML 文件极大概率破坏结构(比如改了标签名、属性值里含 或引号),必须用解析器。Python 标准库的 xml.etree.ElementTree 足够处理大多数非 DTD/命名空间复杂的场景。
关键点:它不保留原始格式(缩进、换行、注释),若需格式化输出,得手动加 xml.dom.minidom 或第三方库;另外它默认不解析 DTD,遇到 会报错,需提前清理或换用 lxml。
- 只替换文本内容(
elem.text)或属性值(elem.attrib[key]),不碰标签名和结构 - 遍历用
tree.iter(),比findall()更稳妥,能覆盖嵌套任意深度的元素 - 修改后调用
tree.write(..., encoding='utf-8', xml_declaration=True),避免写入乱码
import xml.etree.ElementTree as ET
def replace_in_xml(file_path, old_text, new_text):
tree = ET.parse(file_path)
root = tree.getroot()
for elem in root.iter():
if elem.text and old_text in elem.text:
elem.text = elem.text.replace(old_text, new_text)
for key, val in elem.attrib.items():
if old_text in val:
elem.attrib[key] = val.replace(old_text, new_text)
tree.write(file_path, encoding='utf-8', xml_declaration=True)
replace_in_xml('config.xml', 'DEV_SERVER', 'PROD_SERVER')
Java 中用 DocumentBuilder 替换节点文本和属性
Java 原生方案依赖 javax.xml.parsers.DocumentBuilder,比 Python 略繁琐,但可控性更强。注意它默认不忽略空白文本节点,elem.getTextContent() 会连带换行和缩进一起返回,直接替换可能污染格式。
常见错误:用 setTextContent() 覆盖整个节点内容,误删子元素;或没设置 DocumentBuilderFactory.setIgnoringElementContentWhitespace(true),导致遍历时多出无意义的 #text 节点。
立即学习“Java免费学习笔记(深入)”;
- 只对
Node.ELEMENT_NODE类型调用getTextContent()和setTextContent() - 属性替换用
elem.setAttribute(attrName, newValue),别用setAttributeNode() - 写回文件必须用
Transformer,且设setOutputProperty(OutputKeys.INDENT, "yes")才有缩进
import javax.xml.parsers.*;
import org.w3c.dom.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.io.*;
public class XmlReplacer {
public static void replaceText(String filePath, String oldStr, String newStr) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setIgnoringElementContentWhitespace(true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new File(filePath));
NodeList allNodes = doc.getElementsByTagName("*");
for (int i = 0; i < allNodes.getLength(); i++) {
Node node = allNodes.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
Element elem = (Element) node;
if (elem.getTextContent() != null && elem.getTextContent().contains(oldStr)) {
elem.setTextContent(elem.getTextContent().replace(oldStr, newStr));
}
NamedNodeMap attrs = elem.getAttributes();
for (int j = 0; j < attrs.getLength(); j++) {
Node attr = attrs.item(j);
if (attr.getNodeValue().contains(oldStr)) {
attr.setNodeValue(attr.getNodeValue().replace(oldStr, newStr));
}
}
}
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer t = tf.newTransformer();
t.setOutputProperty(OutputKeys.INDENT, "yes");
t.transform(new DOMSource(doc), new StreamResult(new FileOutputStream(filePath)));
}
}
处理含命名空间或 CDATA 的 XML 文件
ElementTree 默认不支持命名空间前缀匹配,写 find('.//ns:tag') 会失败;CDATA 块在解析后变成普通文本,但若原始 XML 里是 ,直接改 elem.text 不会影响其 CDATA 标识——也就是说,你改完保存时,CDATA 会退化成普通文本,再读取就丢失语义。
- 命名空间:注册前缀,如
ET.register_namespace('ns', 'http://example.com/ns'),查找时用完整 URI 字典:root.find('.//{http://example.com/ns}item') - CDATA:标准库无法识别/保留 CDATA,必须换
lxml.etree,用etree.CDATA(text)显式包裹新内容 - 特殊字符(
&,):解析后已转义为实体,替换时按转义后形式操作,比如把zuojiankuohaophpcn换成youjiankuohaophpcn
Shell + sed / xmlstar 快速批量替换(仅限简单场景)
如果 XML 非常规整、无嵌套同名标签、无属性值含目标字符串,且你只要“快”,不用强一致性,sed -i 是最快的。但一旦结构稍复杂,sed 就会误伤——比如把 和 一起替换了,而后者本不该动。
更靠谱的轻量方案是 xmlstar(需单独安装),它支持 XPath,能精准定位节点:
-
xmlstar --inplace -u "//server/name" -v "PROD_SERVER" config.xml→ 只改元素文本 -
xmlstar --inplace -u "//*[@env='dev']/@env" -v "prod" config.xml→ 只改env="dev"属性 - 不支持正则替换,只能整值替换;若要部分替换(如 URL 中的 host),仍得先用
xmlstar提取,再用sed处理,最后写回
真正需要可靠、可维护、可复用的替换逻辑,还是回到 Python 或 Java 解析器。临时救急才用命令行工具。










