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.

5 lipca 2011

cwRsync portable

Często używam rsync do robienia backupów albo do synchronizowania zawartości folderów na wielu serwerach. Ostatnio postanowiłem, że kupię sobie dysk przenośny o dużej pojemności i wrzucę tam wszystkie najważniejsze swoje rzeczy. Chciałem mieć możliwość robienia tego z dowolnego miejsca przez net, więc pomyślałem o rsync.

Wszystko chciałem uruchamiać pod Windows, na dowolnym kompie do którego się podepnę. Stworzyłem prostą strukturę katalogów w głównym folderze dysku:
\bin
\backup
Zawartość z katalogu bin z folderu instalacyjnego cwRsync wrzuciłem do katalogu bin na dysku przenośnym.

Dorzuciłem też plik cwrsync.cmd do głównego katalogu. Poniżej zawartość pliku:
@echo off
setlocal
set CWRSYNCHOME=%~d0\bin
set CYGWIN=nontsec
set HOME=%HOMEDRIVE%%HOMEPATH%
set PATH=%~d0\bin;%PATH%

cygpath -a backup > backup.d
set /p BACKUP= < backup.d
del backup.d

rsync -rtlv --compress user@host::moduł %BACKUP%/ < %~d0\plik_z_hasłem
Problem z rsync polegał na tym, że ścieżka docelowa musi być absolutną ścieżką w formacie cygwin, np.: /cygdrive/h/backup. Wszelkie próby z relatywną ścieżką zawodziły.
Wykorzystałem także możliwość prostej autoryzacji w rsync, jedynym sposobem przekazania hasła, było proste przekierowanie go z zewnętrznego pliku.

14 czerwca 2011

EVE Online i Skill Training Queue

Znowu się nudziłem i napisałem sobie mały projekt w PHP, który informuje mnie o kolejce skilli w EVE Online, bez potrzeby logowania się do gry.
Jeśli ktoś będzie zainteresowany źródłem, dorzucę do bloga.

Jeszcze pomyślę o "oklejeniu" tego jakimś javascriptem, aby dane wyglądały jeszcze bardziej "na bieżąco".

11 czerwca 2011

Ubiquiti cz.2

Dzisiaj rzutem na taśmę skaner do Ubiquiti. W odróżnieniu od Mikrotika, Ubiquiti odpowiada na adres z którego zostało wysłane zapytanie. Ale w tym skrypcie nasłuchujemy na dowolnym adresie, tak jak w skanerze dla Mikrotików. No i miały być szczegóły techniczne. Skaner rozsyła z portu 50122 na port 10001 paczkę 0x01000000 po broadcaście. Urządzenie wysyła odpowiedź na port 50122. Trochę dłubaniny było przy zmiennym formacie ramki ale dość łatwo było ominąć ten problem. Pierwszy rodzaj ramki:
@s = unpack("H6H2H6H12NH6H12H8(H2n/a*)*", $ramka);

$s[0]   sygnatura (0x010000)
$s[1]   nie pamiętam :)
$s[2]   ??
$s[3]   adres MAC
$s[4]   ??
$s[5]   ??
$s[6]   ??
$s[7]   ??
(
    $s[8]   typ pola
    X       długość łańcucha (automatycznie wchłaniane przez unpack)
    $s[9]   łańcuch
)
...
Drugi rodzaj ramki:
@s = unpack("H6H2H6H12NH6H12H14(H2n/a*)*", $ramka);

$s[0]   sygnatura (0x010000)
$s[1]   nie pamiętam :)
$s[2]   ??
$s[3]   adres MAC
$s[4]   ??
$s[5]   ??
$s[6]   ??
$s[7]   ?? (tutaj długość jest większa o 3 bajty)
(
    $s[8]   typ pola
    X       długość łańcucha (automatycznie wchłaniane przez unpack)
    $s[9]   łańcuch
)
...
Zidentyfikowane typy pól:
0x03 - wersja softu
0x0a - uptime
0x0b - nadana nazwa dla urządzenia
0x0c - model urządzenia
0x0d - SSID do którego jest podpięte urządzenie gdy pracuje w trybie klienta
No i skrypt:
#!/usr/bin/perl

