more work on image reads

This commit is contained in:
John Bintz 2011-03-15 10:55:09 -04:00
parent 3aede05d1b
commit 6695575762
10 changed files with 263 additions and 43 deletions

5
.autotest Normal file
View File

@ -0,0 +1,5 @@
Autotest.add_hook :initialize do |at|
at.add_mapping(%r{^spec/sample_files/([^/]+)/.*}, true) { |_, m|
"spec/avm/#{m[1]}_spec.rb"
}
end

1
.rspec
View File

@ -3,4 +3,3 @@
--format progress --format progress
--format RSpec::Core::Formatters::QuickFixFormatter --format RSpec::Core::Formatters::QuickFixFormatter
--out .quickfix.txt --out .quickfix.txt

View File

@ -1,8 +1,13 @@
require 'avm/creator' require 'avm/creator'
require 'avm/xmp' require 'avm/xmp'
require 'avm/image_type'
require 'avm/image_quality'
module AVM module AVM
class Image class Image
DUBLIN_CORE_FIELDS = [ :title, :description ]
AVM_SINGLE_FIELDS = [ 'Distance.Notes', 'ReferenceURL', 'Credit', 'Date', 'ID', 'Type', 'Image.ProductQuality' ]
attr_reader :creator attr_reader :creator
def initialize(options = {}) def initialize(options = {})
@ -16,20 +21,25 @@ module AVM
creator.add_to_document(document) creator.add_to_document(document)
document.get_refs do |refs| document.get_refs do |refs|
[ :title, :description ].each do |field| DUBLIN_CORE_FIELDS.each do |field|
refs[:dublin_core].add_child(%{<dc:#{field}>#{alt_li_tag(send(field))}</dc:#{field}>}) refs[:dublin_core].add_child(%{<dc:#{field}>#{alt_li_tag(send(field))}</dc:#{field}>})
end end
refs[:photoshop].add_child(%{<photoshop:Headline>#{headline}</photoshop:Headline>}) refs[:photoshop].add_child(%{<photoshop:Headline>#{headline}</photoshop:Headline>})
{ AVM_SINGLE_FIELDS.zip([distance_notes, reference_url, credit, string_date, id, image_type, image_quality]).each do |tag, value|
'Distance.Notes' => distance_notes, refs[:avm].add_child(%{<avm:#{tag}>#{value.to_s}</avm:#{tag}>}) if value
'ReferenceURL' => reference_url, end
'Credit' => credit,
'Date' => string_date, distance_nodes = []
'ID' => id, distance_nodes << rdf_li(light_years) if light_years
}.each do |tag, value| if redshift
refs[:avm].add_child(%{<avm:#{tag}>#{value}</avm:#{tag}>}) if value distance_nodes << rdf_li('-') if distance_nodes.empty?
distance_nodes << rdf_li(redshift)
end
if !distance_nodes.empty?
refs[:avm].add_child(%{<avm:Distance><rdf:Seq>#{distance_nodes.join}</rdf:Seq></avm:Distance>})
end end
end end
@ -41,7 +51,11 @@ module AVM
end end
def image_type def image_type
@options[:type] (AVM::ImageType.const_get(@options[:type].to_sym).new rescue nil)
end
def image_quality
(AVM::ImageQuality.const_get(@options[:quality].to_sym).new rescue nil)
end end
def date def date
@ -60,7 +74,39 @@ module AVM
def self.from_xml(string) def self.from_xml(string)
document = AVM::XMP.from_string(string) document = AVM::XMP.from_string(string)
image = new options = {}
document.get_refs do |refs|
DUBLIN_CORE_FIELDS.each do |field|
if node = refs[:dublin_core].at_xpath(".//dc:#{field}//rdf:li[1]")
options[field] = node.text
end
end
AVM_SINGLE_FIELDS.zip([ :distance_notes, :reference_url, :credit, :date, :id, :type, :quality ]).each do |tag, field|
if node = refs[:avm].at_xpath("./avm:#{tag}")
options[field] = node.text
end
end
if node = refs[:photoshop].at_xpath('./photoshop:Headline')
options[:headline] = node.text
end
if node = refs[:avm].at_xpath('./avm:Distance')
list_values = node.search('.//ref:li').collect { |li| li.text }
case list_values.length
when 1
options[:light_years] = list_values.first
when 2
options[:light_years] = (list_values.first == '-') ? nil : list_values.first
options[:redshift] = list_valueslast
end
end
end
image = new(options)
image.creator.from_xml(self, document) image.creator.from_xml(self, document)
image image
end end
@ -73,6 +119,10 @@ module AVM
def alt_li_tag(text) def alt_li_tag(text)
%{<rdf:Alt><rdf:li xml:lang="x-default">#{text}</rdf:li></rdf:Alt>} %{<rdf:Alt><rdf:li xml:lang="x-default">#{text}</rdf:li></rdf:Alt>}
end end
def rdf_li(text)
%{<rdf:li>#{text}</rdf:li>}
end
end end
end end

14
lib/avm/image_quality.rb Normal file
View File

@ -0,0 +1,14 @@
module AVM
module ImageQuality
%w{Good Moderate Poor}.each do |type|
klass = Class.new do
def to_s
self.class.to_s.split('::').last
end
end
AVM::ImageQuality.const_set(type.to_sym, klass)
end
end
end

14
lib/avm/image_type.rb Normal file
View File

@ -0,0 +1,14 @@
module AVM
module ImageType
%w{Observation Artwork Photographic Planetary Simulation Chart Collage}.each do |type|
klass = Class.new do
def to_s
self.class.to_s.split('::').last
end
end
AVM::ImageType.const_set(type.to_sym, klass)
end
end
end

View File

@ -5,7 +5,8 @@ module AVM
PREFIXES = { PREFIXES = {
'dc' => 'Dublin Core', 'dc' => 'Dublin Core',
'Iptc4xmpCore' => 'IPTC', 'Iptc4xmpCore' => 'IPTC',
'Photoshop' => 'Photoshop' 'photoshop' => 'Photoshop',
'avm' => 'AVM'
} }
attr_reader :doc attr_reader :doc
@ -39,13 +40,24 @@ module AVM
end end
def ensure_descriptions_findable! def ensure_descriptions_findable!
added = []
doc.search('//rdf:Description').each do |description| doc.search('//rdf:Description').each do |description|
if first_child = description.first_element_child if first_child = description.first_element_child
if first_child.namespace if first_child.namespace
description['about'] = PREFIXES[first_child.namespace.prefix] prefix = first_child.namespace.prefix
description['rdf:about'] = PREFIXES[prefix]
added << prefix
end end
end end
end end
PREFIXES.each do |prefix, about|
if !added.include?(prefix)
doc.at_xpath('//rdf:RDF').add_child(%{<rdf:Description rdf:about="#{about}" />})
end
end
end end
def dublin_core def dublin_core
@ -65,17 +77,17 @@ module AVM
end end
def at_rdf_description(about) def at_rdf_description(about)
@doc.at_xpath(%{//rdf:Description[@about="#{about}"]}) @doc.at_xpath(%{//rdf:Description[@rdf:about="#{about}"]})
end end
def empty_xml_doc def empty_xml_doc
Nokogiri::XML(<<-XML) Nokogiri::XML(<<-XML)
<x:xmpmeta xmlns:x="adobe:ns:meta/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <x:xmpmeta xmlns:x="adobe:ns:meta/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:RDF> <rdf:RDF>
<rdf:Description about="Dublin Core" /> <rdf:Description rdf:about="Dublin Core" />
<rdf:Description about="IPTC" /> <rdf:Description rdf:about="IPTC" />
<rdf:Description about="Photoshop" /> <rdf:Description rdf:about="Photoshop" />
<rdf:Description about="AVM" /> <rdf:Description rdf:about="AVM" />
</rdf:RDF> </rdf:RDF>
</x:xmpmeta> </x:xmpmeta>
XML XML

View File

@ -15,7 +15,7 @@ describe AVM::Image do
let(:credit) { 'Credit' } let(:credit) { 'Credit' }
let(:date) { '2010-01-01' } let(:date) { '2010-01-01' }
let(:id) { 'ID' } let(:id) { 'ID' }
let(:type) { 'Obvservation' } let(:type) { 'Observation' }
let(:image_quality) { 'Good' } let(:image_quality) { 'Good' }
let(:redshift) { 'Redshift' } let(:redshift) { 'Redshift' }
let(:light_years) { 'Light years' } let(:light_years) { 'Light years' }
@ -31,17 +31,13 @@ describe AVM::Image do
:date => date, :date => date,
:id => id, :id => id,
:type => type, :type => type,
:image_quality => image_quality, :quality => image_quality,
:redshift => redshift, :redshift => redshift,
:light_years => light_years :light_years => light_years
} } } }
end end
describe '#initialize' do def self.has_most_options
with_all_options
it { should be_a_kind_of(AVM::Image) }
its(:creator) { should be_a_kind_of(AVM::Creator) } its(:creator) { should be_a_kind_of(AVM::Creator) }
its(:title) { should == title } its(:title) { should == title }
its(:headline) { should == headline } its(:headline) { should == headline }
@ -51,20 +47,74 @@ describe AVM::Image do
its(:credit) { should == credit } its(:credit) { should == credit }
its(:date) { should == Time.parse(date) } its(:date) { should == Time.parse(date) }
its(:id) { should == id } its(:id) { should == id }
its(:image_type) { should == type } its(:image_type) { should be_a_kind_of eval("AVM::ImageType::#{type}") }
its(:image_quality) { should == image_quality } its(:image_quality) { should be_a_kind_of eval("AVM::ImageQuality::#{image_quality}") }
its(:redshift) { should == redshift } end
its(:light_years) { should == light_years }
describe '#initialize' do
with_all_options
it { should be_a_kind_of(AVM::Image) }
has_most_options
its(:distance) { should == [ light_years, redshift ] } its(:distance) { should == [ light_years, redshift ] }
end end
describe '.from_xml' do
let(:image) { AVM::Image.from_xml(File.read(file_path)) }
subject { image }
context "nothing in it" do
let(:file_path) { 'spec/sample_files/image/nothing.xmp' }
its(:title) { should be_nil }
its(:headline) { should be_nil }
its(:description) { should be_nil }
its(:distance_notes) { should be_nil }
its(:reference_url) { should be_nil }
its(:credit) { should be_nil }
its(:date) { should be_nil }
its(:id) { should be_nil }
its(:image_type) { should be_nil }
its(:image_quality) { should be_nil }
its(:redshift) { should be_nil }
its(:light_years) { should be_nil }
end
context "image in it" do
context "distance in light years" do
let(:file_path) { 'spec/sample_files/image/light_years.xmp' }
has_most_options
its(:redshift) { should be_nil }
end
context "distaince in redshift" do
let(:file_path) { 'spec/sample_files/image/redshift.xmp' }
end
context "distance in both" do
let(:file_path) { 'spec/sample_files/image/both.xmp' }
end
context "distance in neither" do
let(:file_path) { 'spec/sample_files/image/neither.xmp' }
end
end
end
describe '#to_xml' do describe '#to_xml' do
let(:xml) { image.to_xml } let(:xml) { image.to_xml }
let(:dublin_core) { xml.at_xpath('//rdf:Description[@about="Dublin Core"]') } let(:dublin_core) { xml.at_xpath('//rdf:Description[@rdf:about="Dublin Core"]') }
let(:photoshop) { xml.at_xpath('//rdf:Description[@about="Photoshop"]') } let(:photoshop) { xml.at_xpath('//rdf:Description[@rdf:about="Photoshop"]') }
let(:avm) { xml.at_xpath('//rdf:Description[@about="AVM"]') } let(:avm) { xml.at_xpath('//rdf:Description[@rdf:about="AVM"]') }
context 'nothing in it' do context 'nothing in it' do
it "should have basic tags" do it "should have basic tags" do
@ -87,23 +137,35 @@ describe AVM::Image do
avm.at_xpath('./avm:Credit').text.should == credit avm.at_xpath('./avm:Credit').text.should == credit
avm.at_xpath('./avm:Date').text.should == date avm.at_xpath('./avm:Date').text.should == date
avm.at_xpath('./avm:ID').text.should == id avm.at_xpath('./avm:ID').text.should == id
avm.at_xpath('./avm:Type').text.should == type
avm.at_xpath('./avm:Image.ProductQuality').text.should == image_quality
end end
context "distance" do context "distance" do
context "no distances" do context "no distances" do
let(:redshift) { nil }
let(:light_years) { nil }
specify { avm.at_xpath('./avm:Distance').should be_nil }
end end
context "redshift only" do context "redshift only" do
let(:light_years) { nil }
specify { avm.at_xpath('./avm:Distance/rdf:Seq/rdf:li[1]').text.should == '-' }
specify { avm.at_xpath('./avm:Distance/rdf:Seq/rdf:li[2]').text.should == redshift }
end end
context "light years only" do context "light years only" do
let(:redshift) { nil }
specify { avm.at_xpath('./avm:Distance/rdf:Seq/rdf:li[1]').text.should == light_years }
specify { avm.at_xpath('./avm:Distance/rdf:Seq/rdf:li[2]').should be_nil }
end end
context "redshift and light years" do context "redshift and light years" do
specify { avm.at_xpath('./avm:Distance/rdf:Seq/rdf:li[1]').text.should == light_years }
specify { avm.at_xpath('./avm:Distance/rdf:Seq/rdf:li[2]').text.should == redshift }
end end
end end
end end

View File

@ -12,18 +12,21 @@ describe AVM::XMP do
refs[:dublin_core] << "<rdf:addedToDublinCore />" refs[:dublin_core] << "<rdf:addedToDublinCore />"
refs[:iptc] << "<rdf:addedToIPTC />" refs[:iptc] << "<rdf:addedToIPTC />"
refs[:photoshop] << '<rdf:addedToPhotoshop />' refs[:photoshop] << '<rdf:addedToPhotoshop />'
refs[:avm] << '<rdf:addedToAVM />'
end end
} }
it "should have gotten the refs correctly" do it "should have gotten the refs correctly" do
xmp.doc.at_xpath('//rdf:Description[@about="Dublin Core"]//rdf:addedToDublinCore').should_not be_nil xmp.doc.at_xpath('//rdf:Description[@rdf:about="Dublin Core"]//rdf:addedToDublinCore').should_not be_nil
xmp.doc.at_xpath('//rdf:Description[@about="IPTC"]//rdf:addedToIPTC').should_not be_nil xmp.doc.at_xpath('//rdf:Description[@rdf:about="IPTC"]//rdf:addedToIPTC').should_not be_nil
xmp.doc.at_xpath('//rdf:Description[@rdf:about="Photoshop"]//rdf:addedToPhotoshop').should_not be_nil
xmp.doc.at_xpath('//rdf:Description[@rdf:about="AVM"]//rdf:addedToAVM').should_not be_nil
end end
end end
describe '.from_string' do describe '.from_string' do
let(:xmp) { self.class.describes.from_string(string) } let(:xmp) { self.class.describes.from_string(string) }
let(:string) { '<xml><node /></xml>' } let(:string) { '<xml xmlns:rdf="cats"><rdf:RDF><node /></rdf:RDF></xml>' }
specify { xmp.doc.at_xpath('//node').should_not be_nil } specify { xmp.doc.at_xpath('//node').should_not be_nil }
end end
@ -42,8 +45,8 @@ describe AVM::XMP do
context 'no nodes within' do context 'no nodes within' do
let(:content) { '' } let(:content) { '' }
[ 'Dublin Core', 'IPTC' ].each do |which| [ 'Dublin Core', 'IPTC', 'Photoshop', 'AVM' ].each do |which|
specify { xmp.doc.at_xpath(%{//rdf:Description[@about="#{which}"]}).should be_nil } specify { xmp.doc.at_xpath(%{//rdf:Description[@rdf:about="#{which}"]}).children.should be_empty }
end end
end end
@ -54,11 +57,17 @@ describe AVM::XMP do
</rdf:Description> </rdf:Description>
<rdf:Description rdf:about="" xmlns:Iptc4xmpCore="http://itpc.org/stf/Iptc4xmpCore/1.0/xmlns/"> <rdf:Description rdf:about="" xmlns:Iptc4xmpCore="http://itpc.org/stf/Iptc4xmpCore/1.0/xmlns/">
<Iptc4xmpCore:CreatorContactInfo rdf:parseType="Resource" /> <Iptc4xmpCore:CreatorContactInfo rdf:parseType="Resource" />
</rdf:Description>
<rdf:Description rdf:about="" xmlns:Photoshop="http://ns.adobe.com/photoshop/1.0/">
<photoshop:Something />
</rdf:Description>
<rdf:Description rdf:about="" xmlns:avm="http://www.communicatingastronomy.org/avm/1.0/">
<avm:Something />
</rdf:Description> </rdf:Description>
XML XML
[ 'Dublin Core', 'IPTC' ].each do |which| [ 'Dublin Core', 'IPTC', 'Photoshop', 'AVM' ].each do |which|
specify { xmp.doc.at_xpath(%{//rdf:Description[@about="#{which}"]}).should_not be_nil } specify { xmp.doc.at_xpath(%{//rdf:Description[@rdf:about="#{which}"]}).should_not be_nil }
end end
end end
end end

View File

@ -0,0 +1,37 @@
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 4.2.2-c063 53.352624, 2008/07/30-18:05:41 ">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:title>:
<rdf:Alt>
<rdf:li xml:lang="x-default">My title</rdf:li>
</rdf:Alt>
</dc:title>
<dc:description>
<rdf:Alt>
<rdf:li xml:lang="x-default">Description</rdf:li>
</rdf:Alt>
</dc:description>
</rdf:Description>
<rdf:Description rdf:about=""
xmlns:Iptc4xmpCore="http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/">
<Iptc4xmpCore:CreatorContactInfo rdf:parseType="Resource">
</Iptc4xmpCore:CreatorContactInfo>
</rdf:Description>
<rdf:Description rdf:about=""
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
<photoshop:Headline>Headline</photoshop:Headline>
</rdf:Description>
<rdf:Description rdf:about=""
xmlns:avm="http://www.communicatingastronomy.org/avm/1.0/">
<avm:Distance.Notes>Distance Notes</avm:Distance.Notes>
<avm:ReferenceURL>Reference URL</avm:ReferenceURL>
<avm:Credit>Credit</avm:Credit>
<avm:Date>2010-01-01</avm:Date>
<avm:ID>ID</avm:ID>
<avm:Type>Observation</avm:Type>
<avm:Image.ProductQuality>Good</avm:Image.ProductQuality>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>

View File

@ -0,0 +1,18 @@
<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core 4.2.2-c063 53.352624, 2008/07/30-18:05:41 ">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:dc="http://purl.org/dc/elements/1.1/">
<dc:creator />
</rdf:Description>
<rdf:Description rdf:about=""
xmlns:Iptc4xmpCore="http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/">
<Iptc4xmpCore:CreatorContactInfo rdf:parseType="Resource">
</Iptc4xmpCore:CreatorContactInfo>
</rdf:Description>
<rdf:Description rdf:about=""
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/">
<photoshop:ColorMode />
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>