The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl -w
#
# ZA.pm
#
# 2013.05.01
# Changes to table references to correct for new sharenet web page layout
# Timothy Boyle

# 2008.02.18
# This version corrects the data downloaded by removing spaces and converting
# cent values into Rand values – this ensures that the Price Editor in GNUCash
# can import the data. The rest of the module and all the hard work
# remains that of Stephen Langenhoven!
# Rolf Endres
#
# 2005.07.19
# Download of South African (ZA) stocks from sharenet
# This version based largely upon FinanceCanada.pm module [any errors
# are my own of course ;-) ]
# Stephen Langenhoven

package Finance::Quote::ZA;
require 5.004;

use strict;

use LWP::UserAgent;
use HTTP::Request::Common;
use HTML::TableExtract;

our $VERSION = '1.35'; # VERSION

my $SHARENET_MAINURL = ("http://www.sharenet.co.za/");
my $SHARENET_URL     = ( $SHARENET_MAINURL . "jse/" );

sub methods {
    return ( za => \&sharenet );
}

sub labels {
    my @labels =
        qw/method source name symbol currency last date isodate high low p_change/;
    return ( sharenet => \@labels );
}

sub sharenet {
    my $quoter  = shift;
    my @symbols = @_;
    my %info;
    my ( $te, $ts, $row );
    my @rows;

    return unless @symbols;

    my $ua = $quoter->user_agent;

    foreach my $symbol (@symbols) {
        my $url = $SHARENET_URL . $symbol;

        # print "[debug]: ", $url, "\n";
        my $response = $ua->request( GET $url);

        # print "[debug]: ", $response->content, "\n";

        if ( !$response->is_success ) {
            $info{ $symbol, "success" }  = 0;
            $info{ $symbol, "errormsg" } = "Error contacting URL";
            next;
        }

        $te = new HTML::TableExtract();
        $te->parse( $response->content );

        # print "[debug]: (parsed HTML)",$te, "\n";

        unless ( $te->first_table_found() ) {

            # print STDERR  "no tables on this page\n";
            $info{ $symbol, "success" }  = 0;
            $info{ $symbol, "errormsg" } = "Parse error";
            next;
        }

        # Debug to dump all tables in HTML...

        #   print "\n \n \n \n[debug]: ++++ ==== ++++ ==== ++++ ==== ++++ ==== START OF TABLE DUMP ++++ ==== ++++ ==== ++++ ==== ++++ ==== \n \n \n \n";
        #
        # foreach $ts ($te->table_states) {;
        #
        #   printf "\n \n \n \n[debug]: //// \\\\ //// \\\\ //// \\\\ //// \\\\ START OF TABLE %d,%d //// \\\\ //// \\\\ //// \\\\ //// \\\\ \n \n \n \n",
        #    $ts->depth, $ts->count;
        #
        #  foreach $row ($ts->rows) {
        #    print "[debug]: ", $row->[0], " | ", $row->[1], " | ", $row->[2], " | ", $row->[3], "\n";
        #   }
        # }
        #
        #   print "\n \n \n \n[debug]: ++++ ==== ++++ ==== ++++ ==== ++++ ==== END OF TABLE DUMP ++++ ==== ++++ ==== ++++ ==== ++++ ==== \n \n \n \n";

        # GENERAL FIELDS
        $info{ $symbol, "success" } = 1;
        $info{ $symbol, "method" }  = "sharenet";

        $info{ $symbol, "symbol" }   = $symbol;
        $info{ $symbol, "currency" } = "ZAR";
        $info{ $symbol, "source" }   = $SHARENET_MAINURL;

        # NAME
        $ts = $te->table_state( 2, 1 );    # new table reference
        if ($ts) {
            (@rows) = $ts->rows;
            $info{ $symbol, "name" } = $rows[2][1];
        }

        $info{ $symbol, "name" } =~ tr/ //d;

        # DATE AND CLOSING PRICE
        $ts = $te->table_state( 3, 0 );   # change table for new sharenet layout

        # print "[debug]: ", "got this far...", "\n";
        # print "[debug]: (table_state)",$ts, "\n";
        if ($ts) {
            (@rows) = $ts->rows;

            # date for last trade sale, high, low
            # sharenet only gives the day and month. We could use today's date, but this would not
            # be correct over weekends and public holidays (if it matters)
            my $date =
                substr( $rows[0][0], 16, 5 )
                . "/";    #extract the day/month from the string and add /

            # this does the same as above in a more robust fashion
            #     my $date  = $rows[0][0]; # day/month plus time plus text
            #     $date =~ s/[^0-9\/]//g; # remove most unwanted characters
            #     $date =~ s/\d{4}$/\//; # remove last 4 digits = time and add / for the year

            my $year =
                ( localtime() )[5]
                + 1900;    # extract year from system time vector
            $date = $date . $year;    # add it to the day/month

            # print $date, "\n"; # we now have the date of the trades as dd/mm/yyyy
            $quoter->store_date( \%info, $symbol, { eurodate => $date } )
                ;                     # gives eurodate and isodate symbols

            # $quoter->store_date(\%info, $symbol, {today => 1}); # could use today's date
            # last traded price
            $info{ $symbol, "last" } = $rows[2][1];
            $info{ $symbol, "last" } =~ tr/ //d;
            $info{ $symbol, "last" } = 0.01 * $info{ $symbol, "last" };

            # highest price today
            $info{ $symbol, "high" } = $rows[16][1];
            $info{ $symbol, "high" } =~ tr/ //d;
            $info{ $symbol, "high" } = 0.01 * $info{ $symbol, "high" };

            # lowest price today
            $info{ $symbol, "low" } = $rows[18][1];
            $info{ $symbol, "low" } =~ tr/ //d;
            $info{ $symbol, "low" } = 0.01 * $info{ $symbol, "low" };

            # percent change from previous close
            $info{ $symbol, "p_change" } = $rows[10][1];
            $info{ $symbol, "p_change" } =~ tr/ //d;

            # actual net change from previous close
            $info{ $symbol, "net" } = $rows[8][1];
            $info{ $symbol, "net" } =~ tr/ //d;

        }

    }

    return wantarray() ? %info : \%info;
}

1;

=head1 NAME

Finance::Quote::ZA - Obtain South African stock and prices from
www.sharenet.co.za

=head1 SYNOPSIS

    use Finance::Quote;

    $q = Finance::Quote->new;

    # Don't know anything about failover yet...

=head1 DESCRIPTION

This module obtains information about South African Stocks from
www.sharenet.co.za.

=head1 LABELS RETURNED

Information available from sharenet may include the following labels:

method source name symbol currency date nav last price

=head1 SEE ALSO

Sharenet website - http://www.sharenet.co.za/

Finance::Quote

=cut