Pokazywanie postów oznaczonych etykietą xml. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą xml. Pokaż wszystkie posty

10 sierpnia 2011

Szablon XSL - słownie złotych

Przy okazji uruchamiania systemu generującego PDFy z fakturami, potrzebowałem czegoś, co zamieni kwoty przedstawione za pomocą liczb na odpowiadający im opis słowny. Tak powstał ten szablon XSL, gotowy do dołączenia w projektach.
Pierwowzorem był znaleziony w necie, pewien algorytm napisany w C#, który to w międzyczasie przerobiłem na Javascript (tego użyłem w innym projekcie związanym z PDF).
I tak oto powstał ten szbalon.
<xsl:template name="get-gr">
  <xsl:param name="amount"/>
  <xsl:variable name="a1" select="floor($amount)"/>
  <xsl:variable name="len" select="string-length($a1)"/>
  <xsl:variable name="a2" select="substring($a1,$len,1)"/>
  <xsl:choose>
    <xsl:when test="$a1 = 1"><xsl:value-of select="document('amount-in-words.xml')/amount-in-words/grosze[1]"/></xsl:when>
    <xsl:when test="$a2 > 1 and $a2 < 5"><xsl:value-of select="document('amount-in-words.xml')/amount-in-words/grosze[2]"/></xsl:when>
    <xsl:otherwise><xsl:value-of select="document('amount-in-words.xml')/amount-in-words/grosze[3]"/></xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="get-zl">
  <xsl:param name="amount"/>
  <xsl:variable name="a1" select="floor($amount)"/>
  <xsl:variable name="len" select="string-length($a1)"/>
  <xsl:variable name="a2" select="substring($a1,$len,1)"/>
  <xsl:choose>
    <xsl:when test="$a1 = 1"><xsl:value-of select="document('amount-in-words.xml')/amount-in-words/zlote[1]"/></xsl:when>
    <xsl:when test="$a2 > 1 and $a2 < 5"><xsl:value-of select="document('amount-in-words.xml')/amount-in-words/zlote[2]"/></xsl:when>
    <xsl:otherwise><xsl:value-of select="document('amount-in-words.xml')/amount-in-words/zlote[3]"/></xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="get-words">
  <xsl:param name="amount"/>
  <xsl:variable name="zl" select="floor($amount)"/>
  <xsl:variable name="len" select="string-length($zl)"/>
  <xsl:if test="$len > 6">
    <xsl:variable name="million2" select="substring($zl,$len -6,1)"/>
    <xsl:variable name="million">
        <xsl:if test="$len > 8"><xsl:value-of select="substring($zl,$len -8,1)"/></xsl:if>
        <xsl:if test="$len > 7"><xsl:value-of select="substring($zl,$len -7,1)"/></xsl:if>
        <xsl:value-of select="substring($zl,$len -6,1)"/>
    </xsl:variable>
    <xsl:if test="$million > 1">
      <xsl:call-template name="get-words">
        <xsl:with-param name="amount" select="$million"/>
      </xsl:call-template>
    </xsl:if>
    <xsl:choose>
      <xsl:when test="$million = 1">
        <xsl:value-of select="document('amount-in-words.xml')//millions[1]"/>
      </xsl:when>
      <xsl:when test="$million > 1 and $million < 5">
        <xsl:value-of select="document('amount-in-words.xml')//millions[2]"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="document('amount-in-words.xml')//millions[3]"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:if>

  <xsl:if test="$len > 3">
    <xsl:variable name="thousend2" select="substring($zl,$len -3,1)"/>
    <xsl:variable name="thousend">
      <xsl:if test="$len > 5"><xsl:value-of select="substring($zl,$len -5,1)"/></xsl:if>
      <xsl:if test="$len > 4"><xsl:value-of select="substring($zl,$len -4,1)"/></xsl:if>
      <xsl:value-of select="substring($zl,$len -3,1)"/>
    </xsl:variable>

    <xsl:if test="$thousend > 1">
      <xsl:call-template name="get-words">
        <xsl:with-param name="amount" select="$thousend"/>
      </xsl:call-template>
    </xsl:if>

    <xsl:choose>
      <xsl:when test="$thousend = 1">
        <xsl:value-of select="document('amount-in-words.xml')//thousends[1]"/>
      </xsl:when>
      <xsl:when test="$thousend2 > 1 and $thousend2 < 5">
        <xsl:value-of select="document('amount-in-words.xml')//thousends[2]"/>
      </xsl:when>
      <xsl:when test="$thousend = '000' and $len > 6">
        <xsl:text></xsl:text>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="document('amount-in-words.xml')//thousends[3]"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:if>

  <xsl:if test="$len > 2">
    <xsl:if test="substring($zl,$len -2,1) != 0">
      <xsl:value-of select="document('amount-in-words.xml')//hundrets[substring($zl,$len -2,1) + 1]"/>
    </xsl:if>
  </xsl:if>

  <xsl:if test="$len > 0">
    <xsl:variable name="to99">
      <xsl:choose>
        <xsl:when test="$len = 1">
          <xsl:value-of select="substring($zl,$len,1)"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:value-of select="substring($zl,$len -1,2)"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>

    <xsl:choose>
      <xsl:when test="$to99 < 20">
        <xsl:choose>
          <xsl:when test="substring($to99,1,1) = 0">
            <xsl:if test="substring($to99,2,1) != 0">
              <xsl:value-of select="document('amount-in-words.xml')//units[substring($to99,2,1) + 1]"/>
            </xsl:if>
          </xsl:when>
          <xsl:otherwise>
            <xsl:if test="$to99 != 0">
              <xsl:value-of select="document('amount-in-words.xml')//units[$to99 + 1]"/>
            </xsl:if>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="document('amount-in-words.xml')//tens[substring($to99,1,1)+1]"/>
        <xsl:if test="substring($to99,2,2) != 0">
          <xsl:value-of select="document('amount-in-words.xml')//units[substring($to99,2,2) + 1]"/>
        </xsl:if>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:if>

  <xsl:if test="$zl = 0">
    <xsl:value-of select="document('amount-in-words.xml')//units[1]"/>
  </xsl:if>
