saloonphp / xml-wrangler
轻松读取和写入 PHP 中的 XML
Requires
- php: ^8.1
- ext-dom: *
- spatie/array-to-xml: ^3.2
- veewee/xml: ^3.1.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.5
- guzzlehttp/guzzle: ^7.8
- illuminate/collections: ^10.30
- pestphp/pest: ^2.24
- phpstan/phpstan: ^1.9
- psr/http-message: ^2.0
- saloonphp/saloon: ^3.0
- spatie/ray: ^1.33
Suggests
- illuminate/collections: Used for the collect and lazyCollect methods when reading XML.
This package is auto-updated.
Last update: 2024-09-03 13:57:31 UTC
README
🌵 XML Wrangler - 轻松读取和写入 PHP 中的 XML
XML Wrangler 是一个简单的 PHP 库,旨在使读取和写入 XML 变得容易。XML Wrangler 考虑到了开发者的使用体验——您可以读取任何类型的 XML 文件,即使是有复杂命名空间的大型 XML 文件。如果 XML 无效,它也会抛出异常!
安装
XML Wrangler 通过 Composer 安装。
composer require saloonphp/xml-wrangler
需要 PHP 8.1+
读取 XML
可以通过将 XML 字符串或文件传递给 XML 读取器并使用多种方法之一搜索和查找特定元素或值来读取 XML。您也可以将每个元素转换为易于遍历的数组。如果您需要访问元素上的属性,可以使用 Element
DTO,这是一个简单的类,用于访问内容和属性。XML Wrangler 提供了在每次只保留一个元素在内存中的同时迭代多个元素的方法。
<breakfast_menu> <food soldOut="false" bestSeller="true"> <name>Belgian Waffles</name> <price>$5.95</price> <description>Two of our famous Belgian Waffles with plenty of real maple syrup</description> <calories>650</calories> </food> <food soldOut="false" bestSeller="false"> <name>Strawberry Belgian Waffles</name> <price>$7.95</price> <description>Light Belgian waffles covered with strawberries and whipped cream</description> <calories>900</calories> </food> <food soldOut="false" bestSeller="true"> <name>Berry-Berry Belgian Waffles</name> <price>$8.95</price> <description>Light Belgian waffles covered with an assortment of fresh berries and whipped cream</description> <calories>900</calories> </food> </breakfast_menu>
<?php use Saloon\XmlWrangler\XmlReader; $reader = XmlReader::fromString($xml); // Retrieve all values as one simple array $reader->values(); // ['breakfast_menu' => [['name' => '...'], ['name' => '...'], ['name' => '...']] // Use dot-notation to find a specific element $reader->value('food.0')->sole(); // ['name' => 'Belgian Waffles', 'price' => '$5.95', ...] // Use the element method to get a simple Element DTO containing attributes and content $reader->element('food.0')->sole(); // Element::class // Use XPath to query the XML $reader->xpathValue('//food[@bestSeller="true"]/name')->get(); // ['Belgian Waffles', 'Berry-Berry Belgian Waffles'] // Use getAttributes() to get the attributes on the elements $reader->element('food.0')->sole()->getAttributes(); // ['soldOut' => false, 'bestSeller' => true] // Use getContent() to get the contents of the elements $reader->element('food.0')->sole()->getContent(); // ['name' => 'Belgian Waffles', 'price' => '$5.95', ...]
写入 XML
写入 XML 与定义一个 PHP 数组并使用键和值定义元素一样简单。当您需要定义具有更多特性(如属性或命名空间)的元素时,可以使用 Element
DTO 来定义更高级的元素。
<?php use Saloon\XmlWrangler\Data\Element; use Saloon\XmlWrangler\XmlWriter; $writer = new XmlWriter; $xml = $writer->write('breakfast_menu', [ 'food' => [ [ 'name' => 'Belgian Waffles', 'price' => '$5.95', 'description' => 'Two of our famous Belgian Waffles with plenty of real maple syrup', 'calories' => '650', ], [ 'name' => 'Strawberry Belgian Waffles', 'price' => '$7.95', 'description' => 'Light Belgian waffles covered with strawberries and whipped cream', 'calories' => '900', ], // You can also use the Element class if you need to define elements with // namespaces or with attributes. Element::make([ 'name' => 'Berry-Berry Belgian Waffles', 'price' => '$8.95', 'description' => 'Light Belgian waffles covered with an assortment of fresh berries and whipped cream', 'calories' => '900', ])->setAttributes(['bestSeller' => 'true']), ], ]);
上述代码将创建以下 XML
<?xml version="1.0" encoding="utf-8"?> <breakfast_menu> <food> <name>Belgian Waffles</name> <price>$5.95</price> <description>Two of our famous Belgian Waffles with plenty of real maple syrup</description> <calories>650</calories> </food> <food> <name>Strawberry Belgian Waffles</name> <price>$7.95</price> <description>Light Belgian waffles covered with strawberries and whipped cream</description> <calories>900</calories> </food> <food bestSeller="true"> <name>Berry-Berry Belgian Waffles</name> <price>$8.95</price> <description>Light Belgian waffles covered with an assortment of fresh berries and whipped cream</description> <calories>900</calories> </food> </breakfast_menu>
文档
读取 XML
本节文档用于使用 XML 读取器。
支持的多种输入类型
XML 读取器可以接受多种输入类型。您可以使用 XML 字符串、文件或提供资源。您还可以直接从 PSR 响应(如来自 Guzzle)或 Saloon 响应读取 XML。
use Saloon\XmlWrangler\XmlReader; $reader = XmlReader::fromString('<?xml version="1.0" encoding="utf-8"?><breakfast_menu>...'); $reader = XmlReader::fromFile('path/to/file.xml'); $reader = XmlReader::fromStream(fopen('path/to/file.xml', 'rb'); $reader = XmlReader::fromPsrResponse($response); $reader = XmlReader::fromSaloonResponse($response);
警告 由于底层 PHP XMLReader 类的限制,
fromStream
、fromPsrResponse
和fromSaloon
方法将在您的机器/服务器上创建一个临时文件来读取,该文件将在读取器析构时自动删除。您需要确保您的机器有足够的存储空间来使用此方法。
将所有内容转换为数组
您可以使用 elements
和 values
方法将整个 XML 文档转换为数组。如果您需要一个值数组,请使用 values
方法——但如果您需要访问元素上的属性,则 elements
方法将返回一个包含 Element
DTO 的数组。
$reader = XmlReader::fromString(...); $elements = $reader->elements(); // Array of `Element::class` DTOs $values = $reader->values(); // Array of values.
注意 如果您正在读取大型 XML 文件,请使用
element
或value
方法。这些方法可以在不耗尽内存的情况下遍历大型 XML 文件。
读取特定值
您可以使用 value
方法获取特定元素的价值。您可以使用点表示法来搜索子元素。您还可以使用整数来找到多个元素的具体位置。此方法以内存高效的方式搜索整个 XML 正文。
此方法将返回一个 LazyQuery
类,该类具有不同的方法来检索数据。
$reader = XmlReader::fromString(' <?xml version="1.0" encoding="utf-8"?> <person> <name>Sammyjo20</name> <favourite-songs> <song>Luke Combs - When It Rains It Pours</song> <song>Sam Ryder - SPACE MAN</song> <song>London Symfony Orchestra - Starfield Suite</song> </favourite-songs> </person> '); $reader->value('person.name')->sole() // 'Sammyjo20' $reader->value('song')->get(); // ['Luke Combs - When It Rains It Pours', 'Sam Ryder - SPACE MAN', ...] $reader->value('song.2')->sole(); // 'London Symfony Orchestra - Starfield Suite'
读取特定元素
您可以使用 element
方法来搜索特定元素。此方法将提供一个包含值和属性的 Element
类。您可以使用点表示法来搜索子元素。您还可以使用整数来找到多个元素的具体位置。此方法以内存高效的方式遍历整个XML体。
此方法将返回一个 LazyQuery
类,该类具有不同的方法来检索数据。
$reader = XmlReader::fromString(' <?xml version="1.0" encoding="utf-8"?> <person> <name>Sammyjo20</name> <favourite-songs> <song>Luke Combs - When It Rains It Pours</song> <song>Sam Ryder - SPACE MAN</song> <song>London Symfony Orchestra - Starfield Suite</song> </favourite-songs> </person> '); $reader->element('name')->sole(); // Element('Sammyjo20') $reader->element('song')->get(); // [Element('Luke Combs - When It Rains It Pours'), Element('Sam Ryder - SPACE MAN'), ...] $reader->element('song.2')->sole(); // Element('London Symfony Orchestra - Starfield Suite')
从XML中删除命名空间
有时候在不需要担心元素命名空间和前缀时,遍历XML文档会更加容易。如果您想删除它们,可以在读取器上使用 removeNamespaces()
方法。
$reader = XmlReader::fromString(...); $reader->removeNamespaces();
懒加载迭代
当搜索大文件时,您可以使用 lazy
或 collectLazy
方法,这些方法将返回一个生成器,每次只保留内存中的一个结果。
$names = $reader->element('name')->lazy(); foreach ($names as $name) { // }
使用Laravel集合
如果您正在使用Laravel,则可以使用 collect
和 collectLazy
方法,这些方法将元素转换为Laravel集合/懒集合。如果您没有使用Laravel,您可以通过Composer安装 illuminate/collections
包以添加此功能。
$names = $reader->value('name')->collect(); $names = $reader->value('name')->collectLazy();
按特定属性搜索
有时您可能想要搜索包含特定属性或值的特定元素。您可以通过向 value
或 element
方法提供第二个参数来实现这一点。这将搜索最后一个元素,如果它们匹配则返回。
$reader = XmlReader::fromString(' <?xml version="1.0" encoding="utf-8"?> <person> <name>Sammyjo20</name> <favourite-songs> <song>Luke Combs - When It Rains It Pours</song> <song>Sam Ryder - SPACE MAN</song> <song recent="true">London Symfony Orchestra - Starfield Suite</song> </favourite-songs> </person> '); $reader->element('song', ['recent' => 'true'])->sole(); // Element('London Symfony Orchestra - Starfield Suite') $reader->value('song', ['recent' => 'true'])->sole(); // 'London Symfony Orchestra - Starfield Suite'
使用XPath读取
XPath 是搜索XML的一种绝佳方式。使用一个字符串,您可以搜索具有特定属性或索引的特定元素。如果您对学习XPath感兴趣,您可以 点击这里查看有用的速查表。
通过XPath读取特定值
您可以使用 xpathValue
方法通过XPath查询找到特定元素的价值。此方法将返回一个具有不同方法来检索数据的 Query
类。
<?php $reader = XmlReader::fromString(...); $reader->xpathValue('//person/favourite-songs/song[3]')->sole(); // 'London Symfony Orchestra - Starfield Suite'
通过XPath读取特定元素
您可以使用 xpathElement
方法通过XPath查询找到特定元素。此方法将返回一个具有不同方法来检索数据的 Query
类。
<?php $reader = XmlReader::fromString(...); $reader->xpathElement('//person/favourite-songs/song[3]')->sole(); // Element('London Symfony Orchestra - Starfield Suite')
警告 由于XPath的限制,用于查询XPath的上述方法不是内存安全的,可能不适用于大型XML文档。
XPath 和未指定前缀的命名空间
您可能会遇到包含未指定前缀的 xmlns
属性的XML文档 - 例如
<container xmlns="http://example.com/xml-wrangler/person" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
当这种情况发生时,XML Wrangler 将自动删除这些未指定前缀的命名空间以提高兼容性。如果您想保留这些命名空间,可以使用 setXpathNamespaceMap
将每个未指定XML命名空间进行映射。
$reader = XmlReader::fromString(...); $reader->setXpathNamespaceMap([ 'root' => 'http://example.com/xml-wrangler/person', ]); $reader->xpathValue('//root:person/root:favourite-songs/root:song[3]')->sole();
写入 XML
本节文档是关于使用XML编写器的。
基本用法
读取器最基本的使用是使用字符串键作为元素名称,以及值作为元素值。编写器接受无限嵌套的数组作为嵌套元素。
use Saloon\XmlWrangler\XmlWriter; $xml = XmlWriter::make()->write('root', [ 'name' => 'Sam', 'twitter' => '@carre_sam', 'facts' => [ 'favourite-song' => 'Luke Combs - When It Rains It Pours' ], ]);
上面的代码将被转换成以下XML
<?xml version="1.0" encoding="utf-8"?> <root> <name>Sam</name> <twitter>@carre_sam</twitter> <facts> <favourite-song>Luke Combs - When It Rains It Pours</favourite-song> </facts> </root>
使用元素DTO
当编写XML时,您通常需要在元素上定义属性和命名空间。您可以使用XML数组中的 Element
类添加具有属性或命名空间的元素。您可以将 Element
类与其他数组和字符串值混合。
use Saloon\XmlWrangler\XmlWriter; use Saloon\XmlWrangler\Data\Element; $xml = XmlWriter::make()->write('root', [ 'name' => 'Sam', 'twitter' => Element::make('@carre_sam')->addAttribute('url', 'https://twitter.com/@carre_sam'), 'facts' => [ 'favourite-song' => 'Luke Combs - When It Rains It Pours' ], 'soap:custom-namespace' => Element::make()->addNamespace('soap', 'http://www.w3.org/2003/05/soap-envelope'), ]);
这将产生以下XML
<?xml version="1.0" encoding="utf-8"?> <root> <name>Sam</name> <twitter url="https://twitter.com/@carre_sam">@carre_sam</twitter> <facts> <favourite-song>Luke Combs - When It Rains It Pours</favourite-song> </facts> <soap:custom-namespace xmlns:soap="http://www.w3.org/2003/05/soap-envelope"/> </root>
值数组
您将经常需要定义一个元素数组。您可以通过提供值或元素类的数组来完成此操作。
use Saloon\XmlWrangler\XmlWriter; use Saloon\XmlWrangler\Data\Element; $xml = XmlWriter::make()->write('root', [ 'name' => 'Luke Combs', 'songs' => [ 'song' => [ 'Fast Car', 'The Kind Of Love We Make', 'Beautiful Crazy', Element::make('She Got The Best Of Me')->addAttribute('hit', 'true'), ], ], ]);
这将产生以下XML
<?xml version="1.0" encoding="utf-8"?> <root> <name>Luke Combs</name> <songs> <song>Fast Car</song> <song>The Kind Of Love We Make</song> <song>Beautiful Crazy</song> <song hit="true">She Got The Best Of Me</song> </songs> </root>
自定义根元素
有时您可能需要更改根元素的名称。这可以通过将 write
方法的第一个参数进行自定义来实现。
$xml = XmlWriter::make()->write('custom-root', [...])
如果您想向根元素添加属性和命名空间,您也可以在这里使用 RootElement
类。
use Saloon\XmlWrangler\Data\RootElement; $rootElement = RootElement::make('root')->addNamespace('soap', 'http://www.w3.org/2003/05/soap-envelope'); $xml = XmlWriter::make()->write($rootElement, [...])
CDATA 元素
如果您需要添加 CDATA 标签,您可以使用 CDATA
类。
use Saloon\XmlWrangler\Data\CDATA;use Saloon\XmlWrangler\XmlWriter; use Saloon\XmlWrangler\Data\Element; $xml = XmlWriter::make()->write('root', [ 'name' => 'Sam', 'custom' => CDATA::make('Here is some CDATA content!'), ]);
这将产生以下XML
<?xml version="1.0" encoding="utf-8"?> <root> <name>Sam</name> <custom><![CDATA[Here is some CDATA content!]]></custom> </root>
可组合元素
有时您可能会在您的应用程序中的多个 XML 请求中重复使用 XML 的一部分。使用 XML Wrangler,您可以创建“可组合”元素,在那里您可以定义您的 XML 内容,并在整个应用程序中重复使用。扩展 Element
类并使用受保护的静态 compose
方法。
<?php use Saloon\XmlWrangler\XmlWriter; use Saloon\XmlWrangler\Data\Element; class BelgianWafflesElement extends Element { protected function compose(): void { $this ->setAttributes([ 'soldOut' => 'false', 'bestSeller' => 'true', ]) ->setContent([ 'name' => 'Belgian Waffles', 'price' => '$5.95', 'description' => 'Two of our famous Belgian Waffles with plenty of real maple syrup', 'calories' => '650', ]); } } $writer = XmlWriter::make()->write('root', [ 'food' => new BelgianWafflesElement, ]);
这将生成如下 XML
<?xml version="1.0" encoding="utf-8"?> <breakfast_menu> <food soldOut="false" bestSeller="true"> <name>Belgian Waffles</name> <price>$5.95</price> <description>Two of our famous Belgian Waffles with plenty of real maple syrup</description> <calories>650</calories> </food> </breakfast_menu>
自定义 XML 编码、版本和独立
默认的 XML 编码是 utf-8
,默认的 XML 版本是 1.0
,默认的独立是 null
(XML 解析器将没有独立属性的相同解释为 false
)。如果您想自定义这些设置,您可以通过在 writer 上使用 setXmlEncoding
、setXmlVersion
和 setXmlStandalone
方法来实现。
use Saloon\XmlWrangler\XmlWriter; $writer = new XmlWriter(); $writer->setXmlEncoding('ISO-8859-1'); $writer->setXmlVersion('2.0'); $writer->setXmlStandalone(true); // $writer->write(...);
这将生成如下的 XML 声明 <?xml version="2.0" encoding="ISO-8859-1" standalone="yes"?>
。
向 XML 添加自定义“处理指令”
您可以通过使用 addProcessingInstruction
方法向 XML 添加自定义“处理指令”。
use Saloon\XmlWrangler\XmlWriter; $writer = new XmlWriter(); $writer->addProcessingInstruction('xml-stylesheet', 'type="text/xsl" href="base.xsl"'); $xml = $writer->write('root', ['name' => 'Sam']);
这将产生以下XML
<?xml version="1.0" encoding="utf-8"?> <?xml-stylesheet type="text/xsl" href="base.xsl"?> <root> <name>Sam</name> </root>
压缩
默认情况下,写入的 XML 没有被压缩。您可以将第三个参数提供给 write
方法以压缩 XML。
use Saloon\XmlWrangler\XmlWriter; $xml = XmlWriter::make()->write('root', [...], minified: true);
致谢
XML Wrangler 是围绕两个非常强大的库的简单包装,这些库做了很多基础工作。这两个库都非常好,值得赞扬!
- veewee/xml - 用于读取和解码 XML,但拥有自己强大的写入引擎。
- spatie/array-to-xml - 一个将数组转换为 XML 的杰出库