datacite-mapping 0.3.0 → 0.4.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.
Files changed (59) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +4 -0
  3. data/CHANGES.md +15 -0
  4. data/README.md +8 -3
  5. data/datacite-mapping.gemspec +2 -3
  6. data/lib/datacite/mapping.rb +1 -1
  7. data/lib/datacite/mapping/affiliation.rb +44 -0
  8. data/lib/datacite/mapping/alternate_identifier.rb +2 -0
  9. data/lib/datacite/mapping/contributor.rb +32 -6
  10. data/lib/datacite/mapping/contributor_name.rb +42 -0
  11. data/lib/datacite/mapping/creator.rb +39 -7
  12. data/lib/datacite/mapping/creator_name.rb +42 -0
  13. data/lib/datacite/mapping/date.rb +12 -0
  14. data/lib/datacite/mapping/date_value.rb +6 -0
  15. data/lib/datacite/mapping/description.rb +6 -2
  16. data/lib/datacite/mapping/empty_filtering_nodes.rb +1 -0
  17. data/lib/datacite/mapping/funding_reference.rb +14 -1
  18. data/lib/datacite/mapping/geo_location.rb +6 -1
  19. data/lib/datacite/mapping/geo_location_box.rb +5 -0
  20. data/lib/datacite/mapping/geo_location_node.rb +4 -2
  21. data/lib/datacite/mapping/geo_location_point.rb +3 -0
  22. data/lib/datacite/mapping/geo_location_polygon.rb +10 -1
  23. data/lib/datacite/mapping/identifier.rb +11 -9
  24. data/lib/datacite/mapping/module_info.rb +2 -2
  25. data/lib/datacite/mapping/name_identifier.rb +10 -6
  26. data/lib/datacite/mapping/name_type.rb +18 -0
  27. data/lib/datacite/mapping/publisher.rb +42 -0
  28. data/lib/datacite/mapping/read_only_nodes.rb +4 -0
  29. data/lib/datacite/mapping/related_identifier.rb +38 -1
  30. data/lib/datacite/mapping/resource.rb +25 -11
  31. data/lib/datacite/mapping/resource_type.rb +4 -0
  32. data/lib/datacite/mapping/rights.rb +33 -6
  33. data/lib/datacite/mapping/subject.rb +3 -2
  34. data/lib/datacite/mapping/title.rb +3 -2
  35. data/spec/data/datacite4/{datacite-example-Box_dateCollected_DataCollector-v4.0.xml → datacite-example-Box_dateCollected_DataCollector-v4.xml} +9 -11
  36. data/spec/data/datacite4/{datacite-example-GeoLocation-v4.0.xml → datacite-example-GeoLocation-v4.xml} +11 -10
  37. data/spec/data/datacite4/{datacite-example-HasMetadata-v4.0.xml → datacite-example-HasMetadata-v4.xml} +18 -13
  38. data/spec/data/datacite4/{datacite-example-ResearchGroup_Methods-v4.0.xml → datacite-example-ResearchGroup_Methods-v4.xml} +14 -12
  39. data/spec/data/datacite4/{datacite-example-ResourceTypeGeneral_Collection-v4.0.xml → datacite-example-ResourceTypeGeneral_Collection-v4.xml} +9 -9
  40. data/spec/data/datacite4/datacite-example-affiliation-v4.xml +114 -0
  41. data/spec/data/datacite4/{datacite-example-complicated-v4.0.xml → datacite-example-complicated-v4.xml} +14 -11
  42. data/spec/data/datacite4/datacite-example-datapaper-v4.xml +32 -0
  43. data/spec/data/datacite4/{datacite-example-dataset-v4.0.xml → datacite-example-dataset-v4.xml} +20 -14
  44. data/spec/data/datacite4/datacite-example-full-v4.xml +114 -0
  45. data/spec/data/datacite4/{datacite-example-fundingReference-v.4.0.xml → datacite-example-fundingReference-v4.xml} +16 -13
  46. data/spec/data/datacite4/datacite-example-polygon-advanced-v4.xml +141 -0
  47. data/spec/data/datacite4/datacite-example-polygon-v4.xml +161 -0
  48. data/spec/data/datacite4/{datacite-example-relationTypeIsIdenticalTo-v4.0.xml → datacite-example-relationTypeIsIdenticalTo-v4.xml} +17 -17
  49. data/spec/data/datacite4/datacite-example-software-v4.xml +66 -0
  50. data/spec/data/datacite4/{datacite-example-video-v4.0.xml → datacite-example-video-v4.xml} +7 -7
  51. data/spec/data/datacite4/{datacite-example-workflow-v4.0.xml → datacite-example-workflow-v4.xml} +13 -11
  52. data/spec/data/datacite4/metadata.xsd +102 -57
  53. data/spec/rspec_custom_matchers.rb +3 -0
  54. data/spec/unit/datacite/mapping/contributor_spec.rb +7 -1
  55. data/spec/unit/datacite/mapping/creator_spec.rb +9 -3
  56. data/spec/unit/datacite/mapping/resource_spec.rb +14 -17
  57. data/spec/unit/datacite/mapping/rights_spec.rb +0 -13
  58. metadata +42 -28
  59. data/spec/data/datacite4/datacite-example-full-v4.0.xml +0 -71
