2011-03-10 22:45:21 +00:00
|
|
|
require 'nokogiri'
|
2011-03-18 16:28:53 +00:00
|
|
|
require 'avm/node'
|
2011-03-10 22:45:21 +00:00
|
|
|
|
|
|
|
module AVM
|
2011-03-23 18:10:02 +00:00
|
|
|
# An XMP document wrapper, providing namespace handling and document reference assistance.
|
2011-03-10 22:45:21 +00:00
|
|
|
class XMP
|
2011-03-14 16:35:29 +00:00
|
|
|
PREFIXES = {
|
|
|
|
'dc' => 'Dublin Core',
|
|
|
|
'Iptc4xmpCore' => 'IPTC',
|
2011-03-15 14:55:09 +00:00
|
|
|
'photoshop' => 'Photoshop',
|
|
|
|
'avm' => 'AVM'
|
2011-03-14 16:35:29 +00:00
|
|
|
}
|
|
|
|
|
2011-03-18 16:28:53 +00:00
|
|
|
REQUIRED_NAMESPACES = {
|
|
|
|
:x => "adobe:ns:meta/",
|
|
|
|
:rdf => "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
|
|
|
|
:dc => "http://purl.org/dc/elements/1.1/",
|
|
|
|
:photoshop => "http://ns.adobe.com/photoshop/1.0/",
|
|
|
|
:avm => "http://www.communicatingastronomy.org/avm/1.0/",
|
|
|
|
:Iptc4xmpCore => "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/"
|
|
|
|
}
|
|
|
|
|
2011-03-10 22:45:21 +00:00
|
|
|
attr_reader :doc
|
|
|
|
|
2011-03-11 18:27:17 +00:00
|
|
|
def initialize(doc = nil)
|
|
|
|
@doc = doc || empty_xml_doc
|
|
|
|
ensure_namespaces!
|
|
|
|
ensure_descriptions_findable!
|
2011-03-10 22:45:21 +00:00
|
|
|
end
|
|
|
|
|
2011-03-11 18:27:17 +00:00
|
|
|
def get_refs
|
2011-03-14 16:35:29 +00:00
|
|
|
yield Hash[[ :dublin_core, :iptc, :photoshop, :avm ].collect { |key| [ key, send(key) ] }]
|
2011-03-10 22:45:21 +00:00
|
|
|
end
|
|
|
|
|
2011-03-11 18:27:17 +00:00
|
|
|
def self.from_string(string)
|
|
|
|
new(Nokogiri::XML(string))
|
|
|
|
end
|
|
|
|
|
2011-03-18 16:28:53 +00:00
|
|
|
def ensure_xmlns(string)
|
|
|
|
string.gsub(%r{([</@])(\w+):}) { |all, matches| $1 + (prefix_map[$2] || $2) + ':' }
|
|
|
|
end
|
|
|
|
|
|
|
|
alias :% :ensure_xmlns
|
|
|
|
|
|
|
|
def ensure_xpath(path)
|
|
|
|
[ ensure_xmlns(path), namespaces ]
|
|
|
|
end
|
|
|
|
|
|
|
|
def search(path, node = doc)
|
|
|
|
node.search(*ensure_xpath(path))
|
|
|
|
end
|
|
|
|
|
|
|
|
def at_xpath(path, node = doc)
|
|
|
|
node.at_xpath(*ensure_xpath(path))
|
|
|
|
end
|
|
|
|
|
|
|
|
def namespaces
|
|
|
|
@namespaces ||= doc.document.collect_namespaces
|
|
|
|
end
|
|
|
|
|
2011-03-10 22:45:21 +00:00
|
|
|
private
|
2011-03-23 18:10:02 +00:00
|
|
|
def current_namespaces
|
|
|
|
doc.document.collect_namespaces
|
|
|
|
end
|
2011-03-18 16:28:53 +00:00
|
|
|
|
2011-03-23 18:10:02 +00:00
|
|
|
def prefix_map
|
|
|
|
@prefix_map ||= Hash[current_namespaces.collect { |prefix, namespace|
|
|
|
|
self.class.get_required_namespace(namespace, prefix.gsub('xmlns:', ''))
|
2011-03-18 16:28:53 +00:00
|
|
|
}.compact]
|
|
|
|
end
|
2011-03-23 18:10:02 +00:00
|
|
|
|
|
|
|
def self.get_required_namespace(namespace, prefix)
|
|
|
|
result = nil
|
|
|
|
REQUIRED_NAMESPACES.each do |original_prefix, target_namespace|
|
|
|
|
result = [ original_prefix.to_s, prefix ] if namespace == target_namespace
|
|
|
|
end
|
|
|
|
result
|
|
|
|
end
|
2011-03-18 16:28:53 +00:00
|
|
|
|
2011-03-11 18:27:17 +00:00
|
|
|
def ensure_namespaces!
|
2011-03-23 18:10:02 +00:00
|
|
|
existing = current_namespaces
|
2011-03-18 16:28:53 +00:00
|
|
|
|
|
|
|
REQUIRED_NAMESPACES.each do |namespace, url|
|
|
|
|
doc.root.add_namespace_definition(namespace.to_s, url) if !existing.values.include?(url)
|
2011-03-11 18:27:17 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def ensure_descriptions_findable!
|
2011-03-15 14:55:09 +00:00
|
|
|
added = []
|
|
|
|
|
2011-03-18 16:28:53 +00:00
|
|
|
search('//rdf:Description').each do |description|
|
2011-03-11 18:27:17 +00:00
|
|
|
if first_child = description.first_element_child
|
2011-03-23 18:10:02 +00:00
|
|
|
if namespace = first_child.namespace
|
|
|
|
prefix = namespace.prefix
|
2011-03-15 14:55:09 +00:00
|
|
|
|
2011-03-18 16:28:53 +00:00
|
|
|
if prefix_description = PREFIXES[prefix_map.index(prefix)]
|
|
|
|
description[self % 'rdf:about'] = prefix_description
|
|
|
|
added << prefix
|
|
|
|
end
|
2011-03-11 18:27:17 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2011-03-15 14:55:09 +00:00
|
|
|
|
2011-03-23 18:10:02 +00:00
|
|
|
ensure_rdf!
|
|
|
|
ensure_missing_descriptions!(added)
|
|
|
|
end
|
2011-03-18 16:28:53 +00:00
|
|
|
|
2011-03-23 18:10:02 +00:00
|
|
|
def ensure_rdf!
|
|
|
|
doc.first_element_child.add_child(self % '<rdf:RDF />') if !at_xpath('//rdf:RDF')
|
|
|
|
end
|
2011-03-18 16:28:53 +00:00
|
|
|
|
2011-03-23 18:10:02 +00:00
|
|
|
def ensure_missing_descriptions!(already_added)
|
2011-03-15 14:55:09 +00:00
|
|
|
PREFIXES.each do |prefix, about|
|
2011-03-23 18:10:02 +00:00
|
|
|
if !already_added.include?(prefix)
|
2011-03-18 16:28:53 +00:00
|
|
|
at_xpath('//rdf:RDF').add_child(self % %{<rdf:Description rdf:about="#{about}" />})
|
2011-03-15 14:55:09 +00:00
|
|
|
end
|
|
|
|
end
|
2011-03-11 18:27:17 +00:00
|
|
|
end
|
|
|
|
|
2011-03-10 22:45:21 +00:00
|
|
|
def dublin_core
|
|
|
|
at_rdf_description "Dublin Core"
|
|
|
|
end
|
|
|
|
|
|
|
|
def iptc
|
|
|
|
at_rdf_description "IPTC"
|
|
|
|
end
|
|
|
|
|
2011-03-14 16:35:29 +00:00
|
|
|
def avm
|
|
|
|
at_rdf_description "AVM"
|
|
|
|
end
|
|
|
|
|
|
|
|
def photoshop
|
|
|
|
at_rdf_description "Photoshop"
|
|
|
|
end
|
|
|
|
|
2011-03-10 22:45:21 +00:00
|
|
|
def at_rdf_description(about)
|
2011-03-18 16:28:53 +00:00
|
|
|
AVM::Node.new(self, at_xpath(%{//rdf:Description[@rdf:about="#{about}"]}))
|
2011-03-10 22:45:21 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def empty_xml_doc
|
2011-03-11 18:27:17 +00:00
|
|
|
Nokogiri::XML(<<-XML)
|
2011-03-10 22:45:21 +00:00
|
|
|
<x:xmpmeta xmlns:x="adobe:ns:meta/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
|
|
|
<rdf:RDF>
|
2011-03-15 14:55:09 +00:00
|
|
|
<rdf:Description rdf:about="Dublin Core" />
|
|
|
|
<rdf:Description rdf:about="IPTC" />
|
|
|
|
<rdf:Description rdf:about="Photoshop" />
|
|
|
|
<rdf:Description rdf:about="AVM" />
|
2011-03-10 22:45:21 +00:00
|
|
|
</rdf:RDF>
|
|
|
|
</x:xmpmeta>
|
|
|
|
XML
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|