</xsl:template>

<xsl:template name="amount-in-words">
  <xsl:param name="amount"/>
  <xsl:call-template name="get-words"><xsl:with-param name="amount" select="$amount"/></xsl:call-template>
  <xsl:call-template name="get-zl"><xsl:with-param name="amount" select="$amount"/></xsl:call-template>
  <xsl:call-template name="get-words"><xsl:with-param name="amount" select="round(($amount - floor($amount))*100)"/></xsl:call-template>
  <xsl:call-template name="get-gr"><xsl:with-param name="amount" select="round(($amount - floor($amount))*100)"/></xsl:call-template>
</xsl:template>
Powyższy fragment kody należy umieścić w dowolnym miejscu istniejącego szablonu. Dodatkowo w katalogu z szablonem, należy umieścić poniższy plik o nazwie amount-in-words.xml:
<?xml version="1.0" encoding="UTF-8"?>    
<amount-in-words>
<grosze>grosz </grosze>
<grosze>grosze </grosze>
<grosze>groszy </grosze>
<zlote>złoty </zlote>
<zlote>złote </zlote>
<zlote>złotych </zlote>
<units>zero </units>
<units>jeden </units>
<units>dwa </units>
<units>trzy </units>
<units>cztery </units>
<units>pięć </units>
<units>sześć </units>
<units>siedem </units>
<units>osiem </units>
<units>dziewięć </units>
<units>dziesięć </units>
<units>jedenaście </units>
<units>dwanaście </units>
<units>trzynaście </units>
<units>czternaście </units>
<units>piętnaście </units>
<units>szesnaście </units>
<units>siedemnaście </units>
<units>osiemnaście </units>
<units>dziewiętnaście </units>
<tens></tens>
<tens>dziesięć </tens>
<tens>dwadzieścia </tens>
<tens>trzydzieści </tens>
<tens>czterdzieści </tens>
<tens>pięćdziesiąt </tens>
<tens>sześćdziesiąt </tens>
<tens>siedemdziesiąt </tens>
<tens>osiemdziesiąt </tens>
<tens>dziewięćdziesiąt </tens>
<hundrets></hundrets>
<hundrets>sto </hundrets>
<hundrets>dwieście </hundrets>
<hundrets>trzysta </hundrets>
<hundrets>czterysta </hundrets>
<hundrets>pięćset </hundrets>
<hundrets>sześćset </hundrets>
<hundrets>siedemset </hundrets>
<hundrets>osiemset </hundrets>
<hundrets>dziewięćset </hundrets>
<thousends>tysiąc </thousends>
<thousends>tysiące </thousends>
<thousends>tysięcy </thousends>
<millions>milion </millions>
<millions>miliony </millions>
<millions>milionów </millions>
</amount-in-words>            
Wywołanie jest proste, polega na wstawieniu poniższego fragmentu w interesującym nas miejscu szblonu:
<xsl:call-template name="amount-in-words">
  <xsl:with-param name="amount" select="value"/>
</xsl:call-template>
Jak na razie, u mnie ma to status beta, jeśli ktoś znajdzie jakieś błędy, zaproponuje poprawki, zapraszam do dyskusji.

20 kwietnia 2011

Apache FOP

