File: eim_xml.rb

package info (click to toggle)
ruby-eim-xml 1.0.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 236 kB
  • sloc: ruby: 2,015; makefile: 5
file content (210 lines) | stat: -rw-r--r-- 4,704 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# Easy IMplementation of XML
#
# Copyright (C) 2006, KURODA Hiraku <hiraku@hinet.mydns.jp>
# You can redistribute it and/or modify it under GPL2.
#

module EimXML
  XML_DECLARATION = %(<?xml version="1.0"?>)

  class PCString
    attr_reader :encoded_string, :src
    alias to_s encoded_string

    ENCODING_MAP = {
      '&' => '&amp;',
      '"' => '&quot;',
      "'" => '&apos;',
      '<' => '&lt;',
      '>' => '&gt;'
    }

    def self.encode(src)
      src.to_s.gsub(/[&"'<>]/) do |m|
        ENCODING_MAP[m]
      end
    end

    def self.[](obj)
      obj.is_a?(PCString) ? obj : PCString.new(obj)
    end

    def initialize(src, encoded = false) # rubocop:disable Style/OptionalBooleanParameter
      @src = src
      @encoded_string = encoded ? src : PCString.encode(src)
    end

    def ==(other)
      other.is_a?(PCString) ? @encoded_string == other.encoded_string : self == PCString.new(other)
    end

    def write_to(out = '')
      out << encoded_string
    end
  end

  class Comment
    def initialize(text)
      raise ArgumentError, "Can not include '--'" if text =~ /--/

      @text = text
    end

    def write_to(out = '')
      out << "<!-- #{@text} -->"
    end
  end

  class Element
    attr_reader :name, :attributes, :contents

    NEST = ' '

    def initialize(name, attributes = {})
      @name = name.to_sym
      @attributes = {}
      @contents = []

      attributes.each do |k, v|
        @attributes[k.to_sym] = v
      end

      yield(self) if block_given?
    end

    def name=(new_name)
      @name = new_name.to_sym
    end
    protected :name=

    def add(content)
      case content
      when nil
        # nothing to do
      when Array
        content.each { |i| add(i) }
      else
        @contents << content
      end
      self
    end
    alias << add

    def name_and_attributes(out = '')
      out << @name.to_s
      @attributes.each do |k, v|
        next unless v

        out << " #{k}='#{v.is_a?(PCString) ? v : PCString.encode(v.to_s)}'"
      end
    end

    def write_to(out = '')
      out << '<'
      name_and_attributes(out)

      if @contents.empty?
        out << ' />'
      else
        out << '>'
        @contents.each do |c|
          case c
          when Element
            c.write_to(out)
          when PCString
            out << c.to_s
          else
            out << PCString.encode(c.to_s)
          end
        end
        out << "</#{@name}>"
      end
      out
    end
    alias to_s write_to
    alias inspect to_s

    def ==(other)
      return false unless other.is_a?(Element)

      @name == other.name && @attributes == other.attributes && @contents == other.contents
    end

    def add_attribute(key, value)
      @attributes[key.to_sym] = value
    end
    alias []= add_attribute

    def [](key)
      if key.is_a?(Integer)
        @contents[key]
      else
        @attributes[key.to_sym]
      end
    end

    def del_attribute(key)
      @attributes.delete(key.to_sym)
    end

    def pcstring_contents
      @contents.select { |c| c.is_a?(String) || c.is_a?(PCString) }.map { |c| c.is_a?(String) ? PCString.new(c) : c }
    end

    def match(obj, attr = nil)
      return match(Element.new(obj, attr)) if attr
      return obj =~ @name.to_s if obj.is_a?(Regexp)
      return @name == obj if obj.is_a?(Symbol)
      return is_a?(obj) if obj.is_a?(Module)

      raise ArgumentError unless obj.is_a?(Element)

      return false unless @name == obj.name

      obj.attributes.all? do |k, v|
        (v.nil? && !@attributes.include?(k)) ||
          (@attributes.include?(k) && (v.is_a?(Regexp) ? v =~ @attributes[k] : PCString[v] == PCString[@attributes[k]]))
      end and obj.contents.all? do |i|
        case i
        when Element
          has_element?(i)
        when String
          pcstring_contents.include?(PCString.new(i))
        when PCString
          pcstring_contents.include?(i)
        when Regexp
          @contents.any? { |c| c.is_a?(String) and i =~ c }
        end
      end
    end
    alias =~ match

    def has?(obj, attr = nil)
      return has?(Element.new(obj, attr)) if attr

      @contents.any? do |i|
        if i.is_a?(Element)
          i.match(obj) || i.has?(obj)
        else
          obj.is_a?(Module) && i.is_a?(obj)
        end
      end
    end
    alias has_element? has?
    alias include? has?

    def find(obj, dst = Element.new(:found))
      return find(Element.new(obj, dst)) if dst.is_a?(Hash)

      dst << self if match(obj)
      @contents.each do |i|
        if i.is_a?(Element)
          i.find(obj, dst)
        elsif obj.is_a?(Module) && i.is_a?(obj)
          dst << i
        end
      end
      dst
    end
  end
end