当前位置: 首页 > news >正文

XML高效处理类 - 专为Office文档XML处理优化

/**
*
* 提供XML读取、写入、修改、查询等高级功能,支持命名空间和复杂XML结构

* 主要功能:
* 1. 复杂路径解析(支持属性筛选、索引、通配符)
* 2. 完整节点类型支持(元素、文本、CDATA、注释、PI)
* 3. 高效元素/属性操作(增删改查、复制、移动)
* 4. 流式处理(低内存占用,适合大型XML)
*/

<?php
namespace BTWord\Processor;use BTWord\Exceptions\DocxProcessingException;
use XMLReader;
use XMLWriter;
use function count;
use function explode;
use function implode;
use function in_array;
use function preg_match;
use function strpos;
/*** XML高效处理类 - 专为Office文档XML处理优化* 提供XML读取、写入、修改、查询等高级功能,支持命名空间和复杂XML结构* * 主要功能:* 1. 复杂路径解析(支持属性筛选、索引、通配符)* 2. 完整节点类型支持(元素、文本、CDATA、注释、PI)* 3. 高效元素/属性操作(增删改查、复制、移动)* 4. 流式处理(低内存占用,适合大型XML)*/
class XmlProcessor
{private XMLReader $reader;private XMLWriter $writer;private array $namespaces = [];private array $namespaceUris = [];
// XML节点类型常量private const NODE_ELEMENT = XMLReader::ELEMENT;private const NODE_END_ELEMENT = XMLReader::END_ELEMENT;private const NODE_TEXT = XMLReader::TEXT;private const NODE_CDATA = XMLReader::CDATA;private const NODE_COMMENT = XMLReader::COMMENT;private const NODE_PI = XMLReader::PI;private const NODE_WHITESPACE = XMLReader::SIGNIFICANT_WHITESPACE;public function __construct(){$this->reader = new XMLReader();$this->writer = new XMLWriter();$this->writer->setIndent(true);$this->writer->setIndentString('  ');}/*** 注册命名空间(支持双向映射,避免前缀冲突)* @param string $prefix 命名空间前缀* @param string $uri 命名空间URI*/public function addNamespace(string $prefix, string $uri): void{$this->namespaces[$prefix] = $uri;$this->namespaceUris[$uri] = $prefix;}/*** 解析XML为数组(流式解析,低内存占用)* @param string $xmlContent XML内容* @param bool $preserveAttributes 是否保留属性(键名带@前缀)* @return array 解析后的数据数组* @throws DocxProcessingException 当XML解析失败时抛出*/public function parseToArray(string $xmlContent, bool $preserveAttributes = true): array{$result = [];$stack = [];$current = &$result;$this->processXmlContent($xmlContent, function () use (&$current, &$stack, $preserveAttributes) {$nodeType = $this->reader->nodeType;$nodeName = $this->reader->name;// 处理开始元素if ($nodeType === self::NODE_ELEMENT) {$element = [];// 处理属性if ($preserveAttributes && $this->reader->hasAttributes) {$attrs = [];while ($this->reader->moveToNextAttribute()) {$attrs['@' . $this->reader->name] = $this->reader->value;}$this->reader->moveToElement();$element = array_merge($element, $attrs);}// 处理子节点容器$element['#children'] = [];$childKey = $nodeName;// 处理重复节点(转为数组)if (isset($current[$childKey])) {if (!is_array($current[$childKey]) || !isset($current[$childKey][0])) {$current[$childKey] = [$current[$childKey]];}$childIndex = count($current[$childKey]);$current[$childKey][$childIndex] = &$element;$stack[] = &$current;$stack[] = $childKey;$stack[] = $childIndex;$current = &$current[$childKey][$childIndex]['#children'];} else {$current[$childKey] = &$element;$stack[] = &$current;$stack[] = $childKey;$stack[] = null;$current = &$current[$childKey]['#children'];}// 空元素处理if ($this->reader->isEmptyElement) {unset($element['#children']); // 空元素无childrenarray_pop($stack); // 移除childIndexarray_pop($stack); // 移除childKey$parent = &$stack[array_pop($stack)];$current = &$parent;}}// 处理结束元素elseif ($nodeType === self::NODE_END_ELEMENT) {if (empty($current)) {array_pop($stack); // 移除childIndex$childKey = array_pop($stack);$parent = &$stack[array_pop($stack)];unset($parent[$childKey]['#children']); // 无children则移除键} else {array_pop($stack); // 移除childIndex$childKey = array_pop($stack);$parent = &$stack[array_pop($stack)];}$current = &$parent;}// 处理文本/CDATA节点elseif (in_array($nodeType, [self::NODE_TEXT, self::NODE_CDATA])) {$value = $this->reader->value;if (empty($current)) {$current['#text'] = $value;} else {$current[] = ['#text' => $value];}}// 处理注释节点elseif ($nodeType === self::NODE_COMMENT) {$current['#comment'] = $this->reader->value;}// 处理PI节点elseif ($nodeType === self::NODE_PI) {$current['#pi_' . $nodeName] = $this->reader->value;}return true;});return $result;}/*** 创建新的XML文档(增强版)* @param string $rootElement 根元素名称,支持命名空间前缀(格式:prefix:element)* @param array $attributes 根元素属性* @param string $version XML版本* @param string $encoding 编码格式* @return string 创建的XML内容*/public function createDocument(string $rootElement,array $attributes = [],string $version = '1.0',string $encoding = 'UTF-8'): string {$this->writer->openMemory();$this->writer->startDocument($version, $encoding);// 处理根元素(带命名空间)$this->startElement($rootElement);$this->writeAttributes($attributes);$this->writer->endElement(); // 关闭根元素$this->writer->endDocument();return $this->writer->outputMemory();}/*** 读取XML文件(支持编码检测)* @param string $filePath XML文件路径* @param string $encoding 预期编码(默认UTF-8)* @return string XML内容* @throws DocxProcessingException 当文件无法打开时抛出*/public function readFile(string $filePath, string $encoding = 'UTF-8'): string{$context = stream_context_create(['http' => ['encoding' => $encoding]]);if (!$this->reader->open($filePath, $encoding, LIBXML_NONET, $context)) {throw new DocxProcessingException('Failed to open XML file: ' . $filePath);}$this->writer->openMemory();$this->processXml();$this->reader->close();return $this->writer->outputMemory();}/*** 向XML添加子元素(支持复杂路径和插入位置)* @param string $xmlString XML内容* @param string $parentPath 父元素路径(支持属性筛选:parent/child[@attr="val"])* @param string $childName 子元素名称,支持命名空间前缀* @param string $childValue 子元素文本值(支持CDATA:前缀加'cdata:'则自动包裹)* @param array $attributes 子元素属性数组* @param bool $prepend 是否前置插入(默认后置)* @return string 更新后的XML内容* @throws DocxProcessingException 当XML解析失败时抛出*/public function addElement(string $xmlString,string $parentPath,string $childName,string $childValue = '',array $attributes = [],bool $prepend = false): string {$pathParser = $this->createPathParser($parentPath);return $this->modifyXml($xmlString, function ($writer, $currentPath, $depth) use ($pathParser,$childName,$childValue,$attributes,$prepend) {static $added = false;$currentNodePath = implode('/', $currentPath);// 前置插入:在父元素开始标签后立即插入if ($this->isElementNode() && !$added) {if ($pathParser->matches($currentNodePath, $this->reader)) {$this->writeElement($childName, $childValue, $attributes);$added = true;}}// 后置插入:在父元素结束标签前插入if ($this->isEndElementNode() && !$added) {$parentPath = implode('/', $currentPath);if ($pathParser->matches($parentPath, $this->reader)) {$this->writeElement($childName, $childValue, $attributes);$added = true;}}return false;});}/*** 更新XML元素值(支持复杂路径和多节点)* @param string $xmlString XML内容* @param string $elementPath 元素路径(支持通配符和属性筛选)* @param string $newValue 新的元素值(支持CDATA:前缀加'cdata:')* @param int $maxUpdates 最大更新数量(-1表示全部)* @return string 更新后的XML内容* @throws DocxProcessingException 当XML解析失败时抛出*/public function updateValue(string $xmlString,string $elementPath,string $newValue,int $maxUpdates = -1): string {$pathParser = $this->createPathParser($elementPath);return $this->modifyXml($xmlString, function ($writer, $currentPath, $depth) use ($pathParser,$newValue,$maxUpdates) {static $updatedCount = 0;$currentNodePath = implode('/', $currentPath);// 检查是否达到最大更新数量if ($maxUpdates > 0 && $updatedCount >= $maxUpdates) {return false;}// 匹配目标元素且为文本节点if ($this->isTextNode() && $pathParser->matches($currentNodePath, $this->reader)) {// 处理CDATAif (strpos($newValue, 'cdata:') === 0) {$this->writer->writeCData(substr($newValue, 5));} else {$this->writer->text($newValue);}$updatedCount++;return true; // 跳过原文本}return false;});}/*** 删除XML元素(支持复杂路径和批量删除)* @param string $xmlString XML内容* @param string $elementPath 元素路径(支持通配符和属性筛选)* @param int $maxDeletions 最大删除数量(-1表示全部)* @return string 更新后的XML内容* @throws DocxProcessingException 当XML解析失败时抛出*/public function removeElement(string $xmlString, string $elementPath, int $maxDeletions = -1): string{$pathParser = $this->createPathParser($elementPath);return $this->modifyXml($xmlString, function ($writer, $currentPath, $depth) use ($pathParser,$maxDeletions) {static $skip = false, $targetDepth = 0, $deletionCount = 0;// 跳过被删除元素的子节点if ($skip) {if ($this->isEndElementNode() && $depth <= $targetDepth) {$skip = false;$deletionCount++;}return true; // 跳过处理}// 达到最大删除数量则停止if ($maxDeletions > 0 && $deletionCount >= $maxDeletions) {return false;}// 匹配目标元素则标记跳过if ($this->isElementNode()) {$currentNodePath = implode('/', $currentPath);if ($pathParser->matches($currentNodePath, $this->reader)) {$skip = true;$targetDepth = $depth - 1;return true; // 跳过元素本身}}return false;});}/*** 复制元素到指定位置* @param string $xmlString XML内容* @param string $sourcePath 源元素路径(支持复杂路径)* @param string $targetParentPath 目标父元素路径* @param string|null $newName 新元素名称(null则保留原名)* @param bool $keepSource 是否保留源元素(默认保留)* @return string 更新后的XML内容* @throws DocxProcessingException 当元素不存在时抛出*/public function copyElement(string $xmlString,string $sourcePath,string $targetParentPath,?string $newName = null,bool $keepSource = true): string {// 提取源元素XML片段$sourceXml = $this->getOuterXml($xmlString, $sourcePath);if ($sourceXml === null) {throw new DocxProcessingException("Source element not found: {$sourcePath}");}// 替换元素名称(如需要)if ($newName) {$sourceXml = preg_replace('/^<(\w+:?)[^>]+>/', "<{$newName}>", $sourceXml, 1);$sourceXml = preg_replace('/<\/(\w+:?)[^>]+>$/', "</{$newName}>", $sourceXml, 1);}// 插入到目标位置$result = $this->addElement($xmlString,$targetParentPath,'', // 临时名称(实际用XML片段)$sourceXml,[],false);// 不保留源元素则删除return $keepSource ? $result : $this->removeElement($result, $sourcePath, 1);}/*** 移动元素到新位置(本质是复制+删除源)* @param string $xmlString XML内容* @param string $sourcePath 源元素路径* @param string $targetParentPath 目标父元素路径* @return string 更新后的XML内容*/public function moveElement(string $xmlString, string $sourcePath, string $targetParentPath): string{return $this->copyElement($xmlString, $sourcePath, $targetParentPath, null, false);}/*** 获取元素的完整XML片段(outer XML)* @param string $xmlString XML内容* @param string $elementPath 元素路径* @return string|null 元素的完整XML片段,未找到则返回null* @throws DocxProcessingException 当XML解析失败时抛出*/public function getOuterXml(string $xmlString, string $elementPath): ?string{$pathParser = $this->createPathParser($elementPath);$fragment = null;$captureWriter = new XMLWriter();$captureWriter->openMemory();$this->processXmlContent($xmlString, function () use ($pathParser,$captureWriter,&$fragment) {static $capturing = false, $targetDepth = 0;if ($capturing) {// 捕获元素的所有节点(包括子节点)$this->copyNodeToWriter($this->reader, $captureWriter);// 捕获结束:当遇到目标深度的结束标签if ($this->isEndElementNode() && $this->reader->depth === $targetDepth) {$capturing = false;$fragment = $captureWriter->outputMemory();return false; // 停止解析}return true;}// 开始捕获:匹配目标元素if ($this->isElementNode()) {$currentPath = $this->buildCurrentPath();if ($pathParser->matches($currentPath, $this->reader)) {$capturing = true;$targetDepth = $this->reader->depth;$this->copyNodeToWriter($this->reader, $captureWriter); // 捕获开始标签}}return true;});return $fragment;}/*** 检查元素是否存在(高效方法)* @param string $xmlString XML内容* @param string $elementPath 元素路径* @return bool 是否存在* @throws DocxProcessingException 当XML解析失败时抛出*/public function exists(string $xmlString, string $elementPath): bool{$pathParser = $this->createPathParser($elementPath);$exists = false;$this->processXmlContent($xmlString, function () use ($pathParser, &$exists) {if ($this->isElementNode()) {$currentPath = $this->buildCurrentPath();if ($pathParser->matches($currentPath, $this->reader)) {$exists = true;return false; // 找到则停止}}return true;});return $exists;}/*** 查找所有匹配路径的元素(增强版)* @param string $xmlString XML内容* @param string $elementPath 元素路径(支持通配符、属性筛选、索引)* @return array 匹配元素数组,每个元素包含:*              - value: 文本值*              - attributes: 属性数组*              - outer_xml: 完整XML片段*              - path: 元素路径*              - depth: 深度* @throws DocxProcessingException 当XML解析失败时抛出*/public function query(string $xmlString, string $elementPath): array{$pathParser = $this->createPathParser($elementPath);$results = [];$currentElement = null;$currentWriter = new XMLWriter();$this->processXmlContent($xmlString, function () use ($pathParser,&$results,&$currentElement,$currentWriter) {if ($this->isElementNode()) {$currentPath = $this->buildCurrentPath();if ($pathParser->matches($currentPath, $this->reader)) {// 初始化当前元素信息$currentElement = ['value' => '','attributes' => $this->getAllAttributes(),'path' => $currentPath,'depth' => $this->reader->depth,'outer_xml' => ''];$results[] = &$currentElement;$currentWriter->openMemory();$this->copyNodeToWriter($this->reader, $currentWriter); // 记录开始标签}}// 收集元素内文本if ($currentElement && $this->isTextNode() && $this->reader->depth === $currentElement['depth'] + 1) {$currentElement['value'] .= $this->reader->value;}// 记录outer_xml(直到元素结束)if ($currentElement && $this->reader->depth >= $currentElement['depth']) {if (!$this->isElementNode() || $this->reader->depth !== $currentElement['depth']) {$this->copyNodeToWriter($this->reader, $currentWriter);}// 元素结束时保存outer_xmlif ($this->isEndElementNode() && $this->reader->depth === $currentElement['depth']) {$currentElement['outer_xml'] = $currentWriter->outputMemory();$currentElement = null;}}return true;});return $results;}/*** (增强版)更新XML元素的属性* @param string $xmlString XML内容* @param string $elementPath 元素路径(支持复杂路径)* @param string $attributeName 属性名称* @param string $newValue 新的属性值* @param bool $addIfMissing 当属性不存在时是否添加* @return string 更新后的XML内容* @throws DocxProcessingException 当XML解析失败时抛出*/public function updateAttribute(string $xmlString,string $elementPath,string $attributeName,string $newValue,bool $addIfMissing = true): string {$pathParser = $this->createPathParser($elementPath);return $this->modifyXml($xmlString, function ($writer, $currentPath, $depth) use ($pathParser,$attributeName,$newValue,$addIfMissing) {static $updatedCount = 0;if ($this->isElementNode()) {$currentNodePath = implode('/', $currentPath);if ($pathParser->matches($currentNodePath, $this->reader)) {// 写入开始标签$this->startElementWithNamespace();// 处理属性(更新或添加)$attrs = $this->getAllAttributes();$attrExists = isset($attrs[$attributeName]);if ($attrExists || $addIfMissing) {$attrs[$attributeName] = $newValue;}$this->writeAttributes($attrs);// 空元素处理if ($this->reader->isEmptyElement) {$this->writer->endElement();}$updatedCount++;return true; // 跳过默认处理}}return false;});}/*** 替换整个元素(包括子元素)* @param string $xmlString XML内容* @param string $elementPath 元素路径* @param string $newValue 新文本值* @param array $newAttributes 新属性数组* @return string 更新后的XML*/public function replaceElement(string $xmlString,string $elementPath,string $newValue = '',array $newAttributes = []): string {$pathParser = $this->createPathParser($elementPath);return $this->modifyXml($xmlString, function ($writer, $currentPath, $depth) use ($pathParser,$newValue,$newAttributes) {static $replacing = false, $targetDepth = 0;$currentNodePath = implode('/', $currentPath);// 处理元素开始标签if ($this->isElementNode() && !$replacing) {if ($pathParser->matches($currentNodePath, $this->reader)) {$replacing = true;$targetDepth = $depth;// 写入新元素开始标签$this->startElementWithNamespace();$this->writeAttributes($newAttributes);// 处理值替换if ($newValue !== '') {$this->writer->text($newValue);$this->writer->endElement();return true;}return true; // 只更新属性,保留内容}}// 处理元素结束标签if ($this->isEndElementNode() && $replacing && $depth === $targetDepth) {$replacing = false;if ($newValue === '') {$this->writer->endElement();}return true;}// 跳过被替换元素的内容if ($replacing) {return true;}return false;});}/*** 批量更新匹配元素* @param string $xmlString XML内容* @param string $elementPath 元素路径* @param callable $updater 更新回调 function(string $value, array $attrs): array* @return string 更新后的XML*/public function batchUpdateElements(string $xmlString,string $elementPath,callable $updater): string {$pathParser = $this->createPathParser($elementPath, true);return $this->modifyXml($xmlString, function ($writer, $currentPath, $depth) use ($pathParser, $updater) {static $updating = false, $targetDepth = 0, $currentValue = '', $currentAttrs = [];$currentNodePath = implode('/', $currentPath);// 开始元素处理if ($this->isElementNode() && $pathParser->matches($currentNodePath, $this->reader)) {$updating = true;$targetDepth = $depth;$currentValue = '';$currentAttrs = $this->getAllAttributes();// 立即更新属性[$newValue, $newAttrs] = $updater('', $currentAttrs);$this->startElementWithNamespace();$this->writeAttributes($newAttrs);return true;}// 收集文本内容if ($updating && $this->isTextNode() && $depth === $targetDepth + 1) {$currentValue .= $this->reader->value;return true;}// 结束元素处理if ($this->isEndElementNode() && $updating && $depth === $targetDepth) {$updating = false;// 应用最终更新[$finalValue, $finalAttrs] = $updater($currentValue, $currentAttrs);$this->writer->text($finalValue);$this->writer->endElement();return true;}return false;});}// 以下为内部辅助方法(保持原实现)/*** 路径解析器(支持复杂路径语法)* @param string $path 路径字符串(如:parent/child[@id="1"][2]、root/** @return object 包含matches方法的解析器对象*/private function createPathParser(string $path): object{$segments = explode('/', $path);$filters = [];$index = null;// 解析每段路径中的筛选条件和索引foreach ($segments as &$segment) {// 解析索引:如element[2]if (preg_match('/(.*)\[(\d+)\]$/', $segment, $m)) {$segment = $m[1];$index = (int)$m[2] - 1; // 转为0基索引}// 解析属性筛选:如element[@attr="val"]if (preg_match('/(.*)\[@([^=]+)=["\']([^"\']+)["\']\]/', $segment, $m)) {$segment = $m[1];$filters[] = ['attr' => trim($m[2]),'value' => trim($m[3])];}}unset($segment);return new class($segments, $filters, $index) {private $segments;private $filters;private $index;private $matchCount = 0;public function __construct($segments, $filters, $index){$this->segments = $segments;$this->filters = $filters;$this->index = $index;}public function matches(string $currentPath, XMLReader $reader): bool{$currentSegments = explode('/', $currentPath);// 路径长度不匹配if (count($currentSegments) !== count($this->segments)) {return false;}// 检查每段路径(支持通配符*)foreach ($this->segments as $i => $segment) {if ($segment === '*') {continue; // 通配符匹配任意段}if ($currentSegments[$i] !== $segment) {return false;}}// 检查属性筛选条件foreach ($this->filters as $filter) {$attrValue = $reader->getAttribute($filter['attr']);if ($attrValue !== $filter['value']) {return false;}}// 检查索引匹配(仅当指定了索引)if ($this->index !== null) {$this->matchCount++;return $this->matchCount - 1 === $this->index;}return true;}};}/*** 构建当前元素的路径字符串(修复版)* @return string 路径字符串(如:root/parent/child)*/private function buildCurrentPath(): string{static $pathStack = [];if ($this->isElementNode()) {$pathStack[] = $this->reader->name;} elseif ($this->isEndElementNode()) {array_pop($pathStack);}return implode('/', $pathStack);}/*** 复制节点到指定XMLWriter* @param XMLReader $reader 源读取器* @param XMLWriter $writer 目标写入器*/private function copyNodeToWriter(XMLReader $reader, XMLWriter $writer): void{switch ($reader->nodeType) {case self::NODE_ELEMENT:$writer->startElement($reader->name);// 复制属性if ($reader->hasAttributes) {$reader->moveToFirstAttribute();do {$writer->writeAttribute($reader->name, $reader->value);} while ($reader->moveToNextAttribute());$reader->moveToElement();}if ($reader->isEmptyElement) {$writer->endElement();}break;case self::NODE_END_ELEMENT:$writer->endElement();break;case self::NODE_TEXT:$writer->text($reader->value);break;case self::NODE_CDATA:$writer->writeCData($reader->value);break;case self::NODE_COMMENT:$writer->writeComment($reader->value);break;case self::NODE_PI:$writer->writePI($reader->name, $reader->value);break;case self::NODE_WHITESPACE:$writer->text($reader->value);break;}}/*** 获取当前元素的所有属性* @return array 属性数组(键为属性名,值为属性值)*/private function getAllAttributes(): array{$attrs = [];if ($this->reader->hasAttributes) {$this->reader->moveToFirstAttribute();do {$attrs[$this->reader->name] = $this->reader->value;} while ($this->reader->moveToNextAttribute());$this->reader->moveToElement();}return $attrs;}// ------------------------------ 基础方法 ------------------------------/*** 开始元素(带命名空间支持)* @param string $name 元素名*/private function startElement(string $name): void{if (strpos($name, ':') !== false) {[$prefix, $localName] = explode(':', $name, 2);if (isset($this->namespaces[$prefix])) {$this->writer->startElementNS($prefix, $localName, $this->namespaces[$prefix]);} else {$this->writer->startElement($name);}} else {$this->writer->startElement($name);}}/*** 带命名空间的元素开始标签写入(基于当前reader节点)*/private function startElementWithNamespace(): void{$this->startElement($this->reader->name);}/*** 写入元素(带命名空间支持)* @param string $name 元素名称* @param string $value 元素值(前缀'cdata:'则自动包裹CDATA)* @param array $attributes 属性数组*/private function writeElement(string $name, string $value = '', array $attributes = []): void{if (empty($name) && !empty($value)) {$this->writer->writeRaw($value); // 写入原始XML片段return;}$this->startElement($name);$this->writeAttributes($attributes);// 处理CDATA值if (strpos($value, 'cdata:') === 0) {$this->writer->writeCData(substr($value, 5));} elseif ($value !== '') {$this->writer->text($value);}$this->writer->endElement();}/*** 写入属性数组* @param array $attributes 属性数组 [属性名 => 值]*/private function writeAttributes(array $attributes): void{foreach ($attributes as $name => $value) {if (strpos($name, ':') !== false) {[$prefix, $local] = explode(':', $name, 2);if (isset($this->namespaces[$prefix])) {$this->writer->writeAttributeNS($prefix, $local, $this->namespaces[$prefix], $value);} else {$this->writer->writeAttribute($name, $value);}} else {$this->writer->writeAttribute($name, $value);}}}/*** 从当前reader写入属性*/private function writeAttributesFromReader(): void{$this->writeAttributes($this->getAllAttributes());}
/*** 通用节点处理(支持所有节点类型)*/private function handleNode(): void{switch ($this->reader->nodeType) {case self::NODE_ELEMENT:$this->startElementWithNamespace();$this->writeAttributesFromReader();if ($this->reader->isEmptyElement) {$this->writer->endElement();}break;case self::NODE_END_ELEMENT:$this->writer->endElement();break;case self::NODE_TEXT:$this->writer->text($this->reader->value);break;case self::NODE_CDATA:$this->writer->writeCData($this->reader->value);break;case self::NODE_COMMENT:$this->writer->writeComment($this->reader->value);break;case self::NODE_PI:$this->writer->writePI($this->reader->name, $this->reader->value);break;case self::NODE_WHITESPACE:$this->writer->text($this->reader->value);break;}}/*** 处理整个XML文档*/private function processXml(): void{while ($this->reader->read()) {$this->handleNode();}}/*** 处理XML内容(通用方法)* @param string $xmlString XML内容* @param callable $processor 处理器回调(返回false则停止解析)*/private function processXmlContent(string $xmlString, callable $processor): void{if (!$this->reader->XML($xmlString)) {throw new DocxProcessingException('Failed to parse XML content');}while ($this->reader->read() && $processor() !== false) {// 处理器控制流程}$this->reader->close();}/*** XML修改通用方法* @param string $xmlString XML内容* @param callable $modifier 修改器回调(返回true则跳过默认处理)* @return string 修改后的XML*/private function modifyXml(string $xmlString, callable $modifier): string{if (!$this->reader->XML($xmlString)) {throw new DocxProcessingException('Failed to parse XML content');}$this->writer->openMemory();$currentPath = [];$depth = 0;while ($this->reader->read()) {// 更新当前路径和深度if ($this->isElementNode()) {$currentPath[] = $this->reader->name;$depth++;} elseif ($this->isEndElementNode()) {array_pop($currentPath);$depth--;}// 执行修改器,判断是否跳过默认处理$skipDefault = $modifier($this->writer, $currentPath, $depth);if ($skipDefault) {continue;}$this->handleNode();}$this->reader->close();return $this->writer->outputMemory();}    /*** 节点类型判断辅助方法*/private function isElementNode(): bool{return $this->reader->nodeType === self::NODE_ELEMENT;}    private function isEndElementNode(): bool{return $this->reader->nodeType === self::NODE_END_ELEMENT;}private function isTextNode(): bool{return in_array($this->reader->nodeType, [self::NODE_TEXT, self::NODE_CDATA]);}/*** 析构函数 - 确保资源正确释放(修复语法错误)*/public function __destruct(){if (isset($this->reader) && $this->reader->nodeType !== XMLReader::NONE) {$this->reader->close();}}
}

http://www.dtcms.com/a/292241.html

相关文章:

  • Mysql-场景篇-2-线上高频访问的Mysql表,如何在线修改表结构影响最小?-1--Mysql8.0版本后的INSTANT DDL方案(推荐)
  • 【MySQL】MySQL基本概念
  • NISP-PTE基础实操——命令执行
  • MySQL高可用主从复制原理及常见问题
  • mysql_innodb_cluster_metadata源数据库
  • n1 armbian docker compose 部署aipan mysql
  • 板凳-------Mysql cookbook学习 (十二--------5)
  • vue3实现高性能pdf预览器功能可行性方案及实践(pdfjs-dist5.x插件使用及自定义修改)
  • Redis高级篇之最佳实践
  • VUE 中父级组件使用JSON.stringify 序列化子组件传递循环引用错误
  • TDengine时序数据库 详解
  • 扣子Coze智能体实战:自动化拆解抖音对标账号,输出完整分析报告(喂饭级教程)
  • STM32-SPI全双工同步通信
  • 什么是分布式事务,分布式事务的解决方案有哪些?
  • PyTorch 模型开发全栈指南:从定义、修改到保存的完整闭环
  • 自编码器表征学习:重构误差与隐空间拓扑结构的深度解析
  • vue2.0 + elementui + i18n:实现多语言功能
  • 智能Agent场景实战指南 Day 18:Agent决策树与规划能力
  • SpringBoot+Mybatis+MySQL+Vue+ElementUI前后端分离版:权限管理(三)
  • Class10简洁实现
  • 图解Spring的循环依赖
  • 2025茶吧机语音控制集成方案
  • 深入解析Hadoop中的推测执行:原理、算法与策略
  • 【华为机试】684. 冗余连接
  • Python编程进阶知识之第三课处理数据(numpy)
  • LSTM+Transformer炸裂创新 精准度至95.65%
  • 【C++】复习重点-汇总2-面向对象(三大特性、类/对象、构造函数、继承与派生、多态、抽象类、this/对象指针、友元、运算符重载、static、类/结构体)
  • vscode gdb调试c语言过程
  • IDEA-自动格式化代码
  • IDEA全局Maven配置