lib/ezid/metadata.rb in ezid-client-0.13.0 vs lib/ezid/metadata.rb in ezid-client-1.0.0

- old
+ new

@@ -5,11 +5,44 @@ # EZID metadata collection for an identifier. # # @api private # class Metadata < SimpleDelegator + + class << self + def metadata_reader(method, alias_as=nil) + define_method method do + self[method.to_s] + end + if alias_as + alias_method alias_as, method + end + end + def metadata_writer(method, alias_as=nil) + define_method "#{method}=" do |value| + self[method.to_s] = value + end + if alias_as + alias_method "#{alias_as}=".to_sym, "#{method}=".to_sym + end + end + + def metadata_accessor(method, alias_as=nil) + metadata_reader method, alias_as + metadata_writer method, alias_as + end + + def metadata_profile(profile, *methods) + methods.each do |method| + element = [profile, method].join(".") + alias_as = [profile, method].join("_") + metadata_accessor element, alias_as + end + end + end + # EZID metadata field/value separator ANVL_SEPARATOR = ": " ELEMENT_VALUE_SEPARATOR = " | " @@ -32,197 +65,99 @@ LINE_CONTINUATION_RE = /\r?\n\s+/ # A line ending LINE_ENDING_RE = /\r?\n/ - # A metadata element - Element = Struct.new(:name, :reader, :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 ) + READONLY = %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 ) + metadata_accessor :_coowners, :coowners + metadata_accessor :_crossref + metadata_accessor :_export, :export + metadata_accessor :_profile, :profile + metadata_accessor :_status, :status + metadata_accessor :_target, :target - # All EZID reserved metadata elements - # @see http://ezid.cdlib.org/doc/apidoc.html#internal-metadata - RESERVED_ELEMENTS = RESERVED_READONLY_ELEMENTS + RESERVED_READWRITE_ELEMENTS + metadata_accessor :crossref + metadata_accessor :datacite + metadata_accessor :erc - def self.initialize! - register_elements - end + metadata_reader :_created + metadata_reader :_datacenter, :datacenter + metadata_reader :_owner, :owner + metadata_reader :_ownergroup, :ownergroup + metadata_reader :_shadowedby, :shadowedby + metadata_reader :_shadows, :shadows + metadata_reader :_updated - def self.registered_elements - @@registered_elements ||= {} - end + metadata_profile :dc, :creator, :title, :publisher, :date, :type + metadata_profile :datacite, :creator, :title, :publisher, :publicationyear, :resourcetype + metadata_profile :erc, :who, :what, :when - def self.register_elements - register_profile_elements - register_reserved_elements + def initialize(data={}) + super coerce(data) end - def self.register_element(accessor, opts={}) - if element = registered_elements[accessor.to_sym] - raise Error, "Element \"#{element.name}\" is registered under the accessor :#{accessor}." - end - element = Element.new(opts.fetch(:name, accessor.to_s)) - element.reader = define_reader(accessor, element.name) - element.writer = define_writer(accessor, element.name) if opts.fetch(:writer, true) - registered_elements[accessor.to_sym] = element + def created + to_time _created end - def self.unregister_element(accessor) - element = registered_elements.delete(accessor) - raise Error, "No element is registered under the accessor :#{accessor}." unless element - remove_method(element.reader) - remove_method(element.writer) if element.writer + def updated + to_time _updated end - def self.register_profile_element(profile, element) - register_element("#{profile}_#{element}", name: "#{profile}.#{element}") - end - - def self.register_profile_elements(profile = nil) - if profile - PROFILES[profile].each { |element| register_profile_element(profile, element) } - else - PROFILES.keys.each do |profile| - register_profile_elements(profile) - register_element(profile) unless profile == "dc" - end - end - end - - def self.register_reserved_elements - RESERVED_ELEMENTS.each do |element| - accessor = (element == "_crossref") ? element : element.sub("_", "") - register_element(accessor, name: element, writer: RESERVED_READWRITE_ELEMENTS.include?(element)) - end - end - - def self.define_reader(accessor, element) - define_method(accessor) do - reader(element) - end - end - - def self.define_writer(accessor, element) - define_method("#{accessor}=") do |value| - writer(element, value) - end - end - - private_class_method :register_elements, - :register_reserved_elements, - :register_profile_elements, - :unregister_element, - :define_reader, - :define_writer - - def initialize(data={}) - super(coerce(data)) - end - # Output metadata in EZID ANVL format # @see http://ezid.cdlib.org/doc/apidoc.html#request-response-bodies # @return [String] the ANVL output def to_anvl(include_readonly = true) - elements = __getobj__.dup # copy, don't modify! - elements.reject! { |k, v| RESERVED_READONLY_ELEMENTS.include?(k) } unless include_readonly - escape_elements(elements).map { |e| e.join(ANVL_SEPARATOR) }.join("\n") + hsh = __getobj__.dup + hsh.reject! { |k, v| READONLY.include?(k) } unless include_readonly + elements = hsh.map do |name, value| + element = [escape(ESCAPE_NAMES_RE, name), escape(ESCAPE_VALUES_RE, value)] + element.join(ANVL_SEPARATOR) + end + elements.join("\n").force_encoding(Encoding::UTF_8) end def to_s to_anvl end - def registered_elements - self.class.registered_elements - end - private - 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 + def to_time(value) + time = value.to_i + (time == 0) ? nil : Time.at(time).utc + end - def writer(element, value) - self[element] = value - end + # Coerce data into a Hash of elements + def coerce(data) + data.to_h + rescue NoMethodError + coerce_string(data) + end - # Coerce data into a Hash of elements - def coerce(data) - data.to_h - rescue NoMethodError - coerce_string(data) - end + # Escape string for sending to EZID host + def escape(regexp, value) + value.gsub(regexp) { |m| URI.encode_www_form_component(m.force_encoding(Encoding::UTF_8)) } + 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 + # Unescape value from EZID host (or other source) + def unescape(value) + value.gsub(UNESCAPE_RE) { |m| URI.decode_www_form_component(m) } + end - # Escape an element name - def escape_name(n) - escape(ESCAPE_NAMES_RE, n) + # Coerce a string of metadata (e.g., from EZID host) into a Hash + # @note EZID host does not send comments or line continuations. + 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 - # Escape an element value - def escape_value(v) - escape(ESCAPE_VALUES_RE, v) - 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!