Thai Ruby

“ Environmentally, Thailand is going straight down the toilet. The rapid depletion of the country’s gem resources is symptomatic of the country’s depletion of other resources, such as forests and fisheries. If residents do not begin to pay greater attention to politics, education and the quality of their leadership (which directly impacts how resources are used), their quality of life and that of their children will be greatly diminished. ”
“ ว่ากันด้านสิ่งแวดล้อมแล้ว, ประเทศไทยกำลังดิ่งลงชักโครก. ทรัพยากรพลอยที่ร่อยหรอลงของประเทศ บอกอาการร่อยหรอของทรัพยากรอื่น ๆ อย่างป่าไม้และประมงด้วย. ถ้าคนไทยยังไม่เริ่มใส่ใจการเมือง การศึกษาและคุณภาพของผู้นำของพวกเขา (ซึ่งส่งผลโดยตรงกับทรัพยากร ว่าจะถูกใช้ยังไง), คุณภาพชีวิตของพวกเขาและลูกหลานจะลดลงอย่างใหญ่หลวง ”
Decline of ruby and sapphire industry in Thailand, Part 2, Richard W. Hughes

🙁

“ Thailand was, is, and probably always will be, one of those glorious places on the planet. A place of enjoyment, a place of warmth, a place of good cheer, a place of jai dee (good heart) and sanuk dee (good fun). Indeed, it is the Land of Smiles. And a smile is always better than a frown. Every time I return, my grin is ear-to-ear. ” — Richard W. Hughes’s Bio

🙂

🙂

Ruby NLP

ตัว Ruby Linguistics นี่น่าสนใจ ตรงวิธีการใช้ .. คือมัน รูบี๊ รูบี้ น่ะ

ตัวอย่าง:


"box".en.plural    #=> "boxes"
"mouse".en.plural  #=> "mice"
"ruby".en.plural   #=> "rubies"

"book".en.a      #=> "a book"
"article".en.a   #=> "an article"

"runs".en.present_participle    #=> "running"
"eats".en.present_participle    #=> "eating"
"spies".en.present_participle   #=> "spying"

"leaving".en.infinitive         #=> "leave"
"left".en.infinitive            #=> "leave"
"leaving".en.infinitive.suffix  #=> "ing"

"cow".en.quantify( 5 )     #=> "several cows"
"cow".en.quantify( 1005 )  #=> "thousands of cows"

อะไรทำนองนี้

update: (2007.04.02) update “NLP softwares in Ruby” link

Ruby gsub, friends and unrelated stuffs

