Basic lexing of HTML tags.
The current implementation is a bit messy. In particular the counting of column numbers is not entirely the way it should be. There are also some problems with nested tags/text that I still have to resolve.
This commit is contained in:
		
							parent
							
								
									d9ef33e1f8
								
							
						
					
					
						commit
						a5a3b8db3f
					
				|  | @ -79,6 +79,8 @@ module Oga | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def emit_text_buffer |     def emit_text_buffer | ||||||
|  |       return if @text_buffer.empty? | ||||||
|  | 
 | ||||||
|       add_token(:T_TEXT, @text_buffer) |       add_token(:T_TEXT, @text_buffer) | ||||||
| 
 | 
 | ||||||
|       @text_buffer = '' |       @text_buffer = '' | ||||||
|  | @ -98,12 +100,8 @@ module Oga | ||||||
|       newline    = '\n' | '\r\n'; |       newline    = '\n' | '\r\n'; | ||||||
|       whitespace = [ \t]; |       whitespace = [ \t]; | ||||||
| 
 | 
 | ||||||
|       action emit_space { |  | ||||||
|         t(:T_SPACE) |  | ||||||
|       } |  | ||||||
| 
 |  | ||||||
|       action emit_newline { |       action emit_newline { | ||||||
|         t(:T_NEWLINE) |         t(:T_TEXT) | ||||||
|         advance_line |         advance_line | ||||||
|       } |       } | ||||||
| 
 | 
 | ||||||
|  | @ -228,8 +226,65 @@ module Oga | ||||||
|         any => buffer_text; |         any => buffer_text; | ||||||
|       *|; |       *|; | ||||||
| 
 | 
 | ||||||
|  |       # Elements | ||||||
|  |       # | ||||||
|  |       # http://www.w3.org/TR/html-markup/syntax.html#syntax-elements | ||||||
|  |       # | ||||||
|  |       element_name  = [a-zA-Z0-9\-_]+; | ||||||
|  |       element_start = '<' element_name; | ||||||
|  | 
 | ||||||
