/**
*
* 提供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.pswp.cn/diannao/92735.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/92735.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/92735.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

星慈光编程虫2号小车讲解第一篇--向前向后

星慈光编程虫2号小车是一款基于微控制器&#xff08;如Arduino&#xff09;的编程教学小车&#xff0c;常用于学习机器人控制和编程基础。本讲解将重点介绍小车的基本运动&#xff1a;前进、后退、左转和右转。这些运动通过控制电机实现&#xff0c;通常涉及调整电机的方向和速…

iOS 加固工具有哪些?快速发布团队的实战方案

在当今快速迭代的 iOS 开发环境中&#xff0c;团队需要在高频上线与应用安全之间找到平衡。快速发布不应牺牲安全性&#xff0c;而安全加固也不应成为阻碍上线的瓶颈。这就要求开发者在加固工具的选型与流程设计上&#xff0c;做到既高效又可靠。 那么&#xff0c;iOS 加固工具…

结构型模式-架构解耦与扩展实践

结构型模式聚焦于对象间的组合关系&#xff0c;通过优化类与对象的装配方式&#xff0c;实现系统的灵活性与可扩展性。在分布式系统中&#xff0c;由于多节点协作、跨网络通信及异构环境集成等特性&#xff0c;传统结构型模式需进行适应性改造&#xff0c;以应对分布式特有的复…

scratch笔记和练习-第三课

角色的大小变化 亮度等特效设置 流程图图形符号 Figma攻略&#xff1a;26个流行流程图符号及其解释 练习 实现在闪动10次后角色缓缓变回原形

Redis MCP 安装与配置完整指南

一、Redis MCP 简介 Redis MCP (Managed Control Plane) 是一个独立于 Redis 服务运行的管理控制平台&#xff0c;用户可通过该平台快速高效地管理和配置 Redis 实例。Redis MCP 可配合开源 Redis 或 Redis Cloud 使用。 二、安装 Redis MCP 服务 Redis MCP 提供多种安装方式&a…

Spring Boot配置文件加载全指南:从基础到Spring Cloud集成

​​​ ​​一、核心概念​ 配置文件默认存在加载顺序优先级主要用途必需依赖bootstrap.yml❌ 无1(最先)最高Spring Cloud上下文初始化spring-cloud-starter-bootstrapbootstrap.properties❌ 无1(略高于.yml)最高同上同上application.yml✅ 自动创建2中等应用核心配置无appl…

Python通关秘籍(六)数据结构——字典

前文复习 五、数据结构 5.1 列表(List) 列表是一种有序的可变数据集合,可以包含不同类型的元素。

自学嵌入式 day33 TCP、HTTP协议(超文本传输协议)

6、黏包问题&#xff08;1&#xff09;、原因&#xff1a;发送方发送数据太快或者接收方接收数据太慢&#xff0c;导致数据在缓冲区缓存。&#xff08;2&#xff09;、解决方法&#xff1a;①发送指定大小数据&#xff08;结构体&#xff09;问题&#xff1a;结构体对齐问题&am…

LinuxShell 的 Here-Document(<< EOF) 笔记250723

LinuxShell 的 Here-Document(<< EOF) 笔记250723 Here-Document(<< EOF) Linux Shell Here Document (<< EOF) 终极指南 Here Document&#xff08;立即文档&#xff09;是 Shell 中用于多行输入重定向的强大功能&#xff0c;其核心语法为 << DELI…

【windows修复】解决windows10,没有【相机] 功能问题

问题: windows10,相机模块,好像是被卸载了,想重新安装 方法简介: 先下载windows store, 然后,在windows store 里面下载 相机功能: 解决: 直接下载官方离线包并手动安装(成功率 90%+) 1 用浏览器打开 https://store.rg-adguard.net 这是微软 CDN 解析站,安…

Python 中字典和 if-else 的选择

一、为什么要写这篇文章&#xff1f; 在 Python 编程中&#xff0c;我们经常需要根据不同的条件做不同的事情。比如&#xff1a; 根据用户等级显示不同的内容根据成绩给出不同的评价根据天气决定穿什么衣服 这时候&#xff0c;我们通常有两种选择&#xff1a; 用 if-else 语句用…

【开源解析】基于HTML5的智能会议室预约系统开发全攻略:从零构建企业级管理平台

&#x1f680; 【开源解析】基于HTML5的智能会议室预约系统开发全攻略&#xff1a;从零构建企业级管理平台 &#x1f308; 个人主页&#xff1a;创客白泽 - CSDN博客 &#x1f4a1; 热爱不止于代码&#xff0c;热情源自每一个灵感闪现的夜晚。愿以开源之火&#xff0c;点亮前行…

中央广播电视总台联合阿里云研究院权威发布《中国人工智能应用发展报告(2025)》:我国依旧需要大力注重人工智能人才的培养

你好&#xff0c;我是杰哥。 中央广播电视总台联合阿里云研究院权威发布《中国人工智能应用发展报告&#xff08;2025&#xff09;》&#xff0c;以下为报告核心看点&#xff1a; 报告首提 “654”体系&#xff1a;揭秘 6大技术趋势、5 新应用场景、4 力产业模型&#xff1b;成…

Visual Studio 2010-.Net Framework 4.0-DevExpress安装

最新版的DevExpress已不支持.Net Framework 4.0&#xff0c;需要下载18.1及以下版本。 17.2.5版DevExpress下载&#xff1a; 百度网盘 请输入提取码

借助Aspose.HTML控件,在 Python 中将 HTML 转换为 Markdown

在这个人工智能时代&#xff0c;Markdown因其易用性而备受重视。这种标记语言易于人类和机器理解。此外&#xff0c;与 HTML 和 DOCX 相比&#xff0c;这种格式更有助于法学硕士 (LLM) 理解文档结构。因此&#xff0c;本指南将介绍如何以 Python 编程方式将HTML转换为 Markdown…

【2026版】Redis面试题

文章目录1. Redis为什么这么快&#xff1f;2. Redis的持久化机制是怎样的&#xff1f;3. Redis 的过期策略是怎么样的&#xff1f;4. Redis的内存淘汰策略是怎么样的&#xff1f;5. 什么是热Key问题&#xff0c;如何解决热key问题&#xff1f;6. 什么是大Key问题&#xff0c;如…

Python编程进阶知识之第四课处理数据(pandas)

目录 简介 1. 安装 Pandas 2.基本数据结构 1.Series &#xff08;1.&#xff09;创建Series &#xff08;2.&#xff09;Series的属性 &#xff08;3.&#xff09;Series 的索引和切片 2.DataFrame &#xff08;1.&#xff09;创建 DataFrame &#xff08;2.&#xff09;…

使用 Vue 实现移动端视频录制与自动截图功能

文章目录技术栈功能介绍video标签属性完整代码js 前端实现将视频Blob转Base64java 后端实现将视频Base64转mp4文件在移动端网页开发中&#xff0c;使用摄像头录制视频并自动生成截图是一个常见的需求&#xff0c;比如身份认证、人脸识别或互动问卷等场景。本文将介绍如何使用 V…

单片机是怎么控制步进电机的?

步进电机作为一种将电脉冲信号转化为角位移的执行机构&#xff0c;其运转依赖于脉冲信号的控制&#xff0c;而单片机作为控制核心&#xff0c;通过输出特定的脉冲信号和方向信号&#xff0c;实现对步进电机的步数、方向、转速的精准控制&#xff0c;整个过程需结合驱动电路、程…

数据库binlog日志查看方案

binlog可以查看当前数据库中所有的修改操作&#xff0c;包含数据和结构的修改&#xff0c;所以掌握数据库日志查看是有必要的 通过客户端连接到mysql 查看binlog日志的存储位置&#xff08;前提是已开启binlog&#xff09; -- 查看日志文件列表 SHOW BINARY LOGS;结果示例-- 这…