openxml-package 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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