質問

I have the following xml file:

<Contract>
    <TotalCosts>31003</TotalCosts>
    <PaymentSchedules>
        <PaymentSchedule>
            <Amount>516.7</Amount>
            <NumberOfPayments>3</NumberOfPayments>
        </PaymentSchedule>
        <PaymentSchedule>
            <Amount>529.7</Amount>
            <NumberOfPayments>1</NumberOfPayments>
        </PaymentSchedule>
        <PaymentSchedule>
            <Amount>516.7</Amount>
            <NumberOfPayments>55</NumberOfPayments>
        </PaymentSchedule>
        <PaymentSchedule>
            <Amount>504.70</Amount>
            <NumberOfPayments>1</NumberOfPayments>
        </PaymentSchedule>
    </PaymentSchedules>
</Contract>

In my schematron file, I would like to make a TotalCosts are the same as the amount in the paymentSchedule.

For that, I need to do the following for each PaymentSchedule:

Amount * NumberOfPayments

After that, I need to take the sum of all PaymentSchedules, and that number should be exactly the same. If you try this for the given example, you will see that the amounts are exactly the same.

To validate this in schematron, I created this schematron file:

<iso:schema xmlns:iso="http://purl.oclc.org/dsdl/schematron"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" queryBinding="xslt2">
    <iso:ns uri="http://www.someurl.com" prefix="func"/>

    <xsl:function name="func:getPaymentScheduleTotal">
        <xsl:param name="contract" as="element()"/>
        <xsl:value-of
            select="sum($contract/PaymentSchedules/PaymentSchedule/(NumberOfPayments * Amount))"/>
    </xsl:function>

    <iso:pattern id="Contract">
        <iso:rule context="Contract">
            <iso:assert test="func:getPaymentScheduleTotal(.) = number(TotalCosts)">amount in paymentschedule(<iso:value-of select="func:getPaymentScheduleTotal(.)"/>)    doesn't match total amount(<iso:value-of select="TotalCosts"/>)</iso:assert>
        </iso:rule>
    </iso:pattern>

</iso:schema>

But this is where I'm running into a problem. I get the following validation result:

amount in paymentschedule(31003.000000000004) doesn't match total amount(31003) (func:getPaymentScheduleTotal(.) = number(TotalCosts)) [assert]

I can't figure out where the .000000000004 is coming from. I can of course use something like floor to round the number, but I believe the .000000000004 shouldn't be there in the first place.

Any ideas?

役に立ちましたか?

解決

The problem that you are experiencing is an expected behavior of how double variables work. The XSLT processor is interpreting Amount and NumberOfPayments as xs:double data types.

As you probably know, xs:double is represented with 64 bits and follows the IEEE754 standard (the specification can be found http://grouper.ieee.org/groups/754/). The main issue is that some numbers cannot be represented as an xs:double so those numbers are represented as the nearest number that can be represented (sometimes it is an intermediate result of an arithmetic operation which is the one that cannot be represented).

However there is a solution for your problem. Instead of using xs:double (which uses an exponent to store the number) you can use xs:decimal which according to the data type specification cannot contain an exponent so it must store the number as it is. So, replacing the value-of expression with

    <xsl:value-of select="sum($contract/PaymentSchedules/PaymentSchedule/(xs:decimal(Amount) * xs:decimal(NumberOfPayments)))" />

should work as expected.

UPDATE: The complete solution would be:

<iso:schema xmlns:iso="http://purl.oclc.org/dsdl/schematron"
            xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
            xmlns:xsl="http://www.w3.org/2001/XMLSchema" queryBinding="xslt2">

    <iso:ns uri="http://www.someurl.com" prefix="func"/>

    <xsl:function name="func:getPaymentScheduleTotal" as="xs:decimal">
        <xsl:param name="contract" as="element()"/>
        <xsl:sequence
            select="sum($contract/PaymentSchedules/PaymentSchedule/(xs:decimal(NumberOfPayments) * xs:decimal(Amount)))"/>
    </xsl:function>

    <iso:pattern id="Contract">
        <iso:rule context="Contract">
            <iso:assert test="func:getPaymentScheduleTotal(.) = xs:decimal(TotalCosts)">amount in paymentschedule(<iso:value-of select="func:getPaymentScheduleTotal(.)"/>)    doesn't match total amount(<iso:value-of select="TotalCosts"/>)</iso:assert>
        </iso:rule>
    </iso:pattern>

</iso:schema>

他のヒント

sounds like that the decimal representation (e.g. 516.7, 529.7 ...) is finite but the binary representation of the same value might not be. Therefore you get the differences in the result. I would say that there is no other option beside rounding the results.

Hope this helps.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top