【问题标题】:PHP/SimpleXML - Arrays generated differently for single child and multiple childrenPHP/SimpleXML - 为单个孩子和多个孩子生成不同的数组
【发布时间】:2014-07-09 02:10:19
【问题描述】:

我正在使用 SimpleXML 来解析来自不同房地产经纪人的房地产清单的 XML 提要。 XML 提要的相关部分如下所示:

<branch name="Trustee Realtors">
    <properties>
        <property>
            <reference>1</reference>
            <price>275000</price>
            <bedrooms>3</bedrooms>
        </property>
        <property>
            <reference>2</reference>
            <price>350000</price>
            <bedrooms>4</bedrooms>
        </property>
        <property>
            <reference>3</reference>
            <price>128500</price>
            <bedrooms>4</bedrooms>
        </property>
    </properties>
</branch>
<branch name="Quick-E-Realty Inc">
    <properties>
        <property>
            <reference>4</reference>
            <price>180995</price>
            <bedrooms>3</bedrooms>
        </property>
    </properties>
</branch>

然后转换成这样的数组:

$xml = file_get_contents($filename);
$xml = simplexml_load_string($xml);
$xml_array = json_decode(json_encode((array) $xml), 1);
$xml_array = array($xml->getName() => $xml_array);

我遇到的问题是,当创建数组时,单个列表的数据在数组中与多个列表的位置不同 - 我不确定如何解释这一点,但如果我 var_dump () 多个项目的数组看起来像这样:

array(3) {
    [0]=>
    array(3) {
        ["reference"]=>
        string(4) "0001"
        ["price"]=>
        string(6) "275000"
        ["bedrooms"]=>
        int(3)
    }
    [1]=>
    array(3) {
        ["reference"]=>
        string(4) "0002"
        ["price"]=>
        string(6) "350000"
        ["bedrooms"]=>
        int(4)
    }
    [2]=>
    array(3) {
    ["reference"]=>
        string(4) "0003"
        ["price"]=>
        string(6) "128500"
        ["bedrooms"]=>
        int(2)
    }
}

如果我 var_dump() 单个列表的数组,它看起来像这样:

array(3) {
    ["reference"]=>
    string(4) "0004"
    ["price"]=>
    string(6) "180995"
    ["bedrooms"]=>
    int(3)
}

但我需要它看起来像这样:

array(1) {
    [0]=>
    array(3) {
        ["reference"]=>
        string(4) "0004"
        ["price"]=>
        string(6) "180995"
        ["bedrooms"]=>
        int(3)
    }
}

这些数组中的每一个都代表来自单个房地产经纪人的房产列表。我不确定这是否只是 SimpleXML 或 json 函数的工作方式,但我需要的是使用相同的格式(包含属性列表的数组是 [0] 键的值)。

提前致谢!

【问题讨论】:

  • 我看不出你的&lt;reference&gt;1&lt;/reference&gt; 怎么会变成0001 这个代码。 json/xml 不会像那样破坏文本节点。
  • 我的意思是,每个“属性”都是“属性”的子编号 x,因此没有正确应用此规则 - 对于单个列表,应该可以访问属性列表详细信息,例如这个 $properties['0']['reference'] 但它必须作为 $properties['reference'] 访问。这完全弄乱了我的代码,因为我无法计算有多少列表或正确解析数据。区别在于,对于多个属性,一个列表会被保存,因为有多个子元素,但对于单个属性而言,情况并非如此。
  • 在您的预期输出中,[0] 的数组键是否需要为 [0] 还是需要为与列表相关的特定值?
  • 数组键 ID 是自动生成的 - 我不需要知道这些 ID 是什么,因为我正在使用 foreach() 但这不适用于单个列表,因为它在做什么而是循环遍历属性 - 如果我使用 count() 来查找数组中的项目数,它们都将返回 3,但对于单个列表,它实际计数的是“参考”、“价格”和“卧室”项目而不是属性的数量。
  • @NoelWhitemore 该死的,我又迟到了。我发布了一个答案,虽然我可能会帮助你解决你所追求的问题。

标签: php xml simplexml


【解决方案1】:

在这里要问自己的一个巨大的“跳出框框思考”问题是:为什么首先要将 SimpleXML 对象转换为数组?

SimpleXML 不仅仅是一个用于解析 XML 然后使用其他东西来操作它的库,它专为完全您将要使用该数组做的事情而设计。

事实上,这个有时有单个元素,有时有多个元素的问题是它相对于普通数组表示的一大优势:对于您知道将是单个的节点,您可以省略[0];但是对于您知道可能有多个节点,您可以使用[0]foreach 循环,也可以使用

下面是一些示例,说明为什么 SimpleXML 与您的 XML 名符其实:

$sxml = simplexml_load_string($xml);