|  |       # First emit the token, then advance the column. This way the column | ||||||
|  |       # number points to the < and not the "p" in <p>. | ||||||
|  |       action open_element { | ||||||
|  |         t(:T_ELEM_OPEN, p) | ||||||
|  | 
 | ||||||
|  |         advance_column | ||||||
|  | 
 | ||||||
|  |         fcall element; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       element_text := |* | ||||||
|  |         ^'<' => buffer_text; | ||||||
|  | 
 | ||||||
|  |         '<' => { | ||||||
|  |           emit_text_buffer | ||||||
|  |           fhold; | ||||||
|  |           fret; | ||||||
|  |         }; | ||||||
|  |       *|; | ||||||
|  | 
 | ||||||
|  |       element := |* | ||||||
|  |         whitespace => { advance_column }; | ||||||
|  | 
 | ||||||
|  |         element_start => open_element; | ||||||
|  | 
 | ||||||
|  |         # Consume the text inside the element. | ||||||
|  |         '>' => { | ||||||
|  |           advance_column | ||||||
|  |           fcall element_text; | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         # Attributes and their values. | ||||||
|  |         element_name | ||||||
|  |           %{ | ||||||
|  |             t(:T_ATTR, @ts, p) | ||||||
|  |           } | ||||||
|  |         '=' (dquote @string_dquote | squote @string_squote); | ||||||
|  | 
 | ||||||
|  |         # Non self-closing tags. | ||||||
|  |         '</' element_name { | ||||||
|  |           emit_text_buffer | ||||||
|  |           t(:T_ELEM_CLOSE, p) | ||||||
|  | 
 | ||||||
|  |           # Advance by two to take the closing </ into account. This is done | ||||||
|  |           # after emitting tokens to ensure that they point to the start of | ||||||
|  |           # the tag. | ||||||
|  |           advance_column(2) | ||||||
|  |           fret; | ||||||
|  |         }; | ||||||
|  |       *|; | ||||||
|  | 
 | ||||||
|       main := |* |       main := |* | ||||||
|         whitespace => emit_space; |  | ||||||
|         newline => emit_newline; |         newline => emit_newline; | ||||||
| 
 | 
 | ||||||
|         doctype_start => { |         doctype_start => { | ||||||
|  | @ -247,19 +302,10 @@ module Oga | ||||||
|           fcall comment; |           fcall comment; | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         # General rules and actions. |         element_start => open_element; | ||||||
|         '<' => { t(:T_SMALLER) }; |  | ||||||
|         '>' => { t(:T_GREATER) }; |  | ||||||
|         '/' => { t(:T_SLASH) }; |  | ||||||
|         '-' => { t(:T_DASH) }; |  | ||||||
|         ']' => { t(:T_RBRACKET) }; |  | ||||||
|         '[' => { t(:T_LBRACKET) }; |  | ||||||
|         ':' => { t(:T_COLON) }; |  | ||||||
|         '!' => { t(:T_BANG) }; |  | ||||||
|         '=' => { t(:T_EQUALS) }; |  | ||||||
| 
 | 
 | ||||||
|         dquote => { t(:T_DQUOTE) }; |         #dquote => { t(:T_DQUOTE) }; | ||||||
|         squote => { t(:T_SQUOTE) }; |         #squote => { t(:T_SQUOTE) }; | ||||||
|       *|; |       *|; | ||||||
|     }%% |     }%% | ||||||
|   end # Lexer |   end # Lexer | ||||||
|  |  | ||||||
|  | @ -0,0 +1,47 @@ | ||||||
|  | require 'spec_helper' | ||||||
|  | 
 | ||||||
|  | describe Oga::Lexer do | ||||||
|  |   context 'elements' do | ||||||
|  |     example 'lex an opening element' do | ||||||
|  |       lex('<p>').should == [ | ||||||
|  |         [:T_ELEM_OPEN, 'p', 1, 1] | ||||||
|  |       ] | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     example 'lex an opening an closing element' do | ||||||
|  |       lex('<p></p>').should == [ | ||||||
|  |         [:T_ELEM_OPEN, 'p', 1, 1], | ||||||
|  |         [:T_ELEM_CLOSE, 'p', 1, 4] | ||||||
|  |       ] | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     example 'lex a paragraph element with text inside it' do | ||||||
|  |       lex('<p>Hello</p>').should == [ | ||||||
|  |         [:T_ELEM_OPEN, 'p', 1, 1], | ||||||
|  |         [:T_TEXT, 'Hello', 1, 4], | ||||||
|  |         [:T_ELEM_CLOSE, 'p', 1, 9] | ||||||
|  |       ] | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     example 'lex a paragraph element with attributes' do | ||||||
|  |       lex('<p class="foo">Hello</p>').should == [ | ||||||
|  |         [:T_ELEM_OPEN, 'p', 1, 1], | ||||||
|  |         [:T_ATTR, 'class', 1, 4], | ||||||
|  |         [:T_STRING, 'foo', 1, 10], | ||||||
|  |         [:T_TEXT, 'Hello', 1, 15], | ||||||
|  |         [:T_ELEM_CLOSE, 'p', 1, 20] | ||||||
|  |       ] | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   context 'nested elements' do | ||||||
|  |     example 'lex a nested element' do | ||||||
|  |       lex('<p><a></a></p>').should == [ | ||||||
|  |         [:T_ELEM_OPEN, 'p', 1, 1], | ||||||
|  |         [:T_ELEM_OPEN, 'a', 1, 4], | ||||||
|  |         [:T_ELEM_CLOSE, 'a', 1, 7], | ||||||
|  |         [:T_ELEM_CLOSE, 'p', 1, 11] | ||||||
|  |       ] | ||||||
|  |     end | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -9,24 +9,17 @@ describe Oga::Lexer do | ||||||
| 
 | 
 | ||||||
|   context 'whitespace' do |   context 'whitespace' do | ||||||
|     example 'lex regular whitespace' do |     example 'lex regular whitespace' do | ||||||
|       lex(' ').should == [[:T_SPACE, ' ', 1, 1]] |       lex(' ').should == [[:T_TEXT, ' ', 1, 1]] | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     example 'lex a newline' do |     example 'lex a newline' do | ||||||
|       lex("\n").should == [[:T_NEWLINE, "\n", 1, 1]] |       lex("\n").should == [[:T_TEXT, "\n", 1, 1]] | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     example 'advance column numbers for spaces' do |  | ||||||
|       lex('  ').should == [ |  | ||||||
|         [:T_SPACE, ' ', 1, 1], |  | ||||||
|         [:T_SPACE, ' ', 1, 2] |  | ||||||
|       ] |  | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     example 'advance line numbers for newlines' do |     example 'advance line numbers for newlines' do | ||||||
|       lex("\n ").should == [ |       lex("\n ").should == [ | ||||||
|         [:T_NEWLINE, "\n", 1, 1], |         [:T_TEXT, "\n", 1, 1], | ||||||
|         [:T_SPACE, ' ', 2, 1] |         [:T_TEXT, ' ', 2, 1] | ||||||
|       ] |       ] | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -1,72 +0,0 @@ | ||||||
| require 'spec_helper' |  | ||||||
| 
 |  | ||||||
| describe Oga::Lexer do |  | ||||||
|   context 'tags' do |  | ||||||
|     example 'lex an opening tag' do |  | ||||||
|       lex('<p>').should == [ |  | ||||||
|         [:T_SMALLER, '<', 1, 1], |  | ||||||
|         [:T_TEXT, 'p', 1, 2], |  | ||||||
|         [:T_GREATER, '>', 1, 3] |  | ||||||
|       ] |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     example 'lex an opening tag with an attribute' do |  | ||||||
|       lex('<p title="Foo">').should == [ |  | ||||||
|         [:T_SMALLER, '<', 1, 1], |  | ||||||
|         [:T_TEXT, 'p', 1, 2], |  | ||||||
|         [:T_SPACE, ' ', 1, 3], |  | ||||||
|         [:T_TEXT, 'title', 1, 4], |  | ||||||
|         [:T_EQUALS, '=', 1, 9], |  | ||||||
|         [:T_DQUOTE, '"', 1, 10], |  | ||||||
|         [:T_TEXT, 'Foo', 1, 11], |  | ||||||
|         [:T_DQUOTE, '"', 1, 14], |  | ||||||
|         [:T_GREATER, '>', 1, 15] |  | ||||||
|       ] |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     example 'lex a tag with text inside it' do |  | ||||||
|       lex('<p>Foo</p>').should == [ |  | ||||||
|         [:T_SMALLER, '<', 1, 1], |  | ||||||
|         [:T_TEXT, 'p', 1, 2], |  | ||||||
|         [:T_GREATER, '>', 1, 3], |  | ||||||
|         [:T_TEXT, 'Foo', 1, 4], |  | ||||||
|         [:T_SMALLER, '<', 1, 7], |  | ||||||
|         [:T_SLASH, '/', 1, 8], |  | ||||||
|         [:T_TEXT, 'p', 1, 9], |  | ||||||
|         [:T_GREATER, '>', 1, 10] |  | ||||||
|       ] |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     example 'lex a tag with an attribute with a dash in it' do |  | ||||||
|       lex('<p foo-bar="baz">').should == [ |  | ||||||
|         [:T_SMALLER, '<', 1, 1], |  | ||||||
|         [:T_TEXT, 'p', 1, 2], |  | ||||||
|         [:T_SPACE, ' ', 1, 3], |  | ||||||
|         [:T_TEXT, 'foo', 1, 4], |  | ||||||
|         [:T_DASH, '-', 1, 7], |  | ||||||
|         [:T_TEXT, 'bar', 1, 8], |  | ||||||
|         [:T_EQUALS, '=', 1, 11], |  | ||||||
|         [:T_DQUOTE, '"', 1, 12], |  | ||||||
|         [:T_TEXT, 'baz', 1, 13], |  | ||||||
|         [:T_DQUOTE, '"', 1, 16], |  | ||||||
|         [:T_GREATER, '>', 1, 17] |  | ||||||
|       ] |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   context 'tags with namespaces' do |  | ||||||
|     example 'lex a tag with a dummy namespace' do |  | ||||||
|       lex('<foo:p></p>').should == [ |  | ||||||
|         [:T_SMALLER, '<', 1, 1], |  | ||||||
|         [:T_TEXT, 'foo', 1, 2], |  | ||||||
|         [:T_COLON, ':', 1, 5], |  | ||||||
|         [:T_TEXT, 'p', 1, 6], |  | ||||||
|         [:T_GREATER, '>', 1, 7], |  | ||||||
|         [:T_SMALLER, '<', 1, 8], |  | ||||||
|         [:T_SLASH, '/', 1, 9], |  | ||||||
|         [:T_TEXT, 'p', 1, 10], |  | ||||||
|         [:T_GREATER, '>', 1, 11] |  | ||||||
|       ] |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| end |  | ||||||
		Loading…
	
		Reference in New Issue