Class: SimpleRSS::XmlElement

Inherits:
Object
  • Object
show all
Defined in:
lib/simple-rss/xml_element.rb

Overview

rbs_inline: enabled

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, attributes, content, parent) ⇒ XmlElement

Returns a new instance of XmlElement.



11
12
13
14
15
16
17
18
19
# File 'lib/simple-rss/xml_element.rb', line 11

def initialize(name, attributes, content, parent)
  @name = name
  @attributes = self.class.attributes(attributes).transform_values { |value| CGI.unescapeHTML(value) }
  @content = content.to_s
  @namespaces = parent.fetch(:namespaces).merge(@attributes.select { |key, _value| key == "xmlns" || key.start_with?("xmlns:") })
  base_url = @attributes["xml:base"]
  @base_urls = parent.fetch(:base_urls).dup
  @base_urls << base_url if base_url
end

Instance Attribute Details

#attributesObject (readonly)

: Hash[String, String]



5
6
7
# File 'lib/simple-rss/xml_element.rb', line 5

def attributes
  @attributes
end

#base_urlsObject (readonly)

: Array



8
9
10
# File 'lib/simple-rss/xml_element.rb', line 8

def base_urls
  @base_urls
end

#contentObject (readonly)

: String



6
7
8
# File 'lib/simple-rss/xml_element.rb', line 6

def content
  @content
end

#nameObject (readonly)

: String



4
5
6
# File 'lib/simple-rss/xml_element.rb', line 4

def name
  @name
end

#namespacesObject (readonly)

: Hash[String, String]



7
8
9
# File 'lib/simple-rss/xml_element.rb', line 7

def namespaces
  @namespaces
end

Class Method Details

.attributes(attributes) ⇒ Object



92
93
94
95
96
97
98
99
100
# File 'lib/simple-rss/xml_element.rb', line 92

def self.attributes(attributes)
  values = {} #: Hash[String, String]
  attributes.scan(/([\w:.-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/m) do
    name = Regexp.last_match(1)
    value = Regexp.last_match(2) || Regexp.last_match(3)
    values[name] = value if name && value
  end
  values
end

.child_elements(content) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/simple-rss/xml_element.rb', line 103

def self.child_elements(content)
  elements = [] #: Array[[String, String, String?]]
  current_element = nil #: [String, String, String?]?
  depth = 0
  body_start = 0
  position = 0

  while (token = SimpleRSS::XML_TAG_PATTERN.match(content, position))
    position = token.end(0)
    attributes = token[3]
    next unless attributes

    if token[1] == "/"
      depth = [depth - 1, 0].max
      if depth.zero? && current_element
        current_element[2] = content[body_start...token.begin(0)]
        current_element = nil
      end
      next
    end

    self_closing = attributes.rstrip.end_with?("/")
    if depth.zero?
      element = [token[2].to_s, attributes, nil] #: [String, String, String?]
      elements << element
      current_element = element unless self_closing
      body_start = token.end(0)
    end
    depth += 1 unless self_closing
  end

  elements
end

Instance Method Details

#childrenObject



22
23
24
25
26
# File 'lib/simple-rss/xml_element.rb', line 22

def children
  self.class.child_elements(content).map do |name, attributes, body|
    self.class.new(name, attributes, body, { namespaces: namespaces, base_urls: base_urls })
  end
end

#matches?(local_name, namespace) ⇒ Boolean

Returns:

  • (Boolean)


34
35
36
# File 'lib/simple-rss/xml_element.rb', line 34

def matches?(local_name, namespace)
  name.split(":").last == local_name && self.namespace == namespace
end

#namespaceObject



29
30
31
# File 'lib/simple-rss/xml_element.rb', line 29

def namespace
  namespaces[name.include?(":") ? "xmlns:#{name.split(":").first}" : "xmlns"]
end

#rawObject



63
64
65
# File 'lib/simple-rss/xml_element.rb', line 63

def raw
  { name: name, namespace: namespace, attributes: attributes, content: content }
end

#rss?(local_name) ⇒ Boolean

Returns:

  • (Boolean)


39
40
41
42
43
# File 'lib/simple-rss/xml_element.rb', line 39

def rss?(local_name)
  return false if name.include?(":") && namespace.nil?

  name.split(":").last == local_name && SimpleRSS::RSS_NAMESPACES.include?(namespace)
end

#selected?(selector) ⇒ Boolean

Returns:

  • (Boolean)


46
47
48
49
50
51
52
53
# File 'lib/simple-rss/xml_element.rb', line 46

def selected?(selector)
  if selector.start_with?("{")
    namespace, local_name = selector.delete_prefix("{").split("}", 2)
    return local_name ? matches?(local_name, namespace) : false
  end

  name == selector
end

#textObject



56
57
58
59
60
# File 'lib/simple-rss/xml_element.rb', line 56

def text
  content.split(/(<!\[CDATA\[.*?\]\]>)/m).map do |part|
    part.start_with?("<![CDATA[") ? part[9...-3].to_s : CGI.unescapeHTML(part.gsub(/<!--.*?-->|<\?.*?\?>/m, ""))
  end.join.strip
end

#xhtml_contentObject



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/simple-rss/xml_element.rb', line 68

def xhtml_content
  scopes = [namespaces]
  content.gsub(SimpleRSS::XML_TAG_PATTERN) do |token|
    name = Regexp.last_match(2).to_s
    attributes = Regexp.last_match(3)
    closing = Regexp.last_match(1) == "/"
    next token unless attributes

    if closing
      context = scopes.last || namespaces
      scopes.pop if scopes.size > 1
    else
      declarations = self.class.attributes(attributes).select { |key, _value| key == "xmlns" || key.start_with?("xmlns:") }
      context = (scopes.last || namespaces).merge(declarations.transform_values { |value| CGI.unescapeHTML(value) })
      scopes << context unless attributes.rstrip.end_with?("/")
    end
    namespace = context[name.include?(":") ? "xmlns:#{name.split(":").first}" : "xmlns"]
    next token unless namespace == "http://www.w3.org/1999/xhtml"

    token.sub(name, name.split(":").last.to_s)
  end.strip
end