@@ -59,6 +59,7 @@ module Datacite
59
59
 
60
60
  def <=>(other)
61
61
  return nil unless other.class == self.class
62
+
62
63
  %i[year month day hour minute sec nsec].each do |v|
63
64
  order = send(v) <=> other.send(v)
64
65
  return order if order.nonzero?
@@ -86,24 +87,28 @@ module Datacite
86
87
  def to_year(val)
87
88
  return val if val.is_a?(Integer)
88
89
  return val.year if val.respond_to?(:year)
90
+
89
91
  matchdata = val.to_s.match(/^[0-9]+/)
90
92
  matchdata[0].to_i if matchdata
91
93
  end
92
94
 
93
95
  def to_month(val)
94
96
  return val.month if val.respond_to?(:month)
97
+
95
98
  matchdata = val.to_s.match(/^[0-9]+-([0-9]{2})(?![0-9])/)
96
99
  matchdata[1].to_i if matchdata
97
100
  end
98
101
 
99
102
  def to_day(val)
100
103
  return val.day if val.respond_to?(:day)
104
+
101
105
  matchdata = val.to_s.match(/^[0-9]+-[0-9]{2}-([0-9]{2})(?![0-9])/)
102
106
  matchdata[1].to_i if matchdata
103
107
  end
104
108
 
105
109
  def to_datetime(val)
106
110
  return val if val.is_a?(DateTime)
111
+
107
112
  DateTime.parse(val.to_s)
108
113
  rescue ArgumentError
109
114
  nil
@@ -112,6 +117,7 @@ module Datacite
112
117
  def to_date(val)
113
118
  return val if val.is_a?(::Date)
114
119
  return ::Date.parse(val.iso8601) if val.respond_to?(:iso8601)
120
+
115
121
  ::Date.parse(val.to_s)
116
122
  rescue ArgumentError
117
123
  nil
@@ -19,6 +19,9 @@ module Datacite
19
19
  # @!parse TABLE_OF_CONTENTS = TableOfContents
20
20
  new :TABLE_OF_CONTENTS, 'TableOfContents'
21
21
 
22
+ # @!parse TECHNICAL_INFO = TechnicalInfo
23
+ new :TECHNICAL_INFO, 'TechnicalInfo'
24
+
22
25
  # @!parse OTHER = Other
23
26
  new :OTHER, 'Other'
24
27
 
@@ -93,9 +96,10 @@ module Datacite
93
96
  @language = value&.strip
94
97
  end
95
98
 
96
- def value=(v)
97
- new_value = v&.strip
99
+ def value=(a_value)
100
+ new_value = a_value&.strip
98
101
  raise ArgumentError, 'Value cannot be empty or nil' unless new_value && !new_value.empty?
102
+
99
103
  @value = new_value
100
104
  end
101
105
 
@@ -8,6 +8,7 @@ module Datacite
8
8
  module EmptyNodeUtils
9
9
  def not_empty(element)
10
10
  return unless element
11
+
11
12
  text = element.text
