【问题标题】:How to use namespaces when writing XML file with SimpleXML使用 SimpleXML 编写 XML 文件时如何使用命名空间
【发布时间】:2012-12-07 22:07:18
【问题描述】:

我正在用 PHP 中的 SimpleXML 编写一个 Google 产品 RSS 提要。我的产品来自数据库并可以正常创建 RSS 文件,但在命名空间方面遇到了问题。

我在 Google 上搜索过 Stack Overflow,发现了数十篇关于如何解析包含命名空间的 XML 提要的帖子,但我的问题实际上是创作一个带有命名空间的 XML 文件。

这是文件应该的样子:

<?xml version="1.0" encoding="UTF-8" ?>
<rss version ="2.0" xmlns:g="http://base.google.com/ns/1.0">
    <!-- content -->
</rss>

这是我的代码:

<?php

$xml = new SimpleXMLElement('<rss></rss>');
$xml->addAttribute('version', '2.0');

$xml->addChild('channel');
$xml->channel->addChild('title', 'Removed');
$xml->channel->addChild('description', 'Removed');
$xml->channel->addChild('link', 'Removed');

foreach ($products as $product) {
    $item = $xml->channel->addChild('item');
    $item->addChild('title', htmlspecialchars($product['title']));
    $item->addChild('description', htmlspecialchars($product['title']));
    $item->addChild('link', $product['url']);
    $item->addChild('id', $product['product_id']);
    $item->addChild('price', $product['price_latest']);
    $item->addChild('brand', $product['range']);
    $item->addChild('condition', 'new');
    $item->addChild('image_link', $product['image']);
}

如何引入g命名空间,既是在根rss元素中声明xmlns,又是作为idpricebrandcondition和@的前缀每个item元素中的987654332@?

【问题讨论】:

  • 我强烈建议使用DOM 来代替。 SimpleXML 就是这样 - 简单。你现在所做的已经离开了简单的领域。为此,您需要一个功能齐全的 API。
  • 你有我如何使用 DOM 添加命名空间的示例吗?
  • @MartinBean manual for DOMDocument's createElementNS中有一个例子。

标签: php simplexml xml-namespaces


【解决方案1】:

这是一个如何使用DOM 执行此操作的示例:

<?php

    $nsUrl = 'http://base.google.com/ns/1.0';

    $doc = new DOMDocument('1.0', 'UTF-8');

    $rootNode = $doc->appendChild($doc->createElement('rss'));
    $rootNode->setAttribute('version', '2.0');
    $rootNode->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:g', $nsUrl);

    $channelNode = $rootNode->appendChild($doc->createElement('channel'));
    $channelNode->appendChild($doc->createElement('title', 'Removed'));
    $channelNode->appendChild($doc->createElement('description', 'Removed'));
    $channelNode->appendChild($doc->createElement('link', 'Removed'));

    foreach ($products as $product) {
        $itemNode = $channelNode->appendChild($doc->createElement('item'));
        $itemNode->appendChild($doc->createElement('title'))->appendChild($doc->createTextNode($product['title']));
        $itemNode->appendChild($doc->createElement('description'))->appendChild($doc->createTextNode($product['title']));
        $itemNode->appendChild($doc->createElement('link'))->appendChild($doc->createTextNode($product['url']));
        $itemNode->appendChild($doc->createElement('g:id'))->appendChild($doc->createTextNode($product['product_id']));
        $itemNode->appendChild($doc->createElement('g:price'))->appendChild($doc->createTextNode($product['price_latest']));
        $itemNode->appendChild($doc->createElement('g:brand'))->appendChild($doc->createTextNode($product['range']));
        $itemNode->appendChild($doc->createElement('g:condition'))->appendChild($doc->createTextNode('new'));
        $itemNode->appendChild($doc->createElement('g:image_link'))->appendChild($doc->createTextNode($product['image']));
    }

    echo $doc->saveXML();

See it working

【讨论】:

  • 谢谢,戴夫。完美的!刚刚自己编写了一个与您的非常相似的有效解决方案:gist.github.com/4344973
  • @MartinBean Cool :-) 你和我之间唯一真正的区别是我为动态值使用显式文本节点创建,这是(恕我直言)处理转义实体的最佳方法,因为你可以传入原始数据,它会自动转义,而不会与htmlspecialchars() 混淆。但是我承认它确实使它更加冗长 - DOM 不是一个简洁的 API,但它是可用的最灵活的 API。
  • 很高兴知道,因为我确实在实际代码中将产品标题等包裹在 htmlspecialchars() 中!我将用显式创建文本节点的方法替换它,就像你所做的那样。
【解决方案2】:

可以使用 SimpleXMLElement 接口完成。恕我直言,这是最狡猾的命令,但它现在有效。

