Class: Idml::Package

Inherits:
Object
  • Object
show all
Defined in:
lib/idml/package.rb

Overview

Read/write access to an IDML package's ZIP container. A Package holds a path; ZIP and part reads are lazy. Typed part access goes through #part(name) which dispatches via Idml::Parts.class_for.

Examples:

Open a package and read the designmap

pkg = Idml::Package.new("path/to/file.idml")
dm = pkg.designmap
puts dm.dom_version

Write a new package

parts = { "mimetype" => "...", "designmap.xml" => "..." }
Idml::Package.write(parts: parts, to: "out.idml")

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ Package

Returns a new instance of Package.



22
23
24
25
26
# File 'lib/idml/package.rb', line 22

def initialize(path)
  raise Errors::PackageNotFound, path.to_s unless File.exist?(path)

  @path = File.expand_path(path)
end

Instance Attribute Details

#pathObject (readonly)

Returns the value of attribute path.



28
29
30
# File 'lib/idml/package.rb', line 28

def path
  @path
end

Class Method Details

.write(parts:, to:) ⇒ Object



167
168
169
170
171
172
173
174
# File 'lib/idml/package.rb', line 167

def self.write(parts:, to:)
  raise Errors::InvalidPackage, "no parts given" if parts.empty?

  output_path = File.expand_path(to)
  FileUtils.mkdir_p(File.dirname(output_path))
  write_to_zip(parts, output_path)
  new(output_path)
end

Instance Method Details

#backing_storyObject



34
35
36
# File 'lib/idml/package.rb', line 34

def backing_story
  @backing_story ||= part("XML/BackingStory.xml")
end

#designmapObject



30
31
32
# File 'lib/idml/package.rb', line 30

def designmap
  @designmap ||= part("designmap.xml")
end

#dom_versionObject



110
111
112
# File 'lib/idml/package.rb', line 110

def dom_version
  designmap&.dom_version
end

#each_partObject



155
156
157
158
159
160
161
162
163
164
165
# File 'lib/idml/package.rb', line 155

def each_part
  return enum_for(:each_part) unless block_given?

  with_zip do |zip_file|
    zip_file.each do |entry|
      next if entry.directory?

      yield entry.name, entry.get_input_stream.read
    end
  end
end

#fontsObject



70
71
72
73
74
# File 'lib/idml/package.rb', line 70

def fonts
  return unless has_part?("Resources/Fonts.xml")

  @fonts ||= part("Resources/Fonts.xml")
end

#graphicObject



76
77
78
79
80
# File 'lib/idml/package.rb', line 76

def graphic
  return unless has_part?("Resources/Graphic.xml")

  @graphic ||= part("Resources/Graphic.xml")
end

#has_part?(name) ⇒ Boolean

Returns:

  • (Boolean)


124
125
126
# File 'lib/idml/package.rb', line 124

def has_part?(name)
  part_names.include?(name)
end

#mappingObject



106
107
108
# File 'lib/idml/package.rb', line 106

def mapping
  @mapping ||= part("XML/Mapping.xml") if has_part?("XML/Mapping.xml")
end

#master_spread_by_id(master_id) ⇒ Object



61
62
63
64
65
66
67
68
# File 'lib/idml/package.rb', line 61

def master_spread_by_id(master_id)
  return nil unless master_id

  name = "MasterSpreads/MasterSpread_#{master_id}.xml"
  return nil unless has_part?(name)

  part(name)
end

#master_spreadsObject



42
43
44
45
46
# File 'lib/idml/package.rb', line 42

def master_spreads
  @master_spreads ||= part_names.grep(%r{\AMasterSpreads/}).map do |n|
    part(n)
  end
end

#part(name) ⇒ Object

Returns a typed part instance for name when a class is registered for that file pattern (e.g. Designmap for designmap.xml). Falls back to Parts::Raw — a lossless XML wrapper — for unmodeled parts. Parsed parts are memoized: rendering resolves stories, preferences, and styles repeatedly (per frame), and each un-memoized read would re-decompress and re-parse the zip entry.



141
142
143
144
# File 'lib/idml/package.rb', line 141

def part(name)
  @part_cache ||= {}
  @part_cache[name] ||= parse_part(name)
end

#part_namesObject



114
115
116
117
118
119
120
121
122
# File 'lib/idml/package.rb', line 114

def part_names
  @part_names ||= with_zip do |zip_file|
    names = []
    zip_file.each do |entry|
      names << entry.name if entry.name && !entry.name.empty?
    end
    names.sort
  end
end

#preferencesObject



95
96
97
98
99
100
# File 'lib/idml/package.rb', line 95

def preferences
  if has_part?("Resources/Preferences.xml")
    @preferences ||=
      part("Resources/Preferences.xml")
  end
end

#read_part(name) ⇒ Object



128
129
130
131
132
# File 'lib/idml/package.rb', line 128

def read_part(name)
  raise Errors::PartNotFound, name unless has_part?(name)

  with_zip { |zip_file| zip_file.read(name) }
end

#spreadsObject



38
39
40
# File 'lib/idml/package.rb', line 38

def spreads
  @spreads ||= part_names.grep(%r{\ASpreads/}).map { |n| part(n) }
end

#storiesObject



48
49
50
# File 'lib/idml/package.rb', line 48

def stories
  @stories ||= part_names.grep(%r{\AStories/}).map { |n| part(n) }
end

#story_by_id(story_id) ⇒ Object



52
53
54
55
56
57
58
59
# File 'lib/idml/package.rb', line 52

def story_by_id(story_id)
  return nil unless story_id

  name = "Stories/Story_#{story_id}.xml"
  return nil unless has_part?(name)

  part(name)
end

#styleObject



82
83
84
85
86
# File 'lib/idml/package.rb', line 82

def style
  return unless has_part?("Resources/Styles.xml")

  @style ||= part("Resources/Styles.xml")
end

#style_mappingObject



88
89
90
91
92
93
# File 'lib/idml/package.rb', line 88

def style_mapping
  if has_part?("Resources/StyleMapping.xml")
    @style_mapping ||=
      part("Resources/StyleMapping.xml")
  end
end

#tagsObject



102
103
104
# File 'lib/idml/package.rb', line 102

def tags
  @tags ||= part("XML/Tags.xml") if has_part?("XML/Tags.xml")
end