// Looping over multiple nodes with the same name
// We could also use $sxml->children() to loop regardless of name
//   or even the shorthand foreach ( $sxml as $children )
foreach ( $sxml->branch as $branch ) {

    // Access an attribute using array index notation
    //   the (string) is optional here, but good habit to avoid
    //   passing around SimpleXML objects by mistake
    echo 'The branch name is: ' . (string)$branch['name'] . "\n";

    // We know there is only one <properties> node, so we can take a shortcut:
    //   $branch->properties means the same as $branch->properties[0]
    // We don't know if there are 1 or many <property> nodes, but it
    //   doesn't matter: we're asking to loop over them, so SimpleXML 
    //   knows what we mean
    foreach ( $branch->properties->property as $property ) {
        echo 'The property reference is ' . (string)$property->reference . "\n";
    }
}

基本上,每当我看到那个丑陋的 json_decode(json_encode( 把戏时,我都会有点畏缩,因为 100 次中有 99 次后面的代码都比使用 SimpleXML 丑得多。

【讨论】:

  • 您为什么首先将 SimpleXML 对象转换为数组? - 一个很好的观点,我没有想到。我遇到了一个与 OP 非常相似的问题,删除了对 array 的转换(正如你所说,这是完全没有必要的)并且得到了我所期望的。
  • 回答“为什么不喜欢使用特定领域的函数来处理这些数据?”是“因为我的 api 的下游用户没有预料到”。
  • @FullDecent 您的 API 的下游用户肯定期待您已经定义和描述给他们的结构,而不是基于上游 XML 数据的有损转换动态生成的结构?
  • @IMSoP 没必要,这就是我们有 XML 模式验证的原因
  • 感谢您的建议。我省略了转换为数组,重写代码以使用 SimpleXML 对象而不是数组,并且代码仍然非常简单,没有增加复杂性。一个子元素的问题就解决了。
【解决方案2】:

SimpleXML 就是这样的古怪。我最近使用它试图使配置文件“更容易”编写,并在此过程中发现 SimpleXML 并不总是一致的。在这种情况下,我认为您将受益于简单地检测 &lt;property&gt; 是否是一组中的唯一一个,如果是,则将其单独包装在一个数组中,然后将其发送到您的循环。

注意:['root'] 存在是因为我需要在您的 XML 周围包裹一个 '&lt;root&gt;&lt;/root&gt;' 元素以使我的测试工作。

//Rebuild the properties listings
$rebuild = array();
foreach($xml_array['root']['branch'] as $key => $branch) {
    $branchName = $branch['@attributes']['name'];
    //Check to see if 'properties' is only one, if it
    //is then wrap it in an array of its own.
    if(is_array($branch['properties']['property']) && !isset($branch['properties']['property'][0])) {
        //Only one propery found, wrap it in an array
        $rebuild[$branchName] = array($branch['properties']['property']);
    } else {
        //Multiple properties found
        $rebuild[$branchName] = $branch['properties']['property'];
    }
}

这将负责重建您的属性。感觉有点hackish。但基本上你在这里检测到缺少多维数组:

if(is_array($branch['properties']['property']) && !isset($branch['properties']['property'][0]))

如果您没有找到多维数组,那么您明确地创建单个&lt;property&gt; 之一。然后要测试一切是否正确重建,您可以使用以下代码:

//Now do your operation...whatever it is.
foreach($rebuild as $branch => $properties) {
    print("Listings for $branch:\n");
    foreach($properties as $property) {
        print("Reference of " . $property['reference'] . " sells at $" . $property['price'] . " for " . $property['bedrooms'] . " bedrooms.\n");
    }
    print("\n");
}

这会产生以下输出:

Listings for Trustee Realtors:
Reference of 1 sells at $275000 for 3 bedrooms.
Reference of 2 sells at $350000 for 4 bedrooms.
Reference of 3 sells at $128500 for 4 bedrooms.

Listings for Quick-E-Realty Inc:
Reference of 4 sells at $180995 for 3 bedrooms.

并且将产生重建的转储:

Array
(
    [Trustee Realtors] => Array
        (
            [0] => Array
                (
                    [reference] => 1
                    [price] => 275000
                    [bedrooms] => 3
                )

            [1] => Array
                (
                    [reference] => 2
                    [price] => 350000
                    [bedrooms] => 4
                )

            [2] => Array
                (
                    [reference] => 3
                    [price] => 128500
                    [bedrooms] => 4
                )

        )

    [Quick-E-Realty Inc] => Array
        (
            [0] => Array
                (
                    [reference] => 4
                    [price] => 180995
                    [bedrooms] => 3
                )

        )

)

我希望这可以帮助您更接近解决问题的方法。

【讨论】:

  • 这里的“古怪”行为仅在您以非预期方式使用 SimpleXML 时才引入(例如 json_decode(json_encode( 技巧)。如果您只是将其保留为对象,并尝试使用 foreach 或引用 -&gt;property[0],您会发现它能够同时以两种方式始终如一地工作。
【解决方案3】:

一种可能性是使用 DOM+XPath 读取 XML。 XML 不仅可以转换为 JSON,而且为特定的 XML 构建特定的 JSON 很容易:

$dom = new DOMDocument();
$dom->loadXml($xml);
$xpath = new DOMXPath($dom);

$result = [];
foreach ($xpath->evaluate('//branch') as $branchNode) {
  $properties = [];
  foreach ($xpath->evaluate('properties/property', $branchNode) as $propertyNode) {
    $properties[] = [
      'reference' => $xpath->evaluate('string(reference)', $propertyNode),
      'price' => (int)$xpath->evaluate('string(price)', $propertyNode),
      'bedrooms' => (int)$xpath->evaluate('string(bedrooms)', $propertyNode)
    ];
  }
  $result[] = [
    'name' => $xpath->evaluate('string(@name)', $branchNode),
    'properties' => $properties
  ];
}

echo json_encode($result, JSON_PRETTY_PRINT);

输出:https://eval.in/154352

[
    {
        "name": "Trustee Realtors",
        "properties": [
            {
                "reference": "1",
                "price": 275000,
                "bedrooms": 3
            },
            {
                "reference": "2",
                "price": 350000,
                "bedrooms": 4
            },
            {
                "reference": "3",
                "price": 128500,
                "bedrooms": 4
            }
        ]
    },
    {
        "name": "Quick-E-Realty Inc",
        "properties": [
            {
                "reference": "4",
                "price": 180995,
                "bedrooms": 3
            }
        ]
    }

【讨论】:

  • 这里是逐行转换为使用 SimpleXML 的相同代码:eval.in/154383 这是一个很好的交易......更简单。
【解决方案4】:

使用SimpleXMLElement Class

 <?php
 $xml = "<body>
 <item>
 <id>2</id>
 </item>
 </body>";
$elem  =  new SimpleXMLElement($xml);
 if($elem->children()->count() === 1){
    $id = $elem->item->addChild(0)->addChild('id',$elem->item->id);
    unset($elem->item->id);
 };

$array =  json_decode(json_encode($elem), true);
print_r($array);

输出:

  Array
  (
    [item] => Array
    (
        [0] => Array
            (
                [id] => 2
            )

    )

  )

【讨论】:

  • 我认为这个解决方案在 2019 年更好
【解决方案5】:

你用过这个吗:

$xml_array['branch']['properties']['property']

作为循环源?尝试使用这个:

$xml_array['branch']['properties']

不要在行尾使用 ['property'],不要使用 3 段,只使用 2 段

<?php
$xml = file_get_contents('simple.xml');
$xml = simplexml_load_string($xml);
$xml_array = json_decode(json_encode((array) $xml), 1);
$xml_array = array($xml->getName() => $xml_array);
print_r($xml_array);
foreach($xml_array['branch']['properties'] as $a){
    print_r($a);
}
?>

【讨论】:

  • 谢谢 - 但这并不能解决问题,因为数组位置仍然是错误的。在 foreach() 循环中,返回的键将是列表中属性的索引(对于多个列表)或属性属性(对于单个列表)。因此,对于多个属性,我可以获得像这样的属性价格 $properties['0']['price'] 但对于单个列表,我必须这样做 $properties['price'] 所以哪个部分都没有关系我循环遍历的数组,仍然会返回错误的值。
【解决方案6】:

为了解决这个问题,你应该选择使用xpath(正如其他提到的),但在我看来,这对于大多数网络开发人员来说并不是一个非常熟悉的工具。我创建了一个非常小的启用作曲家的包,它解决了这个问题。归功于 symfony 包 CssSelector (https://symfony.com/doc/current/components/css_selector.html),它将 CSS 选择器重写为 xpath 选择器。我的包只是一个瘦包装器,它实际上处理了您在最常见的情况下使用 PHP 对 XML 所做的事情。你可以在这里找到它:https://github.com/diversen/simple-query-selector

use diversen\querySelector;

// Load simple XML document
$xml = simplexml_load_file('test2.xml');


// Get all branches as DOM elements 
$elems = querySelector::getElementsAsDOM($xml, 'branch');

foreach($elems as $elem) {
    // Get attribute name
    echo $elem->attributes()->name . "\n";
    // Get properties as array
    $props = querySelector::getElementsAsAry($elem, 'property');
    print_r($props); // You will get the array structure you expect
}

你也可以(如果你不关心分支名称)只是这样做:

$elems = querySelector::getElementsAsAry($xml, 'property');

【讨论】:

    【解决方案7】:

    测试解析后的XML是否有多个标签,或者是单个标签转换为数组,而不是重建数组,您可以只测试以下情况:

    <?php    
    
    if (is_array($info[0])) {
        foreach ($info as $fields) {
            // Do something...
        } 
    } else {
       // Do something else...
    }
    

    【讨论】:

      【解决方案8】:

      试试看=)

      $xml = simplexml_load_string($xml_raw, "SimpleXMLElement", LIBXML_NOCDATA);
      $json = json_encode($xml);
      $array = json_decode($json, TRUE);
      $marray['RepairSheets']['RepairSheet'][0] = $array['RepairSheets']['RepairSheet'];
      $array = (isset($array['RepairSheets']['RepairSheet'][0]) == true) ? $array : $marray;
      

      【讨论】:

      • 欢迎兄弟。请提供描述性答案而不是仅代码答案,以便每个用户都了解您所做的事情。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-03-11
      • 1970-01-01
      • 1970-01-01
      • 2018-08-30
      • 2012-02-09
      • 1970-01-01
      相关资源
      最近更新 更多