PHP -通过循环n个无界元素获取xml值



我正在获取一些xml并将其转换为类似于下面的csv。有些记录有额外的n个(无界)元素(entityevents)。我怎样才能获取它们,以及将它们写入第二个(mm) csv文件?

这是我的结构:

XML文件:

<abc:ABCData xmlns:abc="http://www.abc-example.com" xmlns:xyz="http:/www.xyz-example.com">
<abc:ABCRecords>
<abc:ABCRecord>
<abc:ABC>5EXZX4LPK</abc:ABC>
<abc:Entity>
<abc:Name>Bornheim</abc:Name>
<abc:EntityEvents>
<abc:EntityEvent>
<abc:EntityEventType>TypeA</abc:EntityEventType>
<abc:EntityEventName>EventA</abc:EntityEventName> 
</abc:EntityEvent>
</abc:EntityEvents>    
</abc:Entity>
</abc:ABCRecord>
<abc:ABCRecord>
<abc:ABC>5967007LI</abc:ABC>
<abc:Entity>
<abc:Name>MOON BANK</abc:Name>
<abc:EntityEvents>
<abc:EntityEvent>
<abc:EntityEventType>TypeB</abc:EntityEventType>
<abc:EntityEventName>EventB</abc:EntityEventName>         
</abc:EntityEvent>
<abc:EntityEvent>
<abc:EntityEventType>TypeC</abc:EntityEventType>
<abc:EntityEventName>EventC</abc:EntityEventName>         
</abc:EntityEvent>
</abc:EntityEvents>                   
</abc:Entity>
</abc:ABCRecord>
<abc:ABCRecord>
<abc:ABC>2792340TZ</abc:ABC>
<abc:Entity>
<abc:Name>SUN BANK</abc:Name>
<abc:EntityEvents>
<abc:EntityEvent>
<abc:EntityEventType>TypeD</abc:EntityEventType>
<abc:EntityEventName>EventD</abc:EntityEventName>         
</abc:EntityEvent>
<abc:EntityEvent>
<abc:EntityEventType>TypeF</abc:EntityEventType>
<abc:EntityEventName>EventF</abc:EntityEventName>         
</abc:EntityEvent>
<abc:EntityEvent>
<abc:EntityEventType>TypeG</abc:EntityEventType>
<abc:EntityEventName>EventG</abc:EntityEventName>         
</abc:EntityEvent>
</abc:EntityEvents>                   
</abc:Entity>
</abc:ABCRecord>   
</abc:ABCRecords>
</abc:ABCData>

PHP文件:

<?php
$reader = new XMLReader();
$reader->open('php://stdin');
$output = fopen('php://stdout', 'w');
fputcsv($output, ['id', 'name']);
$xmlns = [
'abc' => 'http://www.abc-example.com'
];
$dom   = new DOMDocument;
$xpath = new DOMXpath($dom);
foreach ($xmlns as $prefix => $namespaceURI) {
$xpath->registerNamespace($prefix, $namespaceURI);
}
while (
$reader->read() && 
(
$reader->localName !== 'ABCRecord' || 
$reader->namespaceURI !== $xmlns['abc']
)
) {
continue;
}
while ($reader->localName === 'ABCRecord') {
if ($reader->namespaceURI === 'http://www.abc-example.com') {
$node = $reader->expand($dom);
fputcsv(
$output, 
[
$xpath->evaluate('string(abc:ABC)', $node),
$xpath->evaluate('string(abc:Entity/abc:Name)', $node)
]
);
}
$reader->next('ABCRecord');
}     

输出1 (CSV):

5EXZX4LPK,Bornheim
5967007LI,"MOON BANK"
2792340TZ,"SUN BANK"  

期望输出2 (CSV):

5EXZX4LPK,TypeA,EventA
5967007LI,TypeB,EventB
5967007LI,TypeC,EventC
2792340TZ,TypeD,EventD
2792340TZ,TypeE,EventE
2792340TZ,TypeF,EventF  

我怎样才能做到这一点?我想把它们写进一个单独的文件,但我是开放的如何实现这一点。我也可以分两步完成,这意味着在一个单独的php文件中。

