วันพุธที่ 8 สิงหาคม พ.ศ. 2550

HACK#54 NoXML อีกทางเลือกหนึ่งสำหรับใช้แทน SOAP::Lite

NoXML เป็นทางเลือกในการใช้แทน SOAP::Lite ซึ่งใช้เพียง regular expression ทำงาน โดยไม่ต้องมี XML parser แต่อย่างใด

NoXML เป็นอีกโมดูลหนึ่ง ที่ใช้แทน SOAP::Lite ได้ ซึ่ง NOXML นี้จะใช้เพียง regular expression ทำงาน โดยปราศจาก XML parser ทุกชนิด ดังที่ชื่อของโมดูลนี้ได้แนะนำไว้

ถ้าหากคุณมีเพียงความรู้พื้นฐานในการติดตั้ง Perl เพื่อใช้งานของคุณเองเท่านั้น รวมทั้งไม่มีทั้ง SOAP::Lite [Hack #52] และ XML::Parser แล้วล่ะก็ NoXML นับเป็นทางเลือกที่ดีทีเดียวสำหรับการแฮ็กเกือบจะทั้งหมดในหนังสือเล่มนี้

  • Tip: ผู้เชี่ยวชาญ XML บางท่านยืนยันว่า ไม่มีอะไรมาแทนที่ XML parser จริงๆได้ ซึ่งเป็นสิ่งที่ถูกต้องอย่างยิ่ง เพราะยังมีประเด็นเรื่องการ Encode และ Hierarchy ที่ regular expression-based parser ไม่สามารถทำได้ แต่สำหรับตัว NOXML นั้นก็ยังเป็นตัวที่ง่ายที่สุดสำหรับการใช้งานและติดตั้ง
NoXML สามารถใช้แทน SOAP::Lite โดยการแก้ไขสคริปต์ที่ใช้ในการแฮ็กเพียงเล็กน้อยเท่านั้น

โค้ดของ NoXML

ไฟล์สำคัญในการแฮ็กในหัวข้อนี้ก็คือ NoXML.pm ซึ่งควรจะบันทึกลงในไดเรกทอรีเดียวกันกับสคริปต์ที่ใช้ในการแฮ็ก


# NoXML.pm

# NoXML [pronounced "no xml"] is a dire-need drop-in

# replacement for SOAP::Lite designed for Google Web API hacking.

package NoXML;

use strict;

no strict "refs";

# LWP for making HTTP requests, XML for parsing Google SOAP

use LWP::UserAgent;

use XML::Simple;

# Create a new NoXML

sub new {

my $self = {};

bless($self);

return $self;

}

# Replacement for the SOAP::Lite-based doGoogleSearch method

sub doGoogleSearch {

my($self, %args);

($self, @args{qw/ key q start maxResults filter restrict

safeSearch lr ie oe /}) = @_;

# grab SOAP request from _ _DATA_ _

my $tell = tell(DATA);

my $soap_request = join '', ;

seek(DATA, $tell, 0);

$soap_request =~ s/\$(\w+)/$args{$1}/ge; #interpolate variables

# Make (POST) a SOAP-based request to Google

my $ua = LWP::UserAgent->new;

my $req = HTTP::Request->new(POST => 'http://api.google.com/search/beta2');

$req->content_type('text/xml');

$req->content($soap_request);

my $res = $ua->request($req);

my $soap_response = $res->as_string;

# Drop the HTTP headers and so forth until the initial xml element

$soap_response =~ s/^.+?(<\?xml)/$1/migs;

# Drop element namespaces for tolerance of future prefix changes

$soap_response =~ s!(<\/?)[\w-]+?:([\w-]+?)!$1$2!g;

# Set up a return dataset

my $return;

# Unescape escaped HTML in the resultset

my %unescape = ('<'=>'<', '>'=>'>', '&'=>'&amp;amp;', '"'=>'"', '''=>"'");

my $unescape_re = join '|' => keys %unescape;

# Divide the SOAP response into the results and other metadata

my($before, $results, $after) = $soap_response =~

m#(^.+)(.+?)(.+$)#migs ;

my $before_and_after = $before . $after;

# Glean as much metadata as possible (while being somewhat lazy ;-)

while ($before_and_after =~ m#([^<]*?)<#migs) {

$return->{$1} = $3; # pack the metadata into the return dataset

}

# Glean the results

my @results;

while ($results =~ m#(.+?)#migs) {

my $item = $1;

my $pairs = {};

while ( $item =~ m#([^<]*)#migs ) {

my($element, $value) = ($1, $2);

$value =~ s/($unescape_re)/$unescape{$1}/g;

$pairs->{$element} = $value;

}

push @results, $pairs;

}

# Pack the results into the return dataset

$return->{resultElements} = \@results;

# Return nice, clean, usable results

return $return;

}

1;

# This is the SOAP message template sent to api.google.com. Variables

# signified with $variablename are replaced by the values of their

# counterparts sent to the doGoogleSearch subroutine.

_ _DATA_ _

<?xml version='1.0' encoding='UTF-8'?>

<SOAP-ENV:Envelope

xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"

xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"

xmlns:xsd="http://www.w3.org/1999/XMLSchema">

<SOAP-ENV:Body>

<ns1:doGoogleSearch xmlns:ns1="urn:GoogleSearch"

SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">

<key xsi:type="xsd:string">$key</key>

<q xsi:type="xsd:string">$q</q>

<start xsi:type="xsd:int">$start</start>

<maxResults xsi:type="xsd:int">$maxResults</maxResults>

<filter xsi:type="xsd:boolean">$filter</filter>

<restrict xsi:type="xsd:string">$restrict</restrict>

<safeSearch xsi:type="xsd:boolean">$safeSearch</safeSearch>

<lr xsi:type="xsd:string">$lr</lr>

<ie xsi:type="xsd:string">$ie</ie>

<oe xsi:type="xsd:string">$oe</oe>

</ns1:doGoogleSearch>

</SOAP-ENV:Body>

</SOAP-ENV:Envelope>


ข้างล่างนี้เป็นตัวอย่างสคริปต์ NoXML ในแง่การใช้งาน ซึ่งก็ไม่แตกต่างอะไรไปจากการแฮ็กในหัวข้ออื่นๆในหนังสือเล่มนี้ จะมีก็เพียงแต่การเน้นข้อความเป็นตัวหนา (bold) ตรงส่วนที่มีการ comment บรรทัดเดิมที่ใช้กับ SOAP::Lite และแทรกบรรทัดใหม่ เพื่อให้ใช้กับ PoXML ได้เท่านั้น

#!/usr/bin/perl

# noxml_google2csv.pl

# Google Web Search Results via NoXML ("no xml") module

# exported to CSV suitable for import into Excel

# Usage: noxml_google2csv.pl "{query}" [> results.csv]

# Your Google API developer's key

my $google_key='insert key here';

use strict;

# use SOAP::Lite;

use NoXML;

$ARGV[0]

or die qq{usage: perl noxml_search2csv.pl "{query}"\n};

# my $google_search = SOAP::Lite->service("file:$google_wdsl");

my $google_search = new NoXML;

my $results = $google_search ->

doGoogleSearch(

$google_key, shift @ARGV, 0, 10, "false",

"", "false", "", "latin1", "latin1"

);

@{$results->{'resultElements'}} or die('No results');

print qq{"title","url","snippet"\n};

foreach (@{$results->{'resultElements'}}) {

$_->{title} =~ s!"!""!g; # double escape " marks

$_->{snippet} =~ s!"!""!g;

my $output = qq{"$_->{title}","$_->{URL}","$_->{snippet}"\n};

$output =~ s!<.+?>!!g; # drop all HTML tags

print $output;

}

Running the Hack

รันสคริปต์นี้ที่ command line โดยการใส่คำถามลงไปที่ command line และระบุให้แสดงผลลัพธ์ในไฟล์ CSV ที่คุณต้องการสร้างขึ้นมา หรือที่ต้องการให้นำผลลัพธ์ไปต่อท้าย (appending) ตัวอย่างเช่นข้างล่างนี้ใช้ “no xml” เป็นคำถาม และกำหนดให้ไฟล์ results.csv เป็นไฟล์รับผลลัพธ์ที่ได้กลับคืนมา

$ perl noxml_google2csv.pl "no xml" > results.csv

หรือจะตัดส่วนของ > และ results.csv ออก เพื่อส่งผลลัพธ์ให้ไปแสดงที่หน้าจอเพื่อตรวจสอบก่อนก็ได้ และเห็นผลลัพธ์ทันที

ผลลัพธ์

% perl noxml_google2csv.pl "no xml"

"title","url","snippet"

"site-comments@w3.org from January 2002: No XML specifications",

"http://lists.w3.org/Archives/Public/site-comments/2002Jan/0015.html",

"No XML specifications. From: Prof. ... Next message: Ian B. Jacobs:

"Re: No XML specifications"; Previous message: Rob Cummings:

"Website design..."; ... "

...

"Re: [xml] XPath with no XML Doc",

"http://mail.gnome.org/archives/xml/2002-March/msg00194.html",

" ... Re: [xml] XPath with no XML Doc. From: "Richard Jinks"

; To: ; Subject:

Re: [xml] XPath with no XML Doc; ... "

การใช้งานและข้อจำกัด

ด้วยวิธีเดียวกันนี้ คุณสามารถดัดแปลงวิธีการแฮ็กด้วย SOAP::Lite ที่ได้อธิบายไว้ในหัวข้อต่างๆ ตลอดหนังสือเล่มนี้ มาเป็นการใช้ PoXML ในการแฮ็กแทนก็ได้ โดยมีข้อกำหนดดังนี้

  1. วางไฟล์ NoXML.pm ไว้ในไดเรกทอรีเดียวกันกับสคริปต์ของเรื่องที่จะแฮ็ก
  2. แทนที่บรรทัดในสคริปต์ที่เป็น use SOAP::Lite; ด้วย use NoXML;
  3. แทนที่ my $google_search = SOAP::Lite->service(“file:$google_wdsl”); ด้วย my $google_search = new NoXML;

อย่างไรก็ตาม ยังมีข้อจำกัดในการใช้งานอยู่บ้าง ในขณะที่ NoXML ทำงานได้ดีในการดึงผลลัพธ์รวมทั้งสรุปผลลัพธ์ที่ได้จากการค้นหา แต่ก็ไม่ประสบผลสำเร็จในด้านการรวบรวมผลลัพธ์ในขั้นละเอียดขึ้น (advanced result) บางอย่าง เช่น <directoryCategories> เป็นต้น

ดูเพิ่มเติม

PoXML [Hack #53] เป็นทางเลือกในการใช้ plain old XML แทน SOAP::Lite

XooMLE [Hack #36] เป็น third-party service ซึ่งเป็นตัวกลาในการ interface ระหว่าง Plain Old XML กับ Google Web API

0 ความคิดเห็น: