evaluate condition assigned to a variable - perl

i am using module Excel::Writer::XLSX and i need to evaluate this condition to check if the sheet was created or not (i need to only execute certain actions if the sheet already exists)
my $sheet = $workbook->add_worksheet($filters{$filter}{description});
how can i do that please ?
knowing that if the sheet already exists, the add_worksheet method "dies" with an error like :
Worksheet name 'All Users', with case ignored, is already used. at ./xxxx.pl line 203.
naturlly, doing something like this doesn't work as i believe it evaluates the variable assignment :
if (my $sheet = $workbook->add_worksheet($filters{$filter}{description});) {
# good, sheet doesn't already exists and was created
# i can place my code here to insert header rows etc./ colors
} else {
# program fires a message like :
# Worksheet name 'XXXXXX', with case ignored, is already used. at XXX
# no need to place code for column headers etc., sheet already exists
}

You could use
my $name = $filters{$filter}{description};
my $sheet =
$workbook->get_worksheet_by_name( $name ) ||
$workbook->add_worksheet( $name );

nvm, i used a hash and i'm checking the hash
if (!$sheets{$filter}) {
$sheets{$filter} = $workbook->add_worksheet($filters{$filter}{description});
works nicely

Related

How would I match the correct hash pair based on a specific string?

I have a simple page hit tracking script that allows for the output to display friendly names instead of urls by using a hash.
UPDATE: I used php to generate the hash below, but used the wrong dynamic page name of item.html. When changed to the correct name, the script returns the desired results. Sorry for wasting anyone's time.
my %LocalAddressTitlePairs = (
'https://www.mywebsite.com/index.html' => 'HOME',
'https://www.mywebsite.com/art_gallery.html' => 'GALLERY',
'https://www.mywebsite.com/cart/item.html?itemID=83&cat=26' => 'Island Life',
'https://www.mywebsite.com/cart/item.html?itemID=11&cat=22' => 'Castaways',
'https://www.mywebsite.com/cart/item.html?itemID=13&cat=29' => 'Pelicans',
and so on..
);
The code for returning the page hits:
sub url_format {
local $_ = $_[0] || '';
if ((m!$PREF{'My_Web_Address'}!i) and (m!^https://(.*)!i) ) {
if ($UseLocalAddressTitlePairs == 1) {
foreach my $Address (keys %LocalAddressTitlePairs) {
return "<a title=\"$Address\" href=\"$_\">$LocalAddressTitlePairs{$Address}</A>" if (m!$_$! eq m!$Address$!);
}
}
my $stub =$1;
return $stub;
}
}
Displaying the log hits will show
HOME with the correct link, GALLERY with the correct url link, but https://www.mywebsite.com/cart/item.html?itemID=83&cat=26
will display a random name instead of what it should be, Island Life for this page.. it has the correct link,-- a different name displays every time the page is loaded.
And, the output for all pages with query strings will display the exact same name. I know the links are correct by clicking thru site pages and checking the log script for my own page visits.
I tried -
while (my($mykey, $Value) = each %LocalAddressTitlePairs) {
return "<a title=\"$mykey\" href=\"$_\">$Value</a>" if(m!$_$! eq m!$mykey$!);
but again, the link is correct but the mykey/Value associated is random too. Way too new to perl to figure this out but I'm doing a lot of online research.
m!$Address$! does not work as expected, because the expression contains special characters such as ?
You need to add escape sequences \Q and \E
m!\Q$Address\E$!
it’s even better to add a check at the beginning of the line, otherwise
my $url = "https://www.mywebsite.com/?foo=bar"
my $bad_url = "https://bad.com?u=https://www.mywebsite.com/?foo=bar"
$bad_url =~ m!\Q$url\E$! ? 1 : 0 # 1, pass
$bad_url =~ m!^\Q$url\E$! ? 1 : 0 # 0, fail

How do I add this value to an Array and it stays in the script

I want to add a command that adds numbers to the array.
This is what i have exactly:
my $ownerids = ('374867065');
Then later in the script i have this:
if($ownerids == $spl2[0]){
if (index($message, "!adduser") != -1) {
$msg = $spl[1];
$send = "<m t=\"User Added $msg\" u=\"$botid\" />\0";
$socket->send($send);
push (my $ownerids, "$msg");
}
}
I am on a chatbox and this is a chatbot, i want to make it when i say !adduser (thereid) it adds them to a list and they can use the bot commands, and also i want a Delete User, If you can help this will be MUCH appretiated.
If you want ownerids to be an array, then you must prefix it with a #
my #ownerids = ('374867065');
Then to add an element, you can push
push #ownerids, "$msg";
However, you're going to need to fix your other references to #ownerids so it's treated like an array. For example, your first if looks like it's intending to see if $spl2[0] is an owner. If that's the case, then you'll need to grep the array:
if(grep {$_ == $spl2[0]} #ownerids) {

Unable to Name Excel Tabs when created in a function

My Goal:
Create a new tab and name it within a function.
My Issue:
When I create the new tab inside of the function I cannot name the tab.
Notes:
When I create the tab outside of the function it gets named correctly.
My Code:
Function Function_CreateWorksheets($Worksheets) {
ForEach ($Worksheet in $Worksheets) {
$Excel_Count_Worksheet++
If ($Excel_Count_Worksheet -gt 3) {$Script:Excel.Worksheets.Add() |Out-Null}
$Script:Excel.Worksheets.Item($Excel_Count_Worksheet).Name = $Worksheet
}
}
#Load-Module -Module grouppolicy -Reload
Get-Variable Excel_* |Remove-Variable -Force
#Create Excel Com Object
$Excel = New-Object -com Excel.Application
# Make the Excel Application Visible to the end user
$Excel.visible = $True
# Create a WorkBook inside the Excel application
# that we can start manipulating.
$Excel_Workbook = $Excel.Workbooks.Add()
$Action_CreateWorksheet =
#=======================================================
# Now that we have a workbook we need to create some
# additional worksheets (Tabs) beyond that initial 3
# that are created when the workbook is opened.
#=======================================================
#$Temp_MakeWorkSheet = $Excel.Worksheets.Add()
#=======================================================
# Once all the sheets are created each of the worksheets
# need to be assigned to a variable so they can be
# manipulated.
#=======================================================
Function_CreateWorksheets -Worksheets "System Summary","Test1","Test2","Test3","Test4"
The problem is that you're assuming that the new worksheet is added as the last worksheet, but by default the Add method adds the new worksheet at the beginning. The script as you have it works for up to three worksheets, but after that it adds new worksheets with default names at the beginning and keeps renaming the last worksheet. You need to add the worksheets at the end.
Change
$Script:Excel.Worksheets.Add()
to
$Script:Excel.Worksheets.Add([System.Reflection.Missing]::Value, $Script:Excel.Worksheets.Item($Script:Excel.Worksheets.Count))
How that works:
The Add method takes four arguments: Before, After, Number, and Type.
$Excel.Worksheets.Item($Excel.Worksheets.Count)) gets the object representing the last worksheet. You want to supply that as the After argument in order to add the new worksheet after the last worksheet.
[System.Reflection.Missing]::Value is a placeholder for the missing Before argument (it can't be null)
APPENDIX
As an afterthought...although it works for this specific script, I find it a little iffy to rely on the default initial configuration of three worksheets, have the function change behavior based on that assumption (rename three worksheets, then start adding more), and rely on a counter variable to determine which worksheet you're renaming rather than renaming the active sheet you just added. Since the function inherits a preexisting workbook rather than creating a new one, I think it's "cleaner" to write a function that will give you a workbook that has blank worksheets with the specified names regardless of the initial configuration.
Maybe it's just my personal philosophical inclination to write functions in ways that are more generally applicable (read: reusable) rather than ad hoc, but I prefer functions that make as few assumptions as possible. Here's how I'd do it:
function Function_CreateWorksheets {
[CmdletBinding()]
param (
[string[]] $WorkSheets,
[object] $Excel
)
for ($i = 1; $i -le $Excel.Worksheets.Count; $i++) {
$Excel.Worksheets.Item($i).Name = "DeleteMe$i"
}
foreach ($Worksheet in $Worksheets) {
$Excel.Worksheets.Add([System.Reflection.Missing]::Value,$Excel.Worksheets.Item($Excel.Worksheets.Count)) | Out-Null
$Excel.ActiveSheet.Name = $Worksheet
}
$Excel.DisplayAlerts = $false
while ($Excel.Worksheets.Count -gt $Worksheets.Count) {
$Excel.Worksheets.Item(1).Delete()
}
$Excel.DisplayAlerts = $true
}
The while loop at the end deletes all the preexisting worksheets. All new worksheets are added at the end, so the loop removes worksheets from the beginning until the number of worksheets in the workbook ($Excel.Worksheets.Count) matches the number of new worksheets ($Worksheets.Count). This needs to come at the end because a workbook has to have at least one worksheet, so you'll get an error if you try to delete all the worksheets before creating any new ones.
The initial for loop renames all the preexisting worksheets so that the function won't break if any of the new names match names that are already in use. You're going to get rid of them anyway, so you don't care what their names are.
By passing the Application object to the function as an argument, you can avoid the need to keep scoping it. You can call the function like this:
Function_CreateWorksheets -Worksheets "System Summary","Test1","Test2","Test3","Test4" -Excel $Excel
(Of course, you can leave off the parameter names -Worksheets and -Excel as long as you maintain the correct order.)
Thanks to your post I was able to get the result I needed. I did a slight variation to make the function reusable.
Function Function_CreateWorksheets {
[CmdletBinding()]
param (
[parameter(Mandatory=$true,ValueFromPipeline=$true)][object] $Excel,
[string[]] $WorkSheets
)
ForEach ($Worksheet in $Worksheets) {
$Script:Excel_Count_Worksheet++
If ($Excel_Count_Worksheet -gt $Excel.Worksheets.Count) {$Excel.Worksheets.Add([System.Reflection.Missing]::Value, $Excel.Worksheets.Item($Script:Excel.Worksheets.Count)) |Out-Null}
$Excel.Worksheets.Item($Excel_Count_Worksheet).Name = $Worksheet
}
While ($Excel.Worksheets.Count -gt $Script:Excel_Count_Worksheet) {
$Excel.Worksheets.Item($Excel.Worksheets.Count).Delete()
}
}

Is there an easy way to add/remove/modify query parameters of a URL in Tritium?

I saw a very manual way of doing this in another post: How do I add a query parameter to a URL?
This doesn't seem very intuitive, but someone there mentioned an easier way to accomplish this using the upcoming "URL scope". Is this feature out yet, and how would I use it?
If you're using the stdlib mixer, you should be able to use the URL scope which provides helper functions for adding, viewing, editing, and removing URL params. Here's a quick example:
$original_url = "http://cuteoverload.com/2013/08/01/buttless-monkey-jams?hi=there"
$new_url = url($original_url) {
log(param("hi"))
param("hello", "world")
remove_param("hi")
}
log($new_url)
Tritium Tester example here: http://tester.tritium.io/9fcda48fa81b6e0b8700ccdda9f85612a5d7442f
Almost forgot, link to docs: http://tritium.io/current (You'll want to click on the URL category).
AFAIK, there's no built-in way of doing so.
I'll post here how I did to append a query param, making sure that it does not get duplicated if already on the url:
Inside your functions/main.ts file, you can declare:
# Adds a query parameter to the URL string in scope.
# The parameter is added as the last parameter in
# the query string.
#
# Sample use:
# $("//a[#id='my_link]") {
# attribute("href") {
# value() {
# appendQueryParameter('MVWomen', '1')
# }
# }
# }
#
# That will add MVwomen=1 to the end of the query string,
# but before any hash arguments.
# It also takes care of deciding if a ? or a #
# should be used.
#func Text.appendQueryParameter(Text %param_name, Text %param_value) {
# this beautiful regex is divided in three parts:
# 1. Get anything until a ? or # is found (or we reach the end)
# 2. Get anything until a # is found (or we reach the end - can be empty)
# 3. Get the remainder (can be empty)
replace(/^([^#\?]*)(\?[^#]*)?(#.*)?$/) {
var('query_symbol', '?')
match(%2, /^\?/) {
$query_symbol = '&'
}
# first, it checks if the %param_name with this %param_value already exists
# if so, we don't do anything
match_not(%2, concat(%param_name, '=', %param_value)) {
# We concatenate the URL until ? or # (%1),
# then the query string (%2), which can be empty or not,
# then the query symbol (either ? or &),
# then the name of the parameter we are appending,
# then an equals sign,
# then the value of the parameter we are appending
# and finally the hash fragment, which can be empty or not
set(concat(%1, %2, $query_symbol, %param_name, '=', %param_value, %3))
}
}
}
The other features you want (remove, modify) can be achieved similarly (by creating a function inside functions/main.ts and leveraging some regex magic).
Hope it helps.

Why is this defined value not recognized as a package or object reference?

I have the code below:
my $content = $response->decoded_content((charset => 'UTF-8'));
my $feed = XML::Feed->parse(\$content) || $logger->error("When retrieving $URL: ", XML::Feed->errstr);
if (defined $feed) {
for my $entry ($feed->entries) {
#DO SOMETHING
}
}
For some site, XML::FEED saying that it can't detect the feed type. This is something I have to look at but this is not my question at the moment.
This sample code is inside a while loop has I'm retrieving different RSS and I would like to have the script running even when some URLs failed.
The defined function seems to not work as I get the error message:
Can't call method "entries" without a package or object reference
Can someone tell me what is the right way to handle the test?
You first have to check the value of $feed.
The error message you describe is obvious: $feed is not a package / object reference, but it can be a simple hash for instance. So it's defined.
Add my favourite debugging line right in front of if(defined):
warn Data::Dumper->new([ $feed ],[ '*feed' ])->Sortkeys(1)->Dump();use Data::Dumper;
and you'll see the value in a nice way.
Without testing I'd say that $feed contains the result of your logger, which might be 1 or 0 or something like that, because you set the value of $feed to XML::Feed->parse, and if this is not successful (undefined) it's the result of $logger->error.
You'd better write it like:
my $feed = XML::Feed->parse(\$content);
if (defined $feed) {
for my $entry ($feed->entries) {
#DO SOMETHING
}
}
else {
$logger->error("When retrieving $URL: ", XML::Feed->errstr);
}
because parse is said to return an object, and I guess it returns undef on error.
The error message means what it says: $feed is neither a package nor an object reference. It passes the defined test because there are many defined values which are neither packages nor object references.
In this particular case, you're seeing this error because you are misuing ||:
my $feed = XML::Feed->parse(\$content) || $logger->error("When retrieving $URL: ", XML::Feed->errstr);
If the parse call should fail and return undef, this evaluates to
my $feed = ( undef || $logger->error("When retrieving $URL: ", XML::Feed->errstr) );
which evaluates to
my $feed = $logger->error("When retrieving $URL: ", XML::Feed->errstr);
. The return value of $logger->error is unknown to me, but presumably it is neither a package nor an object reference. And if it were one, it probably would be the wrong one to put in a variable named $feed.
The documentation for XML::Feed mentions parsing with a construct like
my $feed = XML::Feed->parse(URI->new('http://example.com/atom.xml'))
or die XML::Feed->errstr;
This is not the same thing. Their respective precedence rules make || and or suitable for different applications; specifically, you should only use || when you want the value on the right-hand side for something. Do not use it only for the short-circuit side effect.
You can solve this by replacing the || with or to get the right evaluation order. While you are there, you probably should also eliminate the redundant defined test.