saloonphp/xml-wrangler

轻松读取和写入 PHP 中的 XML

v1.3.1 2024-08-03 13:50 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 类的限制,fromStreamfromPsrResponsefromSaloon 方法将在您的机器/服务器上创建一个临时文件来读取,该文件将在读取器析构时自动删除。您需要确保您的机器有足够的存储空间来使用此方法。

将所有内容转换为数组

您可以使用 elementsvalues 方法将整个 XML 文档转换为数组。如果您需要一个值数组,请使用 values 方法——但如果您需要访问元素上的属性,则 elements 方法将返回一个包含 Element DTO 的数组。

$reader = XmlReader::fromString(...);

$elements = $reader->elements(); // Array of `Element::class` DTOs

$values = $reader->values(); // Array of values.

注意 如果您正在读取大型 XML 文件,请使用 elementvalue 方法。这些方法可以在不耗尽内存的情况下遍历大型 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();

懒加载迭代

当搜索大文件时,您可以使用 lazycollectLazy 方法,这些方法将返回一个生成器,每次只保留内存中的一个结果。

$names = $reader->element('name')->lazy();

foreach ($names as $name) {
    //
}

使用Laravel集合

如果您正在使用Laravel,则可以使用 collectcollectLazy 方法,这些方法将元素转换为Laravel集合/懒集合。如果您没有使用Laravel,您可以通过Composer安装 illuminate/collections 包以添加此功能。

$names = $reader->value('name')->collect();

$names = $reader->value('name')->collectLazy();

按特定属性搜索

有时您可能想要搜索包含特定属性或值的特定元素。您可以通过向 valueelement 方法提供第二个参数来实现这一点。这将搜索最后一个元素,如果它们匹配则返回。

$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 上使用 setXmlEncodingsetXmlVersionsetXmlStandalone 方法来实现。

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 的杰出库

其他致谢