Module: AIXM::Refinements

Defined in:
lib/aixm/refinements.rb

Constant Summary collapse

UPTRANS_FILTER =
%r(
  [^A-Z0-9, !"&#$%'\(\)\*\+\-\./:;<=>\[email protected]\[\\\]\^_\|\{\}]
)x.freeze
UPTRANS_MAP =
{
  'Ä' => 'AE',
  'Ö' => 'OE',
  'Ü' => 'UE',
  'Æ' => 'AE',
  'Œ' => 'OE',
  "Å" => "Aa",
  "Ø" => "Oe"
}.freeze

Instance Method Summary collapse

Instance Method Details

#decaptureObject

Note:

This is a refinement for Regexp

Replace all groups with non-caputuring groups

Examples:

/^(foo)(?<name>bar)/.decapture   # => /^(?:foo)(?:bar)/

159
160
161
162
163
# File 'lib/aixm/refinements.rb', line 159

refine Regexp do
  def decapture
    Regexp.new(to_s.gsub(/\(\?<\w+>|(?<![^\\]\\)\((?!\?)/, '(?:'))
  end
end

#indent(number) ⇒ String

Note:

This is a refinement for String

Indent every line of a string with number spaces.

Examples:

"foo\nbar".indent(2)
# => "  foo\n  bar"

Parameters:

  • number (Integer)

    number of spaces

Returns:

  • (String)

    line indended string


175
176
177
178
179
180
# File 'lib/aixm/refinements.rb', line 175

refine String do
  def indent(number)
    whitespace = ' ' * number
    gsub(/^/, whitespace)
  end
end

#insert_payload_hash(region: , element: ) ⇒ String

Note:

This is a refinement for String

Calculate the UUIDv3 hash of an AIXM/OFMX XML string and insert it into the AIXM/OFMX XML string as an mid attribute.

If the region is explicitly set to nil or false, the AIXM/OFMX string is returned unchanged.

Parameters:

  • region (String)

    OFMX region (e.g. “LF”)

  • element (String)

    tag to calculate the payload hash for (default: first element in the string)

Returns:

  • (String)

    AIXM/OFMX XML with UUIDv3 inserted as mid attribute

Raises:

  • (ArgumentError)

    if the given element is not found or no element at all

See Also:

  • String#payload_hash

236
237
238
239
240
241
242
243
# File 'lib/aixm/refinements.rb', line 236

refine String do
  def insert_payload_hash(region:, element: nil)
    return self unless region
    element = $1 if element.nil? && match(/<([^?].*?)[\s>]/)
    hash = payload_hash(region: region, element: element)
    sub(/(<#{element})([^>]*?)(\s+mid=".*?")?/, %Q(\\1 mid="#{hash}"\\2))
  end
end

#lookup(key_or_value, fallback = omitted=true) ⇒ Object

Note:

This is a refinement for Hash

Fetch a value from the hash, but unlike Hash#fetch, if key_or_value is no hash key, check whether key_or_value is a hash value and if so return it.

Examples:

h = { one: 1, two: 2, three: 3, four: :three }
h.lookup(:one)              # => 1
h.lookup(1)                 # => 1
h.lookup(:three)            # => 3 (key has priority over value)
h.lookup(:foo)              # => KeyError
h.lookup(:foo, :fallback)   # => :fallback
h.lookup(:foo, nil)         # => nil

Parameters:

  • key_or_value (Object)

    key or value of the hash

  • fallback (Object) (defaults to: omitted=true)

    fallback value

Raises:

  • (KeyError)

    if neither a matching hash key nor hash value are found and no fallback value has been passed


129
130
131
132
133
134
135
# File 'lib/aixm/refinements.rb', line 129

refine Hash do
  def lookup(key_or_value, fallback=omitted=true)
    self[key_or_value] ||
      (key_or_value if has_value?(key_or_value)) ||
      (omitted ? fail(KeyError, "key or value `#{key_or_value}' not found") : fallback)
  end
end

#payload_hash(region: , element: ) ⇒ String

Note:

This is a refinement for String

Calculate the UUIDv3 hash of an AIXM/OFMX XML string.

A word of warning: This is a minimalistic implementation for the AIXM gem and won't work unless the following conditions are met:

  • the XML string must be AIXM/OFMX

  • the XML string must be valid

  • the XML string must be pretty-printed

Examples:

xml.payload_hash(region: 'LF', element: 'Ser')
# => "269b1f18-cabe-3c9e-1d71-48a7414a4cb9"

Parameters:

  • region (String)

    OFMX region (e.g. “LF”)

  • element (String)

    tag to calculate the payload hash for (default: first element in the string)

Returns:

  • (String)

    UUIDv3

Raises:

  • (ArgumentError)

    if the given element is not found or no element at all


202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/aixm/refinements.rb', line 202

refine String do
  def payload_hash(region:, element: nil)
    element = $1 if element.nil? && match(/<([^?].*?)[\s>]/)
    fail(ArgumentError, "no element found") unless element
    fail(ArgumentError, "element `#{element}' not found") unless match? /<#{element}[\s>]/
    gsub(%r((?:mid|source)="[^"]*"), '').   # remove existing mid and source attributes
      sub(%r(\A.*?(?=<#{element}))m, '').   # remove everything before first <element>
      sub(%r(</#{element}>.*\z)m, '').   # remove everything after and including first </element>
      sub(%r(\A(<\w+Uid)\w+), '\1').   # remove Uid name extension
      scan(%r(<([\w-]+)([^>]*)>([^<]*))).each_with_object([region.upcase]) do |(e, a, t), m|
        m << e << a.scan(%r(([\w-]+)="([^"]*)")).sort.flatten << t
      end.
      flatten.
      keep_if { |s| s.match?(/[^[:space:]]/m) }.
      compact.
      to_uuid
  end
end

#then_ifObject

Note:

This is a refinement for Object

Same as Object#then but only applied if the condition is true.

Examples:

"foobar".then_if(false) { |s| s.gsub(/o/, 'i') }   # => "foobar"
"foobar".then_if(true) { |s| s.gsub(/o/, 'i') }    # => "fiibar"

Returns:

  • (Object)

146
147
148
149
150
# File 'lib/aixm/refinements.rb', line 146

refine Object do
  def then_if(condition, &block)
    condition ? self.then(&block) : self
  end
end

#to_ddFloat

Note:

This is a refinement for String

Convert DMS angle to DD or nil if the notation is not recognized.

Supported notations:

  • {-}{DD}D°MM'SS{.SS}“{[NESW]}

  • {-}{DD}D MM SS{.SS} {[NESW]}

  • -}{DD}DMMSS{.SS

Quite a number of typos are tolerated such as the wrong use of minute ' and second markers as well as the use of decimal comma , instead of dot ..

Examples:

%q(43°12'77.92").to_dd
# => 43.22164444444445
"431277.92S".to_dd
# => -43.22164444444445
%q(-123).to_dd
# => nil

Returns:

  • (Float)

    angle in DD notation


267
268
269
270
271
272
273
274
275
276
277
# File 'lib/aixm/refinements.rb', line 267

refine String do
  def to_dd
    if match = self.match(DMS_RE)
      "#{match['sgn']}1".to_i * "#{:- if match['hem_sw']}1".to_i * (
        match['deg'].to_f +
        match['min'].to_f/60 +
        match['sec'].tr(',', '.').to_f/3600
      )
    end
  end
end

#to_digestString

Note:

This is a refinement for Array

Builds a 4 byte hex digest from the Array payload.

Examples:

['foo', :bar, nil, [123]].to_digest
# => "f3920098"

Returns:

  • (String)

    4 byte hex


27
28
29
30
31
# File 'lib/aixm/refinements.rb', line 27

refine Array do
  def to_digest
    ::Digest::SHA512.hexdigest(flatten.map(&:to_s).join('|'))[0, 8]
  end
end

#to_dms(padding = 3) ⇒ String

Note:

This is a refinement for Float

Convert DD angle to DMS with the degrees zero padded to padding length.

Examples:

43.22164444444445.to_dms(2)
# => "43°12'77.92\""
43.22164444444445.to_dms
# => "043°12'77.92\""

Parameters:

  • padding (Integer) (defaults to: 3)

    number of digits for the degree part

Returns:

  • (String)

    angle in DMS notation {-}D°MM'SS.SS“


62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/aixm/refinements.rb', line 62

refine Float do
  def to_dms(padding=3)
    degrees = self.abs.floor
    minutes = ((self.abs - degrees) * 60).floor
    seconds = (self.abs - degrees - minutes.to_f / 60) * 3600
    minutes, seconds = minutes + 1, 0 if seconds.round(2) == 60
    degrees, minutes = degrees + 1, 0 if minutes == 60
    %Q(%s%0#{padding}d°%02d'%05.2f") % [
      ('-' if self.negative?),
      self.abs.truncate,
      minutes.abs.truncate,
      seconds.abs
    ]
  end
end

#to_radFloat

Note:

This is a refinement for Float

Convert an angle from degree to radian.

Examples:

45.to_rad
# => 0.7853981633974483

Returns:

  • (Float)

    radian angle


87
88
89
90
91
# File 'lib/aixm/refinements.rb', line 87

refine Float do
  def to_rad
    self * Math::PI / 180
  end
end

#to_timeTime

Note:

This is a refinement for String

Parse string to date and time.

Examples:

'2018-01-01 15:00'.to_time
# => 2018-01-01 15:00:00 +0100

Returns:

  • (Time)

    date and time


288
289
290
291
292
# File 'lib/aixm/refinements.rb', line 288

refine String do
  def to_time
    Time.parse(self)
  end
end

#to_uuidString

Note:

This is a refinement for Array

Builds a UUID version 3 digest from the Array payload.

Examples:

['foo', :bar, nil, [123]].to_uuid
# => "a68e9aae-81ef-c6f1-d954-2eefe2820c50"

Returns:

  • (String)

    UUID version 3


42
43
44
45
46
# File 'lib/aixm/refinements.rb', line 42

refine Array do
  def to_uuid
    ::Digest::MD5.hexdigest(flatten.map(&:to_s).join('|')).unpack("a8a4a4a4a12").join("-")
  end
end

#trimInteger, Float

Note:

This is a refinement for Float

Convert whole numbers to Integer and leave all other untouched.

Examples:

3.0.trim
# => 3
3.3.trim
# => 3.3

Returns:

  • (Integer, Float)

    converted Float


104
105
106
107
108
# File 'lib/aixm/refinements.rb', line 104

refine Float do
  def trim
    (self % 1).zero? ? self.to_i : self
  end
end

#uptransString

Note:

This is a refinement for String

Upcase and transliterate to match the reduced character set for AIXM names and titles.

See UPTRANS_MAP for supported diacryts and UPTRANS_FILTER for the list of allowed characters in the returned value.

Examples:

"Nîmes-Alès".uptrans
# => "NIMES-ALES"
"Zürich".uptrans
# => "ZUERICH"

Returns:

  • (String)

    upcased and transliterated String


309
310
311
312
313
314
315
316
317
318
# File 'lib/aixm/refinements.rb', line 309

refine String do
  def uptrans
    self.dup.tap do |string|
      string.upcase!
      string.gsub!(/(#{UPTRANS_MAP.keys.join('|')})/, UPTRANS_MAP)
      string.unicode_normalize!(:nfd)
      string.gsub!(UPTRANS_FILTER, '')
    end
  end
end