Class: ActiveSanction::Parsers::XmlRecords::Record
- Inherits:
-
Object
- Object
- ActiveSanction::Parsers::XmlRecords::Record
- Extended by:
- T::Sig
- Defined in:
- lib/active_sanction/parsers/xml_records/record.rb
Overview
One record element and everything under it: the UN's <INDIVIDUAL>,
Canada's <record>. Fields are read by path relative to the record,
and a nested element is itself a Record, which is what lets an adapter
walk repeated children without knowing how the file was parsed.
record.name # => "INDIVIDUAL"
record["DATAID"] # => "6907993"
record["INDIVIDUAL_DATE_OF_BIRTH/YEAR"]
record.values("NATIONALITY/VALUE") # => ["Chad", "Sudan"]
record.nodes("INDIVIDUAL_ALIAS") # => [Record, Record]
record["@dateGenerated"] # an attribute, XPath-style
Absent, empty, and blank are one answer
An element that is missing, self-closing, or holds only whitespace all
read as nil. That is not laziness about the difference; it is the only
reading that survives the UN, which files placeholder aliases as
<INDIVIDUAL_ALIAS><QUALITY/><ALIAS_NAME/></INDIVIDUAL_ALIAS> and means
nothing at all by them. An adapter that had to distinguish the three
would produce blank-valued Names for every one of those placeholders.
Why #[] does not raise the way a CSV Row does
DelimitedTable::Row raises on a column its table never declared, since
a table's shape is fixed and an unknown name there is a typo. XML has
no such shape: one <INDIVIDUAL> carries elements the next one omits,
so an absent path is ordinary and #[] answers nil. #fetch is there for
the field an adapter considers mandatory, and it names the record and
what the record does carry when the field is missing.
Instance Attribute Summary collapse
- #attributes ⇒ Hash{String => T.untyped} readonly
- #children ⇒ Array<Record> readonly
-
#line ⇒ Integer?
readonly
nil where the backend reports no position.
-
#name ⇒ String
readonly
The element's own name, with any namespace prefix already removed by the backend -- see Backends.local_name.
- #table ⇒ XmlRecords readonly protected
Instance Method Summary collapse
-
#[](path) ⇒ String?
The first value at
path, or nil if nothing is there. - #attribute(key) ⇒ String?
- #children_named(wanted) ⇒ Array<Record> protected
-
#fetch(path, default = UNSET) ⇒ T.untyped
For a field the adapter treats as mandatory.
- #initialize(table:, name:, attributes: {}, text: nil, children: [], line: nil) ⇒ void constructor
- #inspect ⇒ String
-
#nodes(path) ⇒ Array<Record>
The elements at
path, as Records, whether or not they hold text -- an adapter reading<INDIVIDUAL_ADDRESS>wants the node, not a value. - #null?(path) ⇒ Boolean
-
#present ⇒ Array<String>
The child element names that actually carry something, which is what a #fetch failure has to print and what makes an unfamiliar list explorable from a console.
-
#text ⇒ String?
This element's own text, with blanks and any declared null sentinel resolved to nil.
- #to_s ⇒ String
-
#values(path) ⇒ Array<String>
Every value at
path, in document order, with blanks dropped.
Constructor Details
#initialize(table:, name:, attributes: {}, text: nil, children: [], line: nil) ⇒ void
69 70 71 72 73 74 75 76 77 |
# File 'lib/active_sanction/parsers/xml_records/record.rb', line 69 def initialize(table:, name:, attributes: {}, text: nil, children: [], line: nil) @table = T.let(table, XmlRecords) @name = T.let(-name.to_s, String) @attributes = T.let(attributes.freeze, T::Hash[String, T.untyped]) @raw_text = T.let(text, T.untyped) @children = T.let(children.freeze, T::Array[Record]) @line = T.let(line, T.nilable(Integer)) freeze end |
Instance Attribute Details
#attributes ⇒ Hash{String => T.untyped} (readonly)
56 57 58 |
# File 'lib/active_sanction/parsers/xml_records/record.rb', line 56 def attributes @attributes end |
#children ⇒ Array<Record> (readonly)
59 60 61 |
# File 'lib/active_sanction/parsers/xml_records/record.rb', line 59 def children @children end |
#line ⇒ Integer? (readonly)
nil where the backend reports no position.
63 64 65 |
# File 'lib/active_sanction/parsers/xml_records/record.rb', line 63 def line @line end |
#name ⇒ String (readonly)
The element's own name, with any namespace prefix already removed by the backend -- see Backends.local_name.
53 54 55 |
# File 'lib/active_sanction/parsers/xml_records/record.rb', line 53 def name @name end |
#table ⇒ XmlRecords (readonly, protected)
146 147 148 |
# File 'lib/active_sanction/parsers/xml_records/record.rb', line 146 def table @table end |
Instance Method Details
#[](path) ⇒ String?
The first value at path, or nil if nothing is there.
86 |
# File 'lib/active_sanction/parsers/xml_records/record.rb', line 86 def [](path) = values(path).first |
#attribute(key) ⇒ String?
111 |
# File 'lib/active_sanction/parsers/xml_records/record.rb', line 111 def attribute(key) = table.value(attributes[key.to_s]) |
#children_named(wanted) ⇒ Array<Record> (protected)
149 |
# File 'lib/active_sanction/parsers/xml_records/record.rb', line 149 def children_named(wanted) = children.select { |child| child.name == wanted } |
#fetch(path, default = UNSET) ⇒ T.untyped
For a field the adapter treats as mandatory. Raises rather than letting a renamed element arrive downstream as a nil nobody notices.
116 117 118 119 120 121 122 123 |
# File 'lib/active_sanction/parsers/xml_records/record.rb', line 116 def fetch(path, default = UNSET) value = self[path] return value unless value.nil? return default unless default.equal?(UNSET) raise MissingKey, "no value at #{path.inspect} in <#{name}>#{" on line #{line}" if line}. " \ "It carries: #{present.join(", ")}" end |
#inspect ⇒ String
141 |
# File 'lib/active_sanction/parsers/xml_records/record.rb', line 141 def inspect = "#<#{self.class} <#{name}>#{" line=#{line}" if line} #{present.join(" ")}>" |
#nodes(path) ⇒ Array<Record>
The elements at path, as Records, whether or not they hold text --
an adapter reading <INDIVIDUAL_ADDRESS> wants the node, not a value.
103 104 105 106 107 108 |
# File 'lib/active_sanction/parsers/xml_records/record.rb', line 103 def nodes(path) steps, attribute = split(path) raise InvalidArgument, "#nodes reads elements, not the attribute #{path.inspect}" if attribute descend(steps) end |
#null?(path) ⇒ Boolean
126 |
# File 'lib/active_sanction/parsers/xml_records/record.rb', line 126 def null?(path) = self[path].nil? |
#present ⇒ Array<String>
The child element names that actually carry something, which is what a #fetch failure has to print and what makes an unfamiliar list explorable from a console.
132 133 134 135 |
# File 'lib/active_sanction/parsers/xml_records/record.rb', line 132 def present names = children.select { |child| !child.text.nil? || child.children.any? }.map(&:name) names.uniq end |
#text ⇒ String?
This element's own text, with blanks and any declared null sentinel resolved to nil. Text belonging to child elements is not included.
82 |
# File 'lib/active_sanction/parsers/xml_records/record.rb', line 82 def text = table.value(@raw_text) |
#to_s ⇒ String
138 |
# File 'lib/active_sanction/parsers/xml_records/record.rb', line 138 def to_s = text.to_s |
#values(path) ⇒ Array<String>
Every value at path, in document order, with blanks dropped. The
answer to a repeated element: the UN files each nationality as its
own <NATIONALITY><VALUE>.
92 93 94 95 96 97 98 |
# File 'lib/active_sanction/parsers/xml_records/record.rb', line 92 def values(path) steps, attribute = split(path) nodes = descend(steps) return nodes.filter_map { |node| node.attribute(attribute) } if attribute nodes.filter_map(&:text) end |