datacite-mapping 0.1.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 (53) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +42 -0
  3. data/.rubocop.yml +28 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +2 -0
  6. data/.yardopts +2 -0
  7. data/CHANGES.md +3 -0
  8. data/Gemfile +3 -0
  9. data/LICENSE.md +22 -0
  10. data/README.md +168 -0
  11. data/Rakefile +49 -0
  12. data/datacite-mapping.gemspec +37 -0
  13. data/examples/reading.rb +75 -0
  14. data/examples/writing.rb +49 -0
  15. data/lib/datacite/mapping.rb +36 -0
  16. data/lib/datacite/mapping/alternate_identifier.rb +45 -0
  17. data/lib/datacite/mapping/contributor.rb +125 -0
  18. data/lib/datacite/mapping/creator.rb +48 -0
  19. data/lib/datacite/mapping/date.rb +153 -0
  20. data/lib/datacite/mapping/description.rb +121 -0
  21. data/lib/datacite/mapping/geo_location.rb +49 -0
  22. data/lib/datacite/mapping/geo_location_box.rb +137 -0
  23. data/lib/datacite/mapping/geo_location_point.rb +102 -0
  24. data/lib/datacite/mapping/identifier.rb +45 -0
  25. data/lib/datacite/mapping/module_info.rb +12 -0
  26. data/lib/datacite/mapping/name_identifier.rb +48 -0
  27. data/lib/datacite/mapping/related_identifier.rb +209 -0
  28. data/lib/datacite/mapping/resource.rb +201 -0
  29. data/lib/datacite/mapping/resource_type.rb +83 -0
  30. data/lib/datacite/mapping/rights.rb +36 -0
  31. data/lib/datacite/mapping/subject.rb +55 -0
  32. data/lib/datacite/mapping/title.rb +69 -0
  33. data/spec/.rubocop.yml +7 -0
  34. data/spec/data/resource.xml +61 -0
  35. data/spec/rspec_custom_matchers.rb +69 -0
  36. data/spec/spec_helper.rb +31 -0
  37. data/spec/unit/datacite/mapping/alternate_identifier_spec.rb +60 -0
  38. data/spec/unit/datacite/mapping/contributor_spec.rb +129 -0
  39. data/spec/unit/datacite/mapping/creator_spec.rb +125 -0
  40. data/spec/unit/datacite/mapping/date_spec.rb +246 -0
  41. data/spec/unit/datacite/mapping/description_spec.rb +89 -0
  42. data/spec/unit/datacite/mapping/geo_location_box_spec.rb +241 -0
  43. data/spec/unit/datacite/mapping/geo_location_point_spec.rb +148 -0
  44. data/spec/unit/datacite/mapping/geo_location_spec.rb +116 -0
  45. data/spec/unit/datacite/mapping/identifier_spec.rb +75 -0
  46. data/spec/unit/datacite/mapping/name_identifier_spec.rb +89 -0
  47. data/spec/unit/datacite/mapping/related_identifier_spec.rb +157 -0
  48. data/spec/unit/datacite/mapping/resource_spec.rb +727 -0
  49. data/spec/unit/datacite/mapping/resource_type_spec.rb +69 -0
  50. data/spec/unit/datacite/mapping/rights_spec.rb +78 -0
  51. data/spec/unit/datacite/mapping/subject_spec.rb +108 -0
  52. data/spec/unit/datacite/mapping/title_spec.rb +113 -0
  53. metadata +262 -0
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'datacite/mapping'
4
+ include Datacite::Mapping
5
+
6
+ # Based on "Example for a simple dataset"
7
+ # http://schema.datacite.org/meta/kernel-3/example/datacite-example-dataset-v3.0.xml
8
+ resource = Resource.new(
9
+ identifier: Identifier.new(value: '10.5072/D3P26Q35R-Test'),
10
+ creators: [
11
+ Creator.new(name: 'Fosmire, Michael'),
12
+ Creator.new(name: 'Wertz, Ruth'),
13
+ Creator.new(name: 'Purzer, Senay')
14
+ ],
15
+ titles: [
16
+ Title.new(value: 'Critical Engineering Literacy Test (CELT)')
17
+ ],
18
+ publisher: 'Purdue University Research Repository (PURR)',
19
+ publication_year: 2013,
20
+ subjects: [
21
+ Subject.new(value: 'Assessment'),
22
+ Subject.new(value: 'Information Literacy'),
23
+ Subject.new(value: 'Engineering'),
24
+ Subject.new(value: 'Undergraduate Students'),
25
+ Subject.new(value: 'CELT'),
26
+ Subject.new(value: 'Purdue University')
27
+ ],
28
+ language: 'en',
29
+ resource_type: ResourceType.new(resource_type_general: ResourceTypeGeneral::DATASET, value: 'Dataset'),
30
+ version: '1',
31
+ descriptions: [
32
+ Description.new(
33
+ type: DescriptionType::ABSTRACT,
34
+ value: 'We developed an instrument, Critical Engineering Literacy Test (CELT), which is a multiple choice instrument
35
+ designed to measure undergraduate students’ scientific and information literacy skills. It requires students to
36
+ first read a technical memo and, based on the memo’s arguments, answer eight multiple choice and six open-ended
37
+ response questions. We collected data from 143 first-year engineering students and conducted an item analysis. The
38
+ KR-20 reliability of the instrument was .39. Item difficulties ranged between .17 to .83. The results indicate low
39
+ reliability index but acceptable levels of item difficulties and item discrimination indices. Students were most
40
+ challenged when answering items measuring scientific and mathematical literacy (i.e., identifying incorrect
41
+ information).'
42
+ )
43
+ ]
44
+ )
45
+
46
+ xml = resource.save_to_xml
47
+ formatter = REXML::Formatters::Pretty.new
48
+ formatter.compact = true
49
+ formatter.write(xml, $stdout)
@@ -0,0 +1,36 @@
1
+ # Module for working with the [DataCite metadata schema](https://schema.datacite.org/meta/kernel-3/index.html)
2
+ module Datacite
3
+ # Maps DataCite XML to Ruby objects
4
+ module Mapping
5
+
6
+ Dir.glob(File.expand_path('../mapping/*.rb', __FILE__)).sort.each(&method(:require))
7
+
8
+ class << self
9
+ attr_writer :log
10
+ end
11
+
12
+ # Gets the logger for the module. Default logger logs to `$stdout`.
13
+ # @return [Logger] the logger
14
+ def self.log
15
+ self.log_device = $stdout unless @log
16
+ @log
17
+ end
18
+
19
+ # Sets the log device. Defaults to `$stdout`
20
+ # @param value [IO] the log device
21
+ def self.log_device=(value)
22
+ @log = new_logger(logdev: value)
23
+ end
24
+
25
+ private
26
+
27
+ def self.new_logger(logdev:, level: Logger::DEBUG, shift_age: 10, shift_size: 1024 * 1024)
28
+ logger = Logger.new(logdev, shift_age, shift_size)
29
+ logger.level = level
30
+ logger.formatter = proc do |severity, datetime, progname, msg|
31
+ "#{datetime.to_time.utc} #{severity} -#{progname}- #{msg}\n"
32
+ end
33
+ logger
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,45 @@
1
+ require 'xml/mapping_extensions'
2
+
3
+ module Datacite
4
+ module Mapping
5
+
6
+ # An identifier or identifiers other than the primary {Identifier}
7
+ # applied to the {Resource}.
8
+ class AlternateIdentifier
9
+ include XML::Mapping
10
+
11
+ root_element_name 'alternateIdentifier'
12
+
13
+ text_node :type, '@alternateIdentifierType'
14
+ text_node :value, 'text()'
15
+
16
+ alias_method :_type=, :type=
17
+ private :_type=
18
+ alias_method :_value=, :value=
19
+ private :_value=
20
+
21
+ # Initializes a new {AlternateIdentifier}
22
+ # @param type [String] the identifier type
23
+ # @param value [String] the identifier value
24
+ def initialize(type:, value:)
25
+ self.type = type
26
+ self.value = value
27
+ end
28
+
29
+ # Sets the type. Cannot be nil.
30
+ # @param val [String] the identifier type
31
+ def type=(val)
32
+ fail ArgumentError, 'No identifier type provided' unless val
33
+ self._type = val
34
+ end
35
+
36
+ # Sets the value. Cannot be nil.
37
+ # @param val [String] the value
38
+ def value=(val)
39
+ fail ArgumentError, 'No identifier value provided' unless val
40
+ self._value = val
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,125 @@
1
+ require 'xml/mapping_extensions'
2
+ require_relative 'name_identifier'
3
+
4
+ module Datacite
5
+ module Mapping
6
+
7
+ # Controlled vocabulary of contributor types.
8
+ class ContributorType < TypesafeEnum::Base
9
+ # @!parse CONTACT_PERSON = ContactPerson
10
+ new :CONTACT_PERSON, 'ContactPerson'
11
+
12
+ # @!parse DATA_COLLECTOR = DataCollector
13
+ new :DATA_COLLECTOR, 'DataCollector'
14
+
15
+ # @!parse DATA_CURATOR = DataCurator
16
+ new :DATA_CURATOR, 'DataCurator'
17
+
18
+ # @!parse DATA_MANAGER = DataManager
19
+ new :DATA_MANAGER, 'DataManager'
20
+
21
+ # @!parse DISTRIBUTOR = Distributor
22
+ new :DISTRIBUTOR, 'Distributor'
23
+
24
+ # @!parse EDITOR = Editor
25
+ new :EDITOR, 'Editor'
26
+
27
+ # @!parse FUNDER = Funder
28
+ new :FUNDER, 'Funder'
29
+
30
+ # @!parse HOSTING_INSTITUTION = HostingInstitution
31
+ new :HOSTING_INSTITUTION, 'HostingInstitution'
32
+
33
+ # @!parse PRODUCER = Producer
34
+ new :PRODUCER, 'Producer'
35
+
36
+ # @!parse PROJECT_LEADER = ProjectLeader
37
+ new :PROJECT_LEADER, 'ProjectLeader'
38
+
39
+ # @!parse PROJECT_MANAGER = ProjectManager
40
+ new :PROJECT_MANAGER, 'ProjectManager'
41
+
42
+ # @!parse PROJECT_MEMBER = ProjectMember
43
+ new :PROJECT_MEMBER, 'ProjectMember'
44
+
45
+ # @!parse REGISTRATION_AGENCY = RegistrationAgency
46
+ new :REGISTRATION_AGENCY, 'RegistrationAgency'
47
+
48
+ # @!parse REGISTRATION_AUTHORITY = RegistrationAuthority
49
+ new :REGISTRATION_AUTHORITY, 'RegistrationAuthority'
50
+
51
+ # @!parse RELATED_PERSON = RelatedPerson
52
+ new :RELATED_PERSON, 'RelatedPerson'
53
+
54
+ # @!parse RESEARCHER = Researcher
55
+ new :RESEARCHER, 'Researcher'
56
+
57
+ # @!parse RESEARCH_GROUP = ResearchGroup
58
+ new :RESEARCH_GROUP, 'ResearchGroup'
59
+
60
+ # @!parse RIGHTS_HOLDER = RightsHolder
61
+ new :RIGHTS_HOLDER, 'RightsHolder'
62
+
63
+ # @!parse SPONSOR = Sponsor
64
+ new :SPONSOR, 'Sponsor'
65
+
66
+ # @!parse SUPERVISOR = Supervisor
67
+ new :SUPERVISOR, 'Supervisor'
68
+
69
+ # @!parse WORK_PACKAGE_LEADER = WorkPackageLeader
70
+ new :WORK_PACKAGE_LEADER, 'WorkPackageLeader'
71
+
72
+ # @!parse OTHER = Other
73
+ new :OTHER, 'Other'
74
+
75
+ end
76
+
77
+ # The institution or person responsible for collecting, creating, or otherwise contributing to the developement of the dataset.
78
+ class Contributor
79
+ include XML::Mapping
80
+
81
+ # @!attribute [rw] name
82
+ # @return [String] the personal name of the contributor, in the format `Family, Given`. Cannot be empty or nil
83
+ text_node :name, 'contributorName'
84
+
85
+ # @!attribute [rw] identifier
86
+ # @return [NameIdentifier, nil] an identifier for the contributor. Optional.
87
+ object_node :identifier, 'nameIdentifier', class: NameIdentifier, default_value: nil
88
+
89
+ # @!attribute [rw] affiliations
90
+ # @return [Array<String>] the contributor's affiliations. Defaults to an empty list.
91
+ array_node :affiliations, 'affiliation', class: String, default_value: []
92
+
93
+ # @!attribute [rw] type
94
+ # @return [ContributorType] the contributor type. Cannot be nil.
95
+ typesafe_enum_node :type, '@contributorType', class: ContributorType
96
+
97
+ alias_method :_name=, :name=
98
+ alias_method :_type=, :type=
99
+ private :_name=
100
+ private :_type=
101
+
102
+ # Initializes a new {Contributor}.
103
+ # @param name [String] the personal name of the contributor, in the format `Family, Given`. Cannot be empty or nil
104
+ # @param identifier [NameIdentifier, nil] an identifier for the contributor. Optional.
105
+ # @param affiliations [Array<Affiliation>] the contributor's affiliations. Defaults to an empty list.
106
+ # @param type [ContributorType] the contributor type. Cannot be nil.
107
+ def initialize(name:, identifier: nil, affiliations: nil, type:)
108
+ self.name = name
109
+ self.identifier = identifier
110
+ self.affiliations = affiliations || []
111
+ self.type = type
112
+ end
113
+
114
+ def name=(value)
115
+ fail ArgumentError, 'Name cannot be empty or nil' unless value && !value.empty?
116
+ self._name = value
117
+ end
118
+
119
+ def type=(value)
120
+ fail ArgumentError, 'Type cannot be nil' unless value
121
+ self._type = value
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,48 @@
1
+ require 'xml/mapping'
2
+ require_relative 'name_identifier'
3
+
4
+ module Datacite
5
+ module Mapping
6
+ # The main researchers involved working on the data, or the authors of the publication in priority order.
7
+ class Creator
8
+ include XML::Mapping
9
+
10
+ # @!attribute [rw] name
11
+ # @return [String] The personal name of the creator, in the format `Family, Given`. Cannot be empty or nil.
12
+ text_node :name, 'creatorName'
13
+
14
+ # @!attribute [rw] identifier
15
+ # @return [NameIdentifier, nil] An identifier for the creator. Optional.
16
+ object_node :identifier, 'nameIdentifier', class: NameIdentifier, default_value: nil
17
+
18
+ # @!attribute [rw] affiliations
19
+ # @return [Array<Affiliation>, nil] The creator's affiliations. Defaults to an empty list.
20
+ array_node :affiliations, 'affiliation', class: String, default_value: []
21
+
22
+ alias_method :_name=, :name=
23
+ private :_name=
24
+
25
+ alias_method :_affiliations=, :affiliations=
26
+ private :_affiliations=
27
+
28
+ # Initializes a new {Creator}.
29
+ # @param name [String] The personal name of the creator, in the format `Family, Given`. Cannot be empty or nil.
30
+ # @param identifier [NameIdentifier, nil] An identifier for the creator. Optional.
31
+ # @param affiliations [Array<Affiliation>, nil] The creator's affiliations. Defaults to an empty list.
32
+ def initialize(name:, identifier: nil, affiliations: [])
33
+ self.name = name
34
+ self.identifier = identifier
35
+ self.affiliations = affiliations
36
+ end
37
+
38
+ def name=(value)
39
+ fail ArgumentError, 'Name cannot be empty or nil' unless value && !value.empty?
40
+ self._name = value
41
+ end
42
+
43
+ def affiliations=(value)
44
+ self._affiliations = value || []
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,153 @@
1
+ require 'xml/mapping_extensions'
2
+
3
+ module Datacite
4
+ module Mapping
5
+
6
+ # Controlled vocabulary of date types.
7
+ class DateType < TypesafeEnum::Base
8
+ # @!parse ACCEPTED = Accepted
9
+ new :ACCEPTED, 'Accepted'
10
+
11
+ # @!parse AVAILABLE = Available
12
+ new :AVAILABLE, 'Available'
13
+
14
+ # @!parse COPYRIGHTED = Copyrighted
15
+ new :COPYRIGHTED, 'Copyrighted'
16
+
17
+ # @!parse COLLECTED = Collected
18
+ new :COLLECTED, 'Collected'
19
+
20
+ # @!parse CREATED = Created
21
+ new :CREATED, 'Created'
22
+
23
+ # @!parse ISSUED = Issued
24
+ new :ISSUED, 'Issued'
25
+
26
+ # @!parse SUBMITTED = Submitted
27
+ new :SUBMITTED, 'Submitted'
28
+
29
+ # @!parse UPDATED = Updated
30
+ new :UPDATED, 'Updated'
31
+
32
+ # @!parse VALID = Valid
33
+ new :VALID, 'Valid'
34
+
35
+ end
36
+
37
+ # Represents a DataCite `<date/>` field, which can be a year, date (year-month-day or just year-month),
38
+ # or ISO8601 datetime.
39
+ #
40
+ # @!attribute [r] year
41
+ # @return [Integer] The year.
42
+ # @!attribute [r] month
43
+ # @return [Integer, nil] The month. Can be `nil` if no month was specified.
44
+ # @!attribute [r] day
45
+ # @return [Integer, nil] The day. Can be `nil` if no day was specified.
46
+ # @!attribute [r] hour
47
+ # @return [Integer, nil] The hour. Can be `nil` if no hour was specified.
48
+ # @!attribute [r] minute
49
+ # @return [Integer, nil] The minutes. Can be `nil` if no minutes were specified.
50
+ # @!attribute [r] sec
51
+ # @return [Integer, nil] The seconds. Can be `nil` if no seconds were specified.
52
+ # @!attribute [r] nsec
53
+ # @return [Integer, nil] The nanoseconds. Can be `nil` if no nanoseconds were specified.
54
+ class Date
55
+ include XML::Mapping
56
+
57
+ # @!attribute [rw] type
58
+ # @return [DateType] the type of date. Cannot be nil.
59
+ typesafe_enum_node :type, '@dateType', class: DateType
60
+ alias_method :_type=, :type=
61
+ private :_type=
62
+
63
+ # @!method value
64
+ # @return [String] The value as a string. May be any [W3C DateTime format](http://www.w3.org/TR/NOTE-datetime).
65
+ text_node :value, 'text()'
66
+ alias_method :_value=, :value=
67
+ private :_value=
68
+
69
+ attr_reader :year
70
+ attr_reader :month
71
+ attr_reader :day
72
+ attr_reader :hour
73
+ attr_reader :minute
74
+ attr_reader :sec
75
+ attr_reader :nsec
76
+
77
+ # Initializes a new `Date`
78
+ #
79
+ # @param type [DateType] the type of date. Cannot be nil.
80
+ # @param value [DateTime, Date, Integer, String] The value, as a `DateTime`, `Date`, or `Integer`,
81
+ # or as a `String` in any [W3C DateTime format](http://www.w3.org/TR/NOTE-datetime)
82
+ def initialize(type:, value:)
83
+ self.type = type
84
+ self.value = value
85
+ end
86
+
87
+ def type=(val)
88
+ fail ArgumentError, 'Date type cannot be nil' unless val
89
+ self._type = val
90
+ end
91
+
92
+ # Sets the value.
93
+ #
94
+ # @param val [DateTime, Date, Integer, String] The value, as a `DateTime`, `Date`, or `Integer`,
95
+ # or as a `String` in any [W3C DateTime format](http://www.w3.org/TR/NOTE-datetime)
96
+ def value=(val) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
97
+ @date_time = to_datetime(val)
98
+ @date = @date_time ? @date_time.to_date : to_date(val)
99
+ @year = to_year(val)
100
+ @month = to_month(val)
101
+ @day = to_day(val)
102
+ new_value = val.respond_to?(:iso8601) ? val.iso8601 : val.to_s
103
+ if new_value.include?('T')
104
+ @hour = @date_time.hour if @date_time
105
+ @minute = @date_time.minute if @date_time
106
+ @sec = @date_time.sec if @date_time
107
+ @nsec = @date_time.to_time.nsec if @date_time
108
+ end
109
+ fail ArgumentError, "Unable to parse date value '#{val}'" unless @year
110
+ self._value = new_value
111
+ end
112
+
113
+ private
114
+
115
+ def to_year(val)
116
+ return val if val.is_a?(Integer)
117
+ return val.year if val.respond_to?(:year)
118
+ matchdata = val.to_s.match(/^[0-9]+/)
119
+ matchdata[0].to_i if matchdata
120
+ end
121
+
122
+ def to_month(val)
123
+ return val.month if val.respond_to?(:month)
124
+ matchdata = val.to_s.match(/^[0-9]+-([0-9]+)/)
125
+ matchdata[1].to_i if matchdata
126
+ end
127
+
128
+ def to_day(val)
129
+ return val.day if val.respond_to?(:day)
130
+ matchdata = val.to_s.match(/^[0-9]+-[0-9]+-([0-9]+)/)
131
+ matchdata[1].to_i if matchdata
132
+ end
133
+
134
+ def to_datetime(val)
135
+ return val if val.is_a?(DateTime)
136
+ DateTime.parse(val.to_s)
137
+ rescue ArgumentError => e
138
+ Mapping.log.debug("Can't extract DateTime from date value '#{val}': #{e}")
139
+ nil
140
+ end
141
+
142
+ def to_date(val)
143
+ return val if val.is_a?(::Date)
144
+ return ::Date.parse(val.iso8601) if val.respond_to?(:iso8601)
145
+ ::Date.parse(val.to_s)
146
+ rescue ArgumentError => e
147
+ Mapping.log.debug("Can't extract Date from date value '#{val}': #{e}")
148
+ nil
149
+ end
150
+
151
+ end
152
+ end
153
+ end