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="??"/>.

16 kwietnia 2011

MNDP cz.3

poniżej zamieszcza skrypt, skanujący za pomocą MNDP otoczenie sieciowe
#!/usr/bin/perl -w

###############################################################################
#
# scan-mikrotik.pl v2.6.0
#
# scan utility for mikrotik
#
# 2011-03-27
# dodano uptime
#
# 2011-03-20
# mindc.net
###############################################################################

use strict;
use IO::Socket;
use Data::Dumper;
use Time::HiRes qw( usleep );

$|=1;

my %links;

print "scan-mikrotik.pl v2.6.0
link  hw                 ip               board      sw      uptime identity
--------------------------------------------------------------------------------
wait for 3 sec...\r";

open my $ph, "-|","ip a";
while ( <$ph> ) {
    $links{$2} = $1 if m@\s(\S+)/\d+\s.*(eth\d+)@;
}
close $ph;

foreach my $link ( keys %links ) {
    unless ( fork ) {
        my $socket = IO::Socket::INET->new(
            Proto => 'UDP',
            PeerPort => 5678,
            PeerAddr => inet_ntoa(INADDR_BROADCAST),
            LocalAddr => $links{$link},
            Broadcast => 1,
            Reuse => 1
        ) or die "cannot bind socket $!\n";
        my $data = {};
        eval {
            my $out = {};
            local $SIG{ALRM} = sub {
                foreach ( sort keys %$data ) {
                    my $o = $data->{$_};
                    printf("%s  %17s  %-16s %-10s %-7s %6s %s\n",
                        $link,
                        $o->{mac},
                        $o->{ip},
                        $o->{board},
                        $o->{sw},
                        sec2human($o->{uptime}),
                        $o->{identity}
                    );
                }
                print "\n" if keys %$data;
                exit;
            };
            alarm 3;
            open (STDIN,"tcpdump -i $link -lnqxt -s 1024 src port 5678 and proto UDP 2>/dev/null |");
            usleep(100000);
            $socket->send(pack("H8",0));
            usleep(100000);
            $socket->send(pack("H8",0));
            usleep(100000);
            $socket->send(pack("H8",0));
            close $socket;
            my $buffer = '';
            my $i = 0;
            while (<>) {
                s/IP \S+.5678 > \S+ UDP, length \d+.*//g;
                s/0x\d\d\d\d://g;
                s/\s+//g;
                $buffer .= $_;
                if ( $_ =~ /^$/ && $buffer ne '' ) {
                    $i++;
                    my $out = mikrotik_header($buffer);
                    $data->{$out->{mac}} = $out;
                    $buffer = '';
                }
            }
            alarm 0;
        };
        exit;
    }
}

1 while ( wait() != -1);
system "killall tcpdump";

exit;

sub mikrotik_header {
    my $buffer = shift;
                         #ip       #udp  #mikrotik
    my @header = unpack("H4nH8H8NN nnnH4 H2H2S(H4n/a*)*", pack("H*",$buffer));

    #ip header
    shift @header; #ip1
    shift @header; #ip_lenght
    shift @header; #ip2
    shift @header; #ip3
    my $ip = shift @header; #ip_src
    shift @header; #ip_dst
    #udp header
    shift @header; #udp_src_port
    shift @header; #udp_dst_port
    shift @header; #udp_lenght
    shift @header; #udp_checksum

    shift @header; #?
    shift @header; #?
    shift @header; #age?

    my $out = {};

    $out->{mac} = '';
    $out->{identity} = '';
    $out->{vendor} = '';
    $out->{sw} = '';
    $out->{board} = '';
    $out->{key} = '';
    $out->{uptime} = 0;

    $out->{ip} = join(".",unpack("C4",pack("N",$ip)));

    for ( my $i = 0; $i < @header ; $i += 2 ) {
        my $type = $header[$i];
        my $value = $header[$i+1];

        $out->{mac} = uc join(":",unpack("(H2)6",$value)) if $type eq '0001';
        $out->{identity} = $value if $type eq '0005';
        $out->{vendor} = $value if $type eq '0008';
        $out->{sw} = $value if $type eq '0007';
        $out->{board} = $value if $type eq '000c';
        $out->{key} = $value if $type eq '000b';
        $out->{uptime} = unpack("L",$value) if $type eq '000a';
#       $out->{unknown2} = $value if $type eq '000d';
    }
    return $out;
}

