Skip to content

Commit 43926a5

Browse files
committed
Extract __attributes__ method into its own module
1 parent 189b4cd commit 43926a5

File tree

7 files changed

+281
-281
lines changed

7 files changed

+281
-281
lines changed

lib/phlex/compiler/method_compiler.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def visit_phlex_attributes(node)
6969
if node.arguments in [Prism::KeywordHashNode[elements: attributes]]
7070
result = attributes.all? { |attribute| attribute in Prism::AssocNode[key: Prism::SymbolNode, value: Prism::StringNode] }
7171
if result
72-
return buffer(Phlex::HTML.allocate.__send__(:__attributes__, eval("{#{node.slice}}")))
72+
return buffer(Phlex::SGML::Attributes.generate_attributes(eval("{#{node.slice}}")))
7373
end
7474
end
7575

lib/phlex/html.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,15 @@ def tag(name, **attributes, &)
5858
if (tag = StandardElements.__registered_elements__[name]) || (tag = name.name.tr("_", "-")).include?("-")
5959
if attributes.length > 0 # with attributes
6060
if block_given # with content block
61-
buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes] ||= __attributes__(attributes)) << ">"
61+
buffer << "<#{tag}" << (Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) << ">"
6262
if tag == "svg"
6363
render Phlex::SVG.new(&)
6464
else
6565
__yield_content__(&)
6666
end
6767
buffer << "</#{tag}>"
6868
else # without content
69-
buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= __attributes__(attributes)) << "></#{tag}>"
69+
buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) << "></#{tag}>"
7070
end
7171
else # without attributes
7272
if block_given # with content block
@@ -87,7 +87,7 @@ def tag(name, **attributes, &)
8787
end
8888

8989
if attributes.length > 0 # with attributes
90-
buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= __attributes__(attributes)) << ">"
90+
buffer << "<#{tag}" << (::Phlex::ATTRIBUTE_CACHE[attributes] ||= Phlex::SGML::Attributes.generate_attributes(attributes)) << ">"
9191
else # without attributes
9292
buffer << "<#{tag}>"
9393
end

lib/phlex/sgml.rb

Lines changed: 1 addition & 262 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22

33
# **Standard Generalized Markup Language** for behaviour common to {HTML} and {SVG}.
44
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-
85
ERBCompiler = ERB::Compiler.new("<>").tap do |compiler|
96
compiler.pre_cmd = [""]
107
compiler.put_cmd = "@_state.buffer.<<"
@@ -463,265 +460,7 @@ def json_escape(string)
463460
end
464461

465462
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('"', "&quot;")
484-
when Symbol
485-
v.name.tr("_", "-").gsub('"', "&quot;")
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('"', "&quot;")
496-
else
497-
__nested_attributes__(v, "#{name}-", buffer)
498-
end
499-
when Array
500-
case k
501-
when :style
502-
__styles__(v).gsub('"', "&quot;")
503-
else
504-
__nested_tokens__(v)
505-
end
506-
when Set
507-
case k
508-
when :style
509-
__styles__(v).gsub('"', "&quot;")
510-
else
511-
__nested_tokens__(v.to_a)
512-
end
513-
when Phlex::SGML::SafeObject
514-
v.to_s.gsub('"', "&quot;")
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('"', "&quot;") << '"'
591-
when Symbol
592-
buffer << " " << base_name << name << '="' << v.name.tr("_", "-").gsub('"', "&quot;") << '"'
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('"', "&quot;") << '"'
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('"', "&quot;")
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))
725464
end
726465

727466
private_class_method def self.method_added(method_name)

0 commit comments

Comments
 (0)