###############################################################################
#
# scan-ubiquiti.pl v1.2.0
#
# scan utility for ubiquiti
#
# 2011-06-11
# przepisano engine na klasyczne sockety
#
# mindc.net
# 2011-03-20
###############################################################################

use strict;
use warnings;
use Socket;
use Time::HiRes qw(gettimeofday);

print "scan-ubiquiti.pl v1.2.0
    hw                 ip               board            sw   uptime name                     ssid
--------------------------------------------------------------------------------------------------------
";

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

my $data = {};

foreach my $saddr ( values %dev ) {
    my $socket;
    socket($socket,PF_INET,SOCK_DGRAM,17);
    setsockopt($socket, SOL_SOCKET, SO_BROADCAST, 1 );
    my $sin = sockaddr_in(50122,inet_aton($saddr));
    bind($socket,$sin);
    my $sout = sockaddr_in(10001,INADDR_BROADCAST);
    send($socket,pack("H8","01000000"),0,$sout);
    send($socket,pack("H8","01000000"),0,$sout);
    send($socket,pack("H8","01000000"),0,$sout);
    close $socket;
}

my $socket;
socket($socket,PF_INET,SOCK_DGRAM,17) or die "socket error - $!";
my $sin = sockaddr_in(50122,INADDR_ANY);
bind($socket,$sin);

my $rin = "";
vec($rin,fileno($socket),1) = 1;

my $end = gettimeofday() + 3;
my $timeout = $end - gettimeofday();

my $i = 0;
while ( select(my $rout = $rin,undef,undef,$timeout) ) {
    $timeout = $end - gettimeofday();
    my $rtime = "";
    my $his = recv($socket,$rtime,1500,0) || die "recv: $!";
    my ($port,$hisaddr) = sockaddr_in($his);
    next if length($rtime) < 8; #skip self
    my $o = ubiquiti_header(inet_ntoa($hisaddr),$rtime);

    if ( not exists($data->{$o->{mac}})) {
        my $sw = parse_sw($o->{sw});
        printf("%2d. %17s  %-16s %-16s %-4s %6s %-24s %s\n",
            ++$i,
            $o->{mac},
            $o->{ip},
            models($o->{board}),
            $sw->{sw_version},
            sec2human($o->{uptime}),
            $o->{name},
            $o->{ssid}
        );
    }
    $data->{$o->{mac}}++;
}
close $socket;
print "\n" if keys %$data;
exit;

sub ubiquiti_header {
    my $ip = shift;
    my $frame = shift;

    my $template = "H6 H2 H6 H12 N H6 H12 H8 (H2n/a*)*";
    my @header = unpack($template,$frame);
    my $signature = shift @header;
    return undef if $signature ne '010000';
    my $t = shift @header;
    shift @header;
    my $mac = shift @header;
    shift @header;
    shift @header;
    shift @header;

    shift @header;

    if ( $header[0] ne '01' ) {
        $template = "H6 H2 H6 H12 N H6 H12 H14 (H2n/a*)*";
        @header = unpack($template, $frame);
        $signature = shift @header;
        return undef if $signature ne '010000';
        $t = shift @header;
        shift @header;
        $mac = shift @header;
        shift @header;
        shift @header;
        shift @header;
        shift @header;
    }

    my $out = {};
    @{$out}{qw(ip mac sw board key uptime ssid)} = ($ip,'','','','',0,'');

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

        $out->{sw} = $value if $type eq '03';
        $out->{board} = $value if $type eq '0c';
        $out->{name} = $value if $type eq '0b';
        $out->{uptime} = unpack("N",$value) if $type eq '0a';
        $out->{ssid} = $value if $type eq '0d';

    }

    $out->{mac} = uc join(":",unpack("(H2)6",pack("H*",$mac)));
    $out->{frame} = $frame;
    $out->{structure} = \@header;
    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";
    }
}

