logo

29 сент. 2009 г.

BIP: Число прописью в RTF-шаблоне

Очень часто, в отчетных документах необходимо дублировать итоговую сумму прописью. Все хорошо, когда итоговая сумма известна ДО генерации отчета (например, посчитана в sql запросе и преобразована в пропись с помощью PL\SQL пакета). А что делать, когда сумма рассчитывается динамически, в самом RTF-шаблоне? Тут нам на помощь приходят XSL-функции и возможность использовать subtemplates(подшаблоны) в RTF-шаблоне.

Написав небольшую XSL-функцию для перевода числа в пропись:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>

<xsl:template match="/">
<a>
<numInWords>
<xsl:call-template name="num-to-word">
<xsl:with-param name="value" select="A/NUM"/>
</xsl:call-template>
</numInWords>
</a>
</xsl:template>

<xsl:template name="num-to-word">
<xsl:param name="value"/>
<xsl:param name="sex" select="'m'"/>
<xsl:variable name="power" select="0"/>

<xsl:variable name="value2">
<xsl:value-of select="translate($value,',','.')"/>
</xsl:variable>

<xsl:choose>
<xsl:when test="floor($value2) > 0">
<xsl:call-template name="float2speech">
<xsl:with-param name="value" select="floor($value2)"/>
<xsl:with-param name="sex" select="m"/>
<xsl:with-param name="power" select="0"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'ноль '"/>
</xsl:otherwise>
</xsl:choose>

<xsl:text> руб. </xsl:text>
<xsl:choose>
<xsl:when test="floor($value2)!=$value2">
<xsl:variable name="kop" select="round((($value2)-floor($value2))*100)"/>
<xsl:choose>
<xsl:when test="$kop > 9">
<xsl:value-of select="concat($kop,' коп.')"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat('0',$kop,' коп.')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'00 коп.'"/>
</xsl:otherwise>
</xsl:choose>

</xsl:template>

<xsl:template name="float2speech">
<xsl:param name="value"/>
<xsl:param name="sex"/>
<xsl:param name="power"/>
<xsl:variable name="ret" select="' '"/>

<xsl:variable name="pp" >
<xsl:choose>
<xsl:when test="$power!=0">
<xsl:if test="$power=1">
<xsl:value-of select="'f'"/>
</xsl:if>
<xsl:if test="$power!=1">
<xsl:value-of select="'m'"/>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$sex"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>

<xsl:variable name="strx">
<xsl:if test="$power!=0">
<xsl:variable name="p">
<xsl:choose>
<xsl:when test="$power!=0">
<xsl:if test="$power=1">
<xsl:value-of select="'f'"/>
</xsl:if>
<xsl:if test="$power!=1">
<xsl:value-of select="'m'"/>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$sex"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>

<xsl:variable name="i" select="floor($value)"/>
<xsl:variable name="x" select="floor(($i mod 100) div 10)"/>

<xsl:variable name="z">
<xsl:choose>
<xsl:when test="$x=1">
<xsl:number value="5"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$i mod 10"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>

<xsl:variable name="ret2">
<xsl:choose>
<xsl:when test="$z=1">
<xsl:if test="$p='m'">
<xsl:value-of select="concat(' ',$ret)"/>
</xsl:if>
<xsl:if test="$p='f'">
<xsl:value-of select="concat('а ',$ret)"/>
</xsl:if>
</xsl:when>
<xsl:when test="$z > 1 and $z < 5">
<xsl:if test="$p='m'">
<xsl:value-of select="concat('а ',$ret)"/>
</xsl:if>
<xsl:if test="$p='f'">
<xsl:value-of select="concat('и ',$ret)"/>
</xsl:if>
</xsl:when>
<xsl:otherwise>
<xsl:if test="$p='m'">
<xsl:value-of select="concat('ов ',$ret)"/>
</xsl:if>
<xsl:if test="$p='f'">
<xsl:value-of select="concat(' ',$ret)"/>
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>

<xsl:choose>
<xsl:when test="($value mod 1000)=0">
<xsl:value-of select="' '"/>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="$power=1">
<xsl:value-of select="concat('тысяч',$ret2)"/>
</xsl:when>
<xsl:when test="$power=2">
<xsl:value-of select="concat('миллион',$ret2)"/>
</xsl:when>
<xsl:when test="$power=3">
<xsl:value-of select="concat('миллиард',$ret2)"/>
</xsl:when>
<xsl:when test="$power=4">
<xsl:value-of select="concat('триллион',$ret2)"/>
</xsl:when>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>

</xsl:if>
</xsl:variable>



