|
2 | 2 |
|
3 | 3 | # **Standard Generalized Markup Language** for behaviour common to {HTML} and {SVG}. |
4 | 4 | class Phlex::SGML |
5 | | - UNSAFE_ATTRIBUTES = Set.new(%w[srcdoc sandbox http-equiv]).freeze |
6 | | - REF_ATTRIBUTES = Set.new(%w[href src action formaction lowsrc dynsrc background ping]).freeze |
7 | | - |
8 | 5 | ERBCompiler = ERB::Compiler.new("<>").tap do |compiler| |
9 | 6 | compiler.pre_cmd = [""] |
10 | 7 | compiler.put_cmd = "@_state.buffer.<<" |
@@ -463,265 +460,7 @@ def json_escape(string) |
463 | 460 | end |
464 | 461 |
|
465 | 462 | private def __render_attributes__(attributes) |
466 | | - @_state.buffer << (Phlex::ATTRIBUTE_CACHE[attributes] ||= __attributes__(attributes)) |
467 | | - end |
468 | | - |
469 | | - private def __attributes__(attributes, buffer = +"") |
470 | | - attributes.each do |k, v| |
471 | | - next unless v |
472 | | - |
473 | | - name = case k |
474 | | - when String then k |
475 | | - when Symbol then k.name.tr("_", "-") |
476 | | - else raise Phlex::ArgumentError.new("Attribute keys should be Strings or Symbols.") |
477 | | - end |
478 | | - |
479 | | - value = case v |
480 | | - when true |
481 | | - true |
482 | | - when String |
483 | | - v.gsub('"', """) |
484 | | - when Symbol |
485 | | - v.name.tr("_", "-").gsub('"', """) |
486 | | - when Integer, Float |
487 | | - v.to_s |
488 | | - when Date |
489 | | - v.iso8601 |
490 | | - when Time |
491 | | - v.respond_to?(:iso8601) ? v.iso8601 : v.strftime("%Y-%m-%dT%H:%M:%S%:z") |
492 | | - when Hash |
493 | | - case k |
494 | | - when :style |
495 | | - __styles__(v).gsub('"', """) |
496 | | - else |
497 | | - __nested_attributes__(v, "#{name}-", buffer) |
498 | | - end |
499 | | - when Array |
500 | | - case k |
501 | | - when :style |
502 | | - __styles__(v).gsub('"', """) |
503 | | - else |
504 | | - __nested_tokens__(v) |
505 | | - end |
506 | | - when Set |
507 | | - case k |
508 | | - when :style |
509 | | - __styles__(v).gsub('"', """) |
510 | | - else |
511 | | - __nested_tokens__(v.to_a) |
512 | | - end |
513 | | - when Phlex::SGML::SafeObject |
514 | | - v.to_s.gsub('"', """) |
515 | | - else |
516 | | - raise Phlex::ArgumentError.new("Invalid attribute value for #{k}: #{v.inspect}.") |
517 | | - end |
518 | | - |
519 | | - lower_name = name.downcase |
520 | | - |
521 | | - unless Phlex::SGML::SafeObject === v |
522 | | - normalized_name = lower_name.delete("^a-z-") |
523 | | - |
524 | | - if value != true && REF_ATTRIBUTES.include?(normalized_name) |
525 | | - case value |
526 | | - when String |
527 | | - if value.downcase.delete("^a-z:").start_with?("javascript:") |
528 | | - # We just ignore these because they were likely not specified by the developer. |
529 | | - next |
530 | | - end |
531 | | - else |
532 | | - raise Phlex::ArgumentError.new("Invalid attribute value for #{k}: #{v.inspect}.") |
533 | | - end |
534 | | - end |
535 | | - |
536 | | - if normalized_name.bytesize > 2 && normalized_name.start_with?("on") && !normalized_name.include?("-") |
537 | | - raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.") |
538 | | - end |
539 | | - |
540 | | - if UNSAFE_ATTRIBUTES.include?(normalized_name) |
541 | | - raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.") |
542 | | - end |
543 | | - end |
544 | | - |
545 | | - if name.match?(/[<>&"']/) |
546 | | - raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.") |
547 | | - end |
548 | | - |
549 | | - if lower_name.to_sym == :id && k != :id |
550 | | - raise Phlex::ArgumentError.new(":id attribute should only be passed as a lowercase symbol.") |
551 | | - end |
552 | | - |
553 | | - case value |
554 | | - when true |
555 | | - buffer << " " << name |
556 | | - when String |
557 | | - buffer << " " << name << '="' << value << '"' |
558 | | - end |
559 | | - end |
560 | | - |
561 | | - buffer |
562 | | - end |
563 | | - |
564 | | - # Provides the nested-attributes case for serializing out attributes. |
565 | | - # This allows us to skip many of the checks the `__attributes__` method must perform. |
566 | | - private def __nested_attributes__(attributes, base_name, buffer = +"") |
567 | | - attributes.each do |k, v| |
568 | | - next unless v |
569 | | - |
570 | | - if (root_key = (:_ == k)) |
571 | | - name = "" |
572 | | - original_base_name = base_name |
573 | | - base_name = base_name.delete_suffix("-") |
574 | | - else |
575 | | - name = case k |
576 | | - when String then k |
577 | | - when Symbol then k.name.tr("_", "-") |
578 | | - else raise Phlex::ArgumentError.new("Attribute keys should be Strings or Symbols") |
579 | | - end |
580 | | - |
581 | | - if name.match?(/[<>&"']/) |
582 | | - raise Phlex::ArgumentError.new("Unsafe attribute name detected: #{k}.") |
583 | | - end |
584 | | - end |
585 | | - |
586 | | - case v |
587 | | - when true |
588 | | - buffer << " " << base_name << name |
589 | | - when String |
590 | | - buffer << " " << base_name << name << '="' << v.gsub('"', """) << '"' |
591 | | - when Symbol |
592 | | - buffer << " " << base_name << name << '="' << v.name.tr("_", "-").gsub('"', """) << '"' |
593 | | - when Integer, Float |
594 | | - buffer << " " << base_name << name << '="' << v.to_s << '"' |
595 | | - when Hash |
596 | | - __nested_attributes__(v, "#{base_name}#{name}-", buffer) |
597 | | - when Array |
598 | | - buffer << " " << base_name << name << '="' << __nested_tokens__(v) << '"' |
599 | | - when Set |
600 | | - buffer << " " << base_name << name << '="' << __nested_tokens__(v.to_a) << '"' |
601 | | - when Phlex::SGML::SafeObject |
602 | | - buffer << " " << base_name << name << '="' << v.to_s.gsub('"', """) << '"' |
603 | | - else |
604 | | - raise Phlex::ArgumentError.new("Invalid attribute value #{v.inspect}.") |
605 | | - end |
606 | | - |
607 | | - if root_key |
608 | | - base_name = original_base_name |
609 | | - end |
610 | | - |
611 | | - buffer |
612 | | - end |
613 | | - end |
614 | | - |
615 | | - private def __nested_tokens__(tokens, sep = " ", gsub_from = nil, gsub_to = "") |
616 | | - buffer = +"" |
617 | | - |
618 | | - i, length = 0, tokens.length |
619 | | - |
620 | | - while i < length |
621 | | - token = tokens[i] |
622 | | - |
623 | | - case token |
624 | | - when String |
625 | | - token = token.gsub(gsub_from, gsub_to) if gsub_from |
626 | | - if i > 0 |
627 | | - buffer << sep << token |
628 | | - else |
629 | | - buffer << token |
630 | | - end |
631 | | - when Symbol |
632 | | - if i > 0 |
633 | | - buffer << sep << token.name.tr("_", "-") |
634 | | - else |
635 | | - buffer << token.name.tr("_", "-") |
636 | | - end |
637 | | - when Integer, Float, Phlex::SGML::SafeObject |
638 | | - if i > 0 |
639 | | - buffer << sep << token.to_s |
640 | | - else |
641 | | - buffer << token.to_s |
642 | | - end |
643 | | - when Array |
644 | | - if token.length > 0 |
645 | | - if i > 0 |
646 | | - buffer << sep << __nested_tokens__(token, sep, gsub_from, gsub_to) |
647 | | - else |
648 | | - buffer << __nested_tokens__(token, sep, gsub_from, gsub_to) |
649 | | - end |
650 | | - end |
651 | | - when nil |
652 | | - # Do nothing |
653 | | - else |
654 | | - raise Phlex::ArgumentError.new("Invalid token type: #{token.class}.") |
655 | | - end |
656 | | - |
657 | | - i += 1 |
658 | | - end |
659 | | - |
660 | | - buffer.gsub('"', """) |
661 | | - end |
662 | | - |
663 | | - # Result is **unsafe**, so it should be escaped! |
664 | | - private def __styles__(styles) |
665 | | - case styles |
666 | | - when Array, Set |
667 | | - styles.filter_map do |s| |
668 | | - case s |
669 | | - when String |
670 | | - if s == "" || s.end_with?(";") |
671 | | - s |
672 | | - else |
673 | | - "#{s};" |
674 | | - end |
675 | | - when Phlex::SGML::SafeObject |
676 | | - value = s.to_s |
677 | | - value.end_with?(";") ? value : "#{value};" |
678 | | - when Hash |
679 | | - next __styles__(s) |
680 | | - when nil |
681 | | - next nil |
682 | | - else |
683 | | - raise Phlex::ArgumentError.new("Invalid style: #{s.inspect}.") |
684 | | - end |
685 | | - end.join(" ") |
686 | | - when Hash |
687 | | - buffer = +"" |
688 | | - i = 0 |
689 | | - styles.each do |k, v| |
690 | | - prop = case k |
691 | | - when String |
692 | | - k |
693 | | - when Symbol |
694 | | - k.name.tr("_", "-") |
695 | | - else |
696 | | - raise Phlex::ArgumentError.new("Style keys should be Strings or Symbols.") |
697 | | - end |
698 | | - |
699 | | - value = case v |
700 | | - when String |
701 | | - v |
702 | | - when Symbol |
703 | | - v.name.tr("_", "-") |
704 | | - when Integer, Float, Phlex::SGML::SafeObject |
705 | | - v.to_s |
706 | | - when nil |
707 | | - nil |
708 | | - else |
709 | | - raise Phlex::ArgumentError.new("Invalid style value: #{v.inspect}") |
710 | | - end |
711 | | - |
712 | | - if value |
713 | | - if i == 0 |
714 | | - buffer << prop << ": " << value << ";" |
715 | | - else |
716 | | - buffer << " " << prop << ": " << value << ";" |
717 | | - end |
718 | | - end |
719 | | - |
720 | | - i += 1 |
721 | | - end |
722 | | - |
723 | | - buffer |
724 | | - end |
| 463 | + @_state.buffer << (Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) |
725 | 464 | end |
726 | 465 |
|
727 | 466 | private_class_method def self.method_added(method_name) |
|
0 commit comments