xml 文件中节点的修改和替换
Modification and replacement of nodes in xml file
我每天都会收到一个 .xml 文件,但需要对元素值进行一些额外的编辑。我已经通过数据透视表在 Excel 中完成了我需要的工作,但该解决方案是不可接受的,因为这会创建新结构,我无法将其保存为 xml,我需要相同的 xml结构作为输出。由于我安装了 PHP Composer,我认为 simpleXML 库是最简单的解决方案。上传原始 .xml 文件并通过 simplexml_load_string 函数进行修改。问题是,我对这种语法不是很精通,我需要一些帮助。这是我的 .xml 文件:
<?xml version="1.0" encoding="ISO-8859-2"?>
<ZCOLL>
<IDOC BEGIN="1">
<EDI_DC40 SEGMENT="1">
<DOCNUM>0000000008857855</DOCNUM>
<SERIAL>20191025143123</SERIAL>
</EDI_DC40>
<Z1COLL_AGENCY SEGMENT="1">
<GPART>0000000101</GPART>
<EMAIL>domain@domain.com</EMAIL>
<Z1COLL_HEADER SEGMENT="1">
<VKONT>200000541301</VKONT>
<GPART>1000447089</GPART>
<VKONA>22611402001</VKONA>
<INKNO>00000000000101953558</INKNO>
<INKBP>0000000101</INKBP>
<INKDAT>20191025</INKDAT>
<INKENDAT>20200123</INKENDAT>
<BANKRUPTDAT>00000000</BANKRUPTDAT>
<CLOSED/>
<UPDATED_DATE>00000000</UPDATED_DATE>
<COLLREFNR>200005413019-024-6</COLLREFNR>
<NAME_ORG1>SOME OTHER NAME</NAME_ORG1>
<LEG_CITY1>SOME OTHER CITY</LEG_CITY1>
<LEG_POST_CODE1>105677</LEG_POST_CODE1>
<LEG_STREET>ADDRESS 2</LEG_STREET>
<LEG_HOUSE_NUM1/>
<BU_SORT2>02226696981</BU_SORT2>
<MAIL_CITY1>CITY 1</MAIL_CITY1>
<MAIL_POST_CODE1>35220</MAIL_POST_CODE1>
<MAIL_STREET>MAIL STREET 1</MAIL_STREET>
<MAIL_HOUSE_NUM1/>
<Z1COLL_ITEM SEGMENT="1">
<OPBEL>000210625857</OPBEL>
<XBLNR>0000198653579124</XBLNR>
<FAEDN>20190916</FAEDN>
<AGDAT>20191025</AGDAT>
<INITAMNT> 80.00</INITAMNT>
<PAYAMNT> 0.00</PAYAMNT>
<WRTOFFAMNT> 0.00</WRTOFFAMNT>
<OPENAMNT> 80.00</OPENAMNT>
<INVAMNT> 80.00</INVAMNT>
<WAERS>HRK</WAERS>
<RECINKDAT>00000000</RECINKDAT>
</Z1COLL_ITEM>
<Z1COLL_ITEM SEGMENT="1">
<OPBEL>000210625857</OPBEL>
<XBLNR>0000198653579124</XBLNR>
<FAEDN>20191016</FAEDN>
<AGDAT>20191025</AGDAT>
<INITAMNT> 80.00</INITAMNT>
<PAYAMNT> 0.00</PAYAMNT>
<WRTOFFAMNT> 0.00</WRTOFFAMNT>
<OPENAMNT> 80.00</OPENAMNT>
<INVAMNT> 80.00</INVAMNT>
<WAERS>HRK</WAERS>
<RECINKDAT>00000000</RECINKDAT>
</Z1COLL_ITEM>
<Z1COLL_ITEM SEGMENT="1">
<OPBEL>019183828875</OPBEL>
<XBLNR>2261140200119081</XBLNR>
<FAEDN>20190816</FAEDN>
<AGDAT>20191025</AGDAT>
<INITAMNT> 159.00</INITAMNT>
<PAYAMNT> 0.00</PAYAMNT>
<WRTOFFAMNT> 0.00</WRTOFFAMNT>
<OPENAMNT> 159.00</OPENAMNT>
<INVAMNT> 159.00</INVAMNT>
<WAERS>CURRENCY</WAERS>
<RECINKDAT>00000000</RECINKDAT>
</Z1COLL_ITEM>
<MSISDNS>
<MSISDN>381653490012</MSISDN>
</MSISDNS>
<MCD_DATA>
<MCD_MONTHS/>
<MCD_AMOUNT/>
</MCD_DATA>
<PHONE_NUM/>
</Z1COLL_HEADER>
</Z1COLL_AGENCY>
</IDOC>
</ZCOLL>
我需要的是,如果 OPBEL 和 XBLNR 元素值相同,则删除该节点并将其替换为 OPBEL 和 XBLNR 的单个重复值以及 INITAMNT、PAYAMNT、WRTOFFAMNT、OPENAMNT 和 INVAMNT 的汇总值,以及 return .xml 具有相同结构的文件。
这是我的代码,我卡在一半了:
if (isset($_POST['submit'])) {
//echo "<pre>";
//print_r($_FILES['file']['name']);
//echo "</pre>";
if(isset($_FILES['file']['name'])) {
$get = file_get_contents($_FILES['file']['tmp_name']);
$arr = simplexml_load_string($get);
foreach ($arr->IDOC->Z1COLL_AGENCY->Z1COLL_HEADER as $element) {
$delete_node = array();
foreach($element->Z1COLL_ITEM as $item) {
$doubles_XBLNR = array_count_values($item->XBLNR);
$doubles_OPBEL = array_count_values($item->OPBEL);
$doubles_no_XBLNR = count($doubles_XBLNR);
$doubles_no_OPBEL = count($doubles_OPBEL);
if (($doubles_no_XBLNR > 2) && ($doubles_no_OPBEL > 2)){
$item->INITAMNT += $item->INITAMNT;
$item->PAYAMNT += $item->PAYAMNT;
$item->WRTOFFAMNT += $item->WRTOFFAMNT;
$$item->OPENAMNT += $item->OPENAMNT;
$item->INVAMNT += $item->INVAMNT;
}
}
echo "<br />";
}
这是需要的 xml 输出:
<?xml version="1.0" encoding="ISO-8859-2"?>
<ZCOLL>
<IDOC BEGIN="1">
<EDI_DC40 SEGMENT="1">
<DOCNUM>0000000008857855</DOCNUM>
<SERIAL>20191025143123</SERIAL>
</EDI_DC40>
<Z1COLL_AGENCY SEGMENT="1">
<GPART>0000000101</GPART>
<EMAIL>domain@domain.com</EMAIL>
<Z1COLL_HEADER SEGMENT="1">
<VKONT>200000541301</VKONT>
<GPART>1000447089</GPART>
<VKONA>22611402001</VKONA>
<INKNO>00000000000101953558</INKNO>
<INKBP>0000000101</INKBP>
<INKDAT>20191025</INKDAT>
<INKENDAT>20200123</INKENDAT>
<BANKRUPTDAT>00000000</BANKRUPTDAT>
<CLOSED/>
<UPDATED_DATE>00000000</UPDATED_DATE>
<COLLREFNR>200005413019-024-6</COLLREFNR>
<NAME_ORG1>SOME OTHER NAME</NAME_ORG1>
<LEG_CITY1>SOME OTHER CITY</LEG_CITY1>
<LEG_POST_CODE1>105677</LEG_POST_CODE1>
<LEG_STREET>ADDRESS 2</LEG_STREET>
<LEG_HOUSE_NUM1/>
<BU_SORT2>02226696981</BU_SORT2>
<MAIL_CITY1>CITY 1</MAIL_CITY1>
<MAIL_POST_CODE1>35220</MAIL_POST_CODE1>
<MAIL_STREET>MAIL STREET 1</MAIL_STREET>
<MAIL_HOUSE_NUM1/>
<Z1COLL_ITEM SEGMENT="1">
<OPBEL>000210625857</OPBEL>
<XBLNR>0000198653579124</XBLNR>
<FAEDN>20190916</FAEDN>
<AGDAT>20191025</AGDAT>
<INITAMNT> 160.00</INITAMNT>
<PAYAMNT> 0.00</PAYAMNT>
<WRTOFFAMNT> 0.00</WRTOFFAMNT>
<OPENAMNT> 160.00</OPENAMNT>
<INVAMNT> 160.00</INVAMNT>
<WAERS>CURRENCY</WAERS>
<RECINKDAT>00000000</RECINKDAT>
</Z1COLL_ITEM>
<Z1COLL_ITEM SEGMENT="1">
<OPBEL>019183828875</OPBEL>
<XBLNR>2261140200119081</XBLNR>
<FAEDN>20190816</FAEDN>
<AGDAT>20191025</AGDAT>
<INITAMNT> 159.00</INITAMNT>
<PAYAMNT> 0.00</PAYAMNT>
<WRTOFFAMNT> 0.00</WRTOFFAMNT>
<OPENAMNT> 159.00</OPENAMNT>
<INVAMNT> 159.00</INVAMNT>
<WAERS>CURRENCY</WAERS>
<RECINKDAT>00000000</RECINKDAT>
</Z1COLL_ITEM>
<MSISDNS>
<MSISDN>381653490012</MSISDN>
</MSISDNS>
<MCD_DATA>
<MCD_MONTHS/>
<MCD_AMOUNT/>
</MCD_DATA>
<PHONE_NUM/>
</Z1COLL_HEADER>
</Z1COLL_AGENCY>
</IDOC>
</ZCOLL>
感谢任何帮助。谢谢。
希望下面的代码能回答您的问题。
您也可以使用 dom
和 xpath
来解析 xml
。我会创建一个搜索数组,其中包含 OPBEL
和 XBLNR
值的出现索引。
然后您可以使用array_unique, array_diff_assoc, array_keys and array_intersect, also see this answer找到重复的节点索引。
现在,您可以删除重复的节点并插入一个具有聚合值的新节点。这是代码,其中 data/input.xml
是您提供的 xml 文件。
$xml = file_get_contents(__DIR__ . '/data/input.xml');
$dom = new \DOMDocument('1.0', 'UTF-8');
@$dom->loadXML($xml);
$xpath = new \DOMXPath($dom);
$nodes = $xpath->query('//Z1COLL_ITEM');
$search = [];
$index = 0;
foreach ($nodes as $node) {
$OPBEL = $xpath->query('OPBEL', $node)->item(0)->nodeValue;
$XBLNR = $xpath->query('XBLNR', $node)->item(0)->nodeValue;
$search[$index] = $OPBEL . $XBLNR;
$index++;
}
// Unique values
$unique = array_unique($search);
// Duplicates
$duplicates = array_diff_assoc($search, $unique);
// Get duplicate keys
$duplicateIndeces = array_keys(array_intersect($search, $duplicates));
$aggregate = [];
$firstNode = $xpath->query('//Z1COLL_ITEM')->item($duplicateIndeces[0]);
// Iterate through the duplicated nodes
foreach (array_slice($duplicateIndeces, 1) as $duplicateIndex) {
$node = $xpath->query('//Z1COLL_ITEM')->item($duplicateIndex);
// Update the desired values for the first occurrence
$xpath->query('INITAMNT', $firstNode)->item(0)->nodeValue += $xpath->query('INITAMNT', $node)->item(0)->nodeValue;
$xpath->query('PAYAMNT', $firstNode)->item(0)->nodeValue += $xpath->query('PAYAMNT', $node)->item(0)->nodeValue;
$xpath->query('WRTOFFAMNT', $firstNode)->item(0)->nodeValue += $xpath->query('WRTOFFAMNT', $node)->item(0)->nodeValue;
$xpath->query('OPENAMNT', $firstNode)->item(0)->nodeValue += $xpath->query('OPENAMNT', $node)->item(0)->nodeValue;
$xpath->query('INVAMNT', $firstNode)->item(0)->nodeValue += $xpath->query('INVAMNT', $node)->item(0)->nodeValue;
// Remove the duplicated node
$node->parentNode->removeChild($node);
}
echo $dom->saveXML();
生成的输出如下:
<?xml version="1.0" encoding="ISO-8859-2"?>
<ZCOLL>
<IDOC BEGIN="1">
<EDI_DC40 SEGMENT="1">
<DOCNUM>0000000008857855</DOCNUM>
<SERIAL>20191025143123</SERIAL>
</EDI_DC40>
<Z1COLL_AGENCY SEGMENT="1">
<GPART>0000000101</GPART>
<EMAIL>domain@domain.com</EMAIL>
<Z1COLL_HEADER SEGMENT="1">
<VKONT>200000541301</VKONT>
<GPART>1000447089</GPART>
<VKONA>22611402001</VKONA>
<INKNO>00000000000101953558</INKNO>
<INKBP>0000000101</INKBP>
<INKDAT>20191025</INKDAT>
<INKENDAT>20200123</INKENDAT>
<BANKRUPTDAT>00000000</BANKRUPTDAT>
<CLOSED/>
<UPDATED_DATE>00000000</UPDATED_DATE>
<COLLREFNR>200005413019-024-6</COLLREFNR>
<NAME_ORG1>SOME OTHER NAME</NAME_ORG1>
<LEG_CITY1>SOME OTHER CITY</LEG_CITY1>
<LEG_POST_CODE1>105677</LEG_POST_CODE1>
<LEG_STREET>ADDRESS 2</LEG_STREET>
<LEG_HOUSE_NUM1/>
<BU_SORT2>02226696981</BU_SORT2>
<MAIL_CITY1>CITY 1</MAIL_CITY1>
<MAIL_POST_CODE1>35220</MAIL_POST_CODE1>
<MAIL_STREET>MAIL STREET 1</MAIL_STREET>
<MAIL_HOUSE_NUM1/>
<Z1COLL_ITEM SEGMENT="1">
<OPBEL>000210625857</OPBEL>
<XBLNR>0000198653579124</XBLNR>
<FAEDN>20190916</FAEDN>
<AGDAT>20191025</AGDAT>
<INITAMNT>160</INITAMNT>
<PAYAMNT>0</PAYAMNT>
<WRTOFFAMNT>0</WRTOFFAMNT>
<OPENAMNT>160</OPENAMNT>
<INVAMNT>160</INVAMNT>
<WAERS>HRK</WAERS>
<RECINKDAT>00000000</RECINKDAT>
</Z1COLL_ITEM>
<Z1COLL_ITEM SEGMENT="1">
<OPBEL>019183828875</OPBEL>
<XBLNR>2261140200119081</XBLNR>
<FAEDN>20190816</FAEDN>
<AGDAT>20191025</AGDAT>
<INITAMNT> 159.00</INITAMNT>
<PAYAMNT> 0.00</PAYAMNT>
<WRTOFFAMNT> 0.00</WRTOFFAMNT>
<OPENAMNT> 159.00</OPENAMNT>
<INVAMNT> 159.00</INVAMNT>
<WAERS>CURRENCY</WAERS>
<RECINKDAT>00000000</RECINKDAT>
</Z1COLL_ITEM>
<MSISDNS>
<MSISDN>381653490012</MSISDN>
</MSISDNS>
<MCD_DATA>
<MCD_MONTHS/>
<MCD_AMOUNT/>
</MCD_DATA>
<PHONE_NUM/>
</Z1COLL_HEADER>
</Z1COLL_AGENCY>
</IDOC>
</ZCOLL>
使用 DOM 文档操作更容易,因为每个部分都由了解其上下文的节点对象表示。对于您的情况,您可以建立一个节点索引,该节点按从 OPBEL 和 XBLNR 值生成的键分组。然后合并分组的节点。
$document = new DOMDocument();
$document->loadXML(getXML());
$xpath = new DOMXPath($document);
// iterate all header elements (do not merge items from different headers)
foreach ($xpath->evaluate('//Z1COLL_HEADER ') as $header) {
$groups = [];
// iterate the items
foreach ($xpath->evaluate('Z1COLL_ITEM', $header) as $item) {
// combine keys into a single string
$groupKey = $xpath->evaluate('concat(OPBEL, "|", XBLNR)', $item);
if (!isset($groups[$groupKey])) {
$groups[$groupKey] = [];
}
// add the current item to a group defined by the generated key
$groups[$groupKey][] = $item;
}
// now filter for groups with multiple items
$groups = array_filter($groups, function($group) { return count($group) > 1; });
// iterate the groups with multiple items
foreach ($groups as $group) {
// extract the first item node from the array
$firstItem = array_shift($group);
// iterate the other items of the group
foreach ($group as $item) {
$merges = ['INITAMNT', 'PAYAMNT', 'WRTOFFAMNT', 'OPENAMNT', 'INVAMNT'];
// iterate the child node names to merge
foreach ($merges as $merge) {
// get the node of the first item
$target = $xpath->evaluate($merge, $firstItem)->item(0);
// get the node of the current item
$source = $xpath->evaluate($merge, $item)->item(0);
// if here are both
if ($target && $source) {
// sum the values and format them
$target->textContent = number_format(
$target->textContent + $source->textContent, 2
);
} elseif ($source) {
// if the child does not exists in the first node move it over
$firstItem->appendChild($source);
}
}
$item->parentNode->removeChild($item);
}
}
}
echo $document->saveXML();
为此我推荐 XSLT。在我看来(在 XSLT 3.0 中):
<xsl:transform version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
expand-text="yes">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="Z1COLL_HEADER">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates select="* except Z1COLL_ITEM"/>
<xsl:for-each-group select="Z1COLL_ITEM" group-by="OPBEL, XBLNR" composite="yes">
<xsl:copy-of select="current-group()[1]/(OPBEL,XBLNR, FAEDN, AGDAT)"/>
<INITAMNT>{sum(current-group()/INITAMT)}</INITAMNT>
<PAYAMNT>{sum(current-group()/PAYAMNT)}</PAYAMNT>
<WRTOFFAMNT>{sum(current-group()/WRTOFFAMNT)}</WRTOFFAMNT>
<OPENAMNT>{sum(current-group()/OPENAMNT)}</OPENAMNT>
<INVAMNT>{sum(current-group()/INVAMNT)}</INVAMNT>
<xsl:copy-of select="current-group()[1]/(WAERS, RECINKDAT)"/>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:transform>
早期 XSLT 版本的解决方案也是可能的,但有点冗长。
此解决方案使用 SimpleXML 并基本上跟踪到目前为止找到的元素,然后如果它找到另一个类似的记录,只需将值添加到找到的第一个记录。然后它使用(在本例中)unset($entry[0]);
从原始文档中删除重复条目...
$xml = simplexml_load_file($_FILES['file']['tmp_name']);
$existing = [];
foreach ( $xml->xpath("//Z1COLL_ITEM") as $entry ) {
$index = $entry->OPBEL."#".$entry->XBLNR;
if ( isset ($existing[$index]) ) {
$existing[$index]->INITAMNT += $entry->INITAMNT;
$existing[$index]->PAYAMNT += $entry->PAYAMNT;
$existing[$index]->WRTOFFAMNT += $entry->WRTOFFAMNT;
$existing[$index]->OPENAMNT += $entry->OPENAMNT;
$existing[$index]->INVAMNT += $entry->INVAMNT;
unset($entry[0]);
}
else {
$existing[$index] = $entry;
}
}
我每天都会收到一个 .xml 文件,但需要对元素值进行一些额外的编辑。我已经通过数据透视表在 Excel 中完成了我需要的工作,但该解决方案是不可接受的,因为这会创建新结构,我无法将其保存为 xml,我需要相同的 xml结构作为输出。由于我安装了 PHP Composer,我认为 simpleXML 库是最简单的解决方案。上传原始 .xml 文件并通过 simplexml_load_string 函数进行修改。问题是,我对这种语法不是很精通,我需要一些帮助。这是我的 .xml 文件:
<?xml version="1.0" encoding="ISO-8859-2"?>
<ZCOLL>
<IDOC BEGIN="1">
<EDI_DC40 SEGMENT="1">
<DOCNUM>0000000008857855</DOCNUM>
<SERIAL>20191025143123</SERIAL>
</EDI_DC40>
<Z1COLL_AGENCY SEGMENT="1">
<GPART>0000000101</GPART>
<EMAIL>domain@domain.com</EMAIL>
<Z1COLL_HEADER SEGMENT="1">
<VKONT>200000541301</VKONT>
<GPART>1000447089</GPART>
<VKONA>22611402001</VKONA>
<INKNO>00000000000101953558</INKNO>
<INKBP>0000000101</INKBP>
<INKDAT>20191025</INKDAT>
<INKENDAT>20200123</INKENDAT>
<BANKRUPTDAT>00000000</BANKRUPTDAT>
<CLOSED/>
<UPDATED_DATE>00000000</UPDATED_DATE>
<COLLREFNR>200005413019-024-6</COLLREFNR>
<NAME_ORG1>SOME OTHER NAME</NAME_ORG1>
<LEG_CITY1>SOME OTHER CITY</LEG_CITY1>
<LEG_POST_CODE1>105677</LEG_POST_CODE1>
<LEG_STREET>ADDRESS 2</LEG_STREET>
<LEG_HOUSE_NUM1/>
<BU_SORT2>02226696981</BU_SORT2>
<MAIL_CITY1>CITY 1</MAIL_CITY1>
<MAIL_POST_CODE1>35220</MAIL_POST_CODE1>
<MAIL_STREET>MAIL STREET 1</MAIL_STREET>
<MAIL_HOUSE_NUM1/>
<Z1COLL_ITEM SEGMENT="1">
<OPBEL>000210625857</OPBEL>
<XBLNR>0000198653579124</XBLNR>
<FAEDN>20190916</FAEDN>
<AGDAT>20191025</AGDAT>
<INITAMNT> 80.00</INITAMNT>
<PAYAMNT> 0.00</PAYAMNT>
<WRTOFFAMNT> 0.00</WRTOFFAMNT>
<OPENAMNT> 80.00</OPENAMNT>
<INVAMNT> 80.00</INVAMNT>
<WAERS>HRK</WAERS>
<RECINKDAT>00000000</RECINKDAT>
</Z1COLL_ITEM>
<Z1COLL_ITEM SEGMENT="1">
<OPBEL>000210625857</OPBEL>
<XBLNR>0000198653579124</XBLNR>
<FAEDN>20191016</FAEDN>
<AGDAT>20191025</AGDAT>
<INITAMNT> 80.00</INITAMNT>
<PAYAMNT> 0.00</PAYAMNT>
<WRTOFFAMNT> 0.00</WRTOFFAMNT>
<OPENAMNT> 80.00</OPENAMNT>
<INVAMNT> 80.00</INVAMNT>
<WAERS>HRK</WAERS>
<RECINKDAT>00000000</RECINKDAT>
</Z1COLL_ITEM>
<Z1COLL_ITEM SEGMENT="1">
<OPBEL>019183828875</OPBEL>
<XBLNR>2261140200119081</XBLNR>
<FAEDN>20190816</FAEDN>
<AGDAT>20191025</AGDAT>
<INITAMNT> 159.00</INITAMNT>
<PAYAMNT> 0.00</PAYAMNT>
<WRTOFFAMNT> 0.00</WRTOFFAMNT>
<OPENAMNT> 159.00</OPENAMNT>
<INVAMNT> 159.00</INVAMNT>
<WAERS>CURRENCY</WAERS>
<RECINKDAT>00000000</RECINKDAT>
</Z1COLL_ITEM>
<MSISDNS>
<MSISDN>381653490012</MSISDN>
</MSISDNS>
<MCD_DATA>
<MCD_MONTHS/>
<MCD_AMOUNT/>
</MCD_DATA>
<PHONE_NUM/>
</Z1COLL_HEADER>
</Z1COLL_AGENCY>
</IDOC>
</ZCOLL>
我需要的是,如果 OPBEL 和 XBLNR 元素值相同,则删除该节点并将其替换为 OPBEL 和 XBLNR 的单个重复值以及 INITAMNT、PAYAMNT、WRTOFFAMNT、OPENAMNT 和 INVAMNT 的汇总值,以及 return .xml 具有相同结构的文件。
这是我的代码,我卡在一半了:
if (isset($_POST['submit'])) {
//echo "<pre>";
//print_r($_FILES['file']['name']);
//echo "</pre>";
if(isset($_FILES['file']['name'])) {
$get = file_get_contents($_FILES['file']['tmp_name']);
$arr = simplexml_load_string($get);
foreach ($arr->IDOC->Z1COLL_AGENCY->Z1COLL_HEADER as $element) {
$delete_node = array();
foreach($element->Z1COLL_ITEM as $item) {
$doubles_XBLNR = array_count_values($item->XBLNR);
$doubles_OPBEL = array_count_values($item->OPBEL);
$doubles_no_XBLNR = count($doubles_XBLNR);
$doubles_no_OPBEL = count($doubles_OPBEL);
if (($doubles_no_XBLNR > 2) && ($doubles_no_OPBEL > 2)){
$item->INITAMNT += $item->INITAMNT;
$item->PAYAMNT += $item->PAYAMNT;
$item->WRTOFFAMNT += $item->WRTOFFAMNT;
$$item->OPENAMNT += $item->OPENAMNT;
$item->INVAMNT += $item->INVAMNT;
}
}
echo "<br />";
}
这是需要的 xml 输出:
<?xml version="1.0" encoding="ISO-8859-2"?>
<ZCOLL>
<IDOC BEGIN="1">
<EDI_DC40 SEGMENT="1">
<DOCNUM>0000000008857855</DOCNUM>
<SERIAL>20191025143123</SERIAL>
</EDI_DC40>
<Z1COLL_AGENCY SEGMENT="1">
<GPART>0000000101</GPART>
<EMAIL>domain@domain.com</EMAIL>
<Z1COLL_HEADER SEGMENT="1">
<VKONT>200000541301</VKONT>
<GPART>1000447089</GPART>
<VKONA>22611402001</VKONA>
<INKNO>00000000000101953558</INKNO>
<INKBP>0000000101</INKBP>
<INKDAT>20191025</INKDAT>
<INKENDAT>20200123</INKENDAT>
<BANKRUPTDAT>00000000</BANKRUPTDAT>
<CLOSED/>
<UPDATED_DATE>00000000</UPDATED_DATE>
<COLLREFNR>200005413019-024-6</COLLREFNR>
<NAME_ORG1>SOME OTHER NAME</NAME_ORG1>
<LEG_CITY1>SOME OTHER CITY</LEG_CITY1>
<LEG_POST_CODE1>105677</LEG_POST_CODE1>
<LEG_STREET>ADDRESS 2</LEG_STREET>
<LEG_HOUSE_NUM1/>
<BU_SORT2>02226696981</BU_SORT2>
<MAIL_CITY1>CITY 1</MAIL_CITY1>
<MAIL_POST_CODE1>35220</MAIL_POST_CODE1>
<MAIL_STREET>MAIL STREET 1</MAIL_STREET>
<MAIL_HOUSE_NUM1/>
<Z1COLL_ITEM SEGMENT="1">
<OPBEL>000210625857</OPBEL>
<XBLNR>0000198653579124</XBLNR>
<FAEDN>20190916</FAEDN>
<AGDAT>20191025</AGDAT>
<INITAMNT> 160.00</INITAMNT>
<PAYAMNT> 0.00</PAYAMNT>
<WRTOFFAMNT> 0.00</WRTOFFAMNT>
<OPENAMNT> 160.00</OPENAMNT>
<INVAMNT> 160.00</INVAMNT>
<WAERS>CURRENCY</WAERS>
<RECINKDAT>00000000</RECINKDAT>
</Z1COLL_ITEM>
<Z1COLL_ITEM SEGMENT="1">
<OPBEL>019183828875</OPBEL>
<XBLNR>2261140200119081</XBLNR>
<FAEDN>20190816</FAEDN>
<AGDAT>20191025</AGDAT>
<INITAMNT> 159.00</INITAMNT>
<PAYAMNT> 0.00</PAYAMNT>
<WRTOFFAMNT> 0.00</WRTOFFAMNT>
<OPENAMNT> 159.00</OPENAMNT>
<INVAMNT> 159.00</INVAMNT>
<WAERS>CURRENCY</WAERS>
<RECINKDAT>00000000</RECINKDAT>
</Z1COLL_ITEM>
<MSISDNS>
<MSISDN>381653490012</MSISDN>
</MSISDNS>
<MCD_DATA>
<MCD_MONTHS/>
<MCD_AMOUNT/>
</MCD_DATA>
<PHONE_NUM/>
</Z1COLL_HEADER>
</Z1COLL_AGENCY>
</IDOC>
</ZCOLL>
感谢任何帮助。谢谢。
希望下面的代码能回答您的问题。
您也可以使用 dom
和 xpath
来解析 xml
。我会创建一个搜索数组,其中包含 OPBEL
和 XBLNR
值的出现索引。
然后您可以使用array_unique, array_diff_assoc, array_keys and array_intersect, also see this answer找到重复的节点索引。
现在,您可以删除重复的节点并插入一个具有聚合值的新节点。这是代码,其中 data/input.xml
是您提供的 xml 文件。
$xml = file_get_contents(__DIR__ . '/data/input.xml');
$dom = new \DOMDocument('1.0', 'UTF-8');
@$dom->loadXML($xml);
$xpath = new \DOMXPath($dom);
$nodes = $xpath->query('//Z1COLL_ITEM');
$search = [];
$index = 0;
foreach ($nodes as $node) {
$OPBEL = $xpath->query('OPBEL', $node)->item(0)->nodeValue;
$XBLNR = $xpath->query('XBLNR', $node)->item(0)->nodeValue;
$search[$index] = $OPBEL . $XBLNR;
$index++;
}
// Unique values
$unique = array_unique($search);
// Duplicates
$duplicates = array_diff_assoc($search, $unique);
// Get duplicate keys
$duplicateIndeces = array_keys(array_intersect($search, $duplicates));
$aggregate = [];
$firstNode = $xpath->query('//Z1COLL_ITEM')->item($duplicateIndeces[0]);
// Iterate through the duplicated nodes
foreach (array_slice($duplicateIndeces, 1) as $duplicateIndex) {
$node = $xpath->query('//Z1COLL_ITEM')->item($duplicateIndex);
// Update the desired values for the first occurrence
$xpath->query('INITAMNT', $firstNode)->item(0)->nodeValue += $xpath->query('INITAMNT', $node)->item(0)->nodeValue;
$xpath->query('PAYAMNT', $firstNode)->item(0)->nodeValue += $xpath->query('PAYAMNT', $node)->item(0)->nodeValue;
$xpath->query('WRTOFFAMNT', $firstNode)->item(0)->nodeValue += $xpath->query('WRTOFFAMNT', $node)->item(0)->nodeValue;
$xpath->query('OPENAMNT', $firstNode)->item(0)->nodeValue += $xpath->query('OPENAMNT', $node)->item(0)->nodeValue;
$xpath->query('INVAMNT', $firstNode)->item(0)->nodeValue += $xpath->query('INVAMNT', $node)->item(0)->nodeValue;
// Remove the duplicated node
$node->parentNode->removeChild($node);
}
echo $dom->saveXML();
生成的输出如下:
<?xml version="1.0" encoding="ISO-8859-2"?>
<ZCOLL>
<IDOC BEGIN="1">
<EDI_DC40 SEGMENT="1">
<DOCNUM>0000000008857855</DOCNUM>
<SERIAL>20191025143123</SERIAL>
</EDI_DC40>
<Z1COLL_AGENCY SEGMENT="1">
<GPART>0000000101</GPART>
<EMAIL>domain@domain.com</EMAIL>
<Z1COLL_HEADER SEGMENT="1">
<VKONT>200000541301</VKONT>
<GPART>1000447089</GPART>
<VKONA>22611402001</VKONA>
<INKNO>00000000000101953558</INKNO>
<INKBP>0000000101</INKBP>
<INKDAT>20191025</INKDAT>
<INKENDAT>20200123</INKENDAT>
<BANKRUPTDAT>00000000</BANKRUPTDAT>
<CLOSED/>
<UPDATED_DATE>00000000</UPDATED_DATE>
<COLLREFNR>200005413019-024-6</COLLREFNR>
<NAME_ORG1>SOME OTHER NAME</NAME_ORG1>
<LEG_CITY1>SOME OTHER CITY</LEG_CITY1>
<LEG_POST_CODE1>105677</LEG_POST_CODE1>
<LEG_STREET>ADDRESS 2</LEG_STREET>
<LEG_HOUSE_NUM1/>
<BU_SORT2>02226696981</BU_SORT2>
<MAIL_CITY1>CITY 1</MAIL_CITY1>
<MAIL_POST_CODE1>35220</MAIL_POST_CODE1>
<MAIL_STREET>MAIL STREET 1</MAIL_STREET>
<MAIL_HOUSE_NUM1/>
<Z1COLL_ITEM SEGMENT="1">
<OPBEL>000210625857</OPBEL>
<XBLNR>0000198653579124</XBLNR>
<FAEDN>20190916</FAEDN>
<AGDAT>20191025</AGDAT>
<INITAMNT>160</INITAMNT>
<PAYAMNT>0</PAYAMNT>
<WRTOFFAMNT>0</WRTOFFAMNT>
<OPENAMNT>160</OPENAMNT>
<INVAMNT>160</INVAMNT>
<WAERS>HRK</WAERS>
<RECINKDAT>00000000</RECINKDAT>
</Z1COLL_ITEM>
<Z1COLL_ITEM SEGMENT="1">
<OPBEL>019183828875</OPBEL>
<XBLNR>2261140200119081</XBLNR>
<FAEDN>20190816</FAEDN>
<AGDAT>20191025</AGDAT>
<INITAMNT> 159.00</INITAMNT>
<PAYAMNT> 0.00</PAYAMNT>
<WRTOFFAMNT> 0.00</WRTOFFAMNT>
<OPENAMNT> 159.00</OPENAMNT>
<INVAMNT> 159.00</INVAMNT>
<WAERS>CURRENCY</WAERS>
<RECINKDAT>00000000</RECINKDAT>
</Z1COLL_ITEM>
<MSISDNS>
<MSISDN>381653490012</MSISDN>
</MSISDNS>
<MCD_DATA>
<MCD_MONTHS/>
<MCD_AMOUNT/>
</MCD_DATA>
<PHONE_NUM/>
</Z1COLL_HEADER>
</Z1COLL_AGENCY>
</IDOC>
</ZCOLL>
使用 DOM 文档操作更容易,因为每个部分都由了解其上下文的节点对象表示。对于您的情况,您可以建立一个节点索引,该节点按从 OPBEL 和 XBLNR 值生成的键分组。然后合并分组的节点。
$document = new DOMDocument();
$document->loadXML(getXML());
$xpath = new DOMXPath($document);
// iterate all header elements (do not merge items from different headers)
foreach ($xpath->evaluate('//Z1COLL_HEADER ') as $header) {
$groups = [];
// iterate the items
foreach ($xpath->evaluate('Z1COLL_ITEM', $header) as $item) {
// combine keys into a single string
$groupKey = $xpath->evaluate('concat(OPBEL, "|", XBLNR)', $item);
if (!isset($groups[$groupKey])) {
$groups[$groupKey] = [];
}
// add the current item to a group defined by the generated key
$groups[$groupKey][] = $item;
}
// now filter for groups with multiple items
$groups = array_filter($groups, function($group) { return count($group) > 1; });
// iterate the groups with multiple items
foreach ($groups as $group) {
// extract the first item node from the array
$firstItem = array_shift($group);
// iterate the other items of the group
foreach ($group as $item) {
$merges = ['INITAMNT', 'PAYAMNT', 'WRTOFFAMNT', 'OPENAMNT', 'INVAMNT'];
// iterate the child node names to merge
foreach ($merges as $merge) {
// get the node of the first item
$target = $xpath->evaluate($merge, $firstItem)->item(0);
// get the node of the current item
$source = $xpath->evaluate($merge, $item)->item(0);
// if here are both
if ($target && $source) {
// sum the values and format them
$target->textContent = number_format(
$target->textContent + $source->textContent, 2
);
} elseif ($source) {
// if the child does not exists in the first node move it over
$firstItem->appendChild($source);
}
}
$item->parentNode->removeChild($item);
}
}
}
echo $document->saveXML();
为此我推荐 XSLT。在我看来(在 XSLT 3.0 中):
<xsl:transform version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
expand-text="yes">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="Z1COLL_HEADER">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:apply-templates select="* except Z1COLL_ITEM"/>
<xsl:for-each-group select="Z1COLL_ITEM" group-by="OPBEL, XBLNR" composite="yes">
<xsl:copy-of select="current-group()[1]/(OPBEL,XBLNR, FAEDN, AGDAT)"/>
<INITAMNT>{sum(current-group()/INITAMT)}</INITAMNT>
<PAYAMNT>{sum(current-group()/PAYAMNT)}</PAYAMNT>
<WRTOFFAMNT>{sum(current-group()/WRTOFFAMNT)}</WRTOFFAMNT>
<OPENAMNT>{sum(current-group()/OPENAMNT)}</OPENAMNT>
<INVAMNT>{sum(current-group()/INVAMNT)}</INVAMNT>
<xsl:copy-of select="current-group()[1]/(WAERS, RECINKDAT)"/>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:transform>
早期 XSLT 版本的解决方案也是可能的,但有点冗长。
此解决方案使用 SimpleXML 并基本上跟踪到目前为止找到的元素,然后如果它找到另一个类似的记录,只需将值添加到找到的第一个记录。然后它使用(在本例中)unset($entry[0]);
从原始文档中删除重复条目...
$xml = simplexml_load_file($_FILES['file']['tmp_name']);
$existing = [];
foreach ( $xml->xpath("//Z1COLL_ITEM") as $entry ) {
$index = $entry->OPBEL."#".$entry->XBLNR;
if ( isset ($existing[$index]) ) {
$existing[$index]->INITAMNT += $entry->INITAMNT;
$existing[$index]->PAYAMNT += $entry->PAYAMNT;
$existing[$index]->WRTOFFAMNT += $entry->WRTOFFAMNT;
$existing[$index]->OPENAMNT += $entry->OPENAMNT;
$existing[$index]->INVAMNT += $entry->INVAMNT;
unset($entry[0]);
}
else {
$existing[$index] = $entry;
}
}