require "delegate" module Ezid # # 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 = " | " # Characters to escape in element values on output to EZID # @see http://ezid.cdlib.org/doc/apidoc.html#request-response-bodies ESCAPE_VALUES_RE = /[%\r\n]/ # 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 # @see http://ezid.cdlib.org/doc/apidoc.html#request-response-bodies UNESCAPE_RE = /%\h\h/ # A comment line COMMENT_RE = /^#.*(\r?\n)?/ # A line continuation LINE_CONTINUATION_RE = /\r?\n\s+/ # A line ending LINE_ENDING_RE = /\r?\n/ # EZID reserved metadata elements that are read-only # @see http://ezid.cdlib.org/doc/apidoc.html#internal-metadata READONLY = %w( _owner _ownergroup _shadows _shadowedby _datacenter _created _updated ) metadata_accessor :_coowners, :coowners metadata_accessor :_crossref metadata_accessor :_export, :export metadata_accessor :_profile, :profile metadata_accessor :_status, :status metadata_accessor :_target, :target metadata_accessor :crossref metadata_accessor :datacite metadata_accessor :erc 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 metadata_profile :dc, :creator, :title, :publisher, :date, :type metadata_profile :datacite, :creator, :title, :publisher, :publicationyear, :resourcetype metadata_profile :erc, :who, :what, :when def initialize(data={}) super coerce(data) end def created to_time _created end def updated to_time _updated 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) 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 private def to_time(value) time = value.to_i (time == 0) ? nil : Time.at(time).utc 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 # Unescape value from EZID host (or other source) 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. 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