Znowu wróciłem do FOP'a. Pomyślałem sobie, że jak znajdę sposób na obliczenie szerokości kolumn w tabeli na podstawie znajdujących się tam danych i ustawię tą szerokość na sztywno, to będzie można spokojnie obejść problem table-layout="fixed". W PHP mamy do dyspozycji funkcję imagettfbbox, dzięki której można wyliczyć szerokość tekstu przy zastosowaniu określonej czcionki. Najprościej można to zrobić tak:
function get_text_width($font,$font_size,$text) {
    $box = imagettfbbox($font_size,0,$font,$text);
    return $box[4];
}
Gdzie: $font - ścieżka do użytej czcionki $font_size - rozmiar czcionki dla której ma być wyliczona szerokość tekstu $text - tekst dla którego wyliczymy szerokość. Przed wygenerowanie właściwej tabeli w XSL-FO, wyliczam maksymalne szerokości dla danych z każdej kolumny i na tej podstawie generuję tagi <fo:table-column column-width="??"/>.

6 kwietnia 2011

Apache FOP na Apache Tomcat cz.4

Nie odpuściłem tego servleta... Po pewnych walkach (przypominam, że to moje pierwsze programowanie w Javie), udało się skompilować fop.war. Dokonane zmiany wyglądają tak:
--- FopServlet.java.org 2010-07-12 21:34:45.000000000 +0200
+++ FopServlet.java     2011-04-06 19:00:01.639060332 +0200
@@ -44,6 +44,10 @@ import org.apache.fop.apps.Fop;
 import org.apache.fop.apps.FopFactory;
 import org.apache.fop.apps.MimeConstants;

+import org.xml.sax.SAXException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
 /**
  * Example servlet to generate a PDF from a servlet.
  * 
@@ -77,6 +81,8 @@ public class FopServlet extends HttpServ /** Name of the parameter used for the XSLT file */ protected static final String XSLT_REQUEST_PARAM = "xslt"; + private final Log log = LogFactory.getLog(FopServlet.class); + /** The TransformerFactory used to create Transformer instances */ protected TransformerFactory transFactory = null; /** The FopFactory used to create Fop instances */ @@ -94,6 +100,13 @@ public class FopServlet extends HttpServ //Configure FopFactory as desired this.fopFactory = FopFactory.newInstance(); this.fopFactory.setURIResolver(this.uriResolver); + try { + fopFactory.setUserConfig(new File("userconf.xml")); + } catch (IOException e) { + log.error("No such file or directory", e); + } catch (SAXException e) { + throw new ServletException(e); + } configureFopFactory(); }
Plik konfiguracyjny userconf.xml wrzucamy po prostu do katalogu z Tomcat'em. Jeśli brak jest pliku, generowany jest log, jeśli są błędy w strukturze xml, zgłaszany jest wyjątek.

Apache FOP na Apache Tomcat cz.3

Ta ścieżka została, odrzucona. Dlaczego? Nie wiem jak podpiąć plik konfiguracyjny. Edycja a później kompilacja nowego servleta nie wchodzi w grę. A potrzebuję własnego pliku konfiguracyjnego aby wrzucić fonty z polskimi znakami. Bez tego ani rusz. Walki skończyły się na tym, że zrobiłem najprostszego wrappera w PHP, generuję plik .fo, zapisuję w tempie, wywołuję bezpośrednio skrypt fop ze ścieżką do zmodyfikowanego pliku konfiguracyjnego i wypluwam do przeglądarki zawartość wygenerowanego pliku PDF. Wrapper wygląda następująco:
<?php
$fo_tmp = tempnam("/tmp", "fop_fo_");
$pdf_tmp = tempnam("/tmp", "fop_pdf_");

$fo = '<?xml version="1.0" encoding="utf-8"?>
<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format">
  <fo:layout-master-set>
    <fo:simple-page-master master-name="first" 
                           page-height="29.7cm" 
                           page-width="21cm"
                           margin-top="1cm"
                           margin-bottom="2cm"
                           margin-left="1cm"
                           margin-right="1cm">
      <fo:region-body margin-top=".5cm"/>
      <fo:region-before extent="0cm"/>
      <fo:region-after extent="0cm"/>
    </fo:simple-page-master>
  </fo:layout-master-set>
  <fo:page-sequence master-reference="first">
    <fo:flow flow-name="xsl-region-body">
      <fo:block">Hello World!</fo:block>
    </fo:flow>
  </fo:page-sequence>
</fo:root>';

file_put_contents($fo_tmp,$fo);
system("/home/tools/fop/fop -c /home/tools/fop/userconf.xml $fo_tmp $pdf_tmp");

$lastmodified = gmdate('D, d M Y H:i:s \G\M\T',time());
header("Content-Type: application/pdf");
header("Last-Modified: $lastmodified");
$pdf = @file_get_contents($pdf_tmp);
header("Content-Lenght: ".strlen($pdf));
header("Content-Length: ".strlen($pdf));
echo $pdf;
@unlink($fo_tmp);
@unlink($pdf_tmp);
exit;
?>
Należy pamiętać o odpowiedniej modyfikacji ścieżek. Po delikatnych modyfikacjach, możemy dostosować ten wrapper do konwersji plików xml+xslt.