sub sec2human {
    my $sec = shift;

    return "" if $sec <= 0;

    my $w = int($sec / 60 / 60 / 24 / 7);
    my $d = int($sec / 60 / 60 / 24 % 7);
    my $h = int($sec / 60 / 60 % 24);
    my $m = int($sec / 60 % 60);
    my $s = int($sec % 60);
    if ( $w ) {
        return sprintf("%dw%dd",$w,$d);
    } elsif ( $d ) {
        return sprintf("%dd%dh",$d,$h);
    } elsif ( $h ) {
        return sprintf("%dh%dm",$h,$m);
    } elsif ( $m ) {
        return sprintf("%dm%ds",$m,$s);
    } else {
        return "${s}s";
    }
}
Działanie jest proste. Skrypt wysyła na każdym z interfejsów zapytanie 0x00000000 i zaczyna nasłuchiwać na odpowiedź w formacie MNDP. Warunkiem działania skrytpu jest istnienie co najmniej jednego adresu IP na danym interfejsie. Niezbyt eleganckie rozwiązanie z killall tcpdump, ale nie mogłem sobie z tym poradzić, gdy nie zamykam STDIN. A może by tak select.

13 kwietnia 2011

Apache FOP vs. RenderX XEP

No to krótkie porównanie... Apache FOP - totalny Open Source, można wykorzystać gdzie się chce i jak się chce. Ogromną zaletą FOP'a jest generowanie plików o minimalnym rozmiarze, prawdopodobnie dołącza do generowanego pliku wyłącznie fonty użyte w dokumencie. Ale niestety, nie można ustawić dla tabeli atrybutu table-layout="auto", co przy generowaniu dynamicznych tabel, jest nie do przyjęcia. RenderX XEP - zamknięte źródła, do użytku za free dostępna jedynie licencja personal. Na dole każdej strony w generowanym dokumencie, dołączana jest stopka RenderX z logiem. Do tego dołącza cały zastaw fontów do generowanego dokumentu co wiąże się z dość dużym nakładem na wielkość pliku. Ale... poprawnie interpretuje atrybut table-layout="auto". Tak więc, zdecydowałem się używać XEP'a ze względu na te tabele...

7 kwietnia 2011

FOP i fonty

Aby użyć dodatkowych fontów w Apache FOP, w pliku konfiguracyjnym w sekcji <fonts>, należy umieścić odpowiednie wpisy. Poniżej zamieszczam prosty skrypt, który generuje nam te wpisy. Używam czcionki DejaVu ponieważ jest "otwarta" i zawiera znaki Unicode.
#!/bin/bash

FOP="/home/tools/fop/"
INCLUD="${FOP}build/fop.jar:${FOP}lib/avalon-framework.jar:${FOP}lib/commons-logging.jar:${FOP}lib/commons-io.jar:${FOP}lib/xmlgraphics-commons.jar"

for font in `find /home/tools/fonts -name "*.ttf"`;do
    NAME=${font%%.*}
    java -cp "${INCLUD}" org.apache.fop.fonts.apps.TTFReader "${font}" "${NAME}.xml"
done

for FONT in `find /home/tools/fonts -name "*.ttf" | sort`;do
    NAME=${FONT%%.*}
    FILE=`basename ${FONT}`

    NAME2=${FILE%%.*}

    echo "<font metrics-url=\"${NAME}.xml\" kerning=\"yes\" embed-url=\"${FONT}\">"

    STYLE="normal"
    WEIGHT="normal"

    if echo ${NAME2} | grep -qi oblique;then
        NAME2=`echo ${NAME2} | sed -re 's/\-?oblique//i'`
        STYLE="oblique"
    fi

    if echo ${NAME2} | grep -qi italic;then
        NAME2=`echo ${NAME2} | sed -re 's/\-?italic//i'`
        STYLE="italic"
    fi


    if echo ${NAME2} | grep -qi bold;then
        NAME2=`echo ${NAME2} | sed -re 's/\-?bold//i'`
        WEIGHT="bold"
    fi

    echo "      <font-triplet name=\"${NAME2}\" style=\"${STYLE}\" weight=\"${WEIGHT}\"/>"
    echo "</font>"

