datacite-mapping 0.1.0

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