1
votes

I have the following code which should add an error if there is an orderEvents element and that element has a child node that is not an orderEvent or empty text. The test cases do a better job of showing what I'm after.

The XPATH expression works in all cases except when there is empty space within the orderEvents element. See test case validVendorPaymentFormat7 below. Here's my XPATH expression.

"./expectedVendorPaymentTransactions/orderEvents/node()[not(self::orderEvent) or self::text()[normalize-space(.)!='']]"

Test Cases:

<TestData>
  <TestcaseList>
    <Testcase>
      <rootIdentifier>validVendorPaymentFormat1</rootIdentifier>
      <expectedVendorPaymentTransactions>
        <orderEvents></orderEvents>
      </expectedVendorPaymentTransactions>
    </Testcase>
    <Testcase>
      <rootIdentifier>validVendorPaymentFormat2</rootIdentifier>
    </Testcase>
    <Testcase>
      <rootIdentifier>validVendorPaymentFormat3</rootIdentifier>
      <expectedVendorPaymentTransactions>
      </expectedVendorPaymentTransactions>
    </Testcase>
    <Testcase>
      <rootIdentifier>validVendorPaymentFormat4</rootIdentifier>
      <expectedVendorPaymentTransactions>
        <orderEvents><orderEvent>SOME_EVENT</orderEvent></orderEvents>
      </expectedVendorPaymentTransactions>
    </Testcase>
     <Testcase>
      <rootIdentifier>validVendorPaymentFormat5</rootIdentifier>
      <expectedVendorPaymentTransactions>
        <orderEvents><orderEvent>SOME_EVENT</orderEvent></orderEvents>
      </expectedVendorPaymentTransactions>
    </Testcase>
    <Testcase>
      <rootIdentifier>validVendorPaymentFormat6</rootIdentifier>
      <expectedVendorPaymentTransactions>
        <orderEvents><orderEvent>SOME_EVENT</orderEvent><orderEvent>SOME_EVENT</orderEvent></orderEvents>
      </expectedVendorPaymentTransactions>
    </Testcase>
    <Testcase>
      <rootIdentifier>validVendorPaymentFormat7</rootIdentifier>
      <expectedVendorPaymentTransactions>
        <orderEvents>
          <orderEvent>SOME_EVENT</orderEvent>
        </orderEvents>
      </expectedVendorPaymentTransactions>
    </Testcase>
  </TestcaseList>
</TestData>

The failing case is validVendorPaymentFormat7

Failure Message:

[exec] Failure:
 [exec]   Order vendor payments format contains elements other than orderEvent for validVendorPaymentFormat7 ["\n          ", "\n        "].
 [exec]   <false> is not true.

Changing the failing case to:

        <Testcase>
          <rootIdentifier>validVendorPaymentFormat7</rootIdentifier>
          <expectedVendorPaymentTransactions>
            <orderEvents><orderEvent>SOME_EVENT</orderEvent></orderEvents>
          </expectedVendorPaymentTransactions>
        </Testcase>

Results in a pass.

Unfortunately on http://www.freeformatter.com/xpath-tester.html test case #7 returns an empty list as expected.

UPDATE - Adding code
Ruby Version: 1.9
irb -v = 0.9.6

Test File:

require 'rexml/document'

xpath = "//expectedVendorPaymentTransactions/orderEvents/node()[not(self::orderEvent) or self::text()[translate(., ' &#10;', '')!='']]"
document = REXML::Document.new <<EOF 
    <Testcase>
      <rootIdentifier>validVendorPaymentFormat7</rootIdentifier>
      <expectedVendorPaymentTransactions>
        <orderEvents>
          <orderEvent>SOME_EVENT</orderEvent>
        </orderEvents>
      </expectedVendorPaymentTransactions>
    </Testcase>
EOF

document2 = REXML::Document.new <<EOF 
    <Testcase>
      <rootIdentifier>validVendorPaymentFormat7</rootIdentifier>
      <expectedVendorPaymentTransactions>
        <orderEvents><orderEvent>SOME_EVENT</orderEvent></orderEvents>
      </expectedVendorPaymentTransactions>
    </Testcase>
EOF

puts "1: #{REXML::XPath.match(document, xpath).inspect}"
puts "2: #{REXML::XPath.match(document2, xpath).inspect}"

Output:

irb(main):001:0> load './test/test_rexp.rb'
1: ["\n          ", "\n        "]
2: []
=> true

Latest Revision of Jens:

irb(main):009:0> load './test/test_rexp.rb'
1: ", "
2: ", "
=> true
2

2 Answers

1
votes

It seems Ruby doesn't handle newlines as whitespace in normalize-space(...). You're interested if it contains anything but whitespace, so just remove all whitespace. translate(...) can get handy. The first parameter is what you're matching, the second string is what to match, and the third tells which characters to replace to; as it's empty, all matched characters will get deleted.

translate(., ' &#10;', '')

I could reproduce the issue with Perl's XPath and it was solved with this query:

/expectedVendorPaymentTransactions/orderEvents/node()[not(self::orderEvent) or self::text()[translate(., ' &#10;', '')!='']]

Update: Ruby does not seem to resolve the XML entities correctly, but you can use \n for newline instead:

translate(., ' \n', '')
1
votes

Dropping the whitespace handling from XPATH and using a rather easy ruby workaround works.

Working code:

require 'rexml/document'
require 'rexml/text'

xpath = "//expectedVendorPaymentTransactions/orderEvents/node()[not(self::orderEvent)]"
document = REXML::Document.new <<EOF 
    <Testcase>
      <rootIdentifier>validVendorPaymentFormat7</rootIdentifier>
      <expectedVendorPaymentTransactions>
        <orderEvents>
          <orderEvent>Fulfill</orderEvent>
        </orderEvents>
      </expectedVendorPaymentTransactions>
    </Testcase>
EOF

document2 = REXML::Document.new <<EOF 
    <Testcase>
      <rootIdentifier>validVendorPaymentFormat7</rootIdentifier>
      <expectedVendorPaymentTransactions>
        <orderEvents><orderEvent>Fulfill</orderEvent></orderEvents>
      </expectedVendorPaymentTransactions>
    </Testcase>
EOF

elements = REXML::XPath.match(document, xpath)
elements.reject! { |element| 
      element.instance_of?(REXML::Text) and element.value.gsub(/[\n ]/,"") == ''
    }
puts "1: #{elements.inspect}"

elements2 = REXML::XPath.match(document2, xpath)
elements2.reject! { |element| 
      element.instance_of?(REXML::Text) and element.value.gsub(/[\n\t ]/,"") == ''
    }
puts "2: #{elements2.inspect}"