517: def simple_selector(statement, values, can_negate = true)
518: tag_name = nil
519: attributes = []
520: pseudo = []
521: negation = []
522:
523:
524:
525: statement.sub!(/^(\*|[[:alpha:]][\w\-]*)/) do |match|
526: match.strip!
527: tag_name = match.downcase unless match == "*"
528: @source << match
529: ""
530: end
531:
532:
533: while true
534:
535: next if statement.sub!(/^#(\?|[\w\-]+)/) do |match|
536: id = $1
537: if id == "?"
538: id = values.shift
539: end
540: @source << "##{id}"
541: id = Regexp.new("^#{Regexp.escape(id.to_s)}$") unless id.is_a?(Regexp)
542: attributes << ["id", id]
543: ""
544: end
545:
546:
547: next if statement.sub!(/^\.([\w\-]+)/) do |match|
548: class_name = $1
549: @source << ".#{class_name}"
550: class_name = Regexp.new("(^|\s)#{Regexp.escape(class_name)}($|\s)") unless class_name.is_a?(Regexp)
551: attributes << ["class", class_name]
552: ""
553: end
554:
555:
556: next if statement.sub!(/^\[\s*([[:alpha:]][\w\-]*)\s*((?:[~|^$*])?=)?\s*('[^']*'|"[^*]"|[^\]]*)\s*\]/) do |match|
557: name, equality, value = $1, $2, $3
558: if value == "?"
559: value = values.shift
560: else
561:
562: value.strip!
563: if (value[0] == ?" or value[0] == ?') and value[0] == value[-1]
564: value = value[1..-2]
565: end
566: end
567: @source << "[#{name}#{equality}'#{value}']"
568: attributes << [name.downcase.strip, attribute_match(equality, value)]
569: ""
570: end
571:
572:
573: next if statement.sub!(/^:root/) do |match|
574: pseudo << lambda do |element|
575: element.parent.nil? or not element.parent.tag?
576: end
577: @source << ":root"
578: ""
579: end
580:
581:
582: next if statement.sub!(/^:nth-(last-)?(child|of-type)\((odd|even|(\d+|\?)|(-?\d*|\?)?n([+\-]\d+|\?)?)\)/) do |match|
583: reverse = $1 == "last-"
584: of_type = $2 == "of-type"
585: @source << ":nth-#{$1}#{$2}("
586: case $3
587: when "odd"
588: pseudo << nth_child(2, 1, of_type, reverse)
589: @source << "odd)"
590: when "even"
591: pseudo << nth_child(2, 2, of_type, reverse)
592: @source << "even)"
593: when /^(\d+|\?)$/
594: b = ($1 == "?" ? values.shift : $1).to_i
595: pseudo << nth_child(0, b, of_type, reverse)
596: @source << "#{b})"
597: when /^(-?\d*|\?)?n([+\-]\d+|\?)?$/
598: a = ($1 == "?" ? values.shift :
599: $1 == "" ? 1 : $1 == "-" ? -1 : $1).to_i
600: b = ($2 == "?" ? values.shift : $2).to_i
601: pseudo << nth_child(a, b, of_type, reverse)
602: @source << (b >= 0 ? "#{a}n+#{b})" : "#{a}n#{b})")
603: else
604: raise ArgumentError, "Invalid nth-child #{match}"
605: end
606: ""
607: end
608:
609: next if statement.sub!(/^:(first|last)-(child|of-type)/) do |match|
610: reverse = $1 == "last"
611: of_type = $2 == "of-type"
612: pseudo << nth_child(0, 1, of_type, reverse)
613: @source << ":#{$1}-#{$2}"
614: ""
615: end
616:
617: next if statement.sub!(/^:only-(child|of-type)/) do |match|
618: of_type = $1 == "of-type"
619: pseudo << only_child(of_type)
620: @source << ":only-#{$1}"
621: ""
622: end
623:
624:
625:
626: next if statement.sub!(/^:empty/) do |match|
627: pseudo << lambda do |element|
628: empty = true
629: for child in element.children
630: if child.tag? or !child.content.strip.empty?
631: empty = false
632: break
633: end
634: end
635: empty
636: end
637: @source << ":empty"
638: ""
639: end
640:
641:
642: next if statement.sub!(/^:content\(\s*(\?|'[^']*'|"[^"]*"|[^)]*)\s*\)/) do |match|
643: content = $1
644: if content == "?"
645: content = values.shift
646: elsif (content[0] == ?" or content[0] == ?') and content[0] == content[-1]
647: content = content[1..-2]
648: end
649: @source << ":content('#{content}')"
650: content = Regexp.new("^#{Regexp.escape(content.to_s)}$") unless content.is_a?(Regexp)
651: pseudo << lambda do |element|
652: text = ""
653: for child in element.children
654: unless child.tag?
655: text << child.content
656: end
657: end
658: text.strip =~ content
659: end
660: ""
661: end
662:
663:
664: if statement.sub!(/^:not\(\s*/, "")
665: raise ArgumentError, "Double negatives are not missing feature" unless can_negate
666: @source << ":not("
667: negation << simple_selector(statement, values, false)
668: raise ArgumentError, "Negation not closed" unless statement.sub!(/^\s*\)/, "")
669: @source << ")"
670: next
671: end
672:
673:
674: break
675: end
676:
677:
678: {:tag_name=>tag_name, :attributes=>attributes, :pseudo=>pseudo, :negation=>negation}
679: end