#!/usr/local/bin/perl -w # ======================================================================== # fat2html - generate HTML from weight and body fat measurements # Andrew Ho (andrew@tellme.com) # # This program generates an HTML table from an XML file provided as an # argument filename, or piped to stdin. It uses FatXML::Parser to do the # parsing; see the documentation for FatXML.pm for the details to the XML # format accepted. # ======================================================================== require 5.005; use strict; use FatXML (); use Time::Local qw(timelocal); use File::Basename qw(basename); use Date::Calc qw(Add_Delta_Days); use vars qw($ME); $ME = basename $0; # ------------------------------------------------------------------------ # Initialization # Parse the XML input to get a FatXML::Measures object. my $parser = FatXML::Parser->new; my $measures = undef; $@ = ''; eval { $measures = @ARGV ? $parser->parsefile(shift) : $parser->parse(\*STDIN); }; if($@ || !defined $measures) { if($@) { $@ =~ s/^\s+//gsm; $@ =~ s/\s+$//gsm; print STDERR $ME, ': parse error: ', $@, "\n"; exit 2; } else { print STDERR $ME, ": general parse error\n"; exit 2; } } # Pull out interesting bits of information. my $max_weight = $measures->max_weight; my $min_weight = $measures->min_weight; my $max_fat = $measures->max_fat; my $min_fat = $measures->min_fat; my @dates = $measures->dates; # Find the first Sunday before or on the first date to start with, # so all our tables cover full weeks. my $begin_date = @dates ? $dates[0] : today(); $begin_date = date_yesterday($begin_date) while date_weekday($begin_date) > 0; my $end_date = @dates ? $dates[$#dates] : today(); my $date = $begin_date; # ------------------------------------------------------------------------ # Print HTML output print << 'EndHTML';


EndHTML # Loop through dates and pull measurements from them, or display # a blank entry for that day. my @weekday_weights = (); my @weekday_fats = (); my @all_weights = (); my @all_fats = (); my @weights = (); my @fats = (); while($date le $end_date) { my $measure = $measures->date($date); my $weight = undef; my $fat = undef; my $weekday = date_weekday($date); if($measure) { $weight = $measure->weight; $fat = $measure->fat; push @all_weights, $weight; push @all_fats, $fat; push @weights, $weight; push @fats, $fat; if(defined $weekday_weights[$weekday]) { push @{$weekday_weights[$weekday]}, $weight; } else { $weekday_weights[$weekday] = [ $weight ]; } if(defined $weekday_fats[$weekday]) { push @{$weekday_fats[$weekday]}, $fat; } else { $weekday_fats[$weekday] = [ $fat ]; } if($weight <= $min_weight) { $weight = sprintf '%0.1f', $weight; } elsif($weight >= $max_weight) { $weight = sprintf '%0.1f', $weight; } else { $weight = sprintf '%0.1f', $weight; } if($fat <= $min_fat) { $fat = sprintf '%0.1f', $fat; } elsif($fat >= $max_fat) { $fat = sprintf '%0.1f', $fat; } else { $fat = sprintf '%0.1f', $fat; } } else { $weight = $fat = ' ' } printf(<< ' EndHTML', $date, $weight, $fat); EndHTML my $tomorrow = date_tomorrow($date); if($weekday == 6) { printf(<< ' EndHTML', mean(\@weights), mean(\@fats));
Date Weight (lb) Fat (%)
%s %s %s
Mean %0.1f %0.1f

EndHTML @weights = (); @fats = (); if($tomorrow le $end_date) { print << ' EndHTML'; EndHTML } } $date = $tomorrow; } # Add code to not print table end if we just printed it unless(date_weekday($date) == 0) { print << ' EndHTML';
Date Weight (lb) Fat (%)

