| | 454 | |
| | 455 | module Kernel |
| | 456 | class << self |
| | 457 | alias_method :blank_slate_method_added, :method_added |
| | 458 | |
| | 459 | # Detect method additions to Kernel and remove them in the |
| | 460 | # BlankSlate class. |
| | 461 | def method_added(name) |
| | 462 | blank_slate_method_added(name) |
| | 463 | return if self != Kernel |
| | 464 | Builder::BlankSlate.hide(name) |
| | 465 | end |
| | 466 | end |
| | 467 | end |
| | 468 | |
| | 469 | class Object |
| | 470 | class << self |
| | 471 | alias_method :blank_slate_method_added, :method_added |
| | 472 | |
| | 473 | # Detect method additions to Object and remove them in the |
| | 474 | # BlankSlate class. |
| | 475 | def method_added(name) |
| | 476 | blank_slate_method_added(name) |
| | 477 | return if self != Object |
| | 478 | Builder::BlankSlate.hide(name) |
| | 479 | end |
| | 480 | end |
| | 481 | end |
| | 482 | module Builder |
| | 483 | |
| | 484 | # BlankSlate provides an abstract base class with no predefined |
| | 485 | # methods (except for <tt>\_\_send__</tt> and <tt>\_\_id__</tt>). |
| | 486 | # BlankSlate is useful as a base class when writing classes that |
| | 487 | # depend upon <tt>method_missing</tt> (e.g. dynamic proxies). |
| | 488 | class BlankSlate |
| | 489 | class << self |
| | 490 | |
| | 491 | # Hide the method named +name+ in the BlankSlate class. Don't |
| | 492 | # hide +instance_eval+ or any method beginning with "__". |
| | 493 | def hide(name) |
| | 494 | undef_method name if |
| | 495 | instance_methods.include?(name.to_s) and |
| | 496 | name !~ /^(__|instance_eval)/ |
| | 497 | end |
| | 498 | end |
| | 499 | |
| | 500 | instance_methods.each { |m| hide(m) } |
| | 501 | end |
| | 502 | |
| | 503 | module XChar # :nodoc: |
| | 504 | |
| | 505 | # See |
| | 506 | # http://intertwingly.net/stories/2004/04/14/i18n.html#CleaningWindows |
| | 507 | # for details. |
| | 508 | CP1252 = { # :nodoc: |
| | 509 | 128 => 8364, # euro sign |
| | 510 | 130 => 8218, # single low-9 quotation mark |
| | 511 | 131 => 402, # latin small letter f with hook |
| | 512 | 132 => 8222, # double low-9 quotation mark |
| | 513 | 133 => 8230, # horizontal ellipsis |
| | 514 | 134 => 8224, # dagger |
| | 515 | 135 => 8225, # double dagger |
| | 516 | 136 => 710, # modifier letter circumflex accent |
| | 517 | 137 => 8240, # per mille sign |
| | 518 | 138 => 352, # latin capital letter s with caron |
| | 519 | 139 => 8249, # single left-pointing angle quotation mark |
| | 520 | 140 => 338, # latin capital ligature oe |
| | 521 | 142 => 381, # latin capital letter z with caron |
| | 522 | 145 => 8216, # left single quotation mark |
| | 523 | 146 => 8217, # right single quotation mark |
| | 524 | 147 => 8220, # left double quotation mark |
| | 525 | 148 => 8221, # right double quotation mark |
| | 526 | 149 => 8226, # bullet |
| | 527 | 150 => 8211, # en dash |
| | 528 | 151 => 8212, # em dash |
| | 529 | 152 => 732, # small tilde |
| | 530 | 153 => 8482, # trade mark sign |
| | 531 | 154 => 353, # latin small letter s with caron |
| | 532 | 155 => 8250, # single right-pointing angle quotation mark |
| | 533 | 156 => 339, # latin small ligature oe |
| | 534 | 158 => 382, # latin small letter z with caron |
| | 535 | 159 => 376, # latin capital letter y with diaeresis |
| | 536 | } |
| | 537 | |
| | 538 | # See http://www.w3.org/TR/REC-xml/#dt-chardata for details. |
| | 539 | PREDEFINED = { |
| | 540 | 38 => '&', # ampersand |
| | 541 | 60 => '<', # left angle bracket |
| | 542 | 62 => '>', # right angle bracket |
| | 543 | } |
| | 544 | |
| | 545 | # See http://www.w3.org/TR/REC-xml/#charsets for details. |
| | 546 | VALID = [ |
| | 547 | [0x9, 0xA, 0xD], |
| | 548 | (0x20..0xD7FF), |
| | 549 | (0xE000..0xFFFD), |
| | 550 | (0x10000..0x10FFFF) |
| | 551 | ] |
| | 552 | end |
| | 553 | |
| | 554 | end |
| | 555 | |
| | 556 | ###################################################################### |
| | 557 | # Enhance the Fixnum class with a XML escaped character conversion. |
| | 558 | # |
| | 559 | class Fixnum |
| | 560 | XChar = Builder::XChar if ! defined?(XChar) |
| | 561 | |
| | 562 | # XML escaped version of chr |
| | 563 | def xchr |
| | 564 | n = XChar::CP1252[self] || self |
| | 565 | n = 42 unless XChar::VALID.find {|range| range.include? n} |
| | 566 | XChar::PREDEFINED[n] or (n<128 ? n.chr : "&##{n};") |
| | 567 | end |
| | 568 | end |
| | 569 | |
| | 570 | |
| | 571 | ###################################################################### |
| | 572 | # Enhance the String class with a XML escaped character version of |
| | 573 | # to_s. |
| | 574 | # |
| | 575 | class String |
| | 576 | # XML escaped version of to_s |
| | 577 | def to_xs |
| | 578 | unpack('U*').map {|n| n.xchr}.join # ASCII, UTF-8 |
| | 579 | rescue |
| | 580 | unpack('C*').map {|n| n.xchr}.join # ISO-8859-1, WIN-1252 |
| | 581 | end |
| | 582 | end |
| | 583 | |
| | 584 | module Builder |
| | 585 | |
| | 586 | # Generic error for builder |
| | 587 | class IllegalBlockError < RuntimeError; end |
| | 588 | |
| | 589 | # XmlBase is a base class for building XML builders. See |
| | 590 | # Builder::XmlMarkup and Builder::XmlEvents for examples. |
| | 591 | class XmlBase < BlankSlate |
| | 592 | |
| | 593 | # Create an XML markup builder. |
| | 594 | # |
| | 595 | # out:: Object receiving the markup. +out+ must respond to |
| | 596 | # <tt><<</tt>. |
| | 597 | # indent:: Number of spaces used for indentation (0 implies no |
| | 598 | # indentation and no line breaks). |
| | 599 | # initial:: Level of initial indentation. |
| | 600 | # |
| | 601 | def initialize(indent=0, initial=0) |
| | 602 | @indent = indent |
| | 603 | @level = initial |
| | 604 | end |
| | 605 | |
| | 606 | # Create a tag named +sym+. Other than the first argument which |
| | 607 | # is the tag name, the arguements are the same as the tags |
| | 608 | # implemented via <tt>method_missing</tt>. |
| | 609 | def tag!(sym, *args, &block) |
| | 610 | self.__send__(sym, *args, &block) |
| | 611 | end |
| | 612 | |
| | 613 | # Create XML markup based on the name of the method. This method |
| | 614 | # is never invoked directly, but is called for each markup method |
| | 615 | # in the markup block. |
| | 616 | def method_missing(sym, *args, &block) |
| | 617 | text = nil |
| | 618 | attrs = nil |
| | 619 | sym = "#{sym}:#{args.shift}" if args.first.kind_of?(Symbol) |
| | 620 | args.each do |arg| |
| | 621 | case arg |
| | 622 | when Hash |
| | 623 | attrs ||= {} |
| | 624 | attrs.merge!(arg) |
| | 625 | else |
| | 626 | text ||= '' |
| | 627 | text << arg.to_s |
| | 628 | end |
| | 629 | end |
| | 630 | if block |
| | 631 | unless text.nil? |
| | 632 | raise ArgumentError, "XmlMarkup cannot mix a text argument with a block" |
| | 633 | end |
| | 634 | _capture_outer_self(block) if @self.nil? |
| | 635 | _indent |
| | 636 | _start_tag(sym, attrs) |
| | 637 | _newline |
| | 638 | _nested_structures(block) |
| | 639 | _indent |
| | 640 | _end_tag(sym) |
| | 641 | _newline |
| | 642 | elsif text.nil? |
| | 643 | _indent |
| | 644 | _start_tag(sym, attrs, true) |
| | 645 | _newline |
| | 646 | else |
| | 647 | _indent |
| | 648 | _start_tag(sym, attrs) |
| | 649 | text! text |
| | 650 | _end_tag(sym) |
| | 651 | _newline |
| | 652 | end |
| | 653 | @target |
| | 654 | end |
| | 655 | |
| | 656 | # Append text to the output target. Escape any markup. May be |
| | 657 | # used within the markup brakets as: |
| | 658 | # |
| | 659 | # builder.p { |b| b.br; b.text! "HI" } #=> <p><br/>HI</p> |
| | 660 | def text!(text) |
| | 661 | _text(_escape(text)) |
| | 662 | end |
| | 663 | |
| | 664 | # Append text to the output target without escaping any markup. |
| | 665 | # May be used within the markup brakets as: |
| | 666 | # |
| | 667 | # builder.p { |x| x << "<br/>HI" } #=> <p><br/>HI</p> |
| | 668 | # |
| | 669 | # This is useful when using non-builder enabled software that |
| | 670 | # generates strings. Just insert the string directly into the |
| | 671 | # builder without changing the inserted markup. |
| | 672 | # |
| | 673 | # It is also useful for stacking builder objects. Builders only |
| | 674 | # use <tt><<</tt> to append to the target, so by supporting this |
| | 675 | # method/operation builders can use other builders as their |
| | 676 | # targets. |
| | 677 | def <<(text) |
| | 678 | _text(text) |
| | 679 | end |
| | 680 | |
| | 681 | # For some reason, nil? is sent to the XmlMarkup object. If nil? |
| | 682 | # is not defined and method_missing is invoked, some strange kind |
| | 683 | # of recursion happens. Since nil? won't ever be an XML tag, it |
| | 684 | # is pretty safe to define it here. (Note: this is an example of |
| | 685 | # cargo cult programming, |
| | 686 | # cf. http://fishbowl.pastiche.org/2004/10/13/cargo_cult_programming). |
| | 687 | def nil? |
| | 688 | false |
| | 689 | end |
| | 690 | |
| | 691 | private |
| | 692 | |
| | 693 | def _escape(text) |
| | 694 | text.to_xs |
| | 695 | end |
| | 696 | |
| | 697 | def _escape_quote(text) |
| | 698 | _escape(text).gsub(%r{"}, '"') # " WART |
| | 699 | end |
| | 700 | |
| | 701 | def _capture_outer_self(block) |
| | 702 | @self = eval("self", block) |
| | 703 | end |
| | 704 | |
| | 705 | def _newline |
| | 706 | return if @indent == 0 |
| | 707 | text! "\n" |
| | 708 | end |
| | 709 | |
| | 710 | def _indent |
| | 711 | return if @indent == 0 || @level == 0 |
| | 712 | text!(" " * (@level * @indent)) |
| | 713 | end |
| | 714 | |
| | 715 | def _nested_structures(block) |
| | 716 | @level += 1 |
| | 717 | block.call(self) |
| | 718 | ensure |
| | 719 | @level -= 1 |
| | 720 | end |
| | 721 | end |
| | 722 | # Create XML markup easily. All (well, almost all) methods sent to |
| | 723 | # an XmlMarkup object will be translated to the equivalent XML |
| | 724 | # markup. Any method with a block will be treated as an XML markup |
| | 725 | # tag with nested markup in the block. |
| | 726 | # |
| | 727 | # Examples will demonstrate this easier than words. In the |
| | 728 | # following, +xm+ is an +XmlMarkup+ object. |
| | 729 | # |
| | 730 | # xm.em("emphasized") # => <em>emphasized</em> |
| | 731 | # xm.em { xmm.b("emp & bold") } # => <em><b>emph & bold</b></em> |
| | 732 | # xm.a("A Link", "href"=>"http://onestepback.org") |
| | 733 | # # => <a href="http://onestepback.org">A Link</a> |
| | 734 | # xm.div { br } # => <div><br/></div> |
| | 735 | # xm.target("name"=>"compile", "option"=>"fast") |
| | 736 | # # => <target option="fast" name="compile"\> |
| | 737 | # # NOTE: order of attributes is not specified. |
| | 738 | # |
| | 739 | # xm.instruct! # <?xml version="1.0" encoding="UTF-8"?> |
| | 740 | # xm.html { # <html> |
| | 741 | # xm.head { # <head> |
| | 742 | # xm.title("History") # <title>History</title> |
| | 743 | # } # </head> |
| | 744 | # xm.body { # <body> |
| | 745 | # xm.comment! "HI" # <!-- HI --> |
| | 746 | # xm.h1("Header") # <h1>Header</h1> |
| | 747 | # xm.p("paragraph") # <p>paragraph</p> |
| | 748 | # } # </body> |
| | 749 | # } # </html> |
| | 750 | # |
| | 751 | # == Notes: |
| | 752 | # |
| | 753 | # * The order that attributes are inserted in markup tags is |
| | 754 | # undefined. |
| | 755 | # |
| | 756 | # * Sometimes you wish to insert text without enclosing tags. Use |
| | 757 | # the <tt>text!</tt> method to accomplish this. |
| | 758 | # |
| | 759 | # Example: |
| | 760 | # |
| | 761 | # xm.div { # <div> |
| | 762 | # xm.text! "line"; xm.br # line<br/> |
| | 763 | # xm.text! "another line"; xmbr # another line<br/> |
| | 764 | # } # </div> |
| | 765 | # |
| | 766 | # * The special XML characters <, >, and & are converted to <, |
| | 767 | # > and & automatically. Use the <tt><<</tt> operation to |
| | 768 | # insert text without modification. |
| | 769 | # |
| | 770 | # * Sometimes tags use special characters not allowed in ruby |
| | 771 | # identifiers. Use the <tt>tag!</tt> method to handle these |
| | 772 | # cases. |
| | 773 | # |
| | 774 | # Example: |
| | 775 | # |
| | 776 | # xml.tag!("SOAP:Envelope") { ... } |
| | 777 | # |
| | 778 | # will produce ... |
| | 779 | # |
| | 780 | # <SOAP:Envelope> ... </SOAP:Envelope>" |
| | 781 | # |
| | 782 | # <tt>tag!</tt> will also take text and attribute arguments (after |
| | 783 | # the tag name) like normal markup methods. (But see the next |
| | 784 | # bullet item for a better way to handle XML namespaces). |
| | 785 | # |
| | 786 | # * Direct support for XML namespaces is now available. If the |
| | 787 | # first argument to a tag call is a symbol, it will be joined to |
| | 788 | # the tag to produce a namespace:tag combination. It is easier to |
| | 789 | # show this than describe it. |
| | 790 | # |
| | 791 | # xml.SOAP :Envelope do ... end |
| | 792 | # |
| | 793 | # Just put a space before the colon in a namespace to produce the |
| | 794 | # right form for builder (e.g. "<tt>SOAP:Envelope</tt>" => |
| | 795 | # "<tt>xml.SOAP :Envelope</tt>") |
| | 796 | # |
| | 797 | # * XmlMarkup builds the markup in any object (called a _target_) |
| | 798 | # that accepts the <tt><<</tt> method. If no target is given, |
| | 799 | # then XmlMarkup defaults to a string target. |
| | 800 | # |
| | 801 | # Examples: |
| | 802 | # |
| | 803 | # xm = Builder::XmlMarkup.new |
| | 804 | # result = xm.title("yada") |
| | 805 | # # result is a string containing the markup. |
| | 806 | # |
| | 807 | # buffer = "" |
| | 808 | # xm = Builder::XmlMarkup.new(buffer) |
| | 809 | # # The markup is appended to buffer (using <<) |
| | 810 | # |
| | 811 | # xm = Builder::XmlMarkup.new(STDOUT) |
| | 812 | # # The markup is written to STDOUT (using <<) |
| | 813 | # |
| | 814 | # xm = Builder::XmlMarkup.new |
| | 815 | # x2 = Builder::XmlMarkup.new(:target=>xm) |
| | 816 | # # Markup written to +x2+ will be send to +xm+. |
| | 817 | # |
| | 818 | # * Indentation is enabled by providing the number of spaces to |
| | 819 | # indent for each level as a second argument to XmlBuilder.new. |
| | 820 | # Initial indentation may be specified using a third parameter. |
| | 821 | # |
| | 822 | # Example: |
| | 823 | # |
| | 824 | # xm = Builder.new(:ident=>2) |
| | 825 | # # xm will produce nicely formatted and indented XML. |
| | 826 | # |
| | 827 | # xm = Builder.new(:indent=>2, :margin=>4) |
| | 828 | # # xm will produce nicely formatted and indented XML with 2 |
| | 829 | # # spaces per indent and an over all indentation level of 4. |
| | 830 | # |
| | 831 | # builder = Builder::XmlMarkup.new(:target=>$stdout, :indent=>2) |
| | 832 | # builder.name { |b| b.first("Jim"); b.last("Weirich) } |
| | 833 | # # prints: |
| | 834 | # # <name> |
| | 835 | # # <first>Jim</first> |
| | 836 | # # <last>Weirich</last> |
| | 837 | # # </name> |
| | 838 | # |
| | 839 | # * The instance_eval implementation which forces self to refer to |
| | 840 | # the message receiver as self is now obsolete. We now use normal |
| | 841 | # block calls to execute the markup block. This means that all |
| | 842 | # markup methods must now be explicitly send to the xml builder. |
| | 843 | # For instance, instead of |
| | 844 | # |
| | 845 | # xml.div { strong("text") } |
| | 846 | # |
| | 847 | # you need to write: |
| | 848 | # |
| | 849 | # xml.div { xml.strong("text") } |
| | 850 | # |
| | 851 | # Although more verbose, the subtle change in semantics within the |
| | 852 | # block was found to be prone to error. To make this change a |
| | 853 | # little less cumbersome, the markup block now gets the markup |
| | 854 | # object sent as an argument, allowing you to use a shorter alias |
| | 855 | # within the block. |
| | 856 | # |
| | 857 | # For example: |
| | 858 | # |
| | 859 | # xml_builder = Builder::XmlMarkup.new |
| | 860 | # xml_builder.div { |xml| |
| | 861 | # xml.stong("text") |
| | 862 | # } |
| | 863 | # |
| | 864 | class XmlMarkup < XmlBase |
| | 865 | |
| | 866 | # Create an XML markup builder. Parameters are specified by an |
| | 867 | # option hash. |
| | 868 | # |
| | 869 | # :target=><em>target_object</em>:: |
| | 870 | # Object receiving the markup. +out+ must respond to the |
| | 871 | # <tt><<</tt> operator. The default is a plain string target. |
| | 872 | # |
| | 873 | # :indent=><em>indentation</em>:: |
| | 874 | # Number of spaces used for indentation. The default is no |
| | 875 | # indentation and no line breaks. |
| | 876 | # |
| | 877 | # :margin=><em>initial_indentation_level</em>:: |
| | 878 | # Amount of initial indentation (specified in levels, not |
| | 879 | # spaces). |
| | 880 | # |
| | 881 | # :escape_attrs=><b>OBSOLETE</em>:: |
| | 882 | # The :escape_attrs option is no longer supported by builder |
| | 883 | # (and will be quietly ignored). String attribute values are |
| | 884 | # now automatically escaped. If you need unescaped attribute |
| | 885 | # values (perhaps you are using entities in the attribute |
| | 886 | # values), then give the value as a Symbol. This allows much |
| | 887 | # finer control over escaping attribute values. |
| | 888 | # |
| | 889 | def initialize(options={}) |
| | 890 | indent = options[:indent] || 0 |
| | 891 | margin = options[:margin] || 0 |
| | 892 | super(indent, margin) |
| | 893 | @target = options[:target] || "" |
| | 894 | end |
| | 895 | |
| | 896 | # Return the target of the builder. |
| | 897 | def target! |
| | 898 | @target |
| | 899 | end |
| | 900 | |
| | 901 | def comment!(comment_text) |
| | 902 | _ensure_no_block block_given? |
| | 903 | _special("<!-- ", " -->", comment_text, nil) |
| | 904 | end |
| | 905 | |
| | 906 | # Insert an XML declaration into the XML markup. |
| | 907 | # |
| | 908 | # For example: |
| | 909 | # |
| | 910 | # xml.declare! :ELEMENT, :blah, "yada" |
| | 911 | # # => <!ELEMENT blah "yada"> |
| | 912 | def declare!(inst, *args, &block) |
| | 913 | _indent |
| | 914 | @target << "<!#{inst}" |
| | 915 | args.each do |arg| |
| | 916 | case arg |
| | 917 | when String |
| | 918 | @target << %{ "#{arg}"} |
| | 919 | when Symbol |
| | 920 | @target << " #{arg}" |
| | 921 | end |
| | 922 | end |
| | 923 | if block_given? |
| | 924 | @target << " [" |
| | 925 | _newline |
| | 926 | _nested_structures(block) |
| | 927 | @target << "]" |
| | 928 | end |
| | 929 | @target << ">" |
| | 930 | _newline |
| | 931 | end |
| | 932 | |
| | 933 | # Insert a processing instruction into the XML markup. E.g. |
| | 934 | # |
| | 935 | # For example: |
| | 936 | # |
| | 937 | # xml.instruct! |
| | 938 | # #=> <?xml version="1.0" encoding="UTF-8"?> |
| | 939 | # xml.instruct! :aaa, :bbb=>"ccc" |
| | 940 | # #=> <?aaa bbb="ccc"?> |
| | 941 | # |
| | 942 | def instruct!(directive_tag=:xml, attrs={}) |
| | 943 | _ensure_no_block block_given? |
| | 944 | if directive_tag == :xml |
| | 945 | a = { :version=>"1.0", :encoding=>"UTF-8" } |
| | 946 | attrs = a.merge attrs |
| | 947 | end |
| | 948 | _special( |
| | 949 | "<?#{directive_tag}", |
| | 950 | "?>", |
| | 951 | nil, |
| | 952 | attrs, |
| | 953 | [:version, :encoding, :standalone]) |
| | 954 | end |
| | 955 | |
| | 956 | # Insert a CDATA section into the XML markup. |
| | 957 | # |
| | 958 | # For example: |
| | 959 | # |
| | 960 | # xml.cdata!("text to be included in cdata") |
| | 961 | # #=> <![CDATA[text to be included in cdata]]> |
| | 962 | # |
| | 963 | def cdata!(text) |
| | 964 | _ensure_no_block block_given? |
| | 965 | _special("<![CDATA[", "]]>", text, nil) |
| | 966 | end |
| | 967 | |
| | 968 | private |
| | 969 | |
| | 970 | # NOTE: All private methods of a builder object are prefixed when |
| | 971 | # a "_" character to avoid possible conflict with XML tag names. |
| | 972 | |
| | 973 | # Insert text directly in to the builder's target. |
| | 974 | def _text(text) |
| | 975 | @target << text |
| | 976 | end |
| | 977 | |
| | 978 | # Insert special instruction. |
| | 979 | def _special(open, close, data=nil, attrs=nil, order=[]) |
| | 980 | _indent |
| | 981 | @target << open |
| | 982 | @target << data if data |
| | 983 | _insert_attributes(attrs, order) if attrs |
| | 984 | @target << close |
| | 985 | _newline |
| | 986 | end |
| | 987 | |
| | 988 | # Start an XML tag. If <tt>end_too</tt> is true, then the start |
| | 989 | # tag is also the end tag (e.g. <br/> |
| | 990 | def _start_tag(sym, attrs, end_too=false) |
| | 991 | @target << "<#{sym}" |
| | 992 | _insert_attributes(attrs) |
| | 993 | @target << "/" if end_too |
| | 994 | @target << ">" |
| | 995 | end |
| | 996 | |
| | 997 | # Insert an ending tag. |
| | 998 | def _end_tag(sym) |
| | 999 | @target << "</#{sym}>" |
| | 1000 | end |
| | 1001 | |
| | 1002 | # Insert the attributes (given in the hash). |
| | 1003 | def _insert_attributes(attrs, order=[]) |
| | 1004 | return if attrs.nil? |
| | 1005 | order.each do |k| |
| | 1006 | v = attrs[k] |
| | 1007 | @target << %{ #{k}="#{_attr_value(v)}"} if v |
| | 1008 | end |
| | 1009 | attrs.each do |k, v| |
| | 1010 | @target << %{ #{k}="#{_attr_value(v)}"} unless order.member?(k) |
| | 1011 | end |
| | 1012 | end |
| | 1013 | |
| | 1014 | def _attr_value(value) |
| | 1015 | case value |
| | 1016 | when Symbol |
| | 1017 | value.to_s |
| | 1018 | else |
| | 1019 | _escape_quote(value.to_s) |
| | 1020 | end |
| | 1021 | end |
| | 1022 | |
| | 1023 | def _ensure_no_block(got_block) |
| | 1024 | if got_block |
| | 1025 | fail IllegalBlockError, |
| | 1026 | "Blocks are not allowed on XML instructions" |
| | 1027 | end |
| | 1028 | end |
| | 1029 | |
| | 1030 | end |
| | 1031 | |
| | 1032 | # Create a series of SAX-like XML events (e.g. start_tag, end_tag) |
| | 1033 | # from the markup code. XmlEvent objects are used in a way similar |
| | 1034 | # to XmlMarkup objects, except that a series of events are generated |
| | 1035 | # and passed to a handler rather than generating character-based |
| | 1036 | # markup. |
| | 1037 | # |
| | 1038 | # Usage: |
| | 1039 | # xe = Builder::XmlEvents.new(hander) |
| | 1040 | # xe.title("HI") # Sends start_tag/end_tag/text messages to the handler. |
| | 1041 | # |
| | 1042 | # Indentation may also be selected by providing value for the |
| | 1043 | # indentation size and initial indentation level. |
| | 1044 | # |
| | 1045 | # xe = Builder::XmlEvents.new(handler, indent_size, initial_indent_level) |
| | 1046 | # |
| | 1047 | # == XML Event Handler |
| | 1048 | # |
| | 1049 | # The handler object must expect the following events. |
| | 1050 | # |
| | 1051 | # [<tt>start_tag(tag, attrs)</tt>] |
| | 1052 | # Announces that a new tag has been found. +tag+ is the name of |
| | 1053 | # the tag and +attrs+ is a hash of attributes for the tag. |
| | 1054 | # |
| | 1055 | # [<tt>end_tag(tag)</tt>] |
| | 1056 | # Announces that an end tag for +tag+ has been found. |
| | 1057 | # |
| | 1058 | # [<tt>text(text)</tt>] |
| | 1059 | # Announces that a string of characters (+text+) has been found. |
| | 1060 | # A series of characters may be broken up into more than one |
| | 1061 | # +text+ call, so the client cannot assume that a single |
| | 1062 | # callback contains all the text data. |
| | 1063 | # |
| | 1064 | class XmlEvents < XmlMarkup |
| | 1065 | def text!(text) |
| | 1066 | @target.text(text) |
| | 1067 | end |
| | 1068 | |
| | 1069 | def _start_tag(sym, attrs, end_too=false) |
| | 1070 | @target.start_tag(sym, attrs) |
| | 1071 | _end_tag(sym) if end_too |
| | 1072 | end |
| | 1073 | |
| | 1074 | def _end_tag(sym) |
| | 1075 | @target.end_tag(sym) |
| | 1076 | end |
| | 1077 | end |
| | 1078 | end |
| | 1079 | |
| | 1080 | module Markaby |
| | 1081 | VERSION = '0.5' |
| | 1082 | |
| | 1083 | class InvalidXhtmlError < Exception; end |
| | 1084 | |
| | 1085 | FORM_TAGS = [ :form, :input, :select, :textarea ] |
| | 1086 | SELF_CLOSING_TAGS = [ :base, :meta, :link, :hr, :br, :param, :img, :area, :input, :col ] |
| | 1087 | NO_PROXY = [ :hr, :br ] |
| | 1088 | |
| | 1089 | # Common sets of attributes. |
| | 1090 | AttrCore = [:id, :class, :style, :title] |
| | 1091 | AttrI18n = [:lang, 'xml:lang'.intern, :dir] |
| | 1092 | AttrEvents = [:onclick, :ondblclick, :onmousedown, :onmouseup, :onmouseover, :onmousemove, |
| | 1093 | :onmouseout, :onkeypress, :onkeydown, :onkeyup] |
| | 1094 | AttrFocus = [:accesskey, :tabindex, :onfocus, :onblur] |
| | 1095 | AttrHAlign = [:align, :char, :charoff] |
| | 1096 | AttrVAlign = [:valign] |
| | 1097 | Attrs = AttrCore + AttrI18n + AttrEvents |
| | 1098 | |
| | 1099 | # All the tags and attributes from XHTML 1.0 Strict |
| | 1100 | class XHTMLStrict |
| | 1101 | class << self |
| | 1102 | attr_accessor :tags, :tagset, :forms, :self_closing, :doctype |
| | 1103 | end |
| | 1104 | @doctype = ["-//W3C//DTD XHTML 1.0 Strict//EN", "DTD/xhtml1-strict.dtd"] |
| | 1105 | @tagset = { |
| | 1106 | :html => AttrI18n + [:id, :xmlns], |
| | 1107 | :head => AttrI18n + [:id, :profile], |
| | 1108 | :title => AttrI18n + [:id], |
| | 1109 | :base => [:href, :id], |
| | 1110 | :meta => AttrI18n + [:id, :http, :name, :content, :scheme, 'http-equiv'.intern], |
| | 1111 | :link => Attrs + [:charset, :href, :hreflang, :type, :rel, :rev, :media], |
| | 1112 | :style => AttrI18n + [:id, :type, :media, :title, 'xml:space'.intern], |
| | 1113 | :script => [:id, :charset, :type, :src, :defer, 'xml:space'.intern], |
| | 1114 | :noscript => Attrs, |
| | 1115 | :body => Attrs + [:onload, :onunload], |
| | 1116 | :div => Attrs, |
| | 1117 | :p => Attrs, |
| | 1118 | :ul => Attrs, |
| | 1119 | :ol => Attrs, |
| | 1120 | :li => Attrs, |
| | 1121 | :dl => Attrs, |
| | 1122 | :dt => Attrs, |
| | 1123 | :dd => Attrs, |
| | 1124 | :address => Attrs, |
| | 1125 | :hr => Attrs, |
| | 1126 | :pre => Attrs + ['xml:space'.intern], |
| | 1127 | :blockquote => Attrs + [:cite], |
| | 1128 | :ins => Attrs + [:cite, :datetime], |
| | 1129 | :del => Attrs + [:cite, :datetime], |
| | 1130 | :a => Attrs + AttrFocus + [:charset, :type, :name, :href, :hreflang, :rel, :rev, :shape, :coords], |
| | 1131 | :span => Attrs, |
| | 1132 | :bdo => AttrCore + AttrEvents + [:lang, 'xml:lang'.intern, :dir], |
| | 1133 | :br => AttrCore, |
| | 1134 | :em => Attrs, |
| | 1135 | :strong => Attrs, |
| | 1136 | :dfn => Attrs, |
| | 1137 | :code => Attrs, |
| | 1138 | :samp => Attrs, |
| | 1139 | :kbd => Attrs, |
| | 1140 | :var => Attrs, |
| | 1141 | :cite => Attrs, |
| | 1142 | :abbr => Attrs, |
| | 1143 | :acronym => Attrs, |
| | 1144 | :q => Attrs + [:cite], |
| | 1145 | :sub => Attrs, |
| | 1146 | :sup => Attrs, |
| | 1147 | :tt => Attrs, |
| | 1148 | :i => Attrs, |
| | 1149 | :b => Attrs, |
| | 1150 | :big => Attrs, |
| | 1151 | :small => Attrs, |
| | 1152 | :object => Attrs + [:declare, :classid, :codebase, :data, :type, :codetype, :archive, :standby, :height, :width, :usemap, :name, :tabindex], |
| | 1153 | :param => [:id, :name, :value, :valuetype, :type], |
| | 1154 | :img => Attrs + [:src, :alt, :longdesc, :height, :width, :usemap, :ismap], |
| | 1155 | :map => AttrI18n + AttrEvents + [:id, :class, :style, :title, :name], |
| | 1156 | :area => Attrs + AttrFocus + [:shape, :coords, :href, :nohref, :alt], |
| | 1157 | :form => Attrs + [:action, :method, :enctype, :onsubmit, :onreset, :accept, :accept], |
| | 1158 | :label => Attrs + [:for, :accesskey, :onfocus, :onblur], |
| | 1159 | :input => Attrs + AttrFocus + [:type, :name, :value, :checked, :disabled, :readonly, :size, :maxlength, :src, :alt, :usemap, :onselect, :onchange, :accept], |
| | 1160 | :select => Attrs + [:name, :size, :multiple, :disabled, :tabindex, :onfocus, :onblur, :onchange], |
| | 1161 | :optgroup => Attrs + [:disabled, :label], |
| | 1162 | :option => Attrs + [:selected, :disabled, :label, :value], |
| | 1163 | :textarea => Attrs + AttrFocus + [:name, :rows, :cols, :disabled, :readonly, :onselect, :onchange], |
| | 1164 | :fieldset => Attrs, |
| | 1165 | :legend => Attrs + [:accesskey], |
| | 1166 | :button => Attrs + AttrFocus + [:name, :value, :type, :disabled], |
| | 1167 | :table => Attrs + [:summary, :width, :border, :frame, :rules, :cellspacing, :cellpadding], |
| | 1168 | :caption => Attrs, |
| | 1169 | :colgroup => Attrs + AttrHAlign + AttrVAlign + [:span, :width], |
| | 1170 | :col => Attrs + AttrHAlign + AttrVAlign + [:span, :width], |
| | 1171 | :thead => Attrs + AttrHAlign + AttrVAlign, |
| | 1172 | :tfoot => Attrs + AttrHAlign + AttrVAlign, |
| | 1173 | :tbody => Attrs + AttrHAlign + AttrVAlign, |
| | 1174 | :tr => Attrs + AttrHAlign + AttrVAlign, |
| | 1175 | :th => Attrs + AttrHAlign + AttrVAlign + [:abbr, :axis, :headers, :scope, :rowspan, :colspan], |
| | 1176 | :td => Attrs + AttrHAlign + AttrVAlign + [:abbr, :axis, :headers, :scope, :rowspan, :colspan], |
| | 1177 | :h1 => Attrs, |
| | 1178 | :h2 => Attrs, |
| | 1179 | :h3 => Attrs, |
| | 1180 | :h4 => Attrs, |
| | 1181 | :h5 => Attrs, |
| | 1182 | :h6 => Attrs |
| | 1183 | } |
| | 1184 | |
| | 1185 | @tags = @tagset.keys |
| | 1186 | @forms = @tags & FORM_TAGS |
| | 1187 | @self_closing = @tags & SELF_CLOSING_TAGS |
| | 1188 | end |
| | 1189 | |
| | 1190 | # Additional tags found in XHTML 1.0 Transitional |
| | 1191 | class XHTMLTransitional |
| | 1192 | class << self |
| | 1193 | attr_accessor :tags, :tagset, :forms, |