datacite-mapping 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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