<xsl:variable name="str">
<xsl:if test="$value > 999">
<xsl:variable name="vd1" select="floor($value div 1000)"/>
<xsl:variable name="str">
<xsl:call-template name="float2speech">
<xsl:with-param name="value" select="$vd1"/>
<xsl:with-param name="sex" select="$pp"/>
<xsl:with-param name="power" select="$power+1"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$str"/>
</xsl:if>
</xsl:variable>

<xsl:variable name="ppp">
<xsl:choose>
<xsl:when test="$pp!=''">
<xsl:value-of select="$pp"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'m'"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>

<xsl:variable name="str2">
<xsl:call-template name="int2speech">
<xsl:with-param name="dig" select="number(substring($value,string-length($value)-2,3))"/>
<xsl:with-param name="sex" select="$ppp"/>
</xsl:call-template>
</xsl:variable>

<xsl:text> </xsl:text>
<xsl:value-of select="concat(concat(translate(substring((normalize-space($str)),1,1),'абвгдежзиклмнопрстуфхцчшщъыьэюя','АБВГДЕЖЗИКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ'),substring(normalize-space($str),2,string-length($str))),' ')"/>
<xsl:value-of select="$str2"/>
<xsl:value-of select="$strx"/>


</xsl:template>

<xsl:template name="int2speech">
<xsl:param name="dig"/>
<xsl:param name="sex"/>
<xsl:variable name="remainder" select="floor(($dig mod 1000) div 100)"/>

<xsl:variable name="ret">
<xsl:choose>
<xsl:when test="$remainder=1">
<xsl:value-of select="'сто '"/>
</xsl:when>
<xsl:when test="$remainder=2">
<xsl:value-of select="'двести '"/>
</xsl:when>
<xsl:when test="$remainder=3">
<xsl:value-of select="'триста '"/>
</xsl:when>
<xsl:when test="$remainder=4">
<xsl:value-of select="'четыреста '"/>
</xsl:when>
<xsl:when test="$remainder=5">
<xsl:value-of select="'пятьсот '"/>
</xsl:when>
<xsl:when test="$remainder=6">
<xsl:value-of select="'шестьсот '"/>
</xsl:when>
<xsl:when test="$remainder=7">
<xsl:value-of select="'семьсот '"/>
</xsl:when>
<xsl:when test="$remainder=8">
<xsl:value-of select="'восемьсот '"/>
</xsl:when>
<xsl:when test="$remainder=9">
<xsl:value-of select="'девятьсот '"/>
</xsl:when>
</xsl:choose>
</xsl:variable>

<xsl:variable name="remainder2" select="floor(($dig mod 100) div 10)"/>
<xsl:variable name="remainder3" select="floor($dig mod 10)"/>

<xsl:variable name="ret2">
<xsl:choose>
<xsl:when test="$remainder2=1">
<xsl:choose>
<xsl:when test="$remainder3=0">
<xsl:value-of select="'десять '"/>
</xsl:when>
<xsl:when test="$remainder3=1">
<xsl:value-of select="'одиннадцать '"/>
</xsl:when>
<xsl:when test="$remainder3=2">
<xsl:value-of select="'двенадцать '"/>
</xsl:when>
<xsl:when test="$remainder3=3">
<xsl:value-of select="'тринадцать '"/>
</xsl:when>
<xsl:when test="$remainder3=4">
<xsl:value-of select="'четырнадцать '"/>
</xsl:when>
<xsl:when test="$remainder3=5">
<xsl:value-of select="'пятнадцать '"/>
</xsl:when>
<xsl:when test="$remainder3=6">
<xsl:value-of select="'шестнадцать '"/>
</xsl:when>
<xsl:when test="$remainder3=7">
<xsl:value-of select="'семнадцать '"/>
</xsl:when>
<xsl:when test="$remainder3=8">
<xsl:value-of select="'восемнадцать '"/>
</xsl:when>
<xsl:when test="$remainder3=9">
<xsl:value-of select="'девятнадцать '"/>
</xsl:when>
</xsl:choose>
</xsl:when>
<xsl:when test="$remainder2=2">
<xsl:value-of select="'двадцать '"/>
</xsl:when>
<xsl:when test="$remainder2=3">
<xsl:value-of select="'тридцать '"/>
</xsl:when>
<xsl:when test="$remainder2=4">
<xsl:value-of select="'сорок '"/>
</xsl:when>
<xsl:when test="$remainder2=5">
<xsl:value-of select="'пятьдесят '"/>
</xsl:when>
<xsl:when test="$remainder2=6">
<xsl:value-of select="'шестьдесят '"/>
</xsl:when>
<xsl:when test="$remainder2=7">
<xsl:value-of select="'семьдесят '"/>
</xsl:when>
<xsl:when test="$remainder2=8">
<xsl:value-of select="'восемьдесят '"/>
</xsl:when>
<xsl:when test="$remainder2=9">
<xsl:value-of select="'девяносто '"/>
</xsl:when>
</xsl:choose>
</xsl:variable>