เราเรียกใช้เมทธอด gsub ของคลาส String (String#gsub) ใน Ruby ได้สองแบบ
จาก API doc:
คำสั่ง => ผลลัพธ์

  1. str.gsub(pattern, replacement) => new_str
  2. str.gsub(pattern) {|match| block } => new_str

ตรง pattern นี่ จะเป็น String ธรรมดา ๆ ก็ได้

เค้าให้ตัวอย่างมาแบบนี้

  • "hello".gsub(/[aeiou]/, '*') #=> "h*ll*"
  • "hello".gsub(/([aeiou])/, '<\1>') #=> "h<e>ll<o>"
  • "hello".gsub(/./) {|s| s[0].to_s + ' '} #=> "104 101 108 108 111 "

แบบ (1) ใช้เป็นแล้ว ก็เหมือน ๆ กับในภาษาอื่น ๆ
ส่วนแบบ (2) ที่ผ่าน code block เข้าเมทธอด ยังไม่เคยลอง .. ก็ลองซะคราวนี้

โจทย์คือ ผมจะแทนตัวอักษร &, ‘, ", < และ > ทั้งหมดในไฟล์ ๆ หนึ่ง
ด้วย XML Entity &amp;, &apos;, &quot;, &lt; และ &gt;

ตอนแรกผมทำแบบนี้


 1: def convert1 (line)
 2:  if line != nil
 3:   line.gsub!(Re_amp,  Amp)
 4:   line.gsub!(Re_apos, Apos)
 5:   line.gsub!(Re_quot, Quot)
 6:   line.gsub!(Re_lt, Lt)
 7:   line.gsub!(Re_gt, Gt)
 8:   return line
 9:  else
10:   return line
11:  end
12: end
13:
14: IO.foreach(ARGV[0]) { |line| puts convert1(line) }

โปรแกรมจะเริ่มทำงานที่บรรทัดสุดท้าย (14) โดยอ่านไฟล์ตามชื่อที่ให้มาทาง command line argument แรก 1
หลังจากนั้น ในแต่ละบรรทัดของไฟล์ ก็จะทำคำสั่ง puts convert1(line) 2
อย่างกรณีนี้ก็คือ ทุก ๆ ครั้งที่อ่าน 1 บรรทัด ก็ให้พิมพ์บรรทัดที่ถูกแปลงแล้วออกมา

เมทธอด foreach ทำให้เราเขียนโค้ดที่อาจจะยาวอย่างน้อยสามสี่บรรทัดในภาษาอื่น ได้เหลือแค่บรรทัดเดียวใน Ruby !

  • IO.foreach(name, sep_string=$/) {|line| block } => nil

อย่าเพิ่งนอกเรื่องไปไกล กลับมาที่ gsub ต่อ …

โอเค จากบรรทัดที่ 14 จะเห็นว่าเราเรียกใช้ฟังก์ชั่น convert1, ซึ่งประกาศไว้ตรงบรรทัดที่ 1-12 (13 เป็นเลขไม่ดี เราขอไม่ใช้ :P)

บรรทัด 3-7 จะเห็นว่าเราเรียก gsub! 3 เป็นชุดเลย
โดยที่ Re_xxx นี่ เป็นออบเจกต์ pattern ที่จะหา &, ‘, … ที่เราเตรียมไว้ล่วงหน้าแล้ว
และพวก Amp, Apos, … ก็เป็น constant 4 ที่มีค่า &amp;, &apos;, …
(ตรงนี้ผมคิดไปเองว่า ถ้าเราเตรียมแพตเทิร์นและสตริงไว้ล่วงหน้า
คงจะช่วยให้ Ruby ไม่ต้องสร้างออบเจกต์แพตเทิร์นทุกครั้งใน “ลูป” foreach (ทุก ๆ บรรทัด)
แต่เนื่องจากไม่รู้ว่า ตัว Ruby runtime ทำงานจริง ๆ ยังไง มี optimization อะไรบ้าง เลยไม่แน่ใจว่ามันจะช่วยจริงรึเปล่า
… เอาว่าช่วยทางใจละกัน อยากเขียนแบบนี้น่ะ)

โค้ดของของแพตเทิร์น (regular expression):


Re_amp  = /&(?![-_a-zA-Z0-9]{1,12};)/  # (skip & in &xxx;)
Re_apos = /\'/
Re_quot = /\"/
Re_lt   = /</
Re_gt   = />/

เอาล่ะ ถึงเรื่องที่จะเล่าละ

ทีนี้ จะเห็นว่า เฮ้ย เราเรียก gsub! ซ้ำ ๆ ตั้งหลายรอบแหน่ะ
1 gsub! ต่อ 1 คู่ (แพตเทิร์น/ตัวอักษรที่จะแทน) ต่อ 1 บรรทัด
ดู ๆ ไป (อย่างเด่นชัด, ตามคำแปลของ LEXiTRON) มันน่าจะมีวิธีที่ .. มีประสิทธิภาพมากกว่านี้สิ

เป็นไปได้รึเปล่า ที่เราจะแทน คู่ (แพตเทิร์น/ตัวอักษร) ได้มากกว่า 1 คู่ ในการเรียก gsub! 1 ครั้ง ?
คำตอบคือ เป็นไปได้ ด้วยการผ่านโค้ดบล็อกเข้า gsub!
โดยให้ตัวโค้ดบล็อกเป็นตัวตัดสินใจเรื่องการแทนที่


 1: Re_escapes = /(&(?![-_a-zA-Z0-9]{1,12};))|(\')|(\")|(<)|(>)/
 2:
 3: def escape (char)
 4:  return case char
 5:   when '&'  then Amp
 6:   when '\'' then Apos
 7:   when '\"' then Quot
 8:   when '< '  then Lt
 9:   when '>'  then Gt
10:   else char
11:  end
12: end
13:
14: def convert2 (line)
15:  if line != nil
16:   line.gsub!(Re_escapes) { |match| match = escape(match) }
17:   return line
18:  else
19:   return line
20:  end
21: end
22:
23: IO.foreach(ARGV[0]) { |line| puts convert2(line) }

ที่โค้ดนี้ จุดที่ convert2 ต่างกับ convert1 ก็คือ ตรงบรรทัดที่ 16
จะเห็นว่า แทนที่เราจะส่ง 2 พารามิเตอร์ — (pattern, replacement)
เราส่ง 1 พารามิเตอร์ กับ 1 บล็อก แทน — (pattern) { block }

โค้ดบล็อก { |match| match = escape(match) } จะถูกเรียกทุกครั้งที่ gsub! พบแพตเทิร์น Re_escapes
โดย match จะเป็นค่าของสิ่งที่พบ
เราก็จัดการแปลง match ด้วยฟังก์ชั่น escape ซะ เท่านั้นก็จบ
เท่ากับว่า เราเรียก gsub! แค่ครั้งเดียว5

ในฟังก์ชั่น escape ก็ไม่มีอะไร, แบบว่าจะโชว์การใช้ case ... when น่ะ
เราสามารถผสม return กับ case ... when ได้ด้วย (แทนที่จะเขียน then return ... ซ้ำ ๆ)

Re_escapes นี่ก็เป็นแพตเทิร์นที่เรายุบรวม Re_amp, … Re_gt เข้าด้วยกัน ไม่มีอะไรมาก

สรุปก็คือ แทนที่เราจะหาทีละตัวอักษร แทนทีละตัวอักษร
เราก็หามันทีละหลาย ๆ ตัวอักษรเลย (ด้วยการผสมแพตเทิร์นเข้าด้วยกัน)
แล้วจากนั้นก็ค่อยไปดูว่า ที่พบน่ะ ตัวไหน แล้วค่อยแทนด้วยตัวที่ตรงคู่ (ตัดสินใจด้วยโค้ดบล็อก)

อยากรู้ว่าสองวิธีนี้ (convert1 vs convert2) จะให้ประสิทธิภาพต่างกันแค่ไหน
ลองทดสอบง่าย ๆ ด้วย Benchmark#bmbm


require 'benchmark'

...

Benchmark.bmbm do |x|
 x.report("convert1:") { IO.foreach(ARGV[0]) { |line| convert1(line) } }
 x.report("convert2:") { IO.foreach(ARGV[0]) { |line| convert2(line) } }
end

ลองรัน6 wellform-demo.rb etlex.xml

ผลลัพธ์:


Rehearsal ---------------------------------------------
convert1:  23.914000   0.070000  23.984000 ( 24.015000)
convert2:  22.813000   0.130000  22.943000 ( 22.973000)
----------------------------------- total: 46.927000sec

                user     system      total        real
convert1:  23.914000   0.100000  24.014000 ( 24.065000)
convert2:  22.913000   0.090000  23.003000 ( 23.063000)

กับไฟล์ etlex.xml ขนาดประมาณ 18 MB (668,938 บรรทัด)
convert2 เร็วกว่า convert1 ประมาณ 1 วินาที (ประมาณ 4%)

คุ้มมั๊ยเนี่ย ? 😛

ก็ .. ถือว่าคุ้มละกัน เพราะได้เรียน Ruby น่ะ 😀


1 ข้อสังเกต: ตรงนี้จะไม่เหมือน command line argument ในสไตล์ C หรือ Python
ที่ argument แรก (0) จะหมายถึงชื่อโปรแกรมที่กำลังรันอยู่
ใน Ruby ถ้าต้องการรู้ชื่อโปรแกรม ให้เรียกจากโลกาตัวแปร (global variable) $0 กลับ

2 ตรงนี้เราเรียกว่า code block
อย่างบรรทัด 14 นี้คือ เราส่งโค้ดบล็อก {…} เข้าเมทธอด foreach
โดย line (ใน | |) เนี่ย ก็เป็นเหมือน argument ของโค้ดบล็อก
(คิดซะว่า เรากำลังเขียนฟังก์ชั่นอันนึงก็ได้ ทำเหมือน argument ปกติ)
โดย line จะถูกแทนภายในเมทธอดที่เราส่งโค้ดบล็อกเข้าไป
(อันนี้คือตามความเข้าใจจากการทดลอง กลไกภาษา Ruby จริง ๆ อาจจะไม่ใช่แบบนี้ก็ได้ ต้องลองอ่านเอกสารดู) กลับ

3 gsub! ต่างกับ gsub ตรงที่ มันจะเปลี่ยนแปลงสตริงต้นฉบับเลย;
ในขณะที่ gsub จะไม่เปลี่ยนแปลงสตริงต้นฉบับ แต่จะคืนสตริงใหม่ออกมา;
พูดอีกอย่าง: โค้ด x.gsub!(a,b) ให้ผลเหมือน x = x.gsub(a,b)
ใน Ruby เมทธอดที่ลงท้ายด้วย ! มีลักษณะแบบนี้เหมือนกันหมด กลับ

4 by convention (ตามข้อตกลง), ค่าคงที่ (constant) ใน Ruby จะขึ้นต้นด้วยตัวพิมพ์ใหญ่ เช่น Book, Song
อย่างไรก็ตาม ข้อควรระวังคือ ในโปรแกรม เราสามารถแก้ไขค่าคงที่ได้ และ Ruby runtime ก็ไม่ห้าม
จะมีก็แค่ขึ้น warning เท่านั้น แต่โปรแกรมจะทำงานต่อไปตามปกติ
ซึ่งตรงนี้นี่ … อืมม ผมไม่แน่ใจว่าเป็นความคิดที่ดีรึเปล่า
อย่างน้อย คนเขียน Java น่ะไม่น่าจะชอบ กลับ

5 ตรงนี้ บางคนอาจจะเห็นว่า บรรทัดที่ 16 กับ 17 น่าจะยุบรวมกันได้
ข้อควรระวังก็คือ ถ้าจะยุบ อย่าลืมเปลี่ยน gsub! เป็น gsub
return line.gsub(Re_escapes) { |match| match = escape(match) }
เพราะในกรณีที่สตริงไม่มีการเปลี่ยนแปลง, gsub! จะคืนค่า nil
แต่ gsub จะคืนค่าสตริงต้นฉบับ
สิ่งที่เราอยากได้ ในโปรแกรมนี้ คืออย่างหลัง กลับ

6 ภาษาอังกฤษ “long run” แปลว่า ในระยะยาว 😛 กลับ

jEdit Ruby Editor Plugin

Ruby Editor Plugin สำหรับ jEdit
Ruby Editor Plugin screenshot

ความสามารถหลัก ๆ ของ jEdit Ruby Editor Plugin

  1. syntax highlighting แสดงสีไวยากรณ์
  2. error list บอกที่ผิด
  3. structure browser แสดงโครงสร้างโปรแกรม
  4. online API documentation คู่มือ API สด
  5. code completion ช่วยเติมโค้ด
  6. more และอื่น ๆ

นอกจาก FreeRIDE กับ SciTE ที่มากับชุดติดตั้ง Ruby แล้ว
IDE อีกตัวที่เหมือนจะมีคนนิยม คือ Mondrian Ruby IDE ยังเป็นเบต้าอยู่ เขียนด้วย Ruby + FOX toolkit
เท่าที่ลองก็โอเค มี (1), (2), (3) และสั่งรัน Ruby ได้จากใน IDE เลย (ถ้ามีที่ผิด โปรแกรมจะวิ่งไปที่บรรทัดแรกที่ผิดทันที)

ถ้าแก้แต่ไฟล์ Ruby อย่างเดียว FreeRIDE น่าจะดีที่สุด
มี (1), (2), (3), (4), สั่งรัน Ruby ได้, และใช้ IRB (Interactive Ruby) ได้ในตัวเลย

ยังไม่เห็นว่า Mondrian จะดีกว่า FreeRIDE ชัด ๆ ตรงไหน …
อาจจะเป็นเรื่องของการจัดการโครงการ (Mondrian มีแนวคิดแบบ Project ทำนองเดียวกับ IDE ชั้นนำทั่วไป อย่าง Visual Studio, Eclipse, NetBeans, … คือเปิดไฟล์ที่เกี่ยวข้องกันเป็นกลุ่มได้)
รวมทั้งความเร็วของตัว IDE .. เหมือน Mondrian จะเร็วกว่าหน่อย ๆ เท่าที่สังเกตตอนเปิด/ปิดโปรแกรม
แต่ก็ยังลองเล่นทั้งสองตัวแค่แป๊บเดียว อาจจะมีอย่างอื่นที่ไม่เห็น

SciTE นี่ก็แก้ไฟล์ได้หลายแบบดี มี (1) และ (2)
หน้าตาค่อนข้างจะดึกดำบรรพ์ไปหน่อย หรือพูดอีกอย่างคือ พื่้น ๆ ไม่มีลูกเล่นมากนัก
ไม่มีอะไรน่าสนใจเป็นพิเศษ .. นอกจากความเบา/เล็ก (lightweight)
นี่ว่าจะลบทิ้งอยู่

เดี๋ยวจะลองลงปลั๊กอิน shell ของ jEdit แล้วใช้ IRB ผ่าน shell plug-in
ถ้าใช้ได้, jEdit ก็น่าจะโอเคสุดแล้ว สำหรับการแก้ไฟล์หลาย ๆ แบบ + Ruby
แต่ต้องระวังเรื่องโหลดไฟล์ยักษ์ ๆ กรณีที่เปิดใช้ปลั๊กอินหลาย ๆ ตัว
(เช่นเปิดไฟล์ XML ขณะใช้ปลั๊กอิน Structure Browser + XML + XML Indenter)
เพราะถึงแม้จะเปิดใช้ได้ แต่เวลาแก้ มันจะอืด ทำงานได้ แต่รำคาญ

Emacs ? เอ่อ … ไม่รับครับ – -“

Analysing LEXiTRON data files, with Ruby

สุดสัปดาห์ที่ผ่านมา นั่งเรียน Ruby (กำลังฮิต mk ก็ทำอย่างเดียวกันโดยไม่ได้นัดหมาย)

ตอนแรกว่าจะลอง Ruby on Rails เลย ..

คืออาทิตย์ก่อนหน้านี้
(สังเกต: ใช้คำว่าว่า อาทิตย์ก่อนหน้า แต่ก็ใช้ สุดสัปดาห์ … สุดอาทิตย์ ไม่มีใครใช้)
นั่งเขียน ‘เว็บบอร์ด’ ใหม่อยู่ (โครงการพันล้าน ทำ ๆ รื้อ ๆ มาหลายชาติ ตอนนี้ Google ออกเบต้าไปเรียบร้อยแล้ว :P)
ก็เขียนด้วย PHP ตามถนัดน่ะแหละ (ซึ่งผมว่า สำหรับเว็บเล็ก ๆ นะ PHP นี่แหละ เร็วสุดแล้ว หมายถึงสร้างนะ แต่ถ้าทำใหญ่ ๆ ก็รกง่ายมาก) แต่คราวนี้แทนที่จะทำอะไรเองหมด ก็ไปลองใช้พวกไลบรารีใน PEAR มั่ง
ซึ่งก็ง่ายดี และทำให้โค้ดสะอาดขึ้นเยอะ (อย่างตัว MDB2 นี่ก็ชอบ .. ถ้ามันใช้ utf-8 ได้นะ)

เขียนไปเขียนมา ก็นึกขึ้นมาได้ว่า เออ อยากลอง Rails มาตั้งนานแล้วนี่นา
งั้นทำไมไม่ลองเขียนด้วย Rails ไปเลยล่ะ ?
ประกอบกับได้ไปยืน/นั่ง/นอนอ่าน(ฟรี) หนังสือ Beyond Java ที่ร้านหนังสือมาสองรอบ (ไปอ่านมาสองรอบ ไม่ใช่อ่านจบสองครั้ง)
นึกได้ดังนั้น ก็เลยลง Ruby กะ Rails อีกรอบ
(หลังจากที่เคยลงและลบไปนานแล้ว เพราะไม่รู้จะลองอะไรดี)

ปรากฏว่า ลง Ruby ได้ แต่ Rails ไม่เวิร์กแฮะ
เซตกับ Apache (บน Windows) ไม่ได้ซะที ลองมันทุกวิธีละ
ก็นั่งทำอยู่ตั้งนาน ยังไงก็ไม่ได้
สุดท้ายเลยไปใช้ WEBrick แทน ที่ port 3000 (โง่อยู่ตั้งนาน โธ่)

เอาล่ะ มี Ruby ละ มี Rails ละ
ลอง Hello, World ไปโผล่ในเว็บเบราเซอร์ได้เรียบร้อยแล้ว
แต่ยังเขียน Ruby ไม่เป็นเลยนี่หว่า .. เอ้า

ก็เลยหัด Ruby ซะก่อน

ไม่รู้จะหัดกับอะไรดี นึกขึ้นมาได้ว่ากำลังอยากแปลงไฟล์ข้อมูล LEXiTRON อยู่
งั้นลองด้วย Ruby เลยละกัน
(เป็นประเภท ไม่ชอบเรียนกับหนังสือ ขี้เกียจทำตาม ชอบแก้ ๆ ผิด ๆ ถูก ๆ ไปเองเรื่อย ๆ มากกว่า ถ้าติดตรงไหนค่อยเปิดอ่าน/ค้นเว็บ)

ทำไปทำมา ตอนนี้ไม่ได้แตะ Rails แล้ว .. อ้าว

เพราะกำลังสนุกกับการ ‘ทำความสะอาด’ ข้อมูลใน LEXiTRON อยู่

ก็ใช้เขียน Ruby นี่แหละ ทำสคริปต์ช่วย พวกแปลงข้อความ/หาข้อความ

ทำไปทำมา จริง ๆ เหมือนเขียน regular expression เล่นมากกว่า 😛

ถ้ามีพื้นฐาน Python มาแล้ว Ruby นี่วันเดียวก็เป็น ถ้าได้ลงมือโค้ดจริง ๆ
คอนเซปต์อย่าง closure นี่ ผมอ่าน ๆ แล้วก็ไม่เข้าใจเท่าไหร่
แต่พอได้ลงมือเองจริง ๆ แล้ว + อ่านโค้ดตัวอย่าง + ดีบัก แป๊บเดียวก็เข้าใจแล้ว

เท่าที่พบ ข้อมูลใน LEXiTRON ค่อนข้างจาก ‘รก’ ทีเดียว …
ไม่ใช่ว่าแปลผิดนะ แต่หมายถึง ไม่ค่อยจะ..สม่ำเสมอ คือ ข้อมูลอย่างเดียวกัน แต่บันทึกไม่เหมือนกัน
เช่นในแท็ก <notes> (หมายเหตุ) ในไฟล์ telex นี่ เรา (โดยสคริปต์ Ruby สั้น ๆ) จะพบทั้ง

  • (บาลี/สันสกฤต) 819 ครั้ง
  • (บาลี, สันสกฤต) 63
  • (บาลี), (สันสกฤต) 21
  • (บาลี – สันสกฤต) 12
  • (บาลีและสันสกฤต) 11
  • บาลี-สันสกฤต 4
  • (บาลี-สันสกฤต) 2

ผมยังไม่ได้เข้าไปไล่ดูในตัวไฟล์คำแปลว่าจริง ๆ แล้ว ทั้งหมดนี้ มีความหมายเหมือนหรือต่างกันยังไง
แต่ยังไงก็ตาม มันน่าจะมีอย่างน้อยบางคู่ ที่มันมีความหมายเหมือนกันล่ะ (แต่บันทึกไม่เหมือน)

… สรุปว่า เริ่มจาก ‘เว็บบอร์ด’ แล้วไปลงที่ LEXiTRON ได้ไงเนี่ย – –

นี่มัน Ruby off Rails ชัด ๆ !

ป.ล. งานนี้ใช้ jEdit + Ruby plug-in และปลั้กอินอื่น ๆ ..
ทั้ง HTML, CSS, XML, PHP, Python, JavaScript และ Ruby เราทำมันทั้งหมดใน jEdit เย้!
(อ้อ อย่าลืมเซต -Xmx เยอะ ๆ ล่ะ ถ้าแก้ไฟล์ใหญ่มาก ๆ แล้วจะให้มัน parse structure ด้วย ไม่งั้นก็แก้ใน mode text ธรรมดาไปเลย
และระวังอย่าเปิดไฟล์ที่มีบรรทัดที่ยาว ๆ และไม่มี whitespace เลย ในขณะที่ใช้ปลั๊กอินอย่าง StructureBrowser ไม่งั้น jEdit มันจะค้างไปเลย อันนี้ลองกับไฟล์ XML ที่แปลงมาจากข้อมูล LEXiTRON ขนาด 20 Mb ทั้งไฟล์มีบรรทัดเดียว และไม่มี whitespace เลย … ผลลัพธ์ เซต -Xmx640m ก็ไม่พอครับ (เครื่องผมแรม 1.5 Gb))

ป.ล. ๒ เดี๋ยวค่อยมาโพสต์เรื่องการแปลงข้อมูล และสคริปต์ที่ใช้จริง ๆ (ด้วยภาษา Ruby) กัน
ถ้าสนใจเรื่องนี้ และอยากอ่านตอนนี้เลย แนะนำให้ไปอ่านของคุณ poonlap ก่อน — เจาะข้อมูล LEXiTRON (Perl)

jEdit Ruby Editor Plugin

หลายขั้นตอนหน่อย แต่ดีทีเดียว (ยังไม่มีให้เลือกโดยตรงจาก Plug-in Manager)

ผมใช้ jEdit อยู่แล้วด้วย (ทั้งสำหรับ XML, PHP, Python) เลยชอบใจ 🙂