打开第二个文件句柄。然后在将节点展开为DOM后,使用表达式获取事件并将其写入第二个文件。

//...
$node = $reader->expand($dom);
// store the identifier
$identifier = $xpath->evaluate('string(abc:ABC)', $node);
fputcsv(
$output, 
[
$identifier,
$xpath->evaluate('string(abc:Entity/abc:Name)', $node)
]
);
// iterate the EntityEvent elements
foreach ($xpath->evaluate('abc:Entity/abc:EntityEvents/abc:EntityEvent', $node) as $event) {
fputcsv(
$detailOutput, 
[
$identifier,
$xpath->evaluate('string(abc:EntityEventType)', $event),
$xpath->evaluate('string(abc:EntityEventName)', $event)
]
); 
}
//...

问题中的代码在XMLReader中实现第一个节点列表迭代,以避免将整个文档加载到内存中。在XMLReader::expand()之后,你得到了一个DOM节点。

用Xpath读取DOM总是两种方法之一。基本位置路径返回一个节点列表(例如:ancestor/parent/child)。结果将始终是一个列表,如果表达式不匹配,它将是一个空列表。Xpath表达式可以变得复杂得多——它们允许条件、嵌套和替代。

如果需要单个值,可以使用Xpath函数强制转换位置路径(例如:string(ancestor/parent/child))。像string()number()这样的函数将转换节点列表中的第一个值或返回默认值。如果表达式本身不匹配,string()将返回一个空字符串。其他方法或操作符的使用也可能导致类型强制转换(例如:count(ancestor/parent/child) > 0)。

但是,如果您可以使用DOM方法/属性从当前节点读取值,我建议这样做。在这种情况下,Xpath是不必要的开销。

// fetch and iterate nodes
foreach ($xpath->evaluate($expression, $contextNode) as $childNode) {
var_dump(
// reading an attribute 
$childNode->getAttribute('attribute-one'),
// the node name (without the namespace prefix)
$childNode->localName,
// using Xpath for nested data
$xpath->evaluate('string(child)', $childNode)
);
}

我的做法会有所不同:

$targets = $xpath->query("//abc:ABCRecord");
foreach ($targets as $target) {
$id = $xpath->query('.//abc:ABC', $target)[0]->nodeValue;
$events = $xpath->query('.//abc:EntityEvent', $target);
foreach ($events as $event) {
$type = $xpath->query('.//abc:EntityEventType', $event)[0]->nodeValue;
$name = $xpath->query('.//abc:EntityEventName', $event)[0]->nodeValue;
fputcsv(
$output,
[
$id,
$type,
$name
]
);
}

}

使用单个XPath表达式和标记名来填充数组。创建一个csv对象应该很简单。

$document = new DOMDocument();
$document->loadXML($xml);
$xpath = new DOMXpath($document);
$csv1 = [];
$csv2 = [];
foreach ($xpath->evaluate("(//abc:ABCRecord/abc:ABC | //abc:Entity/abc:Name) | (//abc:ABCRecord/abc:ABC | //abc:ABCRecord//abc:EntityEvent)") as $ele) {
if($ele -> localName == 'ABC'){
$n = $ele->nodeValue;
}
if($ele -> localName == 'Name'){
$csv1[] = $n . ','. $ele -> nodeValue;
}else if($ele -> localName == 'EntityEvent'){
$csv2[] = $n . ','. $ele -> nodeValue;
}
}
var_dump($csv1);
var_dump($csv2);

结果

array(3) {
[0]=>
string(18) "5EXZX4LPK,Bornheim"
[1]=>
string(19) "5967007LI,MOON BANK"
[2]=>
string(18) "2792340TZ,SUN BANK"
}
array(6) {
[0]=>
string(16) "5EXZX4LPK,EventA"
[1]=>
string(16) "5967007LI,EventB"
[2]=>
string(16) "5967007LI,EventC"
[3]=>
string(16) "2792340TZ,EventD"
[4]=>
string(16) "2792340TZ,EventF"
[5]=>
string(16) "2792340TZ,EventG"
}

最新更新