XML :: LibXML和命名空间

时间:2016-10-11 01:10:47

标签: xml perl namespaces

我熟悉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 /命名空间问题更有经验的建议/指导。

2 个答案:

答案 0 :(得分:2)

你的观点大致正确。问题是您的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 :(得分:-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 }
相关问题