neu-mods 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4271827888541fa8ae2c61c52a6d3d73b79c7ee3ae3ae114ba391e3aae5e16a5
4
- data.tar.gz: a59905f08b0092b40ade775cf95d846941d210b922aca213b5f1ee7b70ad864d
3
+ metadata.gz: fb4c30744c189d3e0b4bfc0f84bb75175685bcb142dd6dc35dc55cfbcbdaf0a2
4
+ data.tar.gz: a5cd73f764edf2da97e05787788124bed168c9dba8caf997307fc53591f05661
5
5
  SHA512:
6
- metadata.gz: 25775e1c92de8bbc6f730980b1cbf78a5f0c78668ab901fb3c84f62e6ab15986bab6270ef37f511dfac367caa275d901b0cde87490a5b7d6f3338fd75d99415a
7
- data.tar.gz: 61726ef704b127856f0fc1f2febe3b35681ffe48b7822a7cbbe1d30a4c8b094071bd681c57a1b3639c09f0ad1f085d4d3d543608fe86a7d2c558fa84ec913dc2
6
+ metadata.gz: 785e3c7485886be0fd7946b61463e2e2b66319cf4c4f30980b6e4b2b0b8ed2ae4348be41a48caa9fbad536901a89e9c73d4d4fd005283bc2cc0dc9c9102d6ff6
7
+ data.tar.gz: 7a331ed220702dbab23bd6d16637f33a3da2f431a17939647ee7d1fd21898b9cf76a4594b26300eb01f8c9ad1b50d0cabcd298cdbff9c363a01a7f7bba6229a7
data/.version CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.2.0
data/README.md CHANGED
@@ -44,8 +44,23 @@ NEU::MODS.compose_title(non_sort: "", title: "What's New",
44
44
  node = doc.primary_title_info.at_xpath("mods:title", NEU::MODS::NAMESPACE)
45
45
  node.content = "New Title" unless NEU::MODS.whitespace_equivalent?(node.text, "New Title")
46
46
  doc.to_xml
47
+
48
+ # Editable creators (for an "advanced metadata" form): structured read,
49
+ # node selection (for replace-on-save), and structure-aware build.
50
+ doc.editable_personal_creators # => [{ given:, family: }] (plain, Creator role)
51
+ doc.editable_corporate_creators # => [{ name: }]
52
+ doc.preserved_names # => [{ name:, role: }] (authority-bearing / non-Creator — read-only)
53
+ doc.editable_creator_nodes("personal") # => live <name> nodes to replace
54
+ doc.build_personal_name(given: "Jenny", family: "Smith") # => a plain personal <name> node
55
+ doc.build_corporate_name(name: "Northeastern University") # => a plain corporate <name> node
47
56
  ```
48
57
 
58
+ The "editable creator" set is plain names — **no `@authority`/`@authorityURI`/
59
+ `@valueURI`** — with a **Creator** role; everything else (authority-controlled or
60
+ other-role names) is `preserved_names`, shown read-only. This mirrors the
61
+ keyword-subject curated-vs-editable split. `build_*_name`'s `role:` defaults to
62
+ `"Creator"` but is parameterised, so a later role-selectable form is non-breaking.
63
+
49
64
  ## Two normalizers, two jobs
50
65
 
51
66
  - `NEU::MODS.whitespace_equivalent?` / `.canonical_ws` — the **no-op guard**: did an
@@ -84,6 +84,28 @@ module NEU
84
84
  end
85
85
  end
86
86
 
87
+ # Editable (depositor-managed) creators: the plain names (no authority
88
+ # markers) with a Creator role, as STRUCTURED parts for form pre-fill --
89
+ # distinct from #names, which composes display strings for the access copy.
90
+ def editable_personal_creators
91
+ editable_creator_nodes("personal").map do |node|
92
+ { given: clean_part(joined_parts(node, "given")), family: clean_part(joined_parts(node, "family")) }
93
+ end
94
+ end
95
+
96
+ def editable_corporate_creators
97
+ editable_creator_nodes("corporate").map { |node| { name: clean_part(non_date_parts_joined(node)) } }
98
+ end
99
+
100
+ # Names the editable form does NOT manage (authority-bearing or non-Creator)
101
+ # -- for read-only display ("these exist; edit via the XML tab"). Composed
102
+ # display string + role, like #names but filtered to the preserved set.
103
+ def preserved_names
104
+ doc.xpath("/mods:mods/mods:name", NAMESPACE)
105
+ .reject { |node| editable_creator_name?(node) }
106
+ .map { |node| { name: name_display_value_w_date(node), role: name_role(node) } }
107
+ end
108
+
87
109
  # --- Scalars / simple arrays --------------------------------------------
88
110
 
89
111
  def languages
@@ -184,6 +206,12 @@ module NEU
184
206
  v.empty? ? nil : v
185
207
  end
186
208
 
209
+ # canonical_ws keeping "" for blank -- for structured form-field values
210
+ # (an empty given/family/org renders as an empty input, not a dropped key).
211
+ def clean_part(str)
212
+ NEU::MODS.canonical_ws(str)
213
+ end
214
+
187
215
  def join_paragraphs(nodes)
188
216
  nodes.map { |n| NEU::MODS.normalize_paragraphs(n.text) }.reject(&:empty?).join("\n\n")
189
217
  end
@@ -39,6 +39,39 @@ module NEU
39
39
  node
40
40
  end
41
41
 
42
+ # The "editable creator" <name> nodes of a given @type ("personal" /
43
+ # "corporate") that the Advanced form manages: plain names (no authority
44
+ # markers) with a Creator role. The write-path counterpart to the
45
+ # editable_*_creators projections; everything else (authority-bearing or
46
+ # non-Creator) is curated and left untouched. Mirrors keyword_subjects.
47
+ def editable_creator_nodes(type)
48
+ doc.xpath("/mods:mods/mods:name[@type='#{type}']", NAMESPACE)
49
+ .select { |n| editable_creator_name?(n) }
50
+ end
51
+
52
+ # Build a plain personal-creator <name> node: namePart[@type=given]/[family]
53
+ # + a text roleTerm. No authority/valueURI (the editable set). `role` is
54
+ # parameterised (default "Creator") so a later role-selectable form is a
55
+ # non-breaking change.
56
+ def build_personal_name(given:, family:, role: "Creator")
57
+ name = build_node("name")
58
+ name["type"] = "personal"
59
+ name.add_child(name_part(given, "given")) unless given.to_s.strip.empty?
60
+ name.add_child(name_part(family, "family")) unless family.to_s.strip.empty?
61
+ name.add_child(role_node(role))
62
+ name
63
+ end
64
+
65
+ # Build a plain corporate-creator <name> node: a single namePart + a text
66
+ # roleTerm. No authority/valueURI.
67
+ def build_corporate_name(name:, role: "Creator")
68
+ node = build_node("name")
69
+ node["type"] = "corporate"
70
+ node.add_child(name_part(name)) unless name.to_s.strip.empty?
71
+ node.add_child(role_node(role))
72
+ node
73
+ end
74
+
42
75
  private
43
76
 
44
77
  def keyword_subject?(subject)
@@ -47,6 +80,28 @@ module NEU
47
80
  topics = subject.element_children
48
81
  topics.any? && topics.all? { |c| c.name == "topic" }
49
82
  end
83
+
84
+ # A name is "editable" (depositor-managed) when it carries no authority
85
+ # markers and resolves to a Creator role. Shared by editable_creator_nodes
86
+ # (write/select) and the editable_*_creators projections (read).
87
+ def editable_creator_name?(node)
88
+ %w[authority authorityURI valueURI].none? { |attr| node[attr] } &&
89
+ name_role(node) == "Creator"
90
+ end
91
+
92
+ def name_part(text, type = nil)
93
+ np = build_node("namePart", text.to_s.strip)
94
+ np["type"] = type if type
95
+ np
96
+ end
97
+
98
+ def role_node(role)
99
+ role_el = build_node("role")
100
+ term = build_node("roleTerm", role)
101
+ term["type"] = "text"
102
+ role_el.add_child(term)
103
+ role_el
104
+ end
50
105
  end
51
106
  end
52
107
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: neu-mods
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Cliff
@@ -14,42 +14,42 @@ dependencies:
14
14
  name: nokogiri
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '1.13'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.13'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ~>
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
33
  version: '3.12'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ~>
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '3.12'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rubocop
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ~>
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
47
  version: '1.60'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ~>
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '1.60'
55
55
  description: 'Nokogiri-native, dependency-light reading/projection contract over MODS
@@ -62,7 +62,7 @@ executables: []
62
62
  extensions: []
63
63
  extra_rdoc_files: []
64
64
  files:
65
- - .version
65
+ - ".version"
66
66
  - Gemfile
67
67
  - README.md
68
68
  - Rakefile
@@ -83,16 +83,16 @@ require_paths:
83
83
  - lib
84
84
  required_ruby_version: !ruby/object:Gem::Requirement
85
85
  requirements:
86
- - - '>='
86
+ - - ">="
87
87
  - !ruby/object:Gem::Version
88
88
  version: '3.0'
89
89
  required_rubygems_version: !ruby/object:Gem::Requirement
90
90
  requirements:
91
- - - '>='
91
+ - - ">="
92
92
  - !ruby/object:Gem::Version
93
93
  version: '0'
94
94
  requirements: []
95
- rubygems_version: 3.0.9
95
+ rubygems_version: 3.4.10
96
96
  signing_key:
97
97
  specification_version: 4
98
98
  summary: Northeastern-flavored MODS XML projection + selection for the DRS.