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

HACK#53 Plain Old XML

PoXML เป็นโมดูลที่สามารถใช้แทน SOAP::Lite ในกรณีที่ไม่สามารถติดตั้ง SOAP::Lite ได้

PoXML นับเป็นทางเลือกทางหนึ่งสำหรับท่านที่ไม่สามารถใช้ SOAP::Lite [Hack #52] ได้ ซึ่งอาจจะเป็นด้วยเพราะการติดตั้งที่ยุ่งยากหรืออะไรก็ตามแต่
  • Tip: ผู้รู้เกี่ยวกับ Perl ยืนยันว่าการติดตั้งโมดูลนี้มีขั้นตอนที่ง่ายมาก อย่างไรก็ตาม หลายๆคนแย้งว่า การติดตั้งไม่ได้ง่ายดายอย่างที่กล่าวเอาไว้นัก
PoXML นั้นสามารถใช้แทน SOAP::Lite ได้ในระดับที่ดีพอใช้ โดยการที่ทำงานร่วมกับ SOAP ของ Google ตามแบบ Plain Old XML โดยการใช้ LWP::UserAgent ในการสร้าง HTTP request และใช้ XML::Simple ในการประมวลผล XML แต่ส่วนที่เป็นข้อดีที่สุดก็คือ มีการแก้ไขสคริปต์เพียง 2 บรรทัดเท่านั้น เมื่อมีการใช้งานแทน SOAP::Lite ในการแฮ็กเป้าหมายต่างๆในหนังสือเล่มนี้

โค้ดของ PoXML

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

ประมวลผล XML แต่ส่วนที่เป็นข้อดีที่สุดก็คือ มีการแก้ไขสคริปต์เพียง 2 บรรทัดเท่านั้น เมื่อมีการใช้งานแทน SOAP::Lite ในการแฮ็กเป้าหมายต่างๆในหนังสือเล่มนี้

โค้ดของ PoXML

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

# PoXML.pm

# PoXML [pronounced "plain old xml"] is a dire-need drop-in

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

package PoXML;

use strict;

no strict "refs";

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

use LWP::UserAgent;

use XML::Simple;

# Create a new PoXML

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;

# Parse the XML

my $results = XMLin($soap_response);

# Normalize and drop the unnecessary encoding bits

my $return = $results->{'Body'}->{'doGoogleSearchResponse'}->{return};

foreach ( keys %{$return} ) {

$return->{$_}->{content} and

$return->{$_} = $return->{$_}->{content} || '';

}

my @items;

foreach my $item ( @{$return->{resultElements}->{item}} ) {

foreach my $key ( keys %$item ) {

$item->{$key} = $item->{$key}->{content} || '';

}

push @items, $item;

}

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

my @categories;

foreach my $key ( keys %{$return->{directoryCategories}->{item}} ) {

$return->{directoryCategories}->{$key} =

$return->{directoryCategories}->{item}->{$key}->{content} || '';

}

# 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>

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

#!/usr/bin/perl

# poxml_google2csv.pl

# Google Web Search Results via PoXML ("plain old xml") module

# exported to CSV suitable for import into Excel

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

# Your Google API developer's key

my $google_key = 'insert key here';

use strict;

# use SOAP::Lite;

use PoXML;

$ARGV[0]

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

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

my $google_search = new PoXML;

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 ที่คุณต้องการสร้างขึ้นมา หรือที่ต้องการให้นำผลลัพธ์ไปต่อท้าย (เดิมมีผลลัพธ์อยู่บ้างแล้ว) ตัวอย่างเช่นข้างล่างนี้ใช้ “plain old xml” เป็นคำถาม และกำหนดไฟล์ results.csv เป็นไฟล์รับผลลัพธ์ที่ได้กลับคืนมา

$ perl poxml_google2csv.pl "plain old xml" > results.csv

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

ผลลัพธ์

% perl poxml_google2csv.pl "plain old xml"

"title","url","snippet"

"XML.com: Distributed XML [Sep. 06, 2000]",

"http://www.xml.com/pub/2000/09/06/distributed.html",

" ... extensible. Unlike plain old XML, there's no sense of

constraining what the document can describe by a DTD or schema.

This means ... "

...

"Plain Old Documentation",

"http://axkit.org/wiki/view/AxKit/PlainOldDocumentation",

" ... perlpodspec - Plain Old Documentation: format specification

and notes. ... Examples: =pod This is a plain Pod paragraph. ...

encodings in Pod parsing would be as in XML ... "


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

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

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

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

ดูเพิ่มเติม

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

XooMLE [Hack #36] เป็น Third-party service ซึ่งเป็นตัวกลางในการเชื่อมต่อระหว่าง Plain Old XML กับ Google We แก้ไขบทความ b API

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