Fetch Ticket message body using 'TestSimple' invoker of 'ArticleCreate' trigger - perl

I'm using invoker TestSimple.pm of trigger 'ArticleCreate'. (Ideally should've created a new invoker!) I'm able to receive the json data of the ticket correctly via REST web service.
I want an additional ticket attribute with this-> 'ticket body'. How can i modify the invoker (perl file) so that it also sends ticket body in the json data? Or is their any other trigger that can give me this out-of-the-box?
I went through OTRS manual and getting confused again-n-again :(
So, if someone can give me proper link that will also do!
System details:
1. OTRS v4.0
2. Apache web server
[Edit] Added the TestSimple.pm file.
This is the link I referred.
#copyright (C) 2001-2014 OTRS AG, http://otrs.com/
# --
# This software comes with ABSOLUTELY NO WARRANTY. For details, see
# the enclosed file COPYING for license information (AGPL). If you
# did not receive this file, see http://www.gnu.org/licenses/agpl.txt.
# --
package Kernel::GenericInterface::Invoker::Test::TestSimple;
use strict;
use warnings;
use Kernel::System::VariableCheck qw(IsString IsStringWithData);
use Kernel::System::Ticket;
our $ObjectManagerDisabled = 1;
=head1 NAME
Kernel::GenericInterface::Invoker::Test::Test - GenericInterface test Invoker backend
=head1 SYNOPSIS
=head1 PUBLIC INTERFACE
=over 4
=cut
=item new()
usually, you want to create an instance of this
by using Kernel::GenericInterface::Invoker->new();
=cut
sub new {
my ( $Type, %Param ) = #_;
# allocate new hash for object
my $Self = {};
bless( $Self, $Type );
# check needed params
if ( !$Param{DebuggerObject} ) {
return {
Success => 0,
ErrorMessage => "Got no DebuggerObject!"
};
}
$Self->{DebuggerObject} = $Param{DebuggerObject};
$Self->{TicketObject} = Kernel::System::Ticket->new(%Param);
return $Self;
}
=item PrepareRequest()
prepare the invocation of the configured remote webservice.
This will just return the data that was passed to the function.
my $Result = $InvokerObject->PrepareRequest(
Data => { # data payload
...
},
);
my %Article = $TicketObject->ArticleGet(
ArticleID => $Param{Data}->{ArticleID},
UserID => $Param{Data}->{CustomerID},
);
$Result = {
Success => 1, # 0 or 1
ErrorMessage => '', # in case of error
Data => { # data payload after Invoker
...
},
};
=cut
sub PrepareRequest {
my ( $Self, %Param ) = #_;
my %TicketInfo = $Self->{TicketObject}->ArticleGet(
ArticleID => $Param{Data}->{ArticleID},
userID => $Param{Data}->{CustomerID},
);
return {
Success => 1,
Data => $TicketInfo{Body},
};
}
=item HandleResponse()
handle response data of the configured remote webservice.
This will just return the data that was passed to the function.
my $Result = $InvokerObject->HandleResponse(
#my $Result = $TicketBodyObject->HandleResponse(
ResponseSuccess => 1, # success status of the remote webservice
ResponseErrorMessage => '', # in case of webservice error
Data => { # data payload
...
},
);
$Result = {
Success => 1, # 0 or 1
ErrorMessage => '', # in case of error
Data => { # data payload after Invoker
%Article
},
};
=cut
sub HandleResponse {
my ( $Self, %Param ) = #_;
# if there was an error in the response, forward it
if ( !$Param{ResponseSuccess} ) {
return {
Success => 0,
ErrorMessage => $Param{ResponseErrorMessage},
};
}
return {
Success => 1,
Data => $Param{Data},
};
}
1;
=back
=head1 TERMS AND CONDITIONS
This software is part of the OTRS project (L<http://otrs.org/>).
This software comes with ABSOLUTELY NO WARRANTY. For details, see
the enclosed file COPYING for license information (AGPL). If you
did not receive this file, see L<http://www.gnu.org/licenses/agpl.txt>.
=cut

PrepareRequest only knows about the TicketID and ArticleID it is operating on.
You'll need to look up the data you need to pass in the PrepareRequest() sub of your Invoker, and then return it in the Data parameter.
Check the code below for an example how to add the article body:
sub PrepareRequest {
my ( $Self, %Param ) = #_;
my %Article = $Kernel::OM->Get('Kernel::System::Ticket')->ArticleGet(
ArticleID => $Param{Data}{ArticleID},
UserID => 1,
);
return {
Success => 1,
Data => {
TicketID => $Param{Data}{TicketID},
ArticleID => $Param{Data}{ArticleID},
Body => $Article{Body},
},
};
}

Related

How come with Catalyst::Controller::REST I get an error about content-type

How come with Catalyst::Controller::REST I keep getting this in dialog
[info] Could not find a serializer for an empty content-type
And, this to the browser
Cannot find a Content-Type supported by your client.
I have this line in my package..
__PACKAGE__->config(default => 'text/yaml');
I also have installed YAML::Syck and URI::Find per the docs
package Sampcat::Controller::Client::Holiday;
__PACKAGE__->config(default => 'text/yaml');
BEGIN { extends 'Catalyst::Controller::REST' }
sub holiday :Chained('../client') :Args :ActionClass('REST') {}
sub holiday_GET {
my ( $self, $c ) = #_;
$self->status_ok( $c, entity => {
'foobar' => 1
} );
$c->stash->{body} = "foo";
}
See this bug on RT for more information
;tldr.. Change
__PACKAGE__->config(default => 'text/yaml');
to
__PACKAGE__->config(default => 'text/x-json');
for the time being.

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/');

How do I use the Adapative Payments "ConvertCurrency" API in Perl?

How can you convert from multiple currencies, using the PayPal API (Adaptive Payments) system? The documents only have stuff for Ruby, iOS, PHP, Rails etc... but not Perl!
https://developer.paypal.com/docs/classic/api/adaptive-payments/ConvertCurrency_API_Operation/
This is only meant as a guideline (to run in command line). It will run via the browser, but you need to add in a header (otherwise it'll give a 500 Internal Server Error)
The perl code is as follows:
currency.cgi
#!/usr/bin/perl
use warnings;
use strict;
use HTTP::Request::Common;
use LWP::UserAgent;
my $user = 'your PayPal API username';
my $password = 'your PayPal API password';
my $signature = 'your PayPal API signature';
my $application_id = 'APP-80W284485P519543T'; # this is the sandbox app ID... so you would just change this to your own app-id when going live
my #currencies = qw/GBP EUR CHF USD AUD/; # Enter all the currency codes you want to convert here
my $url = 'https://svcs.sandbox.paypal.com/AdaptivePayments/ConvertCurrency'; # remove the "sandbox." part of this URL, when its ready to go live...
my $ua = LWP::UserAgent->new();
my $headers = HTTP::Headers->new(
'X-PAYPAL-SECURITY-USERID' => $user,
'X-PAYPAL-SECURITY-PASSWORD' => $password,
'X-PAYPAL-SECURITY-SIGNATURE' => $signature,
'X-PAYPAL-APPLICATION-ID' => $application_id,
'X-PAYPAL-DEVICE-IPADDRESS' => $ENV{REMOTE_ADDR},
'X-PAYPAL-REQUEST-DATA-FORMAT' => 'JSON',
'X-PAYPAL-RESPONSE-DATA-FORMAT' => 'JSON'
);
foreach (#currencies) {
print qq|\nGetting exchange rates for $_.... \n|;
my ($status,$vals) = get_converted_amounts($_);
if ($vals->{error}) {
print qq|There was an error: $vals->{error}\n|;
exit;
} else {
print qq|Got conversion rates of:\n|;
foreach (#currencies) {
if ($vals->{$_}) {
print qq|\t$_ => $vals->{$_}\n|;
}
}
}
}
sub get_converted_amounts {
my ($currency_from) = #_;
my #currencies_to_grab;
foreach (#currencies) {
next if $_ eq $currency_from; # We dont wanna bother asking it to convert from this currency, into this currency =)
push #currencies_to_grab, $_;
}
my $json_var = {
requestEnvelope => {
detailLevel => "ReturnAll",
errorLanguage => "en_US",
},
baseAmountList => [{ 'currency' => { 'code' => $currency_from, 'amount' => 1 } }],
convertToCurrencyList => [{ currencyCode => \#currencies_to_grab }]
};
use JSON;
my $new_json = JSON::to_json($json_var);
my $request = HTTP::Request->new( 'POST', $url, $headers, $new_json );
my $response = $ua->request( $request );
my $json_returned = decode_json($response->decoded_content);
if ($json_returned->{error}[0]) {
return (0, { error => "There was an error: $json_returned->{error}[0]->{message} ($json_returned->{error}[0]->{errorId}) " });
}
my $vals;
foreach (#{$json_returned->{estimatedAmountTable}->{currencyConversionList}[0]->{currencyList}->{currency}}) {
$vals->{$_->{code}} = $_->{amount};
}
return (1,$vals);
}
Running it:
You would simply run it via SSH/Telnet, with:
perl /path/to/script/currency.cgi
You can play around with the currency codes (be sure to only use the currencyCode values found here: https://developer.paypal.com/docs/classic/api/adaptive-payments/ConvertCurrency_API_Operation/, as these are the only ones that are supported)
Although this converts from a given currency into the other related currencies (good if you want to run the script every couple of hours, and store the conversion rates) - it wouldn't be hard to tweak it so you can do:
convert(from_currency,to_currency,amount)
Hopefully this will save someone a bit of time (as I spent almost a day trying to get this going)

How do I add more than one over method to a mojolicious route?

I have the following code:
$r->find('user')->via('post')->over(authenticated => 1);
Given that route I can get to the user route passing through the authenticated check
that is setup using Mojolicious::Plugin::Authentication.
I want add another 'over' to that route.
$r->find('user')->via('post')->over(authenticated => 1)->over(access => 1);
That appears to override authenticated 'over' though.
I tried breaking up the routes with names like:
my $auth = $r->route('/')->over(authenticated => 1)
->name('Authenticated Route');
$access = $auth->route('/user')->over(access => 1)->name('USER_ACCESS');
That didn't work at all though. Neither of the 'over's are being accessed.
My routes are things like /user, /item, set up using MojoX::JSON::RPC::Service.
So, I don't have things like /user/:id to set up sub routes.( not sure that matters )
All routes are like /user, sent with parameters.
I've got a condition like:
$r->add_condition(
access => sub {
# do some stuff
},
);
that is the 'access' in $r->route('/user')->over(access => 1);
In short, the routes work fine when using:
$r->find('user')->via('post')->over(authenticated => 1);
But I'm unable to add a 2nd route.
So, what am I missing in setting up these routes with multiple conditions?
Is it possible to add multiple conditions to a single route /route_name?
You can just use both conditions in over like in this test:
use Mojolicious::Lite;
# dummy conditions storing their name and argument in the stash
for my $name (qw(foo bar)) {
app->routes->add_condition($name => sub {
my ($route, $controller, $to, #args) = #_;
$controller->stash($name => $args[0]);
});
}
# simple foo and bar dump action
sub dump {
my $self = shift;
$self->render_text(join ' ' => map {$self->stash($_)} qw(foo bar));
}
# traditional route with multiple 'over'
app->routes->get('/frst')->over(foo => 'yo', bar => 'works')->to(cb => \&dump);
# lite route with multiple 'over'
get '/scnd' => (foo => 'hey', bar => 'cool') => \&dump;
# test the lite app above
use Test::More tests => 4;
use Test::Mojo;
my $t = Test::Mojo->new;
# test first route
$t->get_ok('/frst')->content_is('yo works');
$t->get_ok('/scnd')->content_is('hey cool');
__END__
1..4
ok 1 - get /frst
ok 2 - exact match for content
ok 3 - get /scnd
ok 4 - exact match for content
Works fine here with Mojolicious 3.38 on perl 5.12.1 - #DavidO is right, maybe bridges can do the job better. :)
In my case I use two under methods:
$r = $app->routes;
$guest = $r->under->to( 'auth#check_level' );
$user = $r->under->to( 'auth#check_level', { required_level => 100 } );
$admin = $r->under->to( 'auth#check_level', { required_level => 200 } );
$guest->get( '/' )->to( 'main#index' );
$user->get( '/user' )->to( 'user#show' );
$super_admin = $admin->under->to( 'manage#check_level', { super_admin => 100 } );
$super_admin->get( '/delete_everything' )->to( 'system#shutdown' );
In this example when any of routes match some under will be called
'/' -> auth#check_level -> main_index
'/user' -> auth#check_level { required_level => 100 } -> 'user#show'
'/delete_everything' -> auth#check_level { required_level => 200 } -> 'manage#check_level', { super_admin => 100 } -> 'system#shutdown'
As you can see before target action in your controller will be run another action called: auth#check_level and manage#check_level
In each those extra actions you just compare stash->{ required_level } with session->{ required_level } you have set when authorize user
package YourApp::Controller::Manage;
sub check_level {
my $self = shift;
my $user_have = $self->session->{ required_level };
my $we_require = $self->stash->{ required_level };
# 'system#shutdown' will be called if user has required level
return 1 if $user_have >= $we_require;
$self->redirect_to( '/you_have_no_access_rights' );
return 0; #This route will not match. 'system#shutdown' will not be called
}
PS Of course I may use cb or just CODEREF which are "close same" to controller action:
$r->under({ cb => \&YourApp::Controller::auth::check_level });
$r->under( \&YourApp::Controller::auth::check_level ); # "same"
But I prefer ->to( 'controller#action' ) syntax. It looks much better
What if we use this approach?
# register condition
$r->add_condition(
chain => sub {
my ($route, $controller, $captures, $checkers) = #_;
for my $checker (#$checkers) {
return 0 unless $checker->($route, $controller, $captures);
}
return 1;
},
);
# ...
# example of using
$r->get('/')->over(chain => [\&checker1, \&checker2])->to(cb => \&foo)->name('bar');

Web::Scraper and Perl

I have the following script that scrapes my schools CS department to get a list of all the courses. I want to be able to extract the CRN (course number) and other important information to put into a database which I can let users browse through a web app.
Here is an example URL:
http://courses.illinois.edu/cis/2011/spring/schedule/CS/411.html
I would like to extract info from pages like this. The first level of the scraper just constructs the individual sites from a list of all of the courses. Once I'm at a course specific catalog page, I use the second scraper to attempt to get all of this info i want. For some reason, although the CRN's and Course Instructors are all 'td' elements. My scraper seems to be returning nothing when scraping. I tried to scrape specifically for 'div' instead and I get a bunch of info for each relevant page. So somehow I'm failing to get the 'td' element, but I'm scraping from the right page.
my $tweets = scraper {
# Parse all LIs with the class "status", store them into a resulting
# array 'tweets'. We embed another scraper for each tweet.
# process "h4.ws-ds-name.detail-title", "array[]" => 'TEXT';
process "div.ws-row", "array[]" => 'TEXT';
};
my $res = $tweets->scrape( URI- >new("http://courses.illinois.edu/cis/2011/spring/schedule/CS/index.html?skinId=2169") );
foreach my $elem (#{$res->{array}}){
my $coursenum = substr($elem,2,4);
my $secondLevel = scraper{
process "td.ws-row", "array2[]" => 'TEXT';
};
my $res2 = $secondLevel->scrape(URI- >new("http://courses.illinois.edu/cis/2011/spring/schedule/CS/$coursenum.html"));
my $num = #{$res2->{array2}};
print $num;
print "---------------------", "\n";
my #curr = #{$res2->{array2}};
foreach my $elem2 (#curr){
$num++;
print $elem2, " ", "\n";
}
print "---------------------", "\n";
}
Any ideas?
Thanks
Looks to me like
my $coursenum = substr($elem,2,4)
should be
my $coursenum = substr($elem,3,3)
The easiest way to go in this case is use
HTML::TableExtract
In case you are looking for data from the table only.
I played a bit with your problem. You can get course id, title and link to individual course page within initial scraper:
my $courses = scraper {
process 'div.ws-row',
'course[]' => scraper {
process 'div.ws-course-number', 'id' => 'TEXT';
process 'div.ws-course-title', 'title' => 'TEXT';
process 'div.ws-course-title a', 'link' => '#href';
};
result 'course';
};
The result of scraping is arrayref with hashrefs like this:
{ id => "CS 103",
title => "Introduction to Programming",
link => bless(do{\(my $o = "http://courses.illinois.edu/cis/2011/spring/schedule/CS/103.html?skinId=2169")}, "URI::http"),
},
....
Then you can do additional scraping for each course from their individual pages and add such information into original structure:
for my $course (#$res) {
my $crs_scraper = scraper {
process 'div.ws-description', 'desc' => 'TEXT';
# ... add more items here
};
my $additional_data = $crs_scraper->scrape(URI->new($course->{link}));
# slice assignment to add them into course definition
#{$course}{ keys %$additional_data } = values %$additional_data;
}
Source combined together is as follows:
use strict; use warnings;
use URI;
use Web::Scraper;
use Data::Dump qw(dump);
my $url = 'http://courses.illinois.edu/cis/2011/spring/schedule/CS/index.html?skinId=2169';
my $courses = scraper {
process 'div.ws-row',
'course[]' => scraper {
process 'div.ws-course-number', 'id' => 'TEXT';
process 'div.ws-course-title', 'title' => 'TEXT';
process 'div.ws-course-title a', 'link' => '#href';
};
result 'course';
};
my $res = $courses->scrape(URI->new($url));
for my $course (#$res) {
my $crs_scraper = scraper {
process 'div.ws-description', 'desc' => 'TEXT';
# ... add more items here
};
my $additional_data = $crs_scraper->scrape(URI->new($course->{link}));
# slice assignment to add them into course definition
#{$course}{ keys %$additional_data } = values %$additional_data;
}
dump $res;