done
Zawartość katalogu /home/tools/fonts przed uruchomieniem skryptu:
DejaVuSans-Bold.ttf
DejaVuSans-BoldOblique.ttf
DejaVuSans-ExtraLight.ttf
DejaVuSans-Oblique.ttf
DejaVuSans.ttf
DejaVuSansCondensed-Bold.ttf
DejaVuSansCondensed-BoldOblique.ttf
DejaVuSansCondensed-Oblique.ttf
DejaVuSansCondensed.ttf
DejaVuSansMono-Bold.ttf
DejaVuSansMono-BoldOblique.ttf
DejaVuSansMono-Oblique.ttf
DejaVuSansMono.ttf
DejaVuSerif-Bold.ttf
DejaVuSerif-BoldItalic.ttf
DejaVuSerif-Italic.ttf
DejaVuSerif.ttf
DejaVuSerifCondensed-Bold.ttf
DejaVuSerifCondensed-BoldItalic.ttf
DejaVuSerifCondensed-Italic.ttf
DejaVuSerifCondensed.ttf
Poniżej rezultat uruchomienia skryptu:
<font metrics-url="/home/tools/fonts/DejaVuSans-Bold.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSans-Bold.ttf">
        <font-triplet name="DejaVuSans" style="normal" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSans-BoldOblique.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSans-BoldOblique.ttf">
        <font-triplet name="DejaVuSans" style="oblique" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSans-ExtraLight.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSans-ExtraLight.ttf">
        <font-triplet name="DejaVuSans-ExtraLight" style="normal" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSans-Oblique.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSans-Oblique.ttf">
        <font-triplet name="DejaVuSans" style="oblique" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSans.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSans.ttf">
        <font-triplet name="DejaVuSans" style="normal" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSansCondensed-Bold.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSansCondensed-Bold.ttf">
        <font-triplet name="DejaVuSansCondensed" style="normal" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSansCondensed-BoldOblique.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSansCondensed-BoldOblique.ttf">
        <font-triplet name="DejaVuSansCondensed" style="oblique" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSansCondensed-Oblique.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSansCondensed-Oblique.ttf">
        <font-triplet name="DejaVuSansCondensed" style="oblique" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSansCondensed.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSansCondensed.ttf">
        <font-triplet name="DejaVuSansCondensed" style="normal" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSansMono-Bold.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSansMono-Bold.ttf">
        <font-triplet name="DejaVuSansMono" style="normal" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSansMono-BoldOblique.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSansMono-BoldOblique.ttf">
        <font-triplet name="DejaVuSansMono" style="oblique" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSansMono-Oblique.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSansMono-Oblique.ttf">
        <font-triplet name="DejaVuSansMono" style="oblique" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSansMono.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSansMono.ttf">
        <font-triplet name="DejaVuSansMono" style="normal" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSerif-Bold.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSerif-Bold.ttf">
        <font-triplet name="DejaVuSerif" style="normal" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSerif-BoldItalic.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSerif-BoldItalic.ttf">
        <font-triplet name="DejaVuSerif" style="italic" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSerif-Italic.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSerif-Italic.ttf">
        <font-triplet name="DejaVuSerif" style="italic" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSerif.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSerif.ttf">
        <font-triplet name="DejaVuSerif" style="normal" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSerifCondensed-Bold.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSerifCondensed-Bold.ttf">
        <font-triplet name="DejaVuSerifCondensed" style="normal" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSerifCondensed-BoldItalic.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSerifCondensed-BoldItalic.ttf">
        <font-triplet name="DejaVuSerifCondensed" style="italic" weight="bold"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSerifCondensed-Italic.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSerifCondensed-Italic.ttf">
        <font-triplet name="DejaVuSerifCondensed" style="italic" weight="normal"/>
</font>
<font metrics-url="/home/tools/fonts/DejaVuSerifCondensed.xml" kerning="yes" embed-url="/home/tools/fonts/DejaVuSerifCondensed.ttf">
        <font-triplet name="DejaVuSerifCondensed" style="normal" weight="normal"/>
</font>

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.