关键是它的工作原理如下,现在,但可能无法继续工作。因此,我绝不会推荐 @DaveRandom 接受的答案。相反,我在这里包含这个答案,以便将来其他人可以阅读此内容并节省一些时间来搜索 SimpleXMLElement 的方法,而只需使用 DaveRandom 的基于 DOM 的方法 :-)

您可以“欺骗”解析器以阻止它从元素名称中删除您的 g: 命名空间前缀,方法是在您的真实前缀之前放置一个“垃圾”前缀,例如 "blah:g:condition"

我已经看到此答案的变体用于属性前缀,但不适用于元素前缀。这些似乎都建议使用"xmlns:yourPrefix:yourAttribute" 并将完全限定的名称空间作为第三个参数传递,实际上(至少,根据我自己的个人测试)xmlns: 部分几乎可以是任何东西(包括空格! ) 在冒号 : 之前,但在第一个冒号之前必须有 something,即 ":g:condition" 不起作用。除非您实际创建一个声明命名空间和前缀的节点,否则呈现的 XML 将是无效的(即,您侵入元素的命名空间前缀将没有声明)。

因此,根据您的原始代码,您将执行以下操作。还要注意在根节点声明中显式添加命名空间(虽然这可能可以通过 API 完成 - 但为什么要麻烦呢?)。

$xml = new SimpleXMLElement('<rss xmlns:g="http://base.google.com/ns/1.0" />'); // May as well chuck the google ns in the root element declaration here, while we're at it, rather than adding it via a separate attribute.
$xml->addAttribute('version', '2.0'); 
// $xml->addAttribute('hack:xmlns:g','http://base.google.com/ns/1.0'); //Or could do this instead...

$xml->addChild('channel');
$xml->channel->addChild('title', 'Removed');
$xml->channel->addChild('description', 'Removed');
$xml->channel->addChild('link', 'Removed');

foreach ($products as $product) {
    $item = $xml->channel->addChild('item');
    $item->addChild('title', htmlspecialchars($product['title']));
    $item->addChild('description', htmlspecialchars($product['title']));
    $item->addChild('link', $product['url']);
    $item->addChild('hack:g:id', $product['product_id']);
    $item->addChild('hack:g:price', $product['price_latest']);
    $item->addChild('hack:g:brand', $product['range']);
    $item->addChild('hack:g:condition', 'new');
    $item->addChild('hack:g:image_link', $product['image']);
}

【讨论】:

  • 你节省了我的时间。谢谢男人:)
【解决方案3】:

这可以通过将命名空间声明添加到根元素创建以及将命名空间添加到addChild 函数来完成。

对原代码的修改:

$products[] = array(
  "title" => "Foobar",
  "description" => "Foo",
  "url" => "https://f.oo.bar",
  "product_id" => "00001",
  "price_latest" => "$3.50",
  "range" => "Foo",
  "image" => "https://f.oo.bar/image.tiff",
);

$xml = new SimpleXMLElement('<rss xmlns:g="http://base.google.com/ns/1.0"/>');
$xml->addAttribute('version', '2.0');

$xml->addChild('channel');
$xml->channel->addChild('title', 'Removed');
$xml->channel->addChild('description', 'Removed');
$xml->channel->addChild('link', 'Removed');

foreach ($products as $product) {
    $item = $xml->channel->addChild('item');
    $item->addChild('title', htmlspecialchars($product['title']));
    $item->addChild('description', htmlspecialchars($product['title']));
    $item->addChild('link', $product['url']);
    $item->addChild('id', $product['product_id'], "http://base.google.com/ns/1.0");
    $item->addChild('price', $product['price_latest'], "http://base.google.com/ns/1.0");
    $item->addChild('brand', $product['range'], "http://base.google.com/ns/1.0");
    $item->addChild('condition', 'new', "http://base.google.com/ns/1.0");
    $item->addChild('image_link', $product['image'], "http://base.google.com/ns/1.0");
}

print_r($xml->asXML());

会产生响应:

<?xml version="1.0"?>
<rss xmlns:g="http://base.google.com/ns/1.0" version="2.0">
  <channel>
    <title>Removed</title>
    <description>Removed</description>
    <link>Removed</link>
    <item>
      <title>Foobar</title>
      <description>Foobar</description><link/>
      <g:id>00001</g:id>
      <g:price>$3.50</g:price>
      <g:brand>Foo</g:brand>
      <g:condition>new</g:condition>
      <g:image_link>https://f.oo.bar/image.tiff</g:image_link>
    </item>
  </channel>
</rss>

【讨论】:

    猜你喜欢
    • 2013-04-30
    • 2011-01-02
    • 1970-01-01
    • 2010-10-10
    • 2016-09-22
    相关资源
    最近更新 更多