구문 분석은 일련의 문자열을 이해하고 우리가 이해할 수 있는 것으로 변환하는 기술입니다. 정규 표현식을 사용할 수 있지만 항상 작업에 적합한 것은 아닙니다.
예를 들어, 정규 표현식으로 HTML을 구문 분석하는 것은 좋은 생각이 아니라는 것은 상식입니다.
Ruby에는 이 작업을 수행할 수 있는 nokogiri가 있지만 자신만의 파서를 구축하여 많은 것을 배울 수 있습니다. 시작하겠습니다!
Ruby로 구문 분석
파서의 핵심은 StringScanner입니다. 수업.
이 클래스는 문자열의 복사본과 위치 포인터를 보유합니다. 포인터를 사용하면 특정 토큰을 검색하기 위해 문자열을 탐색할 수 있습니다.
우리가 사용할 방법은 다음과 같습니다:
- .피크
- .scan_until
- .get
또 다른 유용한 방법은 .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 ) 위치를 확인합니다.
파서 클래스
파서 클래스는 대부분의 작업이 발생하는 곳입니다. 파싱하려는 텍스트 스니펫으로 이를 초기화하고 이에 대한 StringScanner를 생성하고 parse 메소드를 호출합니다.
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> ">"
StringScanner의 피크 입력 버퍼의 다음 기호가 여는 태그인지 확인합니다. 이 경우 find_tag 및 콘텐츠 찾기 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을 사용할 것입니다.
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' 브랜치를 볼 수도 있습니다.
결론
파서를 작성하는 것은 흥미로운 주제이며 때때로 꽤 복잡해질 수도 있습니다.
자신만의 파서를 처음부터 만들고 싶지 않다면 소위 '파서 생성기' 중 하나를 사용할 수 있습니다. Ruby에는 나무 꼭대기와 파슬리가 있습니다.