EndHTML } print << 'EndHTML';
     EndHTML my $delta_weight = sprintf '%0.1f', 0.0 + sprintf '%0.1f', $measures->date($dates[$#dates])->ewma_weight - $measures->date($dates[0])->ewma_weight ; my $delta_fat = sprintf '%0.1f', 0.0 + sprintf '%0.1f', $measures->date($dates[$#dates])->ewma_fat - $measures->date($dates[0])->ewma_fat ; my $ewma_weight = sprintf '%0.1f', $measures->date($dates[$#dates])->ewma_weight ; my $ewma_fat = sprintf '%0.1f', $measures->date($dates[$#dates])->ewma_fat ; my $mean_weight = sprintf '%0.1f', $measures->mean_weight; my $mean_fat = sprintf '%0.1f', $measures->mean_fat; my $weight_spread = sprintf '%0.1f', abs($measures->max_weight - $measures->min_weight); my $fat_spread = sprintf '%0.1f', abs($measures->max_fat - $measures->min_fat); my $stddev_weight = sprintf '%0.1f', $measures->stddev_weight; my $stddev_fat = sprintf '%0.1f', $measures->stddev_fat; print << "EndHTML";
Statistics
Measurement Weight (lb) Fat (%)
Weighted Delta $delta_weight $delta_fat
Weighted Average $ewma_weight $ewma_fat
Mean $mean_weight $mean_fat
Minimum $min_weight $min_fat
Maximum $max_weight $max_fat
Spread $weight_spread $fat_spread
Standard Deviation $stddev_weight $stddev_fat

EndHTML foreach(0 .. $#weekday_weights) { my $weekday = ( qw( Sunday Monday Tuesday Wednesday Thursday Friday Saturday) )[$_]; my $weight = mean($weekday_weights[$_]); my $fat = mean($weekday_fats[$_]); $weight = sprintf '%0.1f', $weight; $fat = sprintf '%0.1f', $fat; printf(<< ' EndHTML', $weekday, $weight, $fat); EndHTML } print << 'EndHTML';
Weekday Means
Weekday Weight (lb) Fat (%)
%s %s %s

Source Code
fat.xml    XML data file
FatXML.pm XML parser
fat2html Generates this HTML file
fat2graph Charts trends for graph
fat2wdayhist Weekday histogram graphic
Makefile Automates this process
fat.tar.gz Source distribution tarball
EndHTML print << 'EndHTML'; EndHTML exit 0; # ------------------------------------------------------------------------ # Internal subroutines # date_weekday($date) takes $date in ISO 8601 like format (four-digit # year, two-digit month, two-digit day, separated by hyphens) and returns # the weekday number (as in $wday returned by localtime() the range is # 0-6, where 0 is Sunday) corresponding to that date. sub date_weekday { my $date = shift; my($year, $month, $day) = split '-', $date, 3; return unless $year && $month && $day; my $time = timelocal(0, 0, 0, $day, $month - 1, $year - 1900); my $wday = (localtime($time))[6]; return $wday; } # delta_days($delta, $date) takes a delta (number of days) and an ISO 8601 # like date and returns the ISO 8601 like date shifted by $delta days. sub delta_days { my $delta = shift; my $date = shift; my($year, $month, $day) = split '-', $date, 3; return unless $year && $month && $day; my($new_year, $new_month, $new_day) = Add_Delta_Days($year, $month, $day, $delta); return sprintf '%04d-%02d-%02d', $new_year, $new_month, $new_day; } # date_yesterday($date) and date_tomorrow($date) are calculate the dates # one day before and after $date, respectively. sub date_yesterday { delta_days(-1, @_) } sub date_tomorrow { delta_days(+1, @_) } # today() returns today's date in ISO 8601 like format. sub today { my($day, $month, $year) = (localtime time)[3,4,5]; return sprintf '%04d-%02d-%02d', $year + 1900, $month + 1, $day; } # mean(@array) or mean($arrayref) returns the mean (average) of all the # elements in @array (or $arrayref). sub mean { return unless @_ > 0; my $values = @_ == 1 ? shift : [ @_ ]; my $total = 0; $total += $_ foreach @$values; return $total / @$values; } # ======================================================================== __END__