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.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;結果示例-- 這…