12
13
  empty = text.nil? || text.strip.empty?
13
14
  warn "Ignoring empty element #{element}" if empty
@@ -15,6 +15,9 @@ module Datacite
15
15
  # @!parse CROSSREF_FUNDER = 'Crossref Funder ID'
16
16
  new :CROSSREF_FUNDER_ID, 'Crossref Funder ID'
17
17
 
18
+ # @!parse ROR = ROR
19
+ new :ROR, 'ROR'
20
+
18
21
  # @!parse OTHER = Other
19
22
  new :OTHER, 'Other'
20
23
  end
@@ -24,18 +27,21 @@ module Datacite
24
27
 
25
28
  # @param type [FunderIdentifierType] the identifier type. Cannot be nil.
26
29
  # @param value [String] the identifier value. Cannot be nil.
27
- def initialize(type:, value:)
30
+ def initialize(type:, scheme_uri: nil, value:)
28
31
  self.type = type
32
+ self.scheme_uri = scheme_uri
29
33
  self.value = value
30
34
  end
31
35
 
32
36
  def value=(value)
33
37
  raise ArgumentError, 'Value cannot be empty or nil' unless value && !value.empty?
38
+
34
39
  @value = value
35
40
  end
36
41
 
37
42
  def type=(value)
38
43
  raise ArgumentError, 'Type cannot be nil' unless value
44
+
39
45
  @type = value
40
46
  end
41
47
 
@@ -47,6 +53,10 @@ module Datacite
47
53
  # @return [FunderIdentifierType] the identifier type. Cannot be nil.
48
54
  typesafe_enum_node :type, '@funderIdentifierType', class: FunderIdentifierType
49
55
 
56
+ # @!attribute [rw] scheme_uri
57
+ # @return [URI, nil] the URI of the identifier scheme. Optional.
58
+ uri_node :scheme_uri, '@schemeURI', default_value: nil
59
+
50
60
  # @!attribute [rw] value
51
61
  # @return [String] the identifier value. Cannot be nil.
52
62
  text_node :value, 'text()'
@@ -62,6 +72,7 @@ module Datacite
62
72
 
63
73
  def value=(value)
64
74
  raise ArgumentError, 'Value cannot be empty or nil' unless value && !value.empty?
75
+
65
76
  @value = value
66
77
  end
67
78
 
@@ -111,8 +122,10 @@ module Datacite
111
122
  def award_number_or_nil(value)
112
123
  return nil unless value
113
124
  return value if value.is_a?(AwardNumber)
125
+
114
126
  new_value = value.to_s.strip
115
127
  return nil if new_value.empty?
128
+
116
129
  AwardNumber.new(value: new_value)
117
130
  end
118
131
  end
@@ -21,11 +21,12 @@ module Datacite
21
21
  # @param box [GeoLocationBox, nil] the latitude-longitude quadrangle containing the area where the data was gathered or about which the data is focused.
22
22
  # @param place [String, nil] the spatial region or named place where the data was gathered or about which the data is focused.
23
23
  # @param polygon [GeoLocationPolygon, nil] a drawn polygon area containing the area where the data was gathered or about which the data is focused.
24
- def initialize(point: nil, box: nil, place: nil, polygon: nil)
24
+ def initialize(point: nil, box: nil, place: nil, polygon: nil, polygons: [])
25
25
  self.point = point
26
26
  self.box = box
27
27
  self.place = place
28
28
  self.polygon = polygon
29
+ self.polygons = polygons
29
30
  end
30
31
 
31
32
  def place=(value)
@@ -50,6 +51,10 @@ module Datacite
50
51
  # # @return [GeoLocationPolygon] a drawn polygon area containing the area where the data was gathered or about which the data is focused.
51
52
  object_node :polygon, 'geoLocationPolygon', class: GeoLocationPolygon, default_value: nil
52
53
 
54
+ # @!attribute [rw] polygons
55
+ # @return [Array<GeoLocationPolygon>] multiple polygons where data is contained
56
+ array_node :polygons, 'geoLocationPolygons', 'geoLocationPolygon', class: GeoLocationPolygon, default_value: []
57
+
53
58
  # @!attribute [rw] place
