The following CSV (on-demand generated from raw data with simple Perl script) file contains outdoor temperature registred every hour starting from 2010 (with DS18B20 sensor):
dayhr;No;y2010;y2011;y2012;y2013;y2014;y2015;day30 d070100;001;14.6;17.5;14.9;10.1;12.9;12.2;0 d070101;002;13.4;16.7;14.1;10.1;12.8;12.5;3600 d070102;003;12.8;16.3;14.3;10.2;12.7;12.1;7200
dayhr
is a label and day30
denotes number
of seconds from
the beginning od the period (for the first observation day30
equals 0, for the second 3600 etc.) The chart was produced
with the following custom R script:
require(ggplot2) library(scales) number_ticks <- function(n) {function(limits) pretty(limits, n)} d <- read.csv("july-by-day.csv", sep = ';', header=T, na.string="NA"); datestart <- ISOdate(2015, 7, 1, tz = ""); d30 <- datestart + d$day30; d[,"d30"] <- d30; ggplot(d, aes(x = d30)) + geom_line(aes(y = y2015, colour = 'y2015'), size=.3) + geom_line(aes(y = y2014, colour = 'y2014'), size=.3) + geom_smooth(aes(y = y2015, colour = 'y2015'), size=1) + geom_smooth(aes(y = y2014, colour = "y2014"), size=1) + ylab(label="Temp [C]") + xlab(label="Observation") + scale_y_continuous(breaks=number_ticks(15)) + scale_x_datetime(breaks = date_breaks("5 days")) + theme(legend.title=element_blank()) + ggtitle("Temperature in July in Sopot") + theme(legend.position=c(.6, .9)) + theme(legend.text=element_text(size=12)); ggsave(file="Temp-M7-2015.pdf", width=15, height=8)
O zmarłych dobrze albo wcale, więc ten tekst nie jest o niespodziewanie zmarłym ,,najbogatszym-Polaku'' dr. Kulczyku, tylko o mediach, masowo publikujących wspomnienia po nim...
W tychże wspomnieniach słowo wizjoner jest odmienianie przez wszystkie przypadki i zwykle poprzedzone przymiotnikiem wielki. Jak wizjoner, to musi być i wizja a tej próżno próżno szukać. Ktoś tam wymienia autostradę (cyt: Ta autostrada jest fajnym pomnikiem tego, co pozostawił dla nas. Był wizjonerem.) inny twierdzi że wizją był browar (kupiony od SkarbuPaństwa i sprzedany dawno temu).
Wielki wizjoner a tak niewiele zostawił?
Previously described bash script allows for uploading a file to PicasaWeb only. With the following (simplified) Perl script one can upload pictures as well as upload with metadata (title description and tags) or create/list albums:
#!/usr/bin/perl use strict; use LWP::UserAgent; use Getopt::Long; use File::MimeInfo; use XML::LibXML; my $AlbumId ="6170354040646469009"; my $profileID="default"; my $Action = 'u'; ## x| u | c | l (default action is Upload) my $entryTitle = ''; my $entryDescription = ''; my $entryKeywords = ''; my $ActionUpload =''; my $ActionList = ''; my $ActionCreate = ''; my $ActionXload = ''; my $ImageFile = ''; my $dummyReq=''; GetOptions("xload" => \$ActionXload, "upload" => \$ActionUpload, "list" => \$ActionList, "create" => \$ActionCreate, "title=s" => \$entryTitle, "description=s" => \$entryDescription, "keywords=s" => \$entryKeywords, "file=s" => \$ImageFile, "album=s" => \$AlbumId, ## UploadFile to Album ) ; ## Determine action: if ( $ActionUpload ) {$Action = 'u'} elsif ( $ActionList ) { $Action = 'l'} elsif ( $ActionCreate ) { $Action = 'c'} elsif ( $ActionXload ) { $Action = 'x'}
OAuth 2.0 authorization is handled with Python script
oauth2picasa.py
. The script is an adapted/copy-pasted fragment
of code borrowd from
picasawebsync
:
### Authenticate with external script (oauth2picasa.py): my $ACCESS_TOKEN=`oauth2picasa.py`; chomp($ACCESS_TOKEN); print STDERR "*** AccessToken: $ACCESS_TOKEN [AlbumId: $AlbumId]\n"; my $req ; my $blog_entry ; my $picasawebURL = "https://picasaweb.google.com/data/feed/api/user/$profileID"; if ( $Action eq 'c' ) {## Action: create album $req = HTTP::Request->new(POST => $picasawebURL ); $req->header( 'Content-Type' => 'application/atom+xml' ); $blog_entry = "<entry xmlns='http://www.w3.org/2005/Atom' xmlns:media='http://search.yahoo.com/mrss/' xmlns:gphoto='http://schemas.google.com/photos/2007'>" . "<title type='text'>$entryTitle</title>" . "<summary type='text'>$entryDescription</summary>" . "<media:group><media:keywords>$entryKeywords</media:keywords></media:group>" . "<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/photos/2007#album'></category></entry>"; $req->content($blog_entry); } elsif ( $Action eq 'l' ) {## Action: list albums $req = HTTP::Request->new(GET => $picasawebURL ); } elsif ( $Action eq 'u' ) {## Action: Upload 1 photo w/o metadata my $mimeType = mimetype($ImageFile); ## https://developers.google.com/picasa-web/docs/2.0/developers_guide_protocol $req = HTTP::Request->new(POST => "$picasawebURL/albumid/$AlbumId" ); $req->header( 'Content-Type' => "$mimeType" ); $req->header( 'Slug' => "$ImageFile" ); ## http://www.perlmonks.org/?node_id=131584 open(FILE, $ImageFile); $req->content(join('',<FILE>)); close(FILE); }
To upload the binary image data along with its metadata, use MIME content
type "multipart/related"; send photo metadata in one part of the POST
body (Content-Type: application/atom+xml
),
and binary-encoded image data in another part.
This is the preferred approach according to
Picasa Web Albums Data API
Picasa Web Albums Data API
elsif ( $Action eq 'x' ) {## Action: Upload 1 photo with metadata # https://groups.google.com/forum/#!topic/google-picasa-data-api/2qRfP0EIFhk my $mimeType = mimetype($ImageFile); $req = HTTP::Request->new(POST => "$picasawebURL/albumid/$AlbumId" ); $req->header( 'Content-Type' => "multipart/related" ); open(FILE, $ImageFile); my $add_photo_metadata = "<entry xmlns='http://www.w3.org/2005/Atom' xmlns:media='http://search.yahoo.com/mrss/'>" . "<title type='text'>$entryTitle</title>" . "<summary type='text'>$entryDescription</summary>" . "<media:group><media:keywords>$entryKeywords</media:keywords></media:group>" . "<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/photos/2007#photo'></category></entry>"; my $add_photo_data = join('',<FILE>); close(FILE); ## http://www.perlmonks.org/?node_id=131584 $req->add_part(HTTP::Message->new(['Content-Type' => 'application/atom+xml'], $add_photo_metadata)); $req->add_part(HTTP::Message->new(['Content-Type' => "$mimeType"], $add_photo_data)); } $req->header( 'Authorization' => "Bearer $ACCESS_TOKEN" ); $req->header( 'GData-Version' => '2' ); ## ### ### my $res ; my $ua = LWP::UserAgent->new; $res = $ua->request($req); if ($res->is_success) { my $decoded_response = $res->decoded_content; print "*** OK *** $decoded_response\n"; }
Usage:
Upload with metadata
picasaweb.pl -xload -title PICTURE-TITLE -descr DESCRIPTION \ -keywords 'TAG1,TAG2' -file FILE.jpg -album ALBUMID
Upload w/o metadata:
picasaweb.pl -upload -file FILE.jpg -album 12345
Create album:
picasaweb.pl -create -title ALBUM-TITLE -descr DESCRIPTION \ -keywords 'TAG1,TAG2'
List album:
picasaweb.pl -list
Source code:
picasaweb.pl
W zeszłą niedzielę przepłynąłem z Elką fragment Krutyni, konkretnie od rezerwatu koło wsi Krutyń do mostu we wsi Ukta (circa 15 km w czasie 3 godzin bez minuty). Opis trasy można znaleźć m.in. na stronach firmy Turystyka Aktywna Wodniak z której usług korzystaliśmy i możemy ją polecić jako solidną. Wypożyczenie kajaka kosztowało 55 PLN czyli tanio.
Miejscem startu było w naszym wypadku Jezioro Krutyńskie (rezerwat Krutynia), do którego zawiózł nas mikrobus Wodniaka. Bus nie może podjechać do samej wody (rezerwat!) więc kajak trzeba 50 m donieść samemu (albo dać zarobić 2 PLN miejscowym chłopakom, którzy przewiozą go wózkiem). Na trasie spływu jest jedna przenioska przy młynie wodnym w miejscowości Zielony Lasek, ale tam też są chłopcy z wózkami (koszt usługi tym razem 5 PLN).
Ślad całej wycieczki (ze zdjęciami) jest też tutaj.
Człowiek z obrazka (ten po prawej) znowy błysnął -- tym razem w Berlinie:
"Czyn Clausa von Stauffenberga, który targnął się na życie samego Hitlera, to ważne ogniwo europejskiego ruchu oporu -- podobnie jak... powstanie warszawskie" -- stwierdził Bronisław Komorowski na wykładzie wygłoszonym w ostatnim tygodniu podczas pożegnalnej podróży do Niemiec.
Jego występ był nie do strawienie nawet dla życzliwych mu mediów, jeszcze w maju agitujących za "najlepszym prezydentem 25 lecia". Newsweek odnosząc się do cytatu z akapitu wyżej: "Przesadził? Raczej tak..." Polityka udaje, że zdarzenia nie było -- no cóż nawet prof. Władyka miałby kłopoty z interpretacją historii wg. Bredzisława. Jedynie GłosCadyka, idąc w zaparte, broni swojego człowieka roku 2014: wszyscy w Niemczech byli brunatni, więc należy docenić, że ktoś był mniej brunatny niż ktoś inny (B.T. Wieliński w tekście: Prezydent Komorowski jedzie do Berlina. Prawica grzmi, że uczci Stauffenberga -- wroga Polski).
Patrona broni też głupszy:
Brak szacunku dla takiej osoby jest brakiem szacunku dla Polaków, którzy w walce z Hitlerem zginęli...
Wszyscy się okazuje walczyli z Hitlerem--jakimś cyborgiem, co sam jeden podbił pół Europy. Nałęcz udając głupiego, i zamiast Niemcy mówiąc Hitler, posługuje się prostacką propagandową kalką: całe zło to Hitler, a reszta systemu, w tym Wehrmacht -- no cóż niewiele mogli, a niektórzy bohatersko walczyli...
Całe szczęście, że Szef i jego dwór odchodzą w niebyt już za 24 dni.
Jak to z Microsoftem bywa nie jest łatwo. Są dwa formaty Excela
-- stary (.xls
) oraz nowy (.xlsx
).
Pakiety Perlowe
Spreadsheet::Excel
oraz Spreadsheet::ParseXLSX
radzą sobie
nieźle, aczkolwiek oczywiście gwarancji nie ma i być nie może skoro
sam Excel czasami siebie samego nie potrafi zinterpretować.
No ale jest jeszcze trzeci format: jak plik .xlsx
jest
zabezpieczone hasłem (password
protected). I na taką okoliczność nie ma zbyt wielu narzędzi.
Można wszakże problem rozwiązać w dwóch krokach korzystając
Libreoffice, który potrafi interpretować pliki Excela i można go
uruchomić w trybie batch:
#!/bin/bash XLS="$1" TMP="${XLS%.*}.xlsx" libreoffice --headless --convert-to xlsx "$XLS" --outdir ./xlsx-temp/ perl xslx2csv.pl ./xlsx-temp/"$TMP" "$OUTFILE"
Powyższy skrypt obsłuży wszystkie rodzaje plików Excela,
zamieniając je najpierw na plik w formacie XLSX
(plik password protected
zostanie
zmieniony na prawdziwy format XLSX,
interpretowalny przez np. Spreadsheet::ParseXLSX
).
Można od razu konwertować do CSV (--convert-to csv
), ale konwersji będzie podlegać
tylko pierwszy arkusz. Jak interesuje nas na przykład drugi, to kicha...
nie da się (a przynajmniej ja nie wiem jak to osiągnąć). Inny problem
to zamiana XLSX→XLSX -- nie ma w LibreOffice możliwości
określenia nazwy pliku wynikowego, a próba:
libreoffice --headless --convert-to xlsx plik.xlsx
Kończy się błędem. Na szczęście jest obejście w postaci
opcji --outdir
. Plik wyjściowy -- o tej samej nazwie
co wejściowy -- jest
zapisywany w innym katalogu i problem rozwiązany.
Po zamianie Excela na ,,kanoniczny'' XLSX do konwersji na CSV można wykorzystać następujący skrypt Perla:
#!/usr/bin/perl # Wykorzystanie perl xslx2csv.pl plik.xslx [numer-arkusza] use Spreadsheet::ParseXLSX; use open ":encoding(utf8)"; use open IN => ":encoding(utf8)", OUT => ":utf8"; $xslxfile = $ARGV[0]; $ArkuszNo = $ARGV[1] || 1; ## domyślnie arkuszu 1 my $source_excel = new Spreadsheet::ParseXLSX; my $source_book = $source_excel->parse("$xslxfile") or die "Could not open source Excel file $xslxfile: $!"; # Zapisuje zawartość wybranego arkusza do hasza %csv my %csv = (); foreach my $sheet_number (0 .. $source_book->{SheetCount}-1) { my $sheet = $source_book->{Worksheet}[$sheet_number]; print STDERR "*** SHEET:", $sheet->{Name}, "/", $sheet_number, "\n"; if ( $ArkuszNo == $sheet_number + 1 ) { next unless defined $sheet->{MaxRow}; next unless $sheet->{MinRow} <= $sheet->{MaxRow}; next unless defined $sheet->{MaxCol}; next unless $sheet->{MinCol} <= $sheet->{MaxCol}; foreach my $row_index ($sheet->{MinRow} .. $sheet->{MaxRow}) { foreach my $col_index ($sheet->{MinCol} .. $sheet->{MaxCol}) { my $source_cell = $sheet->{Cells}[$row_index][$col_index]; if ($source_cell) { $csv{$row_index}{$col_index} = $source_cell->Value; } } } } }
Arkusz jest w haszu %csv
.
Jak go przekształcić/wydrukować itp. pozostawiam inwencji ewentualnego czytelnika.
GoogleCL przestało działać, bo Google przestało obsługiwać wersję OAuth 1.0. Ponadto, wygląda na to, że dostosowanie tego użytecznego narzędzia do wersji OAuth 2.0 bynajmniej nie jest trywialne na co wskazują liczne (ale do tej pory bezskuteczne) prośby i wołania o aktualizację GoogleCL, które można znaleźć w Internecie.
Ponieważ poszukiwania
w miarę podobnego zamiennika zakończyły
się niepowodzeniem, nie pozostało nic innego zmajstrować coś samodzielnie.
Autoryzację OAuth 2.0 mam już opanową -- obsługuje ją Pythonowy skrypt oauth2picasa.py
.
(Skrypt jest (zapożyczonym) fragmentem z projektu
picasawebsync
).
Wystarczyło
dorobić następujący prosty skrypt Perlowy (por. także:
Publishing
a blog post):
#!/usr/bin/perl # *** Wyslanie posta na blogger.com *** use strict; use LWP::UserAgent; use XML::LibXML; use Getopt::Long; my $profileID="default"; my $blogID = '1928418645181504144'; # Identyfikator bloga my $blog_entry ; ## Na wypadek gdy ktoś ma kilka blogów moża podać na któr ## ma być wysłany post używając opcji -blog GetOptions( "blog=s" => \$blogID, "post=s" => \$blog_entry) ; if ( $blog_entry eq '' ) { print STDERR "*** USAGE: $0 -b blog -p message (-b is optional) ***\n" } ## sprawdź czy post jest well formed: my $parser = XML::LibXML->new(); eval {my $res_ = $parser->parse_string($blog_entry) }; if ($@) { die "*** Error parsing post message! \n"; } my $ACCESS_TOKEN=`oauth2blogger.py`; # pobierz ACCESS_TOKEN print STDERR "*** AccessToken: $ACCESS_TOKEN ***\n"; my $req = HTTP::Request->new( POST => "https://www.blogger.com/feeds/$blogID/posts/default"); $req->header( 'Content-Type' => 'application/atom+xml' ); $req->header( 'Authorization' => "Bearer $ACCESS_TOKEN" ); $req->header( 'GData-Version' => '2' ); $req->content($blog_entry); my $ua = LWP::UserAgent->new; my $res = $ua->request($req); # Jeżeli coś jest nie tak poniższe drukuje verbatim: # http://www.perlmonks.org/bare/?node_id=464442 # $ua->prepare_request($req); print($req->as_string); exit ; if ($res->is_success) { my $decoded_response = $res->decoded_content; print STDERR "*** OK *** $decoded_response\n"; } else { die $res->status_line; }
Wykorzystanie:
perl blogger_upload.pl -p 'treść-posta'
Treść posta musi być oczywiście w formacie xHTML i zawierać
się wewnątrz elementu content
, który z kolei
jest wewnątrz elementu entry
.
Element entry
zawiera także title
określający tytuł posta, oraz elementy category
zawierające
tagi. Przykładem może być coś takiego:
<entry xmlns='http://www.w3.org/2005/Atom'> <title type='text'>Marriage!</title> <content type='xhtml'> <div xmlns="http://www.w3.org/1999/xhtml"> <p>Mr. Darcy has proposed marriage to me!</p> <p>He is the last man on earth I would ever desire to marry.</p> <p>Whatever shall I do?</p> </div> </content> <category scheme="http://www.blogger.com/atom/ns#" term="marriage" /> <category scheme="http://www.blogger.com/atom/ns#" term="Mr. Darcy" /> </entry>
Opisany skrypt jest tutaj:
blogger_upload.pl
.