Addressbook migration to Nokia

2010-12-26 perl Nokia PC Suite unicode csv Text::CSV_XS BOM

This christmas I finally decided to upgrade my venerable cell phone to something newer. Old one, Siemens ME45, served me well for eight years, but its second battery life is close to death and I got feeling new gizmo would be nice.

I ended up buying Nokia 6303i. This is simple script to help me convert addressbook into new device.

The old Siemens came with serial RS232 data cable and I have small program to backup data from phone into text file. Addressbook in particular is simple comma-separated (CSV) file. First column is either ‘'’SM’’’ (SIM card) or ‘'’AD’’’ (Address book). Second column is entry number. Remainder are properties of the contact:

....
SM;009;+420776123456;"Sam"
SM;010;+420604234567;"Falco"
AD;0;Magda;;+420777345678;;;;;;;;;;;
AD;1;Jerry;;+420777456789;;;;;;;;;;;VIP
....

Nokia comes with PC Suite that allows to import utf-16 encoded 55-column CSV. Exact format I got by exporting one hand-made entry into file. The @me45_into_col mapping array gives for each column from Siemens format specific column in Nokia one.

The most tricky part was to encode output file properly and add correct BOM (byte-order mark) to the start of file. These two lines do the job:

open my $nokia_out, ">:raw:encoding(utf-16le):utf8", $ARGV[1] or die;
print {$nokia_out} "\x{FEFF}";

First is opening file that is encoding internal perl utf-8 into utf-16 little endian. The second line writes single letter - two bytes: 0xFE, 0xFF - into the beginning of file to indicate encoding. All BOMs defined are:

  • 0x00 0x00 0xFE 0xFF … utf-32, big-endian
  • 0xFF 0xFE 0x00 0x00 … utf-32, little-endian
  • 0xFE 0xFF … utf-16, big-endian
  • 0xFF 0xFE … utf-16, little-endian
  • 0xEF 0xBB 0xBF … utf-8

Script can be called as:

perl convert_list.pl siemens.csv nokia.csv

convert_list.pl

use strict;
use utf8;
use Text::CSV_XS;

# mapping of Siemens columns to Nokia columns
my @me45_into_col = (
    undef,
    undef,
    3,       # Last Name
    1,       # First Name
    13,      # Phone
    6,       # Company
    22,      # Street
    24,      # City
    23,      # Postcode
    26,      # Country
    14,      # Tel Office
    27,      # Tel Mobile
    16,      # Fax
    15,      # E-Mail
    18,      # Web Page
    undef,   # Group
);

my @nokia_header = (
    # ... entries copied from exported CSV file
);

die "error: two files expected as input\n"
  . "syntax: $0 infile outfile\n"
    unless @ARGV == 2;

open my $me45_in,   "<", $ARGV[0] or die;

# prepare output file with BOM
open my $nokia_out, ">:raw:encoding(utf-16le):utf8", $ARGV[1] or die;
print {$nokia_out} "\x{FEFF}";

# CSV header
my $csv = Text::CSV_XS->new({ binary => 1, sep_char => ';' });
if($csv->combine(@nokia_header)) {
    print {$nokia_out} $csv->string,"\n";
}

# data
while(<$me45_in>) {
    unless($csv->parse($_)) {
        warn "Bad formatting: $_";
        next;
    }
    my @me45_fields = $csv->fields;

    next if $me45_fields[0] ne "AD";

    my @out = (undef) x @nokia_header;
    for(my $i = 0; $i < @me45_into_col; $i++) {
        next unless defined $me45_into_col[$i];
        $out[$me45_into_col[$i]] = $me45_fields[$i];
    }

    unless($csv->combine(@out)) {
        warn "Bad data: @out";
        next;
    }
    print {$nokia_out} $csv->string,"\n";
}