Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

วิธีสร้าง Parser ด้วย Ruby

การแยกวิเคราะห์เป็นศิลปะในการทำความเข้าใจกลุ่มสตริงและแปลงเป็นบางสิ่งที่เราเข้าใจได้ คุณสามารถใช้นิพจน์ทั่วไปได้ แต่ไม่เหมาะกับงานเสมอไป

ตัวอย่างเช่น เป็นความรู้ทั่วไปที่การแยกวิเคราะห์ HTML ด้วยนิพจน์ทั่วไปอาจไม่ใช่ความคิดที่ดี

ใน Ruby เรามี nokogiri ที่สามารถทำงานนี้ให้เราได้ แต่คุณสามารถเรียนรู้ได้มากมายด้วยการสร้าง parser ของคุณเอง เริ่มกันเลย!

การแยกวิเคราะห์ด้วย Ruby

แกนหลักของ parser ของเราคือ StringScanner ชั้นเรียน

คลาสนี้มีสำเนาของสตริงและตัวชี้ตำแหน่ง ตัวชี้จะช่วยให้เราข้ามสตริงเพื่อค้นหาโทเค็นบางตัวได้

วิธีการที่เราจะใช้คือ:

  • .peek
  • .scan_until
  • .getch

อีกวิธีหนึ่งที่มีประโยชน์คือ .scan (โดยไม่ต้องจนกว่า)

หมายเหตุ :

หากไม่มี StringScanner ให้คุณลองเพิ่ม require 'strscan'

ฉันเขียนการทดสอบสองครั้งเป็นเอกสารเพื่อให้เราเข้าใจว่าคลาสนี้ควรทำงานอย่างไร:

describe StringScanner do
  let (:buff) { StringScanner.new "testing" }

  it "can peek one step ahead" do
    expect(buff.peek 1).to eq "t"
  end

  it "can read one char and return it" do
    expect(buff.getch).to eq "t"
    expect(buff.getch).to eq "e"
  end
end

สิ่งสำคัญอย่างหนึ่งที่ควรสังเกตเกี่ยวกับคลาสนี้คือ เมธอดบางอย่างเลื่อนตัวชี้ตำแหน่ง (ดึง สแกน ) ในขณะที่คนอื่นไม่ (แอบดู ). คุณสามารถตรวจสอบสแกนเนอร์ของคุณได้ทุกเมื่อ (โดยใช้ .inspect หรือ p ) เพื่อดูว่าอยู่ที่ไหน

คลาส parser

คลาส parser เป็นที่ที่งานส่วนใหญ่เกิดขึ้น เราจะเริ่มต้นด้วยตัวอย่างข้อความที่เราต้องการแยกวิเคราะห์ และจะสร้าง StringScanner สำหรับสิ่งนั้นและเรียกวิธีการแยกวิเคราะห์:

def initialize(str)
  @buffer = StringScanner.new(str)
  @tags   = []
  parse
end

ในการทดสอบเราให้คำจำกัดความดังนี้:

let(:parser) { Parser.new "<body>testing</body> <title>parsing with ruby</title>" }

เราจะเจาะลึกว่าชั้นเรียนนี้ทำงานอย่างไรในระยะเวลาสั้นๆ แต่ก่อนอื่น มาดูส่วนสุดท้ายของโปรแกรมกันก่อน

คลาสแท็ก

คลาสนี้เรียบง่ายมาก ส่วนใหญ่ทำหน้าที่เป็นคอนเทนเนอร์และคลาสข้อมูลสำหรับผลการแยกวิเคราะห์

class Tag
  attr_reader :name
  attr_accessor :content

  def initialize(name)
    @name = name
  end
end

มาวิเคราะห์กัน!

ในการแยกวิเคราะห์บางอย่าง เราจะต้องดูที่ข้อความที่ป้อนเพื่อค้นหารูปแบบ ตัวอย่างเช่น เรารู้ว่าโค้ด HTML มีรูปแบบดังนี้:

<tag>contents</tag>

เห็นได้ชัดว่ามีสององค์ประกอบที่แตกต่างกันที่เราสามารถระบุได้ที่นี่ ชื่อแท็กและข้อความภายในแท็ก หากเราจะกำหนดไวยากรณ์ที่เป็นทางการโดยใช้รูปแบบ BNF จะมีลักษณะดังนี้:

tag = <opening_tag> <contents> <closing_tag>
opening_tag = "<" <tag_name> ">"
closing_tag = "</" <tag_name> ">"

เราจะใช้แอบดู .ของ StringScanners เพื่อดูว่าสัญลักษณ์ถัดไปในบัฟเฟอร์อินพุตของเราเป็นแท็กเปิดหรือไม่ หากเป็นกรณีนี้ เราจะเรียก find_tag และ find_content วิธีการในคลาส Parser ของเรา:

def parse_element
  if @buffer.peek(1) == '<'
    @tags << find_tag
    last_tag.content = find_content
  end
end

find_tag วิธีการจะ:

  • 'ใช้' อักขระแท็กเปิด
  • สแกนจนกว่าจะพบสัญลักษณ์ปิด (“>”)
  • สร้างและส่งคืนออบเจกต์แท็กใหม่ที่มีชื่อแท็ก

นี่คือรหัส สังเกตว่าเราต้องสับ .อย่างไร อักขระสุดท้าย ทั้งนี้เป็นเพราะ scan_until รวม '>' ไว้ในผลลัพธ์ และเราไม่ต้องการสิ่งนั้น

def find_tag
  @buffer.getch
  tag = @buffer.scan_until />/
  Tag.new(tag.chop)
end

ขั้นตอนต่อไปคือการค้นหาเนื้อหาภายในแท็ก ซึ่งไม่น่าจะยากเกินไป เนื่องจากวิธี scan_until จะทำให้ตัวชี้ตำแหน่งเลื่อนไปยังตำแหน่งที่ถูกต้อง เราจะใช้ scan_until อีกครั้งเพื่อค้นหาแท็กปิดและส่งคืนเนื้อหาแท็ก

วิธีสร้าง Parser ด้วย Ruby

def find_content
  tag = last_tag.name
  content = @buffer.scan_until /<\/#{tag}>/
  content.sub("</#{tag}>", "")
end

ตอนนี้ :

สิ่งที่เราต้องทำคือเรียก parse_element วนซ้ำจนกว่าเราจะไม่พบแท็กเพิ่มเติมในบัฟเฟอร์อินพุตของเรา

def parse
  until @buffer.eos?
    skip_spaces
    parse_element
  end
end

คุณสามารถค้นหาโค้ดทั้งหมดได้ที่นี่: https://github.com/matugm/simple-parser คุณยังสามารถดูที่สาขา 'nested_tags' สำหรับเวอร์ชันขยายที่สามารถจัดการกับแท็กภายในแท็กอื่นได้

บทสรุป

การเขียน parser เป็นหัวข้อที่น่าสนใจและบางครั้งก็อาจซับซ้อนได้เช่นกัน

หากคุณไม่ต้องการสร้าง parser ของคุณเองตั้งแต่เริ่มต้น คุณสามารถใช้หนึ่งใน 'ตัวสร้าง parser' ที่เรียกว่า ใน Ruby เรามียอดไม้และผักชีฝรั่ง