2016-10-11 189 views
0

我熟悉XML,并将其用于一些简单的用途。但是,我被要求创建一个自定义解决方案(必须使用Perl)来将付款提交给AuthorizeNet的网关。 AuthNet不支持Perl,所以我是独立的。XML :: LibXML和名称空间

我使用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,其中它提到使用允许忽略名称空间的“本地名称”函数。我认为这可能会绕过这个问题,当我尝试它时,我终于看到了一些数据显示。

foreach my $transres ($dom->findnodes("//*[local-name()='resultCode']")) { 
    print "Result: " . $transres->to_literal() . "<br />\n"; 
} 

这导致所需/期望值“Ok”的返回。它也适用于迄今我尝试过的其他节点。但是,我通常不愿意使用“只是忽略它”类型的解决方案,除非它们是真正合适的并且需要。

我的问题归结为这个问题:我使用的bypass-namespace方法是否最终从返回的XML数据中获取数据是可取的?

我想知道为什么AuthNet在返回数据中指定了多个名称空间,然后在数据中不使用它们 - 至少是我当前回收的数据。如果我绕过代码中的命名空间,但后来又如何,特别是当我们开始运行时,不同的命名空间会变得相关?我其实并不知道,AuthNet所谓的“支持”我已经能够达到的范围从无益到完全没有反应。

任何有关Perl/XML /名称空间问题经验丰富的建议/指导将不胜感激。

+0

是的! @Borodin,那些是我正在寻找的提示。您在命名空间问题上的说明有所不同,并帮助我解决了这个问题。我现在有一些工作代码可以成功解析AuthNet返回数据。 – user2226161

回答

1

您是在广义上是正确的路线。问题是,你的XML数据使用它适用于那些没有明确的命名空间前缀

xmlns="AnetApi/xml/v1/schema/AnetApiSchema.xsd" 

当你观察到的,xsixsd前缀为您的数据未使用的所有标识默认命名空间,所以没有必要在你的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
<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 = (
    "&" => "&amp;", 
    "<" => "&lt;", 
    ">" => "&gt;", 
    '"' => "&quot;", 
    "'" => "&#39;", 
); 
my $class = join '', map quotemeta, keys(%escapes); 
my $re = qr/([$class])/; 

sub text_to_html { shift =~ s/$re/$escapes{$1}/rg }