PHP - 通过遍历 n 个无界元素获取 xml 值
PHP - Fetching xml values with looping over n unbounded element
我正在获取一些 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 文件中。
使用单个 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"
}
打开一个辅助文件句柄。然后把节点展开成DOM后,用表达式把events取出来,写到第二个文件里。
//...
$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 始终是两者之一。基本位置路径 return 是一个节点列表(示例:ancestor/parent/child
)。结果将始终是一个列表,如果表达式不匹配,它将是一个空列表。 Xpath 表达式可以变得更加复杂 - 它们允许条件、嵌套和替代项。
如果您需要单个值,您可以使用 Xpath 函数转换位置路径(示例:string(ancestor/parent/child)
)。 string()
或 number()
之类的函数会将节点列表中的第一个值或 return 转换为默认值。如果表达式本身不匹配,string()
将 return 一个空字符串。其他方法或运算符的使用也可能导致类型转换(例如:count(ancestor/parent/child) > 0
)。
但是,如果您可以使用 DOM methods/properties 从当前节点读取值,我建议您这样做。在这种情况下,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
]
);
}
}
我正在获取一些 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 文件中。
使用单个 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"
}
打开一个辅助文件句柄。然后把节点展开成DOM后,用表达式把events取出来,写到第二个文件里。
//...
$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 始终是两者之一。基本位置路径 return 是一个节点列表(示例:ancestor/parent/child
)。结果将始终是一个列表,如果表达式不匹配,它将是一个空列表。 Xpath 表达式可以变得更加复杂 - 它们允许条件、嵌套和替代项。
如果您需要单个值,您可以使用 Xpath 函数转换位置路径(示例:string(ancestor/parent/child)
)。 string()
或 number()
之类的函数会将节点列表中的第一个值或 return 转换为默认值。如果表达式本身不匹配,string()
将 return 一个空字符串。其他方法或运算符的使用也可能导致类型转换(例如:count(ancestor/parent/child) > 0
)。
但是,如果您可以使用 DOM methods/properties 从当前节点读取值,我建议您这样做。在这种情况下,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
]
);
}
}