我熟悉XML,已将其用于一些简单的用途。但是,我被要求创建一个自定义解决方案(必须使用Perl)来向AuthorizeNet的网关提交付款。 AuthNet不支持Perl,所以我独自一人。
我使用的是AuthNet的latest method,它依赖于以XML格式发送和接收交易数据。
我已成功使用Perl模块XML::LibXML成功提交并批准了测试(沙盒)交易。
我在尝试解析返回数据时遇到了麻烦。我终于想通了我第一次处理名称空间。我得到的沙箱返回数据如下。
<createTransactionResponse
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd">
<refId />
<messages>
<resultCode>Ok</resultCode>
<message>
<code>I00001</code>
<text>Successful.</text>
</message>
</messages>
<transactionResponse>
<responseCode>1</responseCode>
<authCode>GTR92Q</authCode>
<avsResultCode>Y</avsResultCode>
<cvvResultCode>P</cvvResultCode>
<cavvResultCode>2</cavvResultCode>
<transId>40003699670</transId>
<refTransID />
<transHash>7E1148E18D5D0E0BE202EB1BCE4B0F09</transHash>
<testRequest>0</testRequest>
<accountNumber>XXXX1111</accountNumber>
<accountType>Visa</accountType>
<messages>
<message>
<code>1</code>
<description>This transaction has been approved.</description>
</message>
</messages>
<userFields>
<userField>
<name>MerchantDefinedFieldName1</name>
<value>MerchantDefinedFieldValue1</value>
</userField>
</userFields>
<transHashSha2 />
</transactionResponse>
</createTransactionResponse>
我需要从中提取某些值。我从AuthNet网关返回上面的数据,将其存储在var $cont
中,然后准备它:
my $dom = XML::LibXML->load_xml(string => $cont);
然后我尝试解析数据节点,搜索某些值,例如:
foreach my $transres ($dom->findnodes("/createTransactionResponse/messages/message")) {
print "Message: " . $transres->to_literal() . "<br />\n";
}
这永远不会返回任何值。然后我意识到我正在处理命名空间问题。我尝试了许多注册命名空间的变体,但无济于事,例如:
my $xpc = XML::LibXML::XPathContext->new($dom);
$xpc->registerNs("xsi", "http://www.w3.org/2001/XMLSchema-instance");
$xpc->registerNs("xsd", "http://www.w3.org/2001/XMLSchema");
foreach my $transres ($xpc->findnodes("//xsi:resultCode")) {
print "Result: " . $transres->to_literal() . "<br />\n";
}
这只是投掷,希望得到某种结果。我注意到,即使返回数据引用了多个命名空间,它们实际上也没有在其余数据中的任何地方使用。我在XML命名空间问题上发现了许多StackOverflow响应,但是当在XML数据的其他地方没有使用任何指定的命名空间时,我永远找不到如何引用节点的示例。比如,什么是默认(如果你愿意)命名空间引用,没有命名空间被引用时 ...如果这是有道理的。
我遇到了一个entry elsewhere in StackOverflow,其中提到了使用“local-name”函数来允许忽略名称空间。我认为这可能会绕过这个问题,当我尝试它时,我终于看到了一些数据显示。
foreach my $transres ($dom->findnodes("//*[local-name()='resultCode']")) {
print "Result: " . $transres->to_literal() . "<br />\n";
}
这导致返回期望/期望值“Ok”。它也适用于我迄今为止尝试过的其他节点。但是,我通常对使用“忽略它”类型的解决方案持谨慎态度,除非它们真正适合并且需要。
我的问题归结为:我用来最终从返回的XML数据访问数据的旁路命名空间方法是否可取?
我想知道为什么AuthNet在返回数据中指定了多个命名空间,然后在数据中不使用任何命名空间 - 至少我当前正在返回的数据。如果我只是绕过我的代码中的命名空间,但稍后,特别是当我们上线时,不同的命名空间变得相关,该怎么办?我实际上不知道,我能够达到的AuthNet所谓的“支持”范围从无益到完全没有反应。
非常感谢那些对Perl / XML /命名空间问题更有经验的建议/指导。
答案 0 :(得分:2)
你的观点大致正确。问题是您的XML数据使用默认命名空间,该命名空间适用于没有显式名称空间前缀的所有标识符
xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd"
如您所见,数据中未使用xsi
和xsd
前缀,因此无需在Perl代码中指定它们。同时,XPath没有隐式命名空间的概念,因此您必须为AuthNet命名空间指定显式缩写并在所有XPath表达式中使用它。
使用的缩写不必与XML数据中定义的缩写相同,显然在默认命名空间的情况下它不能匹配。
以下是我如何编码您对resultCode
元素的访问权限。我总是喜欢使用文档根目录中的完整XPath表达式,而不是使用//anet:resultCode
my $dom = XML::LibXML->load_xml( string => $cont );
my $xpc = XML::LibXML::XPathContext->new( $dom );
$xpc->registerNs( anet => 'AnetApi/xml/v1/schema/AnetApiSchema.xsd' );
for my $transres ( $xpc->findnodes('/anet:createTransactionResponse/anet:messages/anet:resultCode' ) ) {
printf "Result: %s<br />\n", $transres->to_literal;
}
答案 1 :(得分:-1)
<ele xmlns="..."><foo/><bar/></ele>
是
的缩写<p:ele xmlns:p="..."><p:foo/><p:bar/></p:ele>
因此您需要名称空间为AnetApi/xml/v1/schema/AnetApiSchema.xsd
且名称为resultCode
的元素。
my $xpc = XML::LibXML::XPathContext->new($dom);
$xpc->registerNs("a", "AnetApi/xml/v1/schema/AnetApiSchema.xsd");
for my $resultCode_node (
$xpc->findnodes("/a:createTransactionResponse/a:messages/a:resultCode")
) {
print("resultCode: " . text_to_html($resultCode_node->textContent()) . "<br />\n");
}
由于您将文本注入HTML,因此您需要以下内容:
my %escapes = (
"&" => "&",
"<" => "<",
">" => ">",
'"' => """,
"'" => "'",
);
my $class = join '', map quotemeta, keys(%escapes);
my $re = qr/([$class])/;
sub text_to_html { shift =~ s/$re/$escapes{$1}/rg }