Using Number::Phone to validate and format - perl

I'm trying to use Number::Phone from CPAN to accomplish 2 tasks:
Validate a Phone Number; and
Format the number in E.164 Notation.
However, I'm unable to figure out how it works. My sample code is:
#!/usr/bin/perl -w
use strict;
use warnings;
use Number::Phone;
foreach my $fnum ( '17888888', '97338888888', '00923455555333', '+97366767777' , '38383838') {
my $phone = Number::Phone->new($fnum);
my $norm = "";
eval {
$norm = $phone->format_using('E123'); # or 'Raw'
print "E164 => '$norm'\n";
} or do {
print STDERR "Unable to parse '$fnum'\n";
}
}
Expected output:
E164 => '+97317888888'
E164 => '+97338888888'
E164 => '+923455555333'
E164 => '+97366767777'
E164 => '+97338383838'
But the results were incorrect. I tried using Number::Phone::Normalize, but still not successful:
#!/usr/bin/perl -w
use strict;
use warnings;
use Number::Phone::Normalize;
my %params = (
'CountryCode'=>'973',
'IntlPrefix' =>'00',
'CountryCodeOut'=>'973',
'IntlPrefixOut' => '+',
);
my $nlz = Number::Phone::Normalize->new( %params );
foreach my $number ('17888888', '97338888888', '00923455555333', '+97366767777' , '38383838') {
my $e164 = $nlz->intl( $number );
print "E164 => '$e164'\n";
}
with the same expected output of:
E164 => '+97317888888'
E164 => '+97338888888'
E164 => '+923455555333'
E164 => '+97366767777'
E164 => '+97338383838'
However, this produced the wrong results too. The snippet Java code below works perfectly, and it's what I'm trying to achieve in Perl.
// Uses libphonenumber: http://code.google.com/p/libphonenumber/
// setenv CLASSPATH .:libphonenumber-8.5.2.jar
// libphonenumber
import com.google.i18n.phonenumbers.PhoneNumberUtil;
import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.google.i18n.phonenumbers.NumberParseException;
import com.google.i18n.phonenumbers.PhoneNumberUtil.PhoneNumberFormat;
public class ValidateList {
public static void main(String[] args) {
try {
if (args.length != 1) {
throw new IllegalArgumentException("Invalid number of arguments.");
}
String file = args[0];
PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
try (java.io.BufferedReader br = new java.io.BufferedReader(new java.io.FileReader(file))) {
String line = null;
while ((line = br.readLine()) != null) {
try {
PhoneNumber phoneNumber = phoneUtil.parse(line, "BH");
boolean isValid = phoneUtil.isValidNumber(phoneNumber);
if (isValid) {
System.out.println( "E164 => " + phoneUtil.format(phoneNumber, PhoneNumberFormat.E164) );
}
else {
System.err.println( "Invalid => " + line);
}
}
catch (NumberParseException e) {
System.err.println("NumberParseException for ("+line+"): " + e.toString());
}
}
}
}
catch (Exception e) {
System.err.println(e);
System.err.println("Usage: java ValidateList <fileNameWithPhoneNumbers>");
}
}
}
% cat input.txt
17888888
97338888888
00923455555333
+97366767777
38383838
% javac -cp libphonenumber-8.5.2.jar ValidateList.java
% java -cp .:libphonenumber-8.5.2.jar ValidateList input.txt
E164 => +97317888888
E164 => +97338888888
E164 => +923455555333
E164 => +97366767777
E164 => +97338383838
Your input is greatly appreciated.

When I run the first example code for the numbers, two of those fail to be parsed:
17888888 - this is obvious, when calling Number::Phone without a country code, this will not be parsed as it's unclear what country this is from
00923455555333 - 923 is, according to a quick google search, the country code for Pakistan. The Wikipedia page for dialing codes in Pakistan shows no 455, leading me to think that this is not a known area code to either Number::Phone or Wikipedia. I suspect it is an invalid number.
So for the first Number: specify which country this is supposed to be from.
If you are certain the other number is correct, you know more about that than the developer of Number::Phone currently and I'm sure he'd be happy to receive your input in the form of a more complete Number::Phone localized package.
The fact that your Java code accepts the (probably) invalid number does not necessarily mean it is more correct, just that it is less picky in what it declares to be a correct number.
Edit:
Asking Phone::Number to parse the input '+923455555333' instead of '00923455555333' leads to the desired output.
Looking at the source of Phone::Number:
# ... processing input arguments
$number = "+$number" unless($number =~ /^\+/);
It becomes clear that the 00 is interpreted as '+00' and then rejected as being an invalid number.
View some discussion on that here
It seems to me you will have to handle this yourself.
One way may be to simply replace leading 00 with '+' - preferably only if parsing failed.
The other number can be parsed if you make it clear what country it should belong to.
Perhaps like so:
my $phone = Number::Phone->new($fnum);
unless ($phone){
$phone = Number::Phone->new('BH',$fnum);
if ( !$phone && $fnum =~ s/^00/+/ ){
# You should probably check the discussion I linked.
# There may well be problems with this approach!
$phone = Number::Phone->new($fnum);
}
}