54
59
  # @return [String, nil] the spatial region or named place where the data was gathered or about which the data is focused.
55
60
  text_node :place, 'geoLocationPlace', default_value: nil
@@ -62,24 +62,28 @@ module Datacite
62
62
  def south_latitude=(value)
63
63
  raise ArgumentError, 'South latitude cannot be nil' unless value
64
64
  raise ArgumentError, "#{value} is not a valid south latitude" unless value >= -90 && value <= 90
65
+
65
66
  @south_latitude = value
66
67
  end
67
68
 
68
69
  def west_longitude=(value)
69
70
  raise ArgumentError, 'West longitude cannot be nil' unless value
70
71
  raise ArgumentError, "#{value} is not a valid west longitude" unless value >= -180 && value <= 180
72
+
71
73
  @west_longitude = value
72
74
  end
73
75
 
74
76
  def north_latitude=(value)
75
77
  raise ArgumentError, 'North latitude cannot be nil' unless value
76
78
  raise ArgumentError, "#{value} is not a valid north latitude" unless value >= -90 && value <= 90
79
+
77
80
  @north_latitude = value
78
81
  end
79
82
 
80
83
  def east_longitude=(value)
81
84
  raise ArgumentError, 'East longitude cannot be nil' unless value
82
85
  raise ArgumentError, "#{value} is not a valid east longitude" unless value >= -180 && value <= 180
86
+
83
87
  @east_longitude = value
84
88
  end
85
89
 
@@ -96,6 +100,7 @@ module Datacite
96
100
  # {GeoLocationBox}
97
101
  def <=>(other)
98
102
  return nil unless other.class == self.class
103
+
99
104
  %i[south_latitude west_longitude north_latitude east_longitude].each do |c|
100
105
  order = send(c) <=> other.send(c)
101
106
  return order if order.nonzero?
@@ -13,6 +13,7 @@ module Datacite
13
13
  def initialize(*args)
14
14
  raise 'No geometry class provided' unless @geom_class
15
15
  raise 'No coordinate elements provided' unless @coord_elements
16
+
16
17
  path, *myargs = super(*args)
17
18
  @path = ::XML::XXPath.new(path)
18
19
  myargs # rubocop:disable Lint/Void
@@ -23,13 +24,14 @@ module Datacite
23
24
  end
24
25
 
25
26
  def extract_attr_value(xml)
26
- return from_text(xml) || from_children(xml)
27
+ from_text(xml) || from_children(xml)
27
28
  rescue StandardError => e
28
29
  raise e, "#{@owner}.#{@attrname}: Can't extract #{self.class} from #{xml}: #{e.message}"
29
30
  end
30
31
 
31
32
  def set_attr_value(xml, value)
32
- raise "Invalid value: expected #{geom_class} instance, was #{value || 'nil'}" unless value && value.is_a?(geom_class)
33
+ raise "Invalid value: expected #{geom_class} instance, was #{value || 'nil'}" unless value&.is_a?(geom_class)
34
+
33
35
  element = @path.first(xml, ensure_created: true)
34
36
 
35
37
  if datacite_3?
@@ -42,12 +42,14 @@ module Datacite
42
42
  def latitude=(value)
43
43
  raise ArgumentError, 'Latitude cannot be nil' unless value
44
44
  raise ArgumentError, "#{value} is not a valid latitude" unless value >= -90 && value <= 90
45
+
45
46
  @latitude = value
46
47
  end
47
48
 
48
49
  def longitude=(value)
49
50
  raise ArgumentError, 'Longitude cannot be nil' unless value
50
51
  raise ArgumentError, "#{value} is not a valid longitude" unless value >= -180 && value <= 180
52
+
51
53
  @longitude = value
52
54
  end
53
55
 
@@ -64,6 +66,7 @@ module Datacite
64
66
  # {GeoLocationPoint}
65
67
  def <=>(other)
66
68
  return nil unless other.class == self.class
69
+
67
70
  %i[latitude longitude].each do |c|
68
71
  order = send(c) <=> other.send(c)
69
72
  return order if order.nonzero?
