Module: NEU::MODS::Selectors
- Included in:
- Document
- Defined in:
- lib/neu/mods/selectors.rb
Overview
Node LOCATION over a parsed MODS document. These return live Nokogiri nodes,
so they serve BOTH the read path (projection reads their text) AND the write
path (Cerberus's MODSMerge mutates the returned nodes in place). That shared
definition is the point: the node an editor changes is provably the node the
projection reads. Mixed into Document; operates on doc.
Instance Method Summary collapse
-
#abstract_nodes ⇒ Object
All top-level
elements (MODS permits several). -
#build_corporate_name(name:, role: "Creator") ⇒ Object
Build a plain corporate-creator
node: a single namePart + a text roleTerm. -
#build_node(name, text = nil) ⇒ Object
Build a namespaced MODS element reusing the document's existing
mods:namespace declaration (so new nodes never re-declare xmlns). -
#build_personal_name(given:, family:, role: "Creator") ⇒ Object
Build a plain personal-creator
node: namePart/[family] + a text roleTerm. -
#editable_creator_nodes(type) ⇒ Object
The "editable creator"
nodes of a given @type ("personal" / "corporate") that the Advanced form manages: plain names (no authority markers) with a Creator role. -
#keyword_subjects ⇒ Object
The "keyword" subjects the simple form manages: attribute-free
elements whose element children are all . -
#primary_title_info ⇒ Object
Top-level primary titleInfo, falling back to the first top-level titleInfo that is NOT a variant.
-
#variant_title?(node) ⇒ Boolean
MODS enumerates titleInfo/@type as exactly abbreviated, translated, alternative and uniform -- every one of them a variant.
Instance Method Details
#abstract_nodes ⇒ Object
All top-level
45 46 47 |
# File 'lib/neu/mods/selectors.rb', line 45 def abstract_nodes doc.xpath("/mods:mods/mods:abstract", NAMESPACE) end |
#build_corporate_name(name:, role: "Creator") ⇒ Object
Build a plain corporate-creator
92 93 94 95 96 97 98 |
# File 'lib/neu/mods/selectors.rb', line 92 def build_corporate_name(name:, role: "Creator") node = build_node("name") node["type"] = "corporate" node.add_child(name_part(name)) unless name.to_s.strip.empty? node.add_child(role_node(role)) node end |
#build_node(name, text = nil) ⇒ Object
Build a namespaced MODS element reusing the document's existing mods:
namespace declaration (so new nodes never re-declare xmlns).
60 61 62 63 64 65 |
# File 'lib/neu/mods/selectors.rb', line 60 def build_node(name, text = nil) node = Nokogiri::XML::Node.new(name, doc) node.namespace = doc.root.namespace_definitions.find { |d| d.prefix == "mods" } node.content = text unless text.nil? node end |
#build_personal_name(given:, family:, role: "Creator") ⇒ Object
Build a plain personal-creator
- a text roleTerm. No authority/valueURI (the editable set).
roleis parameterised (default "Creator") so a later role-selectable form is a non-breaking change.
81 82 83 84 85 86 87 88 |
# File 'lib/neu/mods/selectors.rb', line 81 def build_personal_name(given:, family:, role: "Creator") name = build_node("name") name["type"] = "personal" name.add_child(name_part(given, "given")) unless given.to_s.strip.empty? name.add_child(name_part(family, "family")) unless family.to_s.strip.empty? name.add_child(role_node(role)) name end |
#editable_creator_nodes(type) ⇒ Object
The "editable creator"
72 73 74 75 |
# File 'lib/neu/mods/selectors.rb', line 72 def editable_creator_nodes(type) doc.xpath("/mods:mods/mods:name[@type='#{type}']", NAMESPACE) .select { |n| editable_creator_name?(n) } end |
#keyword_subjects ⇒ Object
The "keyword" subjects the simple form manages: attribute-free
54 55 56 |
# File 'lib/neu/mods/selectors.rb', line 54 def keyword_subjects doc.xpath("/mods:mods/mods:subject", NAMESPACE).select { |s| keyword_subject?(s) } end |
#primary_title_info ⇒ Object
Top-level primary titleInfo, falling back to the first top-level titleInfo that is NOT a variant. Scoped to direct children of mods:mods so a relatedItem's nested titleInfo (e.g. a series title) is never matched.
MODS does not require usage="primary", so the fallback fires often, and an unfiltered one let document order decide the record's title. It also decided which node an edit form wrote to: MODSMerge overwrites the node this returns, so an alternative title reached here was destroyed on the next title edit, leaving the record with no primary title at all. nil is the right answer instead -- MODSMerge creates a proper primary titleInfo from nil, and each variant is projected under its own field.
Where a record carries two UNTYPED titleInfo and marks neither, .first decides and the second reaches no field. That is the schema's answer rather than a shortfall: @usage is fixed="primary" and exists precisely to nominate the principal title, @type is a closed enumeration of the four variants, and MODS 3.5 gives a legitimate second untyped title an duplicate carries none of those, so MODS gives it no meaning to preserve, and it stays in the preservation XML with no projected field.
31 32 33 34 |
# File 'lib/neu/mods/selectors.rb', line 31 def primary_title_info doc.at_xpath("/mods:mods/mods:titleInfo[@usage='primary']", NAMESPACE) || doc.xpath("/mods:mods/mods:titleInfo", NAMESPACE).reject { |ti| variant_title?(ti) }.first end |
#variant_title?(node) ⇒ Boolean
MODS enumerates titleInfo/@type as exactly abbreviated, translated, alternative and uniform -- every one of them a variant. So the presence of any @type marks a variant, which also keeps an unrecognised or misspelled value out of the write path rather than guessing at it.
40 41 42 |
# File 'lib/neu/mods/selectors.rb', line 40 def variant_title?(node) !NEU::MODS.canonical_ws(node["type"].to_s).empty? end |