Related

Get blob uploaded data with pure Perl

In Javascript, I am sending a blob using XHR by the following code:
var v=new FormData();
v.append("EFD",new Blob([...Uint8Array...]));
var h=new XMLHttpRequest();
h.setRequestHeader("Content-type","multipart/form-data; charset=utf-8");
h.open("POST","...url...");
h.send(v);
In the server, I have created in Perl the following function, that suppose to implement CGI->param and CGI->upload:
# QS (Query String) receive in argument string for single parameter or array of many required parameters.
# If string been supplied: Return the value of the parameter or undef if missing.
# If array been supplied, a hash will be returned with keys for param names and their corresponding values.
# If the first argument is undef, then return hash with ALL available parameters.
sub QS {
my $b=$ENV{'QUERY_STRING'};
if($ENV{'REQUEST_METHOD'} eq "POST") {
read(STDIN,$b,$ENV{'CONTENT_LENGTH'}) or die "E100";
}
my $e=$_[0]; my $t=&AT($e); my $r={}; my #q=split(/&/,$b);
my %p=(); if($t eq "A") { %p=map { $_=>1 } #{$e}; }
foreach my $i(#q) {
my ($k,$s)=split(/=/,$i); $s=~tr/+//; $s=~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
if($t eq "") { $r->{$k}=$s; }
elsif($t eq "A") { if($p{$k}) { $r->{$k}=$s; } }
elsif($k eq $_[0]) { return $s; }
}
return $r;
}
# AT is a function for determining type of an object, and also a quck way to distinguish between just a string and a number.
sub AT {
if(!defined $_[0]) { return ""; } my $v=ref($_[0]);
if($v eq "") { return ($_[0]*1 eq $_[0])?"N":"S"; }
my $k={"ARRAY"=>"A","HASH"=>"H"};
return $k->{$v}||$_[0]->{_obt}||$v;
}
So in the main program it will be called as:
my $EFD=&FW::QS("EFD"); # FW = The module name where QS and AT are.
When I issuing the POST from the client, the script in the server does not pop-up any errors, and does not terminates - it continues to run and run and run.... Endlessly.... Consuming 100% CPU time and 100% memory - without any explanation.
I have these in the beginning of the script, though:
use strict;
use warnings;
use diagnostics;
but it still behave in such a way that I need to kill the script in order to terminate it...
Anyone know what I did wrong...? No infinite loop here, as far as I know... If I change the Blob to regular classic way of "...url...?EFD=dhglkhserkhgoi" then it works just fine, but I want a Blob....
Thanks a lot
This QS function is only usable for POSTs with an application/x-www-urlencoded body, which yours isn't.

MongoDB/Perl: find_one doesn't return data after unrelated code

mongodb is v4.0.5
Perl is 5.26.3
MongoDB Perl driver is 2.0.3
This Data::Dumper output shows what's driving me crazy
INFO - $VAR1 = [
'275369249826930689 1',
{
'conf' => {
'param' => 'argument'
},
'id' => '275369249826930689',
'lastmsg' => '604195211232139552',
'_id' => bless( {
'oid' => ']:\',&�h�GeR'
}, 'BSON::OID' )
}
];
352832438449209345 275369249826930689
INFO - $VAR1 = [
'275369249826930689 2'
];
The second INFO - $VAR1 should show the same content as the first one. This is the original code, which I have (see below) broken down to find the culprit.
ddump(["$userid 1",
$c_identities->find_one({
channel => 'chan1',
id => $userid,
})
]);
my #filtered = reverse
grep { $_->{author}->{id} == $userid } #{$answers};
ddump(["$userid 2",
$c_identities->find_one({
channel => 'chan1',
id => $userid,
})
]);
ddump is just a wrapper for Data::Dumper. If I remove the "my #filtered" line, the second find one again returns the expected result (a MongoDB document). $answers is just a listref of hashes - no objects - from some API, completely unrelated to MongoDB.
So I broke the "reverse grep" code down to see where the culprit is. The say are the two numbers you see between the dumpers above. This is what I can do, to get answer from the second find_one:
for my $answer (#{$answers}) {
say $answer->{author}->{id}, ' ', $userid;
push #filtered, $answer;
}
As long as I do just this, the second find_one delivers a result. If, however, I do this:
for my $answer (#{$answers}) {
say $answer->{author}->{id}, ' ', $userid;
if ($answer->{author}->{id} == $userid) {
}
push #filtered, $answer;
}
I get the output from above (where the second dumper yields no return from the find_one. It's insane - the if-clause containing the numeric eq causes the second find_one to fail! This is also the grep body in the intended code.
What's going on here? How can this have possibly any effect on the MongoDB methods?
Using the numeric comparison operator == numifies the value, but it's probably too large to fit into an integer and becomes a float. It can also just become an integer and lose double quotes when serialized to JSON or similar format. Using eq instead of == keeps the value unchanged.

Error handling with invalid MAC entry with Perl Net::MAC module

I'm attempting write a Perl script that asks the user to enter a MAC address. I'm using the Net::MAC module to convert whatever format MAC address the user enters to a standard format for me to use later in the script. I've got most of it working but I can't seem to figure out how to handle a situation in which they enter an invalid MAC. Something that couldn't possibly be a MAC address. Such as an entry that includes letters that aren't A-F.
I'm thinking something like the following should work but when it dies it just flat out kills the entire script rather than re-asks the user to enter the MAC again.
use Net::MAC;
my $proceed = "no";
while ($proceed eq "no"){
print "Enter the MAC address: ";
my $mac;
$mac = <>;
chomp($mac);
my $tempMac = Net::MAC->new('mac' => $mac, 'die' => 0);
if ($tempMac->die() eq "0"){
print "Looks like you entered an invalid MAC address. Please try again.\n";
} else {
my $newMac = $tempMac->convert('base' => 16,'bit_group' => 8,'delimeter' => ":");
$proceed = "yes";
}
}
Should this instead use something like a Try, Catch statement? I think in other words, I need to know how to appropriately handle the die() event without having the script completely bail on me.
See https://mvp.kablamo.org/essentials/die-eval for some info on how exceptions work in Perl and how to handle them. Consider Syntax::Keyword::Try for a familiar try/catch paradigm.
use Syntax::Keyword::Try;
my $newMac;
try {
my $tempMac = Net::MAC->new('mac' => $mac);
$newMac = $tempMac->convert('base' => 16,'bit_group' => 8,'delimeter' => ":");
$proceed = "yes";
} catch {
print "Looks like you entered an invalid MAC address. Error: $# Please try again.\n";
}
You can also validate the mac address before passing it to Net::MAC using something like Regexp::Common.
use Regexp::Common 'net';
my $newMac;
if ($mac =~ m/$RE{net}{MAC}/) {
my $tempMac = Net::MAC->new('mac' => $mac);
$newMac = $tempMac->convert('base' => 16,'bit_group' => 8,'delimeter' => ":");
$proceed = "yes";
} else {
print "Looks like you entered an invalid MAC address. Please try again.\n";
}

ExifTool Perl Library Module: How to separate keyword- from subject-values

I am using ExifToolVersion : 9.13 to read out metainformations of a pdf-file to formfields, where users can edit the values.
With a second perl-script I write these changed values back to the file.
That works fine with the exception, that subject-values appear in keyword-tags and keyword-values in subject-tags, although I write the new values explizite to each tag.
$exifTool->SetNewValue($tag[$i], \#keywords, Replace => 1);
$exifTool->SetNewValue($tag[$i], $file_beschreibung, Replace => 1);
$exifTool->SetNewValue($data[$i]=>\#keywords, Group0 => 'PDF');
$exifTool->SetNewValue($data[$i]=>$file_beschreibung, Group0 => 'PDF');
I tried to write an empty value to the XMP tags, but that doesn't work
$exifTool->SetNewValue($data[$i]=>$leer, Group0 => 'XMP');
Is there a way to to avoid the concatenation of both values?
I now found, that I have to clear all XMP-Tags
my #data = ("Author","Keywords","ModifyDate","Rights","Title","Subject");
my $elemente = #data;
for ($i=0; $i<$elemente; $i++)
{
if ($i==1)
{
if (my $tagname =~ m/^XMP-.*:$data[$i]/)
{
$exifTool->SetNewValue($tagname=>'', Group => 'XMP');
}
$exifTool->SetNewValue($data[$i]=>\#keywords, Group => 'PDF');
}
if ($i==5)
{
my $tagname = "XMP-dc:".$data[$i];
$exifTool->SetNewValue($tagname=>'', Group => 'XMP');
$exifTool->SetNewValue($data[$i]=>$file_beschreibung, Group => 'PDF');
}
}
This works fine. Thank you for helping!

How to loop through subarrays of a SOAP::Lite response in Perl?

I have a Perl script that is successfully getting a response from my ShoreTel Phone server. The server provides information on what calls are currently connected for the extension entered. However I am having issues looping through the sub arrays to get more than one response when there are multiple items. In this case I want to get each of the caller IDs that is currently connected.
My SOAP:LITE request is successfully pulling data from the server using the following code:
use strict;
use warnings;
use SOAP::Lite;
use CGI;
use Data::Dumper;
my $myWebService = SOAP::Lite
-> uri('http://www.ShoreTel.com/ProServices/SDK/Web')
-> proxy('http://10.1.##.##:8070/ShoreTelWebSDK/WebService')
-> on_action(sub {sprintf '%s/ShoreTelWebService/%s', $_[0], $_[1]});
my $query = new CGI;
my $ip = $query->remote_host; # IP address of remote party...use later as unique identifier
my $myClientID = $query->param('MyClientID'); # Possible client ID from previous script passed into us.
my $extnNr = $query->param('MyExtn'); # Has to be at least an extension number so we know who to status.
my $url = CGI::url(-path_info=>1); # What is my URL?
# There should be an extension number given, else what would we status.
if (defined($refreshNr) && defined($extnNr) && ($extnNr ne '') && ($refreshNr ne ''))
{
# If there is a client ID defined, use it...otherwise registering and getting a client ID
# is the first thing we need to do when using our web service.
unless (defined($myClientID))
{
# To use our service, we need to register ourselves as a client...use remote IP address
# as a unique name for association to this session.
my $regClientResult = $myWebService->RegisterClient(SOAP::Data->name('clientName' => $ip));
if ($regClientResult->fault)
{
print '<p>FAULT', $myClientID->faultcode, ', ', $myClientID->faultstring;
}
else
{
# Retrieve client ID which we will be using for subsequent communication.
$myClientID = $regClientResult->valueof('//RegisterClientResponse/RegisterClientResult/');
}
}
if (defined($myClientID))
{
# Use our web service to open the line. This is necessary to get a line ID.
# print '<br>Client ID ', $myClientID, ' has been registered.<br>';
my $openResult = $myWebService->OpenLine(SOAP::Data->name('clientHandle' => $myClientID), SOAP::Data->name('lineAddress' => $extnNr));
my $lineID = $openResult->valueof('//OpenLineResponse/OpenLineResult/lineID/');
my $lineType = $openResult->valueof('//OpenLineResponse/OpenLineResult/lineType/');
my $lineName = $openResult->valueof('//OpenLineResponse/OpenLineResult/lineName/');
my $lineState = $openResult->valueof('//OpenLineResponse/OpenLineResult/lineState/');
# Call GetActiveCalls to see if anything is going on with this line.
my $result = $myWebService->GetActiveCalls(SOAP::Data->name('clientHandle' => $myClientID), SOAP::Data->name('lineID' => $lineID));
my $callID = $result->valueof('//GetActiveCallsResponse/GetActiveCallsResult/ShoreTelCallStateInfo/callInfo/callID/');
if ($callID ne '')
{
# print '<br>Call ID is ', $callID;
my $isExternal = $result->valueof('//GetActiveCallsResponse/GetActiveCallsResult/ShoreTelCallStateInfo/callInfo/isExternal/');
my $isInbound = $result->valueof('//GetActiveCallsResponse/GetActiveCallsResult/ShoreTelCallStateInfo/callInfo/isInbound/');
my $callReason = $result->valueof('//GetActiveCallsResponse/GetActiveCallsResult/ShoreTelCallStateInfo/callInfo/callReason/');
my $connectedID = $result->valueof('//GetActiveCallsResponse/GetActiveCallsResult/ShoreTelCallStateInfo/callInfo/connectedID/');
my $connectedIDName = $result->valueof('//GetActiveCallsResponse/GetActiveCallsResult/ShoreTelCallStateInfo/callInfo/connectedIDName/');
my $callerID = $result->valueof('//GetActiveCallsResponse/GetActiveCallsResult/ShoreTelCallStateInfo/callInfo/callerID/');
my $callerIDName = $result->valueof('//GetActiveCallsResponse/GetActiveCallsResult/ShoreTelCallStateInfo/callInfo/callerIDName/');
my $calledID = $result->valueof('//GetActiveCallsResponse/GetActiveCallsResult/ShoreTelCallStateInfo/callInfo/calledID/');
my $calledIDName = $result->valueof('//GetActiveCallsResponse/GetActiveCallsResult/ShoreTelCallStateInfo/callInfo/calledIDName/');
my $callState = $result->valueof('//GetActiveCallsResponse/GetActiveCallsResult/ShoreTelCallStateInfo/callState/');
my $callStateDetail = $result->valueof('//GetActiveCallsResponse/GetActiveCallsResult/ShoreTelCallStateInfo/callStateDetail/');
# Print call information.
print <<EndOfCallInfo;
HTML CODE
EndOfCallInfo
}
else
{
print <<EndOfCallInfo2;
HTML CODE
EndOfCallInfo2
}
}
}
But I am only able to access the first result in the multidimensional array.
I have tried looping through the results using
for my $t ($result->result({ShoreTelCallStateInfo}{callInfo}')) {
print $t->{callerID} . "\n";}
But I am getting absolutely no results. It appears that the the loop is not even entered.
The following code I have works fine, but only pulls the first caller ID, in this case 1955.
my $callerID = $result->valueof('//GetActiveCallsResponse/GetActiveCallsResult/ShoreTelCallStateInfo/callInfo/callerID/');
What can I do to make my loop work?
So that you can see what I am receiving from the server I have included the response from the SOAP Server using DUMP :
$VAR1 = { 'ShoreTelCallStateInfo' => [
{ 'callStateDetail' => 'Active',
'callState' => 'OnHold',
'callInfo' =>
{ 'callerIDName' => 'Joel LASTNAME',
'callID' => '69105', 'lineID' => '3947',
'connectedIDName' => 'VM-Forward',
'calledID' => '2105',
'callerID' => '1955',
'isInbound' => 'false',
'calledIDName' => 'VM-Forward',
'callReason' => 'None',
'callUniqueID' => '1369702515',
'connectedID' => '2105',
'isExternal' => 'false',
'callGUID' => '{00030000-66C2-537E-3FD8-0010492377D9}'
}
},
{ 'callStateDetail' => 'Active',
'callState' => 'Connected',
'callInfo' =>
{ 'callerIDName' => 'LASTNAME Joel ',
'callID' => '71649',
'lineID' => '3947',
'connectedIDName' => 'LASTNAME Joel ',
'calledID' => '1955',
'callerID' => '+1385#######',
'isInbound' => 'true',
'calledIDName' => 'Joel LASTNAME',
'callReason' => 'None',
'callUniqueID' => '1117287558',
'connectedID' => '+1385#######',
'isExternal' => 'true',
'callGUID' => '{00030000-66C5-537E-3FD8-0010492377D9}'
}
}
]
};
Just a guess...
The following code I have works fine, but only pulls the first caller
ID, in this case 1955.
my $callerID = $result->valueof('//GetActiveCallsResponse/GetActiveCallsResult/ShoreTelCallStateInfo/callInfo/callerID/');
What can I do to make my loop work?
SOAP::Lite docs say:
valueof()
Returns the value of a (previously) matched node. It accepts a node
path. In this case, it returns the value of matched node, but does not
change the current node. Suitable when you want to match a node and
then navigate through node children:
$som->match('/Envelope/Body/[1]'); # match method
$som->valueof('[1]'); # result
$som->valueof('[2]'); # first out parameter (if present)
The returned value depends on the context. In a scalar context it will
return the first element from matched nodeset. In an array context it
will return all matched elements.
Does this give the behavior you expect? It imposes list context on the valueof method.
for my $callerID ($result->valueof('//GetActiveCallsResponse/GetActiveCallsResult/ShoreTelCallStateInfo/callInfo/callerID/')) {
...
# do something with each callerID
}
or
my #callerIDs = $result->valueof('//GetActiveCallsResponse/GetActiveCallsResult/ShoreTelCallStateInfo/callInfo/callerID/');