/* * [CanadaPostageRates.java] * * Summary: Probe Canadian post office to find the latest postage rates to post at http://mindprod.com/jgloss/postage.html. * * Copyright: (c) 2014-2017 Roedy Green, Canadian Mind Products, http://mindprod.com * * Licence: This software may be copied and used freely for any purpose but military. * http://mindprod.com/contact/nonmil.html * * Requires: JDK 1.8+ * * Created with: JetBrains IntelliJ IDEA IDE http://www.jetbrains.com/idea/ * * Version History: * 1.0 2014-04-06 initial release */ package com.mindprod.repair; import com.mindprod.common18.BigDate; import com.mindprod.common18.ST; import com.mindprod.fastcat.FastCat; import com.mindprod.http.Get; import com.mindprod.hunkio.HunkIO; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.text.DecimalFormat; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import static java.lang.System.*; /** * Probe Canadian post office to find the latest postage rates to post at http://mindprod.com/jgloss/postage.html. * Trigger with E:\com\mindprod\repair\GrabPostageRates.exe * * @author Roedy Green, Canadian Mind Products * @version 1.0 2014-04-06 initial release * @see USPostageRates * @since 2014-04-06 */ class CanadaPostageRates { private static final int[] LOW_GRAMS_ON_WEIGHT_CLASS = { 0, 30, 50 }; private static final int[] HIGH_GRAMS_ON_WEIGHT_CLASS = { 30, 50, 100 }; private static final Pattern FIND_DOLLAR = Pattern.compile( ">\\s*\\$([0-9.]+)\\s*<" ); /** * format for display and input of sale amount */ private static final DecimalFormat RATIOFORMAT = new DecimalFormat( "#,##0.00" ); // they have three variants // can 0-30g Permanent (P) stamps $0.85 // int 0-30g Stamps(s) $2.50 // usa 0-30g Stamp(s) $1.20 // https://www.canadapost.ca/cpo/mc/personal/postalcode/fpc.jsf now has summary. private static final String[] CANADIAN_DESCRIPTIONS_FOR_COUNTRIES = { "letter within Canada", "letter to the USA", "international letter" }; private static final String[] CANADIAN_DESCRIPTIONS_FOR_WEIGHTS = { "light", "heavy", "overweight" }; /** * stringe just before the prices we want */ private static final String LEAD = "

Postal Rates

"; // get data direct from canada post. Else get in indirectly from downloaded file. private static final boolean DIRECT = true; /** * cost of one basic Canadian stamp, used for computing ratios */ private static double canadianBaseRate; /** * used to alternate stripes */ private static int lineCounter; private static String calcCanadianDescription( int country, int weight ) { return CANADIAN_DESCRIPTIONS_FOR_WEIGHTS[ weight ] + " " + CANADIAN_DESCRIPTIONS_FOR_COUNTRIES[ country ]; } private static String generateOneCanadianTableLine( final String desc, final int weightClass, final String price ) { // generate something like this: // $0.851.00light letter within Canada0 to 30 grams // group lines by threes, alternating light/dark backgrounds final String css = ( lineCounter++ / 3 % 2 == 0 ) ? "stripe1" : "stripe2"; final int fromGrams = LOW_GRAMS_ON_WEIGHT_CLASS[ weightClass ]; final int toGrams = HIGH_GRAMS_ON_WEIGHT_CLASS[ weightClass ]; final FastCat sb = new FastCat( 17 ); sb.append( "" ); sb.append( "$", price, "" ); sb.append( "", RATIOFORMAT.format( Double.parseDouble( price ) / canadianBaseRate ), "" ); sb.append( "", desc, "" ); sb.append( "", fromGrams, " to ", toGrams, " grams" ); return sb.toString(); } static String probeAllCanadianPrices() throws java.io.IOException { String result; if ( DIRECT ) { try { final Get get = new Get(); get.setInstanceFollowRedirects( true ); get.setConnectTimeout( ( int ) TimeUnit.SECONDS.toMillis( 40 ) ); // fails with too too many redirects // https://www.canadapost.ca/cpotools/apps/far/business/findARate?execution=e1s1 result = get.send( new URL( "https://www.canadapost.ca/cpo/mc/personal/postalcode/fpc.jsf" ), Get.UTF8 ); } catch ( MalformedURLException e ) { return ">>>bad url"; } } else { // manually save https://www.canadapost.ca/cpo/mc/personal/postalcode/fpc.jsf to C:\temp\canrates.html File source = new File( "C:/temp/canrates.html" ); assert source.exists() : source.toString() + " is missing."; result = HunkIO.readEntireFile( new File( "C:/temp/canrates.html" ), HunkIO.UTF8 ); } assert !ST.isEmpty( result ) : "missing raw data"; // trim off chunk at front. int p = result.indexOf( LEAD ); if ( p < 0 ) { throw new IllegalArgumentException( "corrupt data. Missing lead." ); } result = result.substring( p + LEAD.length() ); // extract 3 rows of 3 numbers String[] price = new String[ 9 ]; Matcher m = FIND_DOLLAR.matcher( result ); for ( int i = 0; i < 9; i++ ) { if ( m.find() ) { price[ i ] = m.group( 1 ); } else { throw new IllegalArgumentException( "corrupt data. Missing $ value." ); } } canadianBaseRate = Double.parseDouble( price[ 0 ] ); final String prelude1 = " \n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + ""; final String postlude = "
Canadian Postage Rates
Canadian Postage\n" + "Rates
Cost $CADRatioTypeWeight
"; final FastCat sb = new FastCat( 13 ); sb.append( prelude1, prelude2, prelude3 ); int j = 0; for ( int country = 0; country < 3; country++ ) { for ( int weightClass = 0; weightClass < 3; weightClass++ ) { final String desc = calcCanadianDescription( country, weightClass ); sb.append( generateOneCanadianTableLine( desc, weightClass, price[ j++ ] ) ); } } sb.append( postlude ); return sb.toSeparatedList( '\n' ); } public static void main( String[] args ) throws IOException { final String guts = probeAllCanadianPrices(); out.println( guts ); HunkIO.writeEntireFile( new File( "E:/mindprod/jgloss/include/postageratesforcanada.htmlfrag" ), guts, HunkIO.UTF8 ); } }