Module: Cetustek::Xml

Defined in:
lib/cetustek/xml.rb

Overview

Every request document in the spec is the same shape: a UTF-8 declaration and a single / root carrying XSDVersion.

Constant Summary collapse

XSD_VERSION =
'2.8'

Class Method Summary collapse

Class Method Details

.append(element, name, value, skip_nil: false) ⇒ Object

Fields whose 備註 says "若未填,預設 X" are left out entirely when nil, so the platform applies its own default instead of parsing an empty value.



37
38
39
40
41
# File 'lib/cetustek/xml.rb', line 37

def append(element, name, value, skip_nil: false)
  return if skip_nil && value.nil?

  element << tag(name, value)
end

.document(root_name) {|root| ... } ⇒ Object

Yields:



14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/cetustek/xml.rb', line 14

def document(root_name)
  doc = Ox::Document.new
  instruct = Ox::Instruct.new(:xml)
  instruct[:version] = '1.0'
  instruct[:encoding] = 'UTF-8'
  doc << instruct

  root = Ox::Element.new(root_name)
  root[:XSDVersion] = XSD_VERSION
  doc << root
  yield root

  Ox.dump(doc).force_encoding('UTF-8')
end

.parse_fields(element, fields) ⇒ Object

Shared response-parsing side: every Query* class turns a response element into a Hash of snake_case keys, so this lives here once instead of once per query class.



60
61
62
# File 'lib/cetustek/xml.rb', line 60

def parse_fields(element, fields)
  fields.to_h { |field| [snake_case(field), text_of(element, field)] }
end

.parse_response(body, nil_values: []) ⇒ Object

Shared guard every Query* class needs before it can parse a response body: nil for an empty answer or one of the documented sentinel strings (e.g. "nodata"); ResultError for a bare result code; otherwise the parsed root element, ready for parse_fields.



47
48
49
50
51
52
53
54
55
# File 'lib/cetustek/xml.rb', line 47

def parse_response(body, nil_values: [])
  text = body.to_s.strip
  return nil if text.empty? || nil_values.include?(text)

  ResultCode.raise!(text, ResultCode::COMMON) unless text.start_with?('<')

  root = Ox.parse(text)
  root.is_a?(Ox::Document) ? root.root : root
end

.snake_case(name) ⇒ Object



68
69
70
# File 'lib/cetustek/xml.rb', line 68

def snake_case(name)
  name.gsub(/([a-z\d])([A-Z])/, '\1_\2').downcase.to_sym
end

.tag(name, value) ⇒ Object

Values are HTML-escaped so that special characters (&, <, >, ", ') in any dynamic field cannot break the XML or be used for injection.



31
32
33
# File 'lib/cetustek/xml.rb', line 31

def tag(name, value)
  Ox::Raw.new("<#{name}>#{CGI.escapeHTML(value.to_s)}</#{name}>")
end

.text_of(element, name) ⇒ Object



64
65
66
# File 'lib/cetustek/xml.rb', line 64

def text_of(element, name)
  element&.locate(name)&.first&.text
end