Designates an XHTML/XML element.
Designates a `
Designates a `
Designates an XHTML/XML comment.
Designates an XHTML doctype or script that is never HTML-escaped.
Designates script, the result of which is output.
Designates script that is always HTML-escaped.
Designates script, the result of which is flattened and output.
Designates script which is run but not output.
When following SILENT_SCRIPT, designates a comment that is not output.
Designates a non-parsed line.
Designates a block of filtered text.
Designates a non-parsed line. Not actually a character.
Keeps track of the ASCII values of the characters that begin a specially-interpreted line.
The value of the character that designates that a line is part of a multiline string.
Try to parse assignments to block starters as best as possible
The Regex that matches a Doctype command.
The Regex that matches a literal string or symbol value
This is a class method so it can be accessed from {Haml::Helpers}.
Iterates through the classes and ids supplied through `.` and `#` syntax, and returns a hash with them as attributes, that can then be merged with another attributes hash.
# File lib/haml/parser.rb, line 430 430: def self.parse_class_and_id(list) 431: attributes = {} 432: list.scan(/([#.])([-:_a-zA-Z0-9]+)/) do |type, property| 433: case type 434: when '.' 435: if attributes['class'] 436: attributes['class'] += " " 437: else 438: attributes['class'] = "" 439: end 440: attributes['class'] += property 441: when '#'; attributes['id'] = property 442: end 443: end 444: attributes 445: end
# File lib/haml/parser.rb, line 691 691: def balance(*args) 692: res = Haml::Shared.balance(*args) 693: return res if res 694: raise SyntaxError.new("Unbalanced brackets.") 695: end
# File lib/haml/parser.rb, line 212 212: def block_keyword(text) 213: return unless keyword = text.scan(BLOCK_KEYWORD_REGEX)[0] 214: keyword[0] || keyword[1] 215: end
# File lib/haml/parser.rb, line 697 697: def block_opened? 698: @next_line.tabs > @line.tabs 699: end
# File lib/haml/parser.rb, line 396 396: def close 397: node, @parent = @parent, @parent.parent 398: @template_tabs -= 1 399: send("close_#{node.type}", node) if respond_to?("close_#{node.type}", :include_private) 400: end
# File lib/haml/parser.rb, line 402 402: def close_filter(_) 403: @flat = false 404: @flat_spaces = nil 405: @filter_buffer = nil 406: end
# File lib/haml/parser.rb, line 408 408: def close_haml_comment(_) 409: @haml_comment = false 410: end
# File lib/haml/parser.rb, line 412 412: def close_silent_script(node) 413: # Post-process case statements to normalize the nesting of "when" clauses 414: return unless node.value[:keyword] == "case" 415: return unless first = node.children.first 416: return unless first.type == :silent_script && first.value[:keyword] == "when" 417: return if first.children.empty? 418: # If the case node has a "when" child with children, it's the 419: # only child. Then we want to put everything nested beneath it 420: # beneath the case itself (just like "if"). 421: node.children = [first, *first.children] 422: first.children = [] 423: end
# File lib/haml/parser.rb, line 627 627: def closes_flat?(line) 628: line && !line.text.empty? && line.full !~ /^#{@flat_spaces}/ 629: end
# File lib/haml/parser.rb, line 671 671: def contains_interpolation?(str) 672: str.include?('#{') 673: end
Renders a line that creates an XHTML tag and has an implicit div because of `.` or `#`.
# File lib/haml/parser.rb, line 358 358: def div(line) 359: tag('%div' + line) 360: end
Renders an XHTML doctype or XML shebang.
# File lib/haml/parser.rb, line 376 376: def doctype(line) 377: raise SyntaxError.new("Illegal nesting: nesting within a header command is illegal.", @next_line.index) if block_opened? 378: version, type, encoding = line[3..1].strip.downcase.scan(DOCTYPE_REGEX)[0] 379: ParseNode.new(:doctype, @index, :version => version, :type => type, :encoding => encoding) 380: end
# File lib/haml/parser.rb, line 382 382: def filter(name) 383: raise Error.new("Invalid filter name \":#{name}\".") unless name =~ /^\w+$/ 384: 385: @filter_buffer = String.new 386: 387: if filter_opened? 388: @flat = true 389: # If we don't know the indentation by now, it'll be set in Line#tabs 390: @flat_spaces = @indentation * (@template_tabs+1) if @indentation 391: end 392: 393: ParseNode.new(:filter, @index, :name => name, :text => @filter_buffer) 394: end
Same semantics as block_opened?, except that block_opened? uses Line#tabs, which doesn’t interact well with filter lines
# File lib/haml/parser.rb, line 703 703: def filter_opened? 704: @next_line.full =~ (@indentation ? /^#{@indentation * @template_tabs}/ : /^\s/) 705: end
# File lib/haml/parser.rb, line 707 707: def flat? 708: @flat 709: end
# File lib/haml/parser.rb, line 248 248: def flat_script(text, escape_html = nil) 249: raise SyntaxError.new("There's no Ruby code for ~ to evaluate.") if text.empty? 250: script(text, escape_html, :preserve) 251: end
# File lib/haml/parser.rb, line 273 273: def haml_comment(text) 274: @haml_comment = block_opened? 275: ParseNode.new(:haml_comment, @index, :text => text) 276: end
# File lib/haml/parser.rb, line 636 636: def handle_multiline(line) 637: return unless is_multiline?(line.text) 638: line.text.slice!(1) 639: while new_line = raw_next_line.first 640: break if new_line == :eod 641: next if new_line.strip.empty? 642: break unless is_multiline?(new_line.strip) 643: line.text << new_line.strip[0...1] 644: end 645: un_next_line new_line 646: end
# File lib/haml/parser.rb, line 653 653: def handle_ruby_multiline(text) 654: text = text.rstrip 655: return text unless is_ruby_multiline?(text) 656: un_next_line @next_line.full 657: begin 658: new_line = raw_next_line.first 659: break if new_line == :eod 660: next if new_line.strip.empty? 661: text << " " << new_line.strip 662: end while is_ruby_multiline?(new_line.strip) 663: next_line 664: text 665: end
Checks whether or not `line` is in a multiline sequence.
# File lib/haml/parser.rb, line 649 649: def is_multiline?(text) 650: text && text.length > 1 && text[1] == MULTILINE_CHAR_VALUE && text[2] == \s\ 651: end
# File lib/haml/parser.rb, line 667 667: def is_ruby_multiline?(text) 668: text && text.length > 1 && text[1] == ,, && text[2] != ?? && text[3..2] != "?\\" 669: end
# File lib/haml/parser.rb, line 217 217: def mid_block_keyword?(text) 218: MID_BLOCK_KEYWORDS.include?(block_keyword(text)) 219: end
# File lib/haml/parser.rb, line 601 601: def next_line 602: text, index = raw_next_line 603: return unless text 604: 605: # :eod is a special end-of-document marker 606: line = 607: if text == :eod 608: Line.new '-#', '-#', '-#', index, self, true 609: else 610: Line.new text.strip, text.lstrip.chomp, text, index, self, false 611: end 612: 613: # `flat?' here is a little outdated, 614: # so we have to manually check if either the previous or current line 615: # closes the flat block, as well as whether a new block is opened. 616: @line.tabs if @line 617: unless (flat? && !closes_flat?(line) && !closes_flat?(@line)) || 618: (@line && @line.text[0] == :: && line.full =~ %[^#{@line.full[/^\s+/]}\s]) 619: return next_line if line.text.empty? 620: 621: handle_multiline(line) 622: end 623: 624: @next_line = line 625: end
# File lib/haml/parser.rb, line 128 128: def parse 129: @root = @parent = ParseNode.new(:root) 130: @haml_comment = false 131: @indentation = nil 132: @line = next_line 133: 134: raise SyntaxError.new("Indenting at the beginning of the document is illegal.", @line.index) if @line.tabs != 0 135: 136: while next_line 137: process_indent(@line) unless @line.text.empty? 138: 139: if flat? 140: text = @line.full.dup 141: text = "" unless text.gsub!(/^#{@flat_spaces}/, '') 142: @filter_buffer << "#{text}\n" 143: @line = @next_line 144: next 145: end 146: 147: @tab_up = nil 148: process_line(@line.text, @line.index) unless @line.text.empty? || @haml_comment 149: if @parent.type != :haml_comment && (block_opened? || @tab_up) 150: @template_tabs += 1 151: @parent = @parent.children.last 152: end 153: 154: if !flat? && @next_line.tabs - @line.tabs > 1 155: raise SyntaxError.new("The line was indented #{@next_line.tabs - @line.tabs} levels deeper than the previous line.", @next_line.index) 156: end 157: 158: @line = @next_line 159: end 160: 161: # Close all the open tags 162: close until @parent.type == :root 163: @root 164: end
# File lib/haml/parser.rb, line 562 562: def parse_new_attribute(scanner) 563: unless name = scanner.scan(/[-:\w]+/) 564: return if scanner.scan(/\)/) 565: return false 566: end 567: 568: scanner.scan(/\s*/) 569: return name, [:static, true] unless scanner.scan(/=/) #/end 570: 571: scanner.scan(/\s*/) 572: unless quote = scanner.scan(/["']/) 573: return false unless var = scanner.scan(/(@@?|\$)?\w+/) 574: return name, [:dynamic, var] 575: end 576: 577: re = /((?:\\.|\#(?!\{)|[^#{quote}\\#])*)(#{quote}|#\{)/ 578: content = [] 579: loop do 580: return false unless scanner.scan(re) 581: content << [:str, scanner[1].gsub(/\\(.)/, '\1')] 582: break if scanner[2] == quote 583: content << [:ruby, balance(scanner, {{, }}, 1).first[0...1]] 584: end 585: 586: return name, [:static, content.first[1]] if content.size == 1 587: return name, [:dynamic, 588: '"' + content.map {|(t, v)| t == :str ? inspect_obj(v)[1...1] : "\#{#{v}}"}.join + '"'] 589: end
# File lib/haml/parser.rb, line 521 521: def parse_new_attributes(line) 522: line = line.dup 523: scanner = StringScanner.new(line) 524: last_line = @index 525: attributes = {} 526: 527: scanner.scan(/\(\s*/) 528: loop do 529: name, value = parse_new_attribute(scanner) 530: break if name.nil? 531: 532: if name == false 533: text = (Haml::Shared.balance(line, ((, ))) || [line]).first 534: raise Haml::SyntaxError.new("Invalid attribute list: #{text.inspect}.", last_line - 1) 535: end 536: attributes[name] = value 537: scanner.scan(/\s*/) 538: 539: if scanner.eos? 540: line << " " << @next_line.text 541: last_line += 1 542: next_line 543: scanner.scan(/\s*/) 544: end 545: end 546: 547: static_attributes = {} 548: dynamic_attributes = "{" 549: attributes.each do |name, (type, val)| 550: if type == :static 551: static_attributes[name] = val 552: else 553: dynamic_attributes << inspect_obj(name) << " => " << val << "," 554: end 555: end 556: dynamic_attributes << "}" 557: dynamic_attributes = nil if dynamic_attributes == "{}" 558: 559: return [static_attributes, dynamic_attributes], scanner.rest, last_line 560: end
# File lib/haml/parser.rb, line 500 500: def parse_old_attributes(line) 501: line = line.dup 502: last_line = @index 503: 504: begin 505: attributes_hash, rest = balance(line, {{, }}) 506: rescue SyntaxError => e 507: if line.strip[1] == ,, && e.message == "Unbalanced brackets." 508: line << "\n" << @next_line.text 509: last_line += 1 510: next_line 511: retry 512: end 513: 514: raise e 515: end 516: 517: attributes_hash = attributes_hash[1...1] if attributes_hash 518: return attributes_hash, rest, last_line 519: end
# File lib/haml/parser.rb, line 447 447: def parse_static_hash(text) 448: attributes = {} 449: scanner = StringScanner.new(text) 450: scanner.scan(/\s+/) 451: until scanner.eos? 452: return unless key = scanner.scan(LITERAL_VALUE_REGEX) 453: return unless scanner.scan(/\s*=>\s*/) 454: return unless value = scanner.scan(LITERAL_VALUE_REGEX) 455: return unless scanner.scan(/\s*(?:,|$)\s*/) 456: attributes[eval(key).to_s] = eval(value).to_s 457: end 458: attributes 459: end
Parses a line into tag_name, attributes, attributes_hash, object_ref, action, value
# File lib/haml/parser.rb, line 462 462: def parse_tag(line) 463: raise SyntaxError.new("Invalid tag: \"#{line}\".") unless match = line.scan(/%([-:\w]+)([-:\w\.\#]*)(.*)/)[0] 464: 465: tag_name, attributes, rest = match 466: raise SyntaxError.new("Illegal element: classes and ids must have values.") if attributes =~ /[\.#](\.|#|\z)/ 467: 468: new_attributes_hash = old_attributes_hash = last_line = nil 469: object_ref = "nil" 470: attributes_hashes = {} 471: while rest 472: case rest[0] 473: when {{ 474: break if old_attributes_hash 475: old_attributes_hash, rest, last_line = parse_old_attributes(rest) 476: attributes_hashes[:old] = old_attributes_hash 477: when (( 478: break if new_attributes_hash 479: new_attributes_hash, rest, last_line = parse_new_attributes(rest) 480: attributes_hashes[:new] = new_attributes_hash 481: when [[ 482: break unless object_ref == "nil" 483: object_ref, rest = balance(rest, [[, ]]) 484: else; break 485: end 486: end 487: 488: if rest 489: nuke_whitespace, action, value = rest.scan(/(<>|><|[><])?([=\/\~&!])?(.*)?/)[0] 490: nuke_whitespace ||= '' 491: nuke_outer_whitespace = nuke_whitespace.include? '>' 492: nuke_inner_whitespace = nuke_whitespace.include? '<' 493: end 494: 495: value = value.to_s.strip 496: [tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace, 497: nuke_inner_whitespace, action, value, last_line || @index] 498: end
# File lib/haml/parser.rb, line 226 226: def plain(text, escape_html = nil) 227: if block_opened? 228: raise SyntaxError.new("Illegal nesting: nesting within plain text is illegal.", @next_line.index) 229: end 230: 231: unless contains_interpolation?(text) 232: return ParseNode.new(:plain, @index, :text => text) 233: end 234: 235: escape_html = @options[:escape_html] if escape_html.nil? 236: script(unescape_interpolation(text, escape_html), !:escape_html) 237: end
Processes and deals with lowering indentation.
# File lib/haml/parser.rb, line 167 167: def process_indent(line) 168: return unless line.tabs <= @template_tabs && @template_tabs > 0 169: 170: to_close = @template_tabs - line.tabs 171: to_close.times {|i| close unless to_close - 1 - i == 0 && mid_block_keyword?(line.text)} 172: end
Processes a single line of Haml.
This method doesn’t return anything; it simply processes the line and adds the appropriate code to `@precompiled`.
# File lib/haml/parser.rb, line 178 178: def process_line(text, index) 179: @index = index + 1 180: 181: case text[0] 182: when DIV_CLASS; push div(text) 183: when DIV_ID 184: return push plain(text) if text[1] == {{ 185: push div(text) 186: when ELEMENT; push tag(text) 187: when COMMENT; push comment(text[1..1].strip) 188: when SANITIZE 189: return push plain(text[3..1].strip, :escape_html) if text[1..2] == "==" 190: return push script(text[2..1].strip, :escape_html) if text[1] == SCRIPT 191: return push flat_script(text[2..1].strip, :escape_html) if text[1] == FLAT_SCRIPT 192: return push plain(text[1..1].strip, :escape_html) if text[1] == \s\ 193: push plain(text) 194: when SCRIPT 195: return push plain(text[2..1].strip) if text[1] == SCRIPT 196: push script(text[1..1]) 197: when FLAT_SCRIPT; push flat_script(text[1..1]) 198: when SILENT_SCRIPT; push silent_script(text) 199: when FILTER; push filter(text[1..1].downcase) 200: when DOCTYPE 201: return push doctype(text) if text[0...3] == '!!!' 202: return push plain(text[3..1].strip, !:escape_html) if text[1..2] == "==" 203: return push script(text[2..1].strip, !:escape_html) if text[1] == SCRIPT 204: return push flat_script(text[2..1].strip, !:escape_html) if text[1] == FLAT_SCRIPT 205: return push plain(text[1..1].strip, !:escape_html) if text[1] == \s\ 206: push plain(text) 207: when ESCAPE; push plain(text[1..1]) 208: else; push plain(text) 209: end 210: end
# File lib/haml/parser.rb, line 221 221: def push(node) 222: @parent.children << node 223: node.parent = @parent 224: end
# File lib/haml/parser.rb, line 591 591: def raw_next_line 592: text = @template.shift 593: return unless text 594: 595: index = @template_index 596: @template_index += 1 597: 598: return text, index 599: end
# File lib/haml/parser.rb, line 239 239: def script(text, escape_html = nil, preserve = false) 240: raise SyntaxError.new("There's no Ruby code for = to evaluate.") if text.empty? 241: text = handle_ruby_multiline(text) 242: escape_html = @options[:escape_html] if escape_html.nil? 243: 244: ParseNode.new(:script, @index, :text => text, :escape_html => escape_html, 245: :preserve => preserve) 246: end
# File lib/haml/parser.rb, line 253 253: def silent_script(text) 254: return haml_comment(text[2..1]) if text[1] == SILENT_COMMENT 255: 256: raise SyntaxError.new(You don't need to use "- end" in Haml. Un-indent to close a block:- if foo? %strong Foo!- else Not foo.%p This line is un-indented, so it isn't part of the "if" block.rstrip, @index - 1) if text[1..1].strip == "end" 257: 258: text = handle_ruby_multiline(text) 259: keyword = block_keyword(text) 260: 261: @tab_up = ["if", "case"].include?(keyword) 262: ParseNode.new(:silent_script, @index, 263: :text => text[1..1], :keyword => keyword) 264: end
# File lib/haml/parser.rb, line 278 278: def tag(line) 279: tag_name, attributes, attributes_hashes, object_ref, nuke_outer_whitespace, 280: nuke_inner_whitespace, action, value, last_line = parse_tag(line) 281: 282: preserve_tag = @options[:preserve].include?(tag_name) 283: nuke_inner_whitespace ||= preserve_tag 284: preserve_tag = false if @options[:ugly] 285: escape_html = (action == '&' || (action != '!' && @options[:escape_html])) 286: 287: case action 288: when '/'; self_closing = true 289: when '~'; parse = preserve_script = true 290: when '=' 291: parse = true 292: if value[0] == == 293: value = unescape_interpolation(value[1..1].strip, escape_html) 294: escape_html = false 295: end 296: when '&', '!' 297: if value[0] == == || value[0] == ~~ 298: parse = true 299: preserve_script = (value[0] == ~~) 300: if value[1] == == 301: value = unescape_interpolation(value[2..1].strip, escape_html) 302: escape_html = false 303: else 304: value = value[1..1].strip 305: end 306: elsif contains_interpolation?(value) 307: value = unescape_interpolation(value, escape_html) 308: parse = true 309: escape_html = false 310: end 311: else 312: if contains_interpolation?(value) 313: value = unescape_interpolation(value, escape_html) 314: parse = true 315: escape_html = false 316: end 317: end 318: 319: attributes = Parser.parse_class_and_id(attributes) 320: attributes_list = [] 321: 322: if attributes_hashes[:new] 323: static_attributes, attributes_hash = attributes_hashes[:new] 324: Buffer.merge_attrs(attributes, static_attributes) if static_attributes 325: attributes_list << attributes_hash 326: end 327: 328: if attributes_hashes[:old] 329: static_attributes = parse_static_hash(attributes_hashes[:old]) 330: Buffer.merge_attrs(attributes, static_attributes) if static_attributes 331: attributes_list << attributes_hashes[:old] unless static_attributes || @options[:suppress_eval] 332: end 333: 334: attributes_list.compact! 335: 336: raise SyntaxError.new("Illegal nesting: nesting within a self-closing tag is illegal.", @next_line.index) if block_opened? && self_closing 337: raise SyntaxError.new("There's no Ruby code for #{action} to evaluate.", last_line - 1) if parse && value.empty? 338: raise SyntaxError.new("Self-closing tags can't have content.", last_line - 1) if self_closing && !value.empty? 339: 340: if block_opened? && !value.empty? && !is_ruby_multiline?(value) 341: raise SyntaxError.new("Illegal nesting: content can't be both given on the same line as %#{tag_name} and nested within it.", @next_line.index) 342: end 343: 344: self_closing ||= !!(!block_opened? && value.empty? && @options[:autoclose].any? {|t| t === tag_name}) 345: value = nil if value.empty? && (block_opened? || self_closing) 346: value = handle_ruby_multiline(value) if parse 347: 348: ParseNode.new(:tag, @index, :name => tag_name, :attributes => attributes, 349: :attributes_hashes => attributes_list, :self_closing => self_closing, 350: :nuke_inner_whitespace => nuke_inner_whitespace, 351: :nuke_outer_whitespace => nuke_outer_whitespace, :object_ref => object_ref, 352: :escape_html => escape_html, :preserve_tag => preserve_tag, 353: :preserve_script => preserve_script, :parse => parse, :value => value) 354: end
# File lib/haml/parser.rb, line 631 631: def un_next_line(line) 632: @template.unshift line 633: @template_index -= 1 634: end
# File lib/haml/parser.rb, line 675 675: def unescape_interpolation(str, escape_html = nil) 676: res = '' 677: rest = Haml::Shared.handle_interpolation str.dump do |scan| 678: escapes = (scan[2].size - 1) / 2 679: res << scan.matched[0...3 - escapes] 680: if escapes % 2 == 1 681: res << '#{' 682: else 683: content = eval('"' + balance(scan, {{, }}, 1)[0][0...1] + '"') 684: content = "Haml::Helpers.html_escape((#{content}))" if escape_html 685: res << '#{' + content + "}"# Use eval to get rid of string escapes 686: end 687: end 688: res + rest 689: end
Disabled; run with --debug to generate this.
Generated with the Darkfish Rdoc Generator 1.1.6.
Renders an XHTML comment.