@@ -13,8 +13,9 @@ module Datacite
13
13
  # @param points [Array<GeoLocationPoint>] an array of points defining the polygon area.
14
14
  # Per the spec, the array should contain at least four points, the first and last being
15
15
  # identical to close the polygon.
16
- def initialize(points:) # TODO: allow simple array of point args, array of hashes
16
+ def initialize(points:, in_polygon_point: nil) # TODO: allow simple array of point args, array of hashes
17
17
  self.points = points
18
+ self.in_polygon_point = in_polygon_point
18
19
  warn "Polygon should contain at least 4 points, but has #{points.size}" if points.size < 4
19
20
  warn "Polygon is not closed; last and first point should be identical, but were: [#{points[0]}], [#{points[-1]}]" unless points[0] == points[-1] || points.size <= 1
20
21
  end
@@ -25,6 +26,7 @@ module Datacite
25
26
 
26
27
  def <=>(other)
27
28
  return nil unless other.class == self.class
29
+
28
30
  points <=> other.points
29
31
  end
30
32
 
@@ -44,6 +46,13 @@ module Datacite
44
46
  marshaller: (proc { |xml, value| marshal_point(xml, value) }),
45
47
  unmarshaller: (proc { |xml| unmarshal_point(xml) })
46
48
 
49
+ # # @!attribute [rw] in_polygon_point
50
+ # # @return [InPolygonPoint] a point within the target polygon
51
+ object_node :in_polygon_point, 'inPolygonPoint',
52
+ default_value: nil,
53
+ marshaller: (proc { |xml, value| marshal_point(xml, value) }),
54
+ unmarshaller: (proc { |xml| unmarshal_point(xml) })
55
+
47
56
  use_mapping :datacite_3
48
57
 
49
58
  # TODO: does this ever get called?
@@ -5,7 +5,7 @@ require 'datacite/mapping/empty_filtering_nodes'
5
5
 
6
6
  module Datacite
7
7
  module Mapping
8
- DOI_PATTERN = %r{10\.\S+/\S+}
8
+ DOI_PATTERN = %r{10\.\S+/\S+}.freeze
9
9
 
10
10
  # The persistent identifier that identifies the resource.
11
11
  #
@@ -26,19 +26,21 @@ module Datacite
26
26
  self.value = value
27
27
  end
28
28
 
29
- def value=(v)
30
- new_value = v&.strip
29
+ def value=(new_value)
30
+ new_value = new_value&.strip
31
31
  raise ArgumentError, 'Identifier must have a non-nil value' unless new_value
32
32
  raise ArgumentError, "Identifier value '#{new_value}' is not a valid DOI" unless new_value.match?(DOI_PATTERN)
33
+
33
34
  @value = new_value
34
35
  end
35
36
 
36
37
  # Sets the identifier type. Should only be called by the XML mapping engine.
37
- # @param v [String]
38
+ # @param new_value [String]
38
39
  # the identifier type (always 'DOI')
39
- def identifier_type=(v)
40
- raise ArgumentError, "Identifier type '#{v}' must be 'DOI'" unless DOI == v
41
- @identifier_type = v
40
+ def identifier_type=(new_value)
41
+ raise ArgumentError, "Identifier type '#{new_value}' must be 'DOI'" unless DOI == new_value
42
+
43
+ @identifier_type = new_value
42
44
  end
43
45
 
44
46
  # Gets the identifiery type.
@@ -51,6 +53,7 @@ module Datacite
51
53
  def self.from_doi(doi_string)
52
54
  match = doi_string.match(DOI_PATTERN)
53
55
  raise ArgumentError, "'#{doi_string}' does not appear to contain a valid DOI" unless match
56
+
54
57
  Identifier.new(value: match[0])
55
58
  end
56
59
 
@@ -60,13 +63,12 @@ module Datacite
60
63
  fallback_mapping :datacite_3, :_default
61
64
  end
62
65
 
63
- # Custom node to warn (but not blow up) if we read an XML `<resource/>` that's
66
+ # Custom node to allow (but ignore) if we read an XML `<resource/>` that's
64
67
  # missing its `<identifier/>`.
65
68
  class IdentifierNode < XML::Mapping::ObjectNode