<xsl:variable name="ret3">
<xsl:choose>
<xsl:when test="$remainder2!=1">
<xsl:choose>
<xsl:when test="$remainder3=1">
<xsl:choose>
<xsl:when test="$sex='f'">
<xsl:value-of select="'одна '"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'один '"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:when test="$remainder3=2">
<xsl:choose>
<xsl:when test="$sex='f'">
<xsl:value-of select="'две '"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'два '"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:when test="$remainder3=3">
<xsl:value-of select="'три '"/>
</xsl:when>
<xsl:when test="$remainder3=4">
<xsl:value-of select="'четыре '"/>
</xsl:when>
<xsl:when test="$remainder3=5">
<xsl:value-of select="'пять '"/>
</xsl:when>
<xsl:when test="$remainder3=6">
<xsl:value-of select="'шесть '"/>
</xsl:when>
<xsl:when test="$remainder3=7">
<xsl:value-of select="'семь '"/>
</xsl:when>
<xsl:when test="$remainder3=8">
<xsl:value-of select="'восемь '"/>
</xsl:when>
<xsl:when test="$remainder3=9">
<xsl:value-of select="'девять '"/>
</xsl:when>
</xsl:choose>
</xsl:when>
</xsl:choose>
</xsl:variable>

<xsl:value-of select="concat($ret,$ret2,$ret3)"/>

</xsl:template>
</xsl:stylesheet>



Можем ее использовать в нашем шаблоне.
Вариант для локального тестирования в BIP Desktop:
<?import:file:///Путь_до_файла/файл_с_функцией.xsl?>

Например:
<?import:file:///E:/projects/my/number2word.xsl?> 

Вариант для BIP Server:
Файл с функцией необходимо поместить в директорию web-приложения, например /home/oracle/bi/OracleBI/oc4j_bi/j2ee/home/applications/xmlpserver/xmlpserver, а в шаблоне использовать следующий тег:
<?import:http://имя_хоста:порт/xmlpserver/файл_с_функцией.xsl?> 

Например:
<?import:http://localhost:9704/xmlpserver/number2word.xsl?>

А далее в нужном месте вызвать нашу функцию:
<?call@inlines:num-to-word?>
<?with-param:value;/ROWS/VALUE?>
<?end call?>

Все здорово, но данная строка всегда будет выводить стандартным для отчета шрифтом (обычно это Arial,12), а это не всегда удобно. Чтобы явно указать форматирование для данной строки поставьте перед ней следующие теги:
Для указания размера шрифта:
<?attribute@incontext:font-size;'8pt'?>

Для указания шрифта:
<?attribute@incontext:font-family;'Arial'?>

Для указания стиля:
<?attribute@incontext:font-style;'Italic'?>

4 комментария:

  1. Этот комментарий был удален автором.

    ОтветитьУдалить
  2. Класс, радовался также как и когда нашел такую штуку на PL/SQL.
    Jaxxx - respect!!!

    200 -ю строку заменил на:
    проверку если $str='' то $str2 писать с большой.
    теперь слова до тысячи также выводятся с большой буквы!
    --
    regards,
    Eldar A.

    ОтветитьУдалить
  3. Как оказалось, в приведенном коде из-за форматирования blogger'а возникли ошибки. Выложил корректную версию xsl-кода тут https://sites.google.com/site/obi2ru/number2word.xsl?attredirects=0&d=1

    ОтветитьУдалить
  4. <xsl:variable name="ls" select="substring(floor($value2),string-length(floor($value2)))"/>
    <xsl:variable name="ls2" select="substring(floor($value2),string-length(floor($value2))-1,1)"/>
    <xsl:choose>
    <xsl:when test="not($ls2=1)">
    <xsl:choose>
    <xsl:when test="$ls=1"> <xsl:text> рубль </xsl:text></xsl:when>
    <xsl:when test="$ls=2 or $ls=3 or $ls=4"> <xsl:text> рубля </xsl:text></xsl:when>
    <xsl:when test="$ls=5 or $ls=6 or $ls=7 or $ls=8 or $ls=9 or $ls=0"><xsl:text> рублей </xsl:text></xsl:when>
    </xsl:choose>
    </xsl:when>
    <xsl:otherwise>
    <xsl:text> рублей </xsl:text>
    </xsl:otherwise>
    </xsl:choose>
    вместо <xsl:text> руб. </xsl:text>
    и получаем склонения рубля в прописи

    ОтветитьУдалить