lib/ezid/metadata.rb in ezid-client-0.4.2 vs lib/ezid/metadata.rb in ezid-client-0.5.0
- old
+ new
@@ -1,18 +1,18 @@
require "delegate"
-require_relative "metadata_elements"
+require "singleton"
module Ezid
#
# EZID metadata collection for an identifier
#
+ # @note Although this API is not private, its direct use is discouraged.
+ # Instead use the metadata element accessors through Ezid::Identifier.
# @api public
#
class Metadata < SimpleDelegator
- include MetadataElements
-
# EZID metadata field/value separator
ANVL_SEPARATOR = ": "
ELEMENT_VALUE_SEPARATOR = " | "
@@ -23,11 +23,11 @@
# Characters to escape in element names on output to EZID
# @see http://ezid.cdlib.org/doc/apidoc.html#request-response-bodies
ESCAPE_NAMES_RE = /[%:\r\n]/
# Character sequence to unescape from EZID
- # http://ezid.cdlib.org/doc/apidoc.html#request-response-bodies
+ # @see http://ezid.cdlib.org/doc/apidoc.html#request-response-bodies
UNESCAPE_RE = /%\h\h/
# A comment line
COMMENT_RE = /^#.*(\r?\n)?/
@@ -35,10 +35,103 @@
LINE_CONTINUATION_RE = /\r?\n\s+/
# A line ending
LINE_ENDING_RE = /\r?\n/
+ # A metadata element
+ Element = Struct.new(:name, :writer)
+
+ # Metadata profiles
+ PROFILES = {
+ "dc" => %w( creator title publisher date type ).freeze,
+ "datacite" => %w( creator title publisher publicationyear resourcetype ).freeze,
+ "erc" => %w( who what when ).freeze,
+ "crossref" => [].freeze
+ }.freeze
+
+ # EZID reserved metadata elements that have time values
+ # @see http://ezid.cdlib.org/doc/apidoc.html#internal-metadata
+ RESERVED_TIME_ELEMENTS = %w( _created _updated )
+
+ # EZID reserved metadata elements that are read-only
+ # @see http://ezid.cdlib.org/doc/apidoc.html#internal-metadata
+ RESERVED_READONLY_ELEMENTS = %w( _owner _ownergroup _shadows _shadowedby _datacenter _created _updated )
+
+ # EZID reserved metadata elements that may be set by clients
+ # @see http://ezid.cdlib.org/doc/apidoc.html#internal-metadata
+ RESERVED_READWRITE_ELEMENTS = %w( _coowners _target _profile _status _export _crossref )
+
+ # All EZID reserved metadata elements
+ # @see http://ezid.cdlib.org/doc/apidoc.html#internal-metadata
+ RESERVED_ELEMENTS = RESERVED_READONLY_ELEMENTS + RESERVED_READWRITE_ELEMENTS
+
+ # Metadata element registry
+ class ElementRegistry < SimpleDelegator
+ include Singleton
+
+ def initialize
+ super(Hash.new)
+ end
+
+ def readers
+ keys
+ end
+
+ def writers
+ keys.select { |k| self[k].writer }.map(&:to_s).map { |k| k.concat("=") }.map(&:to_sym)
+ end
+ end
+
+ def self.initialize!
+ register_elements
+ define_element_accessors
+ end
+
+ def self.elements
+ ElementRegistry.instance
+ end
+
+ def self.register_elements
+ register_profile_elements
+ register_reserved_elements
+ elements.freeze
+ end
+
+ def self.define_element_accessors
+ elements.each do |accessor, element|
+ define_method(accessor) { reader(element.name) }
+
+ if element.writer
+ define_method("#{accessor}=") { |value| writer(element.name, value) }
+ end
+ end
+ end
+
+ def self.register_element(accessor, element, opts={})
+ writer = opts.fetch(:writer, true)
+ elements[accessor] = Element.new(element, writer).freeze
+ end
+
+ def self.register_profile_elements
+ PROFILES.each do |profile, profile_elements|
+ profile_elements.each do |element|
+ register_element("#{profile}_#{element}".to_sym, "#{profile}.#{element}")
+ end
+ register_element(profile.to_sym, profile) unless profile == "dc"
+ end
+ end
+
+ def self.register_reserved_elements
+ RESERVED_ELEMENTS.each do |element|
+ accessor = ((element == "_crossref") ? element : element.sub("_", "")).to_sym
+ register_element(accessor, element, writer: RESERVED_READWRITE_ELEMENTS.include?(element))
+ end
+ end
+
+ private_class_method :register_element, :register_elements, :register_reserved_elements,
+ :register_profile_elements, :define_element_accessors
+
def initialize(data={})
super(coerce(data))
end
# Output metadata in EZID ANVL format
@@ -54,61 +147,76 @@
to_anvl
end
private
- # Coerce data into a Hash of elements
- def coerce(data)
- data.to_h
- rescue NoMethodError
- coerce_string(data)
- end
+ def reader(element)
+ value = self[element]
+ if RESERVED_TIME_ELEMENTS.include?(element)
+ time = value.to_i
+ value = (time == 0) ? nil : Time.at(time).utc
+ end
+ value
+ end
- # Escape elements hash keys and values
- def escape_elements(hsh)
- hsh.each_with_object({}) do |(n, v), memo|
- memo[escape_name(n)] = escape_value(v)
+ def writer(element, value)
+ self[element] = value
end
- end
- # Escape an element name
- def escape_name(n)
- escape(ESCAPE_NAMES_RE, n)
- end
+ # Coerce data into a Hash of elements
+ def coerce(data)
+ data.to_h
+ rescue NoMethodError
+ coerce_string(data)
+ end
- # Escape an element value
- def escape_value(v)
- escape(ESCAPE_VALUES_RE, v)
- end
+ # Escape elements hash keys and values
+ def escape_elements(hsh)
+ hsh.each_with_object({}) do |(n, v), memo|
+ memo[escape_name(n)] = escape_value(v)
+ end
+ end
- # Escape string for sending to EZID host
- # @see http://ezid.cdlib.org/doc/apidoc.html#request-response-bodies
- # @param re [Regexp] the regular expression to match for escaping
- # @param s [String] the string to escape
- # @return [String] the escaped string
- def escape(re, s)
- s.gsub(re) { |m| URI.encode_www_form_component(m.force_encoding(Encoding::UTF_8)) }
- end
+ # Escape an element name
+ def escape_name(n)
+ escape(ESCAPE_NAMES_RE, n)
+ end
- # Unescape value from EZID host (or other source)
- # @see http://ezid.cdlib.org/doc/apidoc.html#request-response-bodies
- # @param value [String] the value to unescape
- # @return [String] the unescaped value
- def unescape(value)
- value.gsub(UNESCAPE_RE) { |m| URI.decode_www_form_component(m) }
- end
-
- # Coerce a string of metadata (e.g., from EZID host) into a Hash
- # @note EZID host does not send comments or line continuations.
- # @param data [String] the string to coerce
- # @return [Hash] the hash of coerced data
- def coerce_string(data)
- data.gsub!(COMMENT_RE, "")
- data.gsub!(LINE_CONTINUATION_RE, " ")
- data.split(LINE_ENDING_RE).each_with_object({}) do |line, memo|
- element, value = line.split(ANVL_SEPARATOR, 2)
- memo[unescape(element.strip)] = unescape(value.strip)
+ # Escape an element value
+ def escape_value(v)
+ escape(ESCAPE_VALUES_RE, v)
end
- end
+ # Escape string for sending to EZID host
+ # @see http://ezid.cdlib.org/doc/apidoc.html#request-response-bodies
+ # @param re [Regexp] the regular expression to match for escaping
+ # @param s [String] the string to escape
+ # @return [String] the escaped string
+ def escape(re, s)
+ s.gsub(re) { |m| URI.encode_www_form_component(m.force_encoding(Encoding::UTF_8)) }
+ end
+
+ # Unescape value from EZID host (or other source)
+ # @see http://ezid.cdlib.org/doc/apidoc.html#request-response-bodies
+ # @param value [String] the value to unescape
+ # @return [String] the unescaped value
+ def unescape(value)
+ value.gsub(UNESCAPE_RE) { |m| URI.decode_www_form_component(m) }
+ end
+
+ # Coerce a string of metadata (e.g., from EZID host) into a Hash
+ # @note EZID host does not send comments or line continuations.
+ # @param data [String] the string to coerce
+ # @return [Hash] the hash of coerced data
+ def coerce_string(data)
+ data.gsub!(COMMENT_RE, "")
+ data.gsub!(LINE_CONTINUATION_RE, " ")
+ data.split(LINE_ENDING_RE).each_with_object({}) do |line, memo|
+ element, value = line.split(ANVL_SEPARATOR, 2)
+ memo[unescape(element.strip)] = unescape(value.strip)
+ end
+ end
+
end
end
+
+Ezid::Metadata.initialize!