66
69
  include EmptyNodeUtils
67
70
  def xml_to_obj(_obj, xml)
68
71
  return super if (element = has_element?(xml)) && not_empty(element)
69
- warn 'Identifier not found; add a valid Identifier to the Resource before saving'
70
72
  end
71
73
 
72
74
  private
@@ -6,9 +6,9 @@ module Datacite
6
6
  NAME = 'datacite-mapping'
7
7
 
8
8
  # The version of this gem
9
- VERSION = '0.3.0'
9
+ VERSION = '0.4.0'
10
10
 
11
11
  # The copyright notice for this gem
12
- COPYRIGHT = 'Copyright (c) 2016 The Regents of the University of California'
12
+ COPYRIGHT = 'Copyright (c) 2019 The Regents of the University of California'
13
13
  end
14
14
  end
@@ -18,14 +18,16 @@ module Datacite
18
18
  self.value = value
19
19
  end
20
20
 
21
- def scheme=(v)
22
- raise ArgumentError, 'Scheme cannot be empty or nil' unless v && !v.empty?
23
- @scheme = v
21
+ def scheme=(new_value)
22
+ raise ArgumentError, 'Scheme cannot be empty or nil' unless new_value && !new_value.empty?
23
+
24
+ @scheme = new_value
24
25
  end
25
26
 
26
- def value=(v)
27
- raise ArgumentError, 'Value cannot be empty or nil' unless v && !v.empty?
28
- @value = v
27
+ def value=(new_value)
28
+ raise ArgumentError, 'Value cannot be empty or nil' unless new_value && !new_value.empty?
29
+
30
+ @value = new_value
29
31
  end
30
32
 
31
33
  root_element_name 'nameIdentifier'
@@ -33,9 +35,11 @@ module Datacite
33
35
  # @!attribute [rw] scheme
34
36
  # @return [String] the name identifier scheme. Cannot be nil.
35
37
  text_node :scheme, '@nameIdentifierScheme'
38
+
36
39
  # @!attribute [rw] scheme_uri
37
40
  # @return [URI, nil] the URI of the identifier scheme. Optional.
38
41
  uri_node :scheme_uri, '@schemeURI', default_value: nil
42
+
39
43
  # @!attribute [rw] value
40
44
  # @return [String] the identifier value. Cannot be nil.
41
45
  text_node :value, 'text()'
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'xml/mapping'
4
+ require 'datacite/mapping/read_only_nodes'
5
+
6
+ module Datacite
7
+ module Mapping
8
+
9
+ # Controlled vocabulary of name types
10
+ class NameType < TypesafeEnum::Base
11
+ # @!parse ORGANIZATIONAL = Organizational
12
+ new :ORGANIZATIONAL, 'Organizational'
13
+
14
+ # @!parse PERSONAL = Personal
15
+ new :PERSONAL, 'Personal'
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'xml/mapping_extensions'
4
+
5
+ module Datacite
6
+ module Mapping
7
+
8
+ # The type of the resource
9
+ class Publisher
10
+ include XML::Mapping
11
+
12
+ # Initializes a new {Publisher}
13
+ # @param language [String, nil] an IETF BCP 47, ISO 639-1 language code identifying the language.
14
+ # @param value [String] name of the publisher
15
+ def initialize(language: nil, value:)
16
+ self.language = language
17
+ self.value = value
18
+ end
19
+
20
+ def language=(value)
21
+ @language = value&.strip
22
+ end
23
+
24
+ def value=(value)
25
+ new_value = value&.strip
26
+ raise ArgumentError, 'Value cannot be empty or nil' unless new_value && !new_value.empty?
27
+
28
+ @value = new_value.strip
29
+ end
30
+
31
+ # @!attribute [rw] language
32
+ # @return [String] an IETF BCP 47, ISO 639-1 language code identifying the language.
33
+ text_node :language, '@xml:lang', default_value: nil
34
+
35
+ # @!attribute [rw] value
36
+ # @return [String] the title itself.
37
+ text_node :value, 'text()'
38
+
39
+ fallback_mapping :datacite_3, :_default
40
+ end
41
+ end
42
+ end