openxml-package 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9125838f28df960214ac225121b29ecabee182e4
4
+ data.tar.gz: 23eefb7c402ecf3b0c500c1241a9e5105c24a7e3
5
+ SHA512:
6
+ metadata.gz: b22bbdd8a0064445003508a9f5c96528cf5950725f863a5c8eba95389885f3767f6c5d44ec325bb4a7e451cdfe788d84afaf2445454472d437606b182d69c93b
7
+ data.tar.gz: 81aa9f271fb5f65ca9e31c5b6a03b99d8f701003d1fd534749c5c87863cc84eb12b72f717e49065d4007502cada293c6ff9b31db6f6b2637ece7c64b2196fd63
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in openxml-package.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Bob Lail
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,121 @@
1
+ # OpenXml::Package
2
+
3
+ A Ruby implementation of [DocumentFormat.OpenXml.Packaging.OpenXmlPackage](http://msdn.microsoft.com/en-us/library/documentformat.openxml.packaging.openxmlpackage_members(v=office.14).aspx) from Microsoft's Open XML SDK.
4
+
5
+ The base class for [Rocx::Package](https://github.com/openxml/openxml-docx/blob/master/lib/openxml-docx/package.rb), [Xlsx::Package](https://github.com/openxml/openxml-xlsx/blob/master/lib/openxml-xlsx/package.rb), and [Pptx::Package](https://github.com/openxml/openxml-pptx/blob/master/lib/openxml-pptx/package.rb).
6
+
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'openxml-package'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install openxml-package
21
+
22
+
23
+
24
+ ## Usage
25
+
26
+ #### Writing
27
+
28
+ You can assemble an Open XML Package in-memory and then write it to disk:
29
+
30
+ ```ruby
31
+ package = OpenXml::Package.new
32
+ package.add_part "content/document.xml", OpenXml::Parts::UnparsedPart.new("<document></document>")
33
+ package.add_part "media/image.png", OpenXml::Parts::UnparsedPart.new(File.open(image_path, "rb", &:read))
34
+ package.write_to "~/Desktop/output.zip"
35
+ ```
36
+
37
+
38
+ #### Reading
39
+
40
+ You can read the contents of an Open XML Package:
41
+
42
+ ```ruby
43
+ OpenXmlPackage.open("~/Desktop/output.zip") do |package|
44
+ package.parts.keys # => ["content/document.xml", "media/image.png"]
45
+ end
46
+ ```
47
+
48
+
49
+ #### Subclassing
50
+
51
+ `OpenXml::Package` is intended to be the base class for libraries that implement Open XML formats for Microsoft Office products.
52
+
53
+ For example, a very simple Microsoft Word document can be defined as follows:
54
+
55
+ ```ruby
56
+ require "openxml/package"
57
+
58
+ module Rocx
59
+ class Package < OpenXml::Package
60
+ attr_reader :document,
61
+ :doc_rels,
62
+ :settings,
63
+ :styles
64
+
65
+ content_types do
66
+ default "png", TYPE_PNG
67
+ override "/word/styles.xml", TYPE_STYLES
68
+ override "/word/settings.xml", TYPE_SETTINGS
69
+ end
70
+
71
+ def initialize
72
+ super
73
+
74
+ rels.add_relationship REL_DOCUMENT, "/word/document.xml"
75
+ @doc_rels = OpenXml::Parts::Rels.new([
76
+ { type: REL_STYLES, target: "/word/styles.xml"},
77
+ { type: REL_SETTINGS, target: "/word/settings.xml"}
78
+ ])
79
+ @settings = Rocx::Parts::Settings.new
80
+ @styles = Rocx::Parts::Styles.new
81
+ @document = Rocx::Parts::Document.new
82
+
83
+ add_part "word/_rels/document.xml.rels", doc_rels
84
+ add_part "word/document.xml", document
85
+ add_part "word/settings.xml", settings
86
+ add_part "word/styles.xml", styles
87
+ end
88
+
89
+ end
90
+ end
91
+ ```
92
+
93
+ This gem also defines two "Parts" that are commonly used in Open XML packages.
94
+
95
+ ##### OpenXml::Parts::ContentTypes
96
+
97
+ Is used to identify the ContentType of all of the files in the package. There are two ways of identifying content types:
98
+
99
+ 1. **Default**: declares the default content type for a file with a given extension
100
+ 2. **Override**: declares the content type for a specific file with the given path inside the package
101
+
102
+ ##### OpenXml::Parts::Rels
103
+
104
+ Is used to identify links within the package
105
+
106
+
107
+
108
+ ## Contributing
109
+
110
+ 1. Fork it ( https://github.com/openxml/openxml-package/fork )
111
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
112
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
113
+ 4. Push to the branch (`git push origin my-new-feature`)
114
+ 5. Create a new Pull Request
115
+
116
+ #### Reference
117
+
118
+ - [DocumentFormat.OpenXml.Packaging.OpenXmlPackage](http://msdn.microsoft.com/en-us/library/documentformat.openxml.packaging.openxmlpackage_members(v=office.14).aspx)
119
+ - [DocumentFormat.OpenXml.Packaging.OpenXmlPartContainer](http://msdn.microsoft.com/en-us/library/documentformat.openxml.packaging.openxmlpartcontainer_members(v=office.14).aspx)
120
+ - [DocumentFormat.OpenXml.Packaging.OpenXmlPart](http://msdn.microsoft.com/en-us/library/documentformat.openxml.packaging.openxmlpart_members(v=office.14).aspx)
121
+ - [System.IO.Packaging.Package](http://msdn.microsoft.com/en-us/library/system.io.packaging.package.aspx)
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "lib"
6
+ t.libs << "test"
7
+ t.pattern = "test/**/*_test.rb"
8
+ t.verbose = false
9
+ end
@@ -0,0 +1 @@
1
+ require "openxml/package"
@@ -0,0 +1,3 @@
1
+ module OpenXmlPackage
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,45 @@
1
+ # Constructing a large XML document (5MB) with the Ox
2
+ # gem is about 4x faster than with Nokogiri and about
3
+ # 5x fater than with Builder.
4
+ #
5
+ # This class mimics the XML Builder DSL.
6
+ require "ox"
7
+
8
+ module OpenXml
9
+ class Builder
10
+
11
+ def initialize
12
+ @document = Ox::Document.new(version: "1.0")
13
+ @current = @document
14
+ yield self if block_given?
15
+ end
16
+
17
+ def to_s
18
+ Ox.dump @document
19
+ end
20
+ alias :to_xml :to_s
21
+
22
+ def method_missing(tag_name, *args)
23
+ new_element = Ox::Element.new(tag_name)
24
+ attributes = args.extract_options!
25
+ attributes.each do |key, value|
26
+ new_element[key] = value
27
+ end
28
+
29
+ if block_given?
30
+ begin
31
+ was_current = @current
32
+ @current = new_element
33
+ yield self
34
+ ensure
35
+ @current = was_current
36
+ end
37
+ elsif value = args.first
38
+ new_element << value.to_s
39
+ end
40
+
41
+ @current << new_element
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,18 @@
1
+ module OpenXml
2
+ class ContentTypesPresets
3
+ attr_reader :defaults, :overrides
4
+
5
+ def initialize
6
+ @defaults, @overrides = {}, {}
7
+ end
8
+
9
+ def default(extension, content_type)
10
+ defaults[extension] = content_type
11
+ end
12
+
13
+ def override(part_name, content_type)
14
+ overrides[part_name] = content_type
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+ module OpenXml
2
+ module Errors
3
+
4
+ class MissingContentTypesPart < StandardError
5
+ end
6
+
7
+ end
8
+ end
@@ -0,0 +1,135 @@
1
+ require "openxml-package/version"
2
+ require "openxml/content_types_presets"
3
+ require "openxml/rubyzip_fix"
4
+ require "openxml/errors"
5
+ require "openxml/types"
6
+ require "openxml/parts"
7
+ require "zip"
8
+
9
+ module OpenXml
10
+ class Package
11
+ attr_reader :parts, :content_types, :rels
12
+
13
+
14
+
15
+ class << self
16
+ def content_types_presets
17
+ @content_types_presets ||= OpenXml::ContentTypesPresets.new
18
+ end
19
+
20
+ def content_types(&block)
21
+ content_types_presets.instance_eval &block
22
+ end
23
+
24
+ def open(path)
25
+ if block_given?
26
+ Zip::File.open(path) do |zipfile|
27
+ yield new(zipfile)
28
+ end
29
+ else
30
+ new Zip::File.open(path)
31
+ end
32
+ end
33
+
34
+ def from_stream(stream)
35
+ stream = StringIO.new(stream) if stream.is_a?(String)
36
+
37
+ # Hack: Zip::Entry.read_c_dir_entry initializes
38
+ # a new Zip::Entry by calling `io.path`. Zip::Entry
39
+ # uses this to open the original zipfile; but in
40
+ # this case, the StringIO _is_ the original.
41
+ def stream.path
42
+ self
43
+ end
44
+
45
+ zipfile = ::Zip::File.new("", true, true)
46
+ zipfile.read_from_stream(stream)
47
+ new(zipfile)
48
+ end
49
+ end
50
+
51
+
52
+
53
+ def initialize(zipfile=nil)
54
+ @zipfile = zipfile
55
+ @parts = {}
56
+
57
+ if zipfile
58
+ read_zipfile!
59
+ else
60
+ set_defaults
61
+ end
62
+ end
63
+
64
+
65
+
66
+ def add_part(path, part)
67
+ @parts[path] = part
68
+ end
69
+
70
+ def get_part(path)
71
+ @parts.fetch(path)
72
+ end
73
+
74
+ def type_of(path)
75
+ raise Errors::MissingContentTypesPart, "We haven't yet read [ContentTypes].xml; but are reading #{path.inspect}" unless content_types
76
+ content_types.of(path)
77
+ end
78
+
79
+
80
+
81
+ def close
82
+ zipfile.close if zipfile
83
+ end
84
+
85
+ def write_to(path)
86
+ File.open(path, "w") do |file|
87
+ file.write to_stream.string
88
+ end
89
+ end
90
+ alias :save :write_to
91
+
92
+ def to_stream
93
+ Zip::OutputStream.write_buffer do |io|
94
+ parts.each do |path, part|
95
+ io.put_next_entry path
96
+ io.write part.content
97
+ end
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ attr_reader :zipfile
104
+
105
+ def read_zipfile!
106
+ zipfile.entries.each do |entry|
107
+ path, part = entry.name, Parts::UnparsedPart.new(entry)
108
+ add_part path, case path
109
+ when "[Content_Types].xml" then @content_types = Parts::ContentTypes.parse(part.content)
110
+ when "_rels/.rels" then @rels = Parts::Rels.parse(part.content)
111
+ else part_for(path, type_of(path), part)
112
+ end
113
+ end
114
+ end
115
+
116
+ protected
117
+
118
+ def set_defaults
119
+ presets = self.class.content_types_presets
120
+ @content_types = Parts::ContentTypes.new(presets.defaults, presets.overrides)
121
+ add_part "[Content_Types].xml", content_types
122
+
123
+ @rels = Parts::Rels.new
124
+ add_part "_rels/.rels", rels
125
+ end
126
+
127
+ def part_for(path, content_type, part)
128
+ case content_type
129
+ when Types::RELATIONSHIPS then Parts::Rels.parse(part.content)
130
+ else part
131
+ end
132
+ end
133
+
134
+ end
135
+ end
@@ -0,0 +1,32 @@
1
+ require "nokogiri"
2
+ require "openxml/builder"
3
+
4
+ module OpenXml
5
+ class Part
6
+ include ::Nokogiri
7
+
8
+ def build_xml
9
+ OpenXml::Builder.new { |xml| yield xml }.to_xml
10
+ end
11
+
12
+ def build_standalone_xml(&block)
13
+ "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" + build_xml(&block)
14
+ end
15
+
16
+ def read
17
+ strip_whitespace to_xml
18
+ end
19
+ alias :content :read
20
+
21
+ def to_xml
22
+ raise NotImplementedError
23
+ end
24
+
25
+ protected
26
+
27
+ def strip_whitespace(xml)
28
+ xml.lines.map(&:strip).join
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,10 @@
1
+ require "openxml/part"
2
+
3
+ module OpenXml
4
+ module Parts
5
+ end
6
+ end
7
+
8
+ Dir.glob("#{File.join(File.dirname(__FILE__), "parts", "*.rb")}").each do |file|
9
+ require file
10
+ end
@@ -0,0 +1,51 @@
1
+ module OpenXml
2
+ module Parts
3
+ class ContentTypes < OpenXml::Part
4
+ attr_reader :defaults, :overrides
5
+
6
+ REQUIRED_DEFAULTS = {
7
+ "xml" => Types::XML,
8
+ "rels" => Types::RELATIONSHIPS
9
+ }.freeze
10
+
11
+ def self.parse(xml)
12
+ document = Nokogiri(xml)
13
+ self.new.tap do |part|
14
+ document.css("Default").each do |default|
15
+ part.add_default default["Extension"], default["ContentType"]
16
+ end
17
+ document.css("Override").each do |default|
18
+ part.add_override default["PartName"], default["ContentType"]
19
+ end
20
+ end
21
+ end
22
+
23
+ def initialize(defaults={}, overrides={})
24
+ @defaults = REQUIRED_DEFAULTS.merge(defaults)
25
+ @overrides = overrides
26
+ end
27
+
28
+ def add_default(extension, content_type)
29
+ defaults[extension] = content_type
30
+ end
31
+
32
+ def add_override(part_name, content_type)
33
+ overrides[part_name] = content_type
34
+ end
35
+
36
+ def of(path)
37
+ overrides.fetch(path, defaults[File.extname(path)[1..-1]])
38
+ end
39
+
40
+ def to_xml
41
+ build_xml do |xml|
42
+ xml.Types(xmlns: "http://schemas.openxmlformats.org/package/2006/content-types") {
43
+ defaults.each { |extension, content_type| xml.Default("Extension" => extension, "ContentType" => content_type) }
44
+ overrides.each { |part_name, content_type| xml.Override("PartName" => part_name, "ContentType" => content_type) }
45
+ }
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,57 @@
1
+ require "securerandom"
2
+
3
+ module OpenXml
4
+ module Parts
5
+ class Rels < OpenXml::Part
6
+ include Enumerable
7
+
8
+ def self.parse(xml)
9
+ document = Nokogiri(xml)
10
+ self.new.tap do |part|
11
+ document.css("Relationship").each do |rel|
12
+ part.add_relationship rel["Type"], rel["Target"], rel["Id"]
13
+ end
14
+ end
15
+ end
16
+
17
+ def initialize(defaults=[])
18
+ @relationships = []
19
+ Array(defaults).each do |default|
20
+ add_relationship(*default.values_at("Type", "Target", "Id"))
21
+ end
22
+ end
23
+
24
+ def add_relationship(type, target, id=nil)
25
+ Relationship.new(type, target, id).tap do |relationship|
26
+ relationships.push relationship
27
+ end
28
+ end
29
+
30
+ def each(&block)
31
+ relationships.each(&block)
32
+ end
33
+
34
+ def to_xml
35
+ build_standalone_xml do |xml|
36
+ xml.Relationships(xmlns: "http://schemas.openxmlformats.org/package/2006/relationships") do
37
+ relationships.each do |rel|
38
+ xml.Relationship("Id" => rel.id, "Type" => rel.type, "Target" => rel.target)
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+
45
+
46
+ class Relationship < Struct.new(:type, :target, :id)
47
+ def initialize(type, target, id=nil)
48
+ super type, target, id || "R#{SecureRandom.hex}"
49
+ end
50
+ end
51
+
52
+ private
53
+ attr_reader :relationships
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,20 @@
1
+ module OpenXml
2
+ module Parts
3
+ class UnparsedPart
4
+
5
+ def initialize(content)
6
+ @content = content
7
+ end
8
+
9
+ def content
10
+ @content = @content.get_input_stream.read if promise?
11
+ @content
12
+ end
13
+
14
+ def promise?
15
+ @content.respond_to? :get_input_stream
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ require "zip"
2
+
3
+ module Zip
4
+ class InputStream
5
+ protected
6
+
7
+ # The problem in RubyZip 1.1.0 is that we only call `seek`
8
+ # when `io` is a File. We need to move the cursor to the
9
+ # right position when `io` is a StringIO as well.
10
+ def get_io(io, offset = 0)
11
+ io = ::File.open(io, "rb") unless io.is_a?(IO) || io.is_a?(StringIO)
12
+ io.seek(offset, ::IO::SEEK_SET)
13
+ io
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ module OpenXml
2
+ module Types
3
+ XML = "application/xml".freeze
4
+ RELATIONSHIPS = "application/vnd.openxmlformats-package.relationships+xml".freeze
5
+ end
6
+ end
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "openxml-package/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "openxml-package"
8
+ spec.version = OpenXmlPackage::VERSION
9
+ spec.authors = ["Bob Lail"]
10
+ spec.email = ["bob.lailfamily@gmail.com"]
11
+
12
+ spec.summary = %q{A Ruby implementation of OpenXmlPackage}
13
+ spec.description = %q{A Ruby implementation of OpenXmlPackage}
14
+ spec.license = "MIT"
15
+ spec.homepage = "https://github.com/openxml/openxml-package"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "rubyzip", "~> 1.1.0"
23
+ spec.add_dependency "nokogiri"
24
+ spec.add_dependency "ox"
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.6"
27
+ spec.add_development_dependency "rake"
28
+ spec.add_development_dependency "rails", ">= 3.2", "< 5.0"
29
+ spec.add_development_dependency "minitest"
30
+ spec.add_development_dependency "minitest-reporters"
31
+ spec.add_development_dependency "minitest-reporters-turn_reporter"
32
+ spec.add_development_dependency "pry"
33
+ spec.add_development_dependency "rr"
34
+ spec.add_development_dependency "simplecov"
35
+ spec.add_development_dependency "shoulda-context"
36
+
37
+ end
@@ -0,0 +1,34 @@
1
+ require "test_helper"
2
+
3
+ class ContentTypesTest < ActiveSupport::TestCase
4
+ attr_reader :content_types
5
+
6
+ WORDPROCESSING_DOCUMENT_TYPE = "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"
7
+
8
+ setup do
9
+ @content_types = OpenXml::Parts::ContentTypes.new(
10
+ {"xml" => OpenXml::Types::XML, "rels" => OpenXml::Types::RELATIONSHIPS},
11
+ {"word/document.xml" => WORDPROCESSING_DOCUMENT_TYPE})
12
+ end
13
+
14
+
15
+ context "Given a path without an override" do
16
+ should "identify the content type from its extension" do
17
+ assert_equal OpenXml::Types::XML, content_types.of("content/some.xml")
18
+ end
19
+ end
20
+
21
+ context "Given a path with an override" do
22
+ should "identify the content type from its path" do
23
+ assert_equal WORDPROCESSING_DOCUMENT_TYPE, content_types.of("word/document.xml")
24
+ end
25
+ end
26
+
27
+ context "Given a path with an unrecognized extension" do
28
+ should "be nil" do
29
+ assert_equal nil, content_types.of("img/screenshot.jpg")
30
+ end
31
+ end
32
+
33
+
34
+ end
@@ -0,0 +1,150 @@
1
+ require "test_helper"
2
+ require "fileutils"
3
+ require "set"
4
+
5
+ class OpenXmlPackageTest < ActiveSupport::TestCase
6
+ attr_reader :package, :temp_file
7
+
8
+
9
+
10
+ context "#add_part" do
11
+ should "accept a path and a part" do
12
+ package = OpenXml::Package.new
13
+ assert_difference "package.parts.count", +1 do
14
+ package.add_part "PATH", OpenXml::Part.new
15
+ end
16
+ end
17
+ end
18
+
19
+
20
+
21
+ context "Writing" do
22
+ setup do
23
+ @temp_file = expand_path "../tmp/test.zip"
24
+ FileUtils.rm temp_file, force: true
25
+ end
26
+
27
+ context "Given a simple part" do
28
+ setup do
29
+ @package = OpenXml::Package.new
30
+ package.add_part "content/document.xml", OpenXml::Parts::UnparsedPart.new(document_content)
31
+ end
32
+
33
+ should "write a valid zip file with the expected parts" do
34
+ package.write_to temp_file
35
+ assert File.exists?(temp_file), "Expected the file #{temp_file.inspect} to have been created"
36
+ assert_equal %w{[Content_Types].xml _rels/.rels content/document.xml},
37
+ Zip::File.open(temp_file).entries.map(&:name)
38
+ end
39
+ end
40
+ end
41
+
42
+
43
+
44
+ context "Reading" do
45
+ context "Given a sample Word document" do
46
+ setup do
47
+ @temp_file = expand_path "./support/sample.docx"
48
+ @expected_contents = Set[
49
+ "[Content_Types].xml",
50
+ "_rels/.rels",
51
+ "docProps/app.xml",
52
+ "docProps/core.xml",
53
+ "docProps/thumbnail.jpeg",
54
+ "word/_rels/document.xml.rels",
55
+ "word/document.xml",
56
+ "word/fontTable.xml",
57
+ "word/media/image1.png",
58
+ "word/settings.xml",
59
+ "word/styles.xml",
60
+ "word/stylesWithEffects.xml",
61
+ "word/theme/theme1.xml",
62
+ "word/webSettings.xml" ]
63
+ end
64
+
65
+ context ".open" do
66
+ setup do
67
+ @package = OpenXml::Package.open(temp_file)
68
+ end
69
+
70
+ teardown do
71
+ package.close
72
+ end
73
+
74
+ should "discover the expected parts" do
75
+ assert_equal @expected_contents, package.parts.keys.to_set
76
+ end
77
+
78
+ should "read their content on-demand" do
79
+ assert_equal web_settings_content, package.get_part("word/webSettings.xml").content
80
+ end
81
+ end
82
+
83
+ context ".from_stream" do
84
+ setup do
85
+ @package = OpenXml::Package.from_stream(File.open(temp_file, "rb", &:read))
86
+ end
87
+
88
+ should "also discover the expected parts" do
89
+ assert_equal @expected_contents, package.parts.keys.to_set
90
+ end
91
+
92
+ should "read their content" do
93
+ assert_equal web_settings_content, package.get_part("word/webSettings.xml").content
94
+ end
95
+ end
96
+
97
+ context "ContentTypes" do
98
+ setup do
99
+ @package = OpenXml::Package.open(temp_file)
100
+ end
101
+
102
+ teardown do
103
+ package.close
104
+ end
105
+
106
+ should "be parsed" do
107
+ assert_equal %w{jpeg png rels xml}, package.content_types.defaults.keys.sort
108
+ assert_equal "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml",
109
+ package.content_types.overrides["/word/document.xml"]
110
+ end
111
+ end
112
+
113
+ context "Rels" do
114
+ setup do
115
+ @package = OpenXml::Package.open(temp_file)
116
+ end
117
+
118
+ teardown do
119
+ package.close
120
+ end
121
+
122
+ should "be parsed" do
123
+ assert_equal %w{docProps/core.xml docProps/app.xml word/document.xml docProps/thumbnail.jpeg},
124
+ package.rels.map(&:target)
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+
131
+
132
+ private
133
+
134
+ def document_content
135
+ <<-STR
136
+ <document>
137
+ <body>Works!</body>
138
+ </document>
139
+ STR
140
+ end
141
+
142
+ def web_settings_content
143
+ "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n<w:webSettings xmlns:mc=\"http://schemas.openxmlformats.org/markup-compatibility/2006\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\" xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" xmlns:w14=\"http://schemas.microsoft.com/office/word/2010/wordml\" mc:Ignorable=\"w14\"><w:allowPNG/><w:doNotSaveAsSingleFile/></w:webSettings>"
144
+ end
145
+
146
+ def expand_path(path)
147
+ File.expand_path(File.join(File.dirname(__FILE__), path))
148
+ end
149
+
150
+ end
Binary file
@@ -0,0 +1,16 @@
1
+ require "rubygems"
2
+
3
+ require "simplecov"
4
+ SimpleCov.start do
5
+ add_filter "test/"
6
+ end
7
+
8
+ require "rails"
9
+ require "rails/test_help"
10
+ require "pry"
11
+ require "rr"
12
+ require "shoulda/context"
13
+ require "minitest/reporters/turn_reporter"
14
+ MiniTest::Reporters.use! Minitest::Reporters::TurnReporter.new
15
+
16
+ require "openxml/package"
metadata ADDED
@@ -0,0 +1,259 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: openxml-package
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Bob Lail
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-11-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rubyzip
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.1.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.1.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: nokogiri
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: ox
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.6'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '3.2'
90
+ - - "<"
91
+ - !ruby/object:Gem::Version
92
+ version: '5.0'
93
+ type: :development
94
+ prerelease: false
95
+ version_requirements: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '3.2'
100
+ - - "<"
101
+ - !ruby/object:Gem::Version
102
+ version: '5.0'
103
+ - !ruby/object:Gem::Dependency
104
+ name: minitest
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ - !ruby/object:Gem::Dependency
118
+ name: minitest-reporters
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ - !ruby/object:Gem::Dependency
132
+ name: minitest-reporters-turn_reporter
133
+ requirement: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ type: :development
139
+ prerelease: false
140
+ version_requirements: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ - !ruby/object:Gem::Dependency
146
+ name: pry
147
+ requirement: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ type: :development
153
+ prerelease: false
154
+ version_requirements: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ - !ruby/object:Gem::Dependency
160
+ name: rr
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ type: :development
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ - !ruby/object:Gem::Dependency
174
+ name: simplecov
175
+ requirement: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - ">="
178
+ - !ruby/object:Gem::Version
179
+ version: '0'
180
+ type: :development
181
+ prerelease: false
182
+ version_requirements: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ - !ruby/object:Gem::Dependency
188
+ name: shoulda-context
189
+ requirement: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - ">="
192
+ - !ruby/object:Gem::Version
193
+ version: '0'
194
+ type: :development
195
+ prerelease: false
196
+ version_requirements: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: '0'
201
+ description: A Ruby implementation of OpenXmlPackage
202
+ email:
203
+ - bob.lailfamily@gmail.com
204
+ executables: []
205
+ extensions: []
206
+ extra_rdoc_files: []
207
+ files:
208
+ - ".gitignore"
209
+ - Gemfile
210
+ - LICENSE.txt
211
+ - README.md
212
+ - Rakefile
213
+ - lib/openxml-package.rb
214
+ - lib/openxml-package/version.rb
215
+ - lib/openxml/builder.rb
216
+ - lib/openxml/content_types_presets.rb
217
+ - lib/openxml/errors.rb
218
+ - lib/openxml/package.rb
219
+ - lib/openxml/part.rb
220
+ - lib/openxml/parts.rb
221
+ - lib/openxml/parts/content_types.rb
222
+ - lib/openxml/parts/rels.rb
223
+ - lib/openxml/parts/unparsed_part.rb
224
+ - lib/openxml/rubyzip_fix.rb
225
+ - lib/openxml/types.rb
226
+ - openxml-package.gemspec
227
+ - test/content_types_test.rb
228
+ - test/package_test.rb
229
+ - test/support/sample.docx
230
+ - test/test_helper.rb
231
+ homepage: https://github.com/openxml/openxml-package
232
+ licenses:
233
+ - MIT
234
+ metadata: {}
235
+ post_install_message:
236
+ rdoc_options: []
237
+ require_paths:
238
+ - lib
239
+ required_ruby_version: !ruby/object:Gem::Requirement
240
+ requirements:
241
+ - - ">="
242
+ - !ruby/object:Gem::Version
243
+ version: '0'
244
+ required_rubygems_version: !ruby/object:Gem::Requirement
245
+ requirements:
246
+ - - ">="
247
+ - !ruby/object:Gem::Version
248
+ version: '0'
249
+ requirements: []
250
+ rubyforge_project:
251
+ rubygems_version: 2.4.8
252
+ signing_key:
253
+ specification_version: 4
254
+ summary: A Ruby implementation of OpenXmlPackage
255
+ test_files:
256
+ - test/content_types_test.rb
257
+ - test/package_test.rb
258
+ - test/support/sample.docx
259
+ - test/test_helper.rb