PHP如何按指定顺序向现有XML文件插入XML节点内容
PHP实现指定位置插入UBL发票XML节点的方案
使用PHP内置的DOMDocument和DOMXPath扩展即可完成需求,不要用字符串拼接、正则替换的方式处理XML,避免出现命名空间丢失、特殊字符转义错误、结构破坏的问题。
实现步骤
- 加载原始XML文档,保留原有命名空间声明,开启格式化输出保证最终文件结构规范
- 导入第一段待插入的UBLExtensions片段,插入到Invoice根节点的第一个子节点位置
- 通过XPath定位ID为ICV的AdditionalDocumentReference节点,找到它的下一个兄弟节点(即ID为PIH的AdditionalDocumentReference节点)作为插入锚点
- 导入第二段待插入的QR节点、Signature节点片段,依次插入到上述锚点之前
- 输出保存最终的XML文件
完整可运行代码
<?php // 1. 加载原始XML文件 $dom = new DOMDocument('1.0', 'UTF-8'); $dom->formatOutput = true; // 开启格式化输出,保持缩进规范 $dom->load('original.xml'); // 替换为你的original.xml实际路径 $root = $dom->documentElement; // 获取根节点<Invoice> // 2. 处理第一个插入项:UBLExtensions 插到根节点首个子节点位置 // 若content1为独立文件,可直接用$content1Dom->load('content1.xml')代替手动写XML内容 $content1 = <<<XML <ext:UBLExtensions xmlns:ext="urn:oasis:names:specification:ubl:schema:xsd:CommonExtensionComponents-2"> <ext:UBLExtension> </ext:UBLExtension> </ext:UBLExtensions> XML; $content1Dom = new DOMDocument(); $content1Dom->loadXML($content1); // 递归导入节点到当前文档上下文 $content1Node = $dom->importNode($content1Dom->documentElement, true); // 插入到根节点的第一个子节点之前 $root->insertBefore($content1Node, $root->firstChild); // 3. 初始化XPath,注册UBL命名空间用于节点查询 $xpath = new DOMXPath($dom); $xpath->registerNamespace('cac', 'urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2'); $xpath->registerNamespace('cbc', 'urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2'); // 找到ID为ICV的AdditionalDocumentReference节点 $icvNode = $xpath->query('//cac:AdditionalDocumentReference[cbc:ID="ICV"]')->item(0); // 锚点为ICV节点的下一个兄弟节点,也就是PIH节点,后续内容插到这个锚点前面 $insertAnchor = $icvNode->nextSibling; // 4. 处理第二个插入项:QR节点和Signature节点插到ICV之后、PIH之前 // 若content2为独立文件,可将文件内容读取后包裹在临时root节点中加载,避免多同级节点加载报错 $content2 = <<<XML <root xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2" xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2"> <cac:AdditionalDocumentReference> <cbc:ID>QR</cbc:ID> <cac:Attachment> <cbc:EmbeddedDocumentBinaryObject mimeCode="text/plain">ARnYp9mE2YHYsdi5INin2YTYsdim2YrYs9mKAg8zMDA0NDczMjgxMDAwMzMDFDIwMjItMDQtMTlUMTE6NTc6MDBaBAYxNTMuNzAFBTIzLjcwBixoMmpNWHFnd05TeEdCY0JCeWJkcUQwb3k1MFNoNlRPQzFoRnZ6L2hONldZPQdYMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEYYMMoOaFYAhMO/steotfZyavr6p11SSlwsK9azmsLY7b1b+FLhqMArhB2dqHKboxqKNfvkKDePhpqjui5hcn0QghANswMm535QvQd/8xuP6t5MyLO1BSrjVQbMB3+E/8DGdECSB2ABXdBDP9qEvus/nj7v63yoBwq+dT6gSBflIyAbKlDg==</cbc:EmbeddedDocumentBinaryObject> </cac:Attachment> </cac:AdditionalDocumentReference> <cac:Signature> <cbc:ID>urn:oasis:names:specification:ubl:signature:Invoice</cbc:ID> <cbc:SignatureMethod>urn:oasis:names:specification:ubl:dsig:enveloped:xades</cbc:SignatureMethod> </cac:Signature> </root> XML; $content2Dom = new DOMDocument(); $content2Dom->loadXML($content2); // 遍历临时root下的所有子节点,依次插入到锚点前 foreach ($content2Dom->documentElement->childNodes as $childNode) { if ($childNode->nodeType === XML_TEXT_NODE && trim($childNode->nodeValue) === '') { continue; // 跳过空白换行节点 } $importedNode = $dom->importNode($childNode, true); $root->insertBefore($importedNode, $insertAnchor); } // 5. 保存最终文件 $dom->save('modified_invoice.xml'); echo "XML插入处理完成,已保存为modified_invoice.xml"; ?>
注意事项
- 代码里待插入的XML片段补全了对应的命名空间声明,避免导入后出现前缀未绑定的错误
- 调用
importNode()时第二个参数必须传true,才能递归导入所有子节点和节点内容 - 如果XML里存在空白文本节点影响nextSibling定位,可以在加载XML时传入
LIBXML_NOBLANKS参数,跳过空白文本节点:$dom->load('original.xml', LIBXML_NOBLANKS); - 生成的文件完全符合UBL 2.1的命名空间规范,不会破坏原有阿拉伯语内容、属性值的结构
内容的提问来源于stack exchange,提问作者Mohammad Khaled
相关产品推荐
相关产品推荐