sub parse_sw {
    my $sw = shift;
    return {} unless $sw;
    my @s = split /\./,$sw;
    $s[2] = $s[2] || '';
    $s[3] = $s[3] || '';
    $s[2] =~ s/v//g;
    my $out = {
    board => $s[0] || '',
    chipset => uc $s[1] || '',
    build => $s[4] || '',
    date => $s[5] || '',
    unkn => $s[6] || ''
    };

    $out->{sw_version} = $s[2].".".$s[3] if $s[2] && $s[3];

    return $out;
}

sub models {
    my $m = shift;
    return 'NanoBridgeM' if $m eq 'NB5';
    return 'WISPStation5' if $m eq 'MS5';
    return 'RocketM' if $m eq 'R5N';
    return 'AirGridM5' if $m eq 'AG5';
    return $m;
};

MNDP cz.4

Zaczął mnie irytować ten tcpdump. Ogarnąłem się trochę i przepisałem część kodu w zwykłych socketach.
#!/usr/bin/perl

###############################################################################
#
# scan-mikrotik.pl v2.8.0
#
# scan utility for mikrotik
#
# 2011-06-11
# przepisano engine na klasyczne sockety
#
# 2011-03-27
# dodano uptime
#
# mindc.net
# 2011-03-20
###############################################################################

use strict;
use warnings;
use Socket;
use Time::HiRes qw(gettimeofday);

print "scan-mikrotik.pl v2.8.0
    hw                 ip               board      sw      uptime identity
--------------------------------------------------------------------------------
";

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

my $data = {};

foreach my $saddr ( values %dev ) {
    my $socket;
    socket($socket,PF_INET,SOCK_DGRAM,17);
    setsockopt($socket, SOL_SOCKET, SO_BROADCAST, 1 );
    my $sin = sockaddr_in(5678,inet_aton($saddr));
    bind($socket,$sin);
    my $sout = sockaddr_in(5678,INADDR_BROADCAST);
    send($socket,pack("H8",0),0,$sout);
    send($socket,pack("H8",0),0,$sout);
    send($socket,pack("H8",0),0,$sout);
    close $socket;
}

my $socket;
socket($socket,PF_INET,SOCK_DGRAM,17) or die "socket error - $!";
my $sin = sockaddr_in(5678,INADDR_ANY);
bind($socket,$sin);

my $rin = "";
vec($rin,fileno($socket),1) = 1;

my $end = gettimeofday() + 3;
my $timeout = $end - gettimeofday();

my $i = 0;
while ( select(my $rout = $rin,undef,undef,$timeout) ) {
    $timeout = $end - gettimeofday();
    my $rtime = "";
    my $his = recv($socket,$rtime,1500,0) || die "recv: $!";
    my ($port,$hisaddr) = sockaddr_in($his);
    next if length($rtime) < 5; #skip self
    my $o = mikrotik_header(inet_ntoa($hisaddr),$rtime);
    if ( not exists($data->{$o->{mac}})) {
        printf("%2d. %17s  %-16s %-10s %-7s %6s %s\n",
            ++$i,
            $o->{mac},
            $o->{ip},
            $o->{board},
            $o->{sw},
            sec2human($o->{uptime}),
            $o->{identity}
        );
    }
    $data->{$o->{mac}}++;
}
close $socket;
print "\n" if keys %$data;
exit;

sub mikrotik_header {
    my $ip = shift;
    my $buffer = shift;

    my @header = unpack("H2H2S(H4n/a*)*", $buffer);

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

    my $out = {};
    @{$out}{qw(ip mac identity vendor sw board key uptime)} = ($ip,'','','','','','',0);

    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";
    }
}