ruby3mf 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d17051b85b5abed1a73edd6d69679e8f31f2160a
4
+ data.tar.gz: 6e5adfe5c57ab16095b832f03fb6405cdc63f730
5
+ SHA512:
6
+ metadata.gz: 6d961a1a3dc64d35425a2f1948b8a8fa1019a743227015be4635db2af6e91e8b9c29b536382970b46485426bda4a7f3ea8f85be964d39d6d65b4dceef6472418
7
+ data.tar.gz: e08a78b05990c4fd8e499297df32a68872271e1a27ac822469d33b46c2103261ae6bcce07578a668d04eda5cb868ce7e09ee7847a1db26b1ad04ea9b5edd8ab6
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .idea
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ ruby3mf
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.3.1
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.0
4
+ before_install: gem install bundler -v 1.11.2
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at mwhit@hp.com, tony.bock@hp.com, jeff.porter@hp.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in ruby3mf.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Mike Whitmarsh, Jeff Porter, and Tony Bock
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # Ruby3mf
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/ruby3mf`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'ruby3mf'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install ruby3mf
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ruby3mf. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
+
37
+
38
+ ## License
39
+
40
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
41
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "ruby3mf"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/lib/ruby3mf.rb ADDED
@@ -0,0 +1,21 @@
1
+ require "ruby3mf/version"
2
+ require "ruby3mf/log3mf"
3
+ require "ruby3mf/document"
4
+ require "ruby3mf/content_types"
5
+ require "ruby3mf/hash"
6
+ require "ruby3mf/model3mf"
7
+ require "ruby3mf/relationships"
8
+ require "ruby3mf/thumbnail3mf"
9
+
10
+
11
+
12
+ require 'zip'
13
+ require 'nokogiri'
14
+ require 'json'
15
+ require 'mimemagic'
16
+ # require 'csv'
17
+
18
+ module Ruby3mf
19
+
20
+ # Your code goes here...
21
+ end
@@ -0,0 +1,11 @@
1
+ class Thumbnail3mf
2
+
3
+ def self.parse(relationship_file, relationships)
4
+ img_type = MimeMagic.by_magic(relationship_file.get_input_stream)
5
+ Log3mf.context "Thumbnail3mf" do |l|
6
+ l.debug "thumbnail is of type: #{img_type}"
7
+ l.error "Expected a png or jpeg thumbnail but the thumbnail was of type #{img_type}", page: 12 unless ['image/png', 'image/jpeg'].include? img_type.type
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,45 @@
1
+ class ContentTypes
2
+
3
+ def self.parse(zip_entry)
4
+ found_types={}
5
+
6
+ Log3mf.context "parse" do |l|
7
+ begin
8
+
9
+ doc = Nokogiri::XML(zip_entry.get_input_stream) do |config|
10
+ config.strict.nonet.noblanks
11
+ end
12
+
13
+ l.warning '[Content_Types].xml must contain exactly one root node' unless doc.children.size == 1
14
+ l.warning '[Content_Types].xml must contain root name Types' unless doc.children.first.name == "Types"
15
+
16
+ required_content_types = ['application/vnd.openxmlformats-package.relationships+xml', 'application/vnd.ms-package.3dmanufacturing-3dmodel+xml']
17
+ optional_content_types = ['application/vnd.ms-printing.printticket+xml']
18
+ all_types = required_content_types + optional_content_types
19
+
20
+ types_node = doc.children.first
21
+ types_node.children.each do |node|
22
+ l.context node.name do |l|
23
+ unless node.name == 'Default'
24
+ l.warning "[Content_Types].xml:#{node.line} contains unexpected element #{node.name}", page: 10
25
+ else
26
+ # l.error "[Content_Types].xml:#{node.line} contains Default node without defined Extension attribute" unless node['Extension'].is_a? String
27
+ # l.error "[Content_Types].xml:#{node.line} contains Default node with unexpected ContentType \"#{node['ContentType']}\"", page: 10 unless all_types.include? node['ContentType']
28
+ l.info "Setting type hash #{node['Extension']}=#{node['ContentType']}"
29
+ found_types[node['Extension']] = node['ContentType']
30
+ end
31
+ end
32
+ end
33
+ required_content_types.each do |req_type|
34
+ l.error "[Content_Types].xml is missing required ContentType \"#{req_type}\"", page: 10 unless found_types.values.include? req_type
35
+ end
36
+ rescue Nokogiri::XML::SyntaxError => e
37
+ l.error "[Content_Types].xml file is not valid XML. #{e}", page: 15
38
+ rescue Zip::Error
39
+ l.error 'Problem extracting [Content_Types].xml from zip file'
40
+ end
41
+ end
42
+
43
+ found_types
44
+ end
45
+ end
@@ -0,0 +1,95 @@
1
+ class Document
2
+
3
+ attr_accessor :models
4
+ attr_accessor :thumbnails
5
+ # attr_accessor :textures
6
+
7
+ # Relationship Type => Class validating relationship type
8
+ RELATIONSHIP_TYPES = {
9
+ 'http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel' => {klass: 'Model3mf', collection: :models},
10
+ 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail' => {klass: 'Thumbnail3mf', collection: :thumbnails} #,
11
+ # 'http://schemas.microsoft.com/3dmanufacturing/2013/01/3dtexture' => {klass: 'Texture3d', collection: :textures}
12
+ }
13
+
14
+ def initialize
15
+ self.models=[]
16
+ self.thumbnails=[]
17
+ # self.textures=[]
18
+ end
19
+
20
+ def self.read(input_file)
21
+ m=self.new
22
+ begin
23
+ Log3mf.context "examining zip" do |l|
24
+ begin
25
+ Zip::File.open(input_file) do |zip_file|
26
+
27
+ # puts "Zip contents:"
28
+ # zip_file.each do |entry|
29
+ # puts entry
30
+ # end
31
+
32
+ l.info "Zip file is valid"
33
+
34
+ l.context "content types" do |l|
35
+ # 1. Get Content Types
36
+ content_type_match = zip_file.glob('\[Content_Types\].xml').first
37
+ if content_type_match
38
+ @types = ContentTypes.parse(content_type_match)
39
+ else
40
+ l.error "Missing required file: [Content_Types].xml", page: 4
41
+ end
42
+ end
43
+
44
+ l.context "relationships" do |l|
45
+ # 2. Get Relationships
46
+ # rel_folders = zip_file.glob('**/_rels')
47
+ # l.fatal_error "Missing any _rels folder", page: 4 unless rel_folders.size>0
48
+
49
+ # 2.1 Validate that the top level _rels/.rel file exists
50
+ rel_file = zip_file.glob('_rels/.rels').first
51
+ l.fatal_error "Missing required file _rels/.rels", page: 4 unless rel_file
52
+
53
+ @relationships=[]
54
+ zip_file.glob('**/*.rels').each do |rel|
55
+ @relationships += Relationships.parse(rel)
56
+ end
57
+ end
58
+
59
+ l.context "relationship elements" do |l|
60
+ # 3. Validate all relationships
61
+ @relationships.each do |rel|
62
+ l.context rel[:target] do |l|
63
+ target = rel[:target].gsub(/^\//, "")
64
+ relationship_file = zip_file.glob(target).first
65
+
66
+ if relationship_file
67
+ relationship_type = RELATIONSHIP_TYPES[rel[:type]]
68
+ if relationship_type.nil?
69
+ l.warning "Relationship file defines a type that is not used in a normal 3mf file: #{rel[:type]}. Ignoring relationship."
70
+ else
71
+ m.send(relationship_type[:collection]) << {
72
+ rel_id: rel[:id],
73
+ target: rel[:target],
74
+ object: Object.const_get(relationship_type[:klass]).parse(relationship_file, @relationships)
75
+ }
76
+ end
77
+ else
78
+ l.error "Relationship Target file #{rel[:target]} not found", page: 11
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ return m
85
+ rescue Zip::Error
86
+ l.fatal_error 'File provided is not a valid ZIP archive', page: 9
87
+ return nil
88
+ end
89
+ end
90
+ rescue Log3mf::FatalError
91
+ #puts "HALTING PROCESSING DUE TO FATAL ERROR"
92
+ return nil
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,58 @@
1
+ # https://gist.github.com/huy/819999
2
+ require 'nokogiri'
3
+
4
+ class Hash
5
+ class << self
6
+ def from_xml(xml_doc)
7
+ begin
8
+ return { xml_doc.root.name.to_sym => xml_node_to_hash(xml_doc.root)}
9
+ rescue Exception => e
10
+ # raise your custom exception here
11
+ end
12
+ end
13
+
14
+ def xml_node_to_hash(node)
15
+ # If we are at the root of the document, start the hash
16
+ if node.element?
17
+ result_hash = {}
18
+ if node.attributes != {}
19
+ attributes = {}
20
+ node.attributes.keys.each do |key|
21
+ attributes[node.attributes[key].name.to_sym] = node.attributes[key].value
22
+ end
23
+ end
24
+ if node.children.size > 0
25
+ node.children.each do |child|
26
+ result = xml_node_to_hash(child)
27
+
28
+ if child.name == "text"
29
+ unless child.next_sibling || child.previous_sibling
30
+ return result unless attributes
31
+ result_hash[child.name.to_sym] = result
32
+ end
33
+ elsif result_hash[child.name.to_sym]
34
+
35
+ if result_hash[child.name.to_sym].is_a?(Object::Array)
36
+ result_hash[child.name.to_sym] << result
37
+ else
38
+ result_hash[child.name.to_sym] = [result_hash[child.name.to_sym]] << result
39
+ end
40
+ else
41
+ result_hash[child.name.to_sym] = result
42
+ end
43
+ end
44
+ if attributes
45
+ #add code to remove non-data attributes e.g. xml schema, namespace here
46
+ #if there is a collision then node content supersets attributes
47
+ result_hash = attributes.merge(result_hash)
48
+ end
49
+ return result_hash
50
+ else
51
+ return attributes
52
+ end
53
+ else
54
+ return node.content.to_s
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,162 @@
1
+ require 'singleton'
2
+
3
+ # Example usage:
4
+
5
+ # Log3mf.context "box.3mf" do |l|
6
+ # --do some stuff here
7
+
8
+ # l.context "[Content-Types].xml" do |l|
9
+ # -- try to parse file. if fail...
10
+ # l.log(:fatal_error, "couldn't parse XML") <<<--- THIS WILL GENERATE FATAL ERROR EXCEPTION
11
+ # end
12
+
13
+ # l.context "examing Relations" do |l|
14
+ # l.log(:error, "a non-fatal error")
15
+ # l.log(:warning, "a warning")
16
+ # l.log(:info, "it is warm today")
17
+ # end
18
+ # end
19
+ #
20
+ # Log3mf.pp
21
+
22
+
23
+ class Log3mf
24
+ include Singleton
25
+
26
+ LOG_LEVELS = [:fatal_error, :error, :warning, :info, :debug]
27
+
28
+ # Allows us to throw FatalErrors if we ever get errors of severity :fatal_error
29
+ class FatalError < RuntimeError
30
+ end
31
+
32
+ def initialize()
33
+ @log_list = []
34
+ @context_stack = []
35
+ @ledger = []
36
+ end
37
+
38
+ def reset_log
39
+ @log_list = []
40
+ @context_stack = []
41
+ end
42
+
43
+ def self.reset_log
44
+ Log3mf.instance.reset_log
45
+ end
46
+
47
+ def context (context_description, &block)
48
+ @context_stack.push(context_description)
49
+ #puts "started context #{@context_stack.join("/")}"
50
+
51
+ retval = block.call(Log3mf.instance)
52
+
53
+ @context_stack.pop
54
+ retval
55
+ end
56
+
57
+ def self.context(context_description, &block)
58
+ Log3mf.instance.context(context_description, &block)
59
+ end
60
+
61
+ def method_missing(name, *args, &block)
62
+ if LOG_LEVELS.include? name.to_sym
63
+ #puts "***** #{name} called from #{caller[0]}"
64
+ log(name.to_sym, *args)
65
+ else
66
+ super
67
+ end
68
+ end
69
+
70
+ def log(severity, message, options={})
71
+ add_to_ledger(caller[1], severity, message, options[:page]) if [:error, :fatal_error].include?(severity)
72
+
73
+ @log_list << ["#{@context_stack.join("/")}", severity, message, options] unless severity==:debug && ENV['LOGDEBUG'].nil?
74
+ #puts "[#{@context_stack.join("/")}] #{severity.to_s.upcase} #{message}"
75
+ raise FatalError if severity == :fatal_error
76
+ end
77
+
78
+ def count_entries(*levels)
79
+ @log_list.select { |i| levels.include? i[1] }.count
80
+ end
81
+
82
+ def self.count_entries(*l)
83
+ Log3mf.instance.count_entries(*l)
84
+ end
85
+
86
+ def spec_link(page)
87
+ "http://3mf.io/wp-content/uploads/2016/03/3MFcoreSpec_1.1.pdf#page=#{page}"
88
+ end
89
+
90
+ def to_json
91
+ @log_list.collect { |ent|
92
+ h = { context: ent[0], severity: ent[1], message: ent[2] }
93
+ h[:spec_ref] = spec_link(ent[3][:page]) if (ent[3] && ent[3][:page])
94
+ h
95
+ }.to_json
96
+ end
97
+
98
+ def self.to_json
99
+ Log3mf.instance.to_json
100
+ end
101
+
102
+ # Pretty print our errors!
103
+ def to_pp
104
+ s = []
105
+ s << "<i>Listing #{@log_list.size} log lines:</i>"
106
+ longest_context = @log_list.collect { |logline| logline[0].size }.max
107
+ longest_severity = @log_list.collect { |logline| logline[1].size }.max
108
+
109
+ @log_list.each do |logline|
110
+ msg = logline[2]
111
+ if logline[3] && logline[3][:page]
112
+ msg = "<a href=\"#{spec_link(logline[3][:page])}\" target=\"_blank\">#{msg}</a>"
113
+ end
114
+ s << "[#{logline[0].ljust(longest_context)}] #{logline[1].to_s.upcase.ljust(longest_severity)} #{msg}"
115
+ end
116
+ s << "<br/>"
117
+ s.map! { |row| row.include?("ERROR") ? "<b>#{row}</b>" : row }
118
+ s.join("<br/>")
119
+ end
120
+
121
+ def self.to_pp
122
+ Log3mf.instance.to_pp
123
+ end
124
+
125
+ def reconcile_ledger
126
+ expected_errors = CSV.read("spec/expected_errors.csv")
127
+
128
+ {expected_errors_not_found: expected_errors - @ledger,
129
+ unexpected_errors: @ledger - expected_errors }
130
+ end
131
+
132
+ def expected_error_messages_for_testcase(filename)
133
+ expected_errors = CSV.read("spec/expected_errors.csv")
134
+ relevant_errors = expected_errors.select { |error| filename.split("/").last.include? error.first }
135
+ relevant_errors.map { |error| {severity: error[2], csv_safe_msg: error[3], page: error[4] } }
136
+ end
137
+
138
+ def csv_safe_transform(s)
139
+ s.gsub(",", "_").gsub("\n", " ").gsub("\"", "_")
140
+ end
141
+
142
+ private
143
+ def add_to_ledger(location, severity, message, page)
144
+ caller_file=location.split(":").first.split("3mf_validator").last
145
+
146
+ csv_safe_msg = csv_safe_transform(message)
147
+ page ||= -1
148
+
149
+ entry = [@context_stack.first, caller_file, severity.to_s, csv_safe_msg, page.to_s]
150
+ unless @ledger.include? entry
151
+ @ledger << entry
152
+ archive_error(entry) if ENV['SAVEERRORS']!=nil
153
+ end
154
+ end
155
+
156
+ def archive_error(entry)
157
+ File.open("tmp_expected_errors.csv", 'a') do |file|
158
+ file.puts entry.join(",")
159
+ end
160
+ end
161
+ end
162
+
@@ -0,0 +1,60 @@
1
+ class Model3mf
2
+
3
+ def self.extract_paths(value)
4
+ if value.is_a? Array
5
+ value.map { |v| extract_paths(v) }
6
+ else
7
+ if value.is_a? Hash
8
+ value[:path]
9
+ else
10
+ nil
11
+ end
12
+ end
13
+ end
14
+
15
+ def self.parse(zip_entry, relationships)
16
+ model_hash = {}
17
+ Log3mf.context "parsing model" do |l|
18
+ begin
19
+ # parse model
20
+ doc = Nokogiri::XML(zip_entry.get_input_stream) do |config|
21
+ config.strict.nonet.noblanks
22
+ end
23
+
24
+ l.info "We Found a Model, and it's XML!"
25
+ model_hash = Hash.from_xml(doc)
26
+ rescue Nokogiri::XML::SyntaxError => e
27
+ l.fatal_error "Model file invalid XML. Exception #{e}"
28
+ doc.errors.each { |error| l.error error }
29
+ end
30
+
31
+ l.context "verifying 3D payload required resources" do |l|
32
+ # find all resources (that are not the object) in model_hash
33
+
34
+ required_resources = []
35
+
36
+ model_hash[:model][:resources].each do |key, value|
37
+ required_resources << extract_paths(value)
38
+ end
39
+
40
+ required_resources.flatten!
41
+ required_resources.compact!
42
+
43
+ # for each, ensure that they exist in @relationships
44
+
45
+ relationship_resources = relationships.map { |relationship| relationship[:target] }
46
+
47
+ missing_resources = (required_resources - relationship_resources)
48
+ if missing_resources.empty?
49
+ l.info "All model required resources are defined in .rels relationship files."
50
+ else
51
+ missing_resources.each { |mr|
52
+ l.error "Missing required resource: #{mr} Resource referenced in model, but not in .rels relationship file", page: 10
53
+ }
54
+ end
55
+
56
+ end
57
+ end
58
+ model_hash
59
+ end
60
+ end
@@ -0,0 +1,51 @@
1
+ class Relationships
2
+
3
+ def self.parse(zip_entry)
4
+ relationships = []
5
+ Log3mf.context "parsing relationships" do |l|
6
+ begin
7
+ # Parse Relationships XML
8
+ doc = Nokogiri::XML(zip_entry.get_input_stream) do |config|
9
+ config.strict.nonet.noblanks
10
+ end
11
+
12
+ # Verify <Relationships><Relationship/></Relationships>
13
+ root_element = doc.children[0]
14
+ if root_element.name == "Relationships"
15
+ relationship_elements = root_element.children
16
+ if relationship_elements.size > 0
17
+ relationship_elements.each do |node|
18
+ if node.is_a?(Nokogiri::XML::Element) && node.name == "Relationship"
19
+ relationships << {target: node['Target'], type: node['Type'], id: node['Id']}
20
+ l.info "adding relationship: #{relationships.last.inspect}"
21
+ else
22
+ unless node.is_a? Nokogiri::XML::Text
23
+ l.info "found non-Relationship node: #{node.name}"
24
+ end
25
+ end
26
+ end
27
+
28
+ if zip_entry.name=="_rels/.rels"
29
+ l.context "Verifying StartPart" do |l|
30
+ start_part_type = "http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"
31
+ start_part_count = relationships.select { |r| r[:type] == start_part_type }.size
32
+ if start_part_count != 1
33
+ l.error "rels/.rels Relationship file has an invalide attribute type for the root 3D Model (StartPart).
34
+ The correct attribute type should be \"#{start_part_type}\"", page: 10
35
+ end
36
+ end
37
+ end
38
+ else
39
+ l.error "No relationship elements found", page: 4
40
+ end
41
+ else
42
+ l.error ".rels XML must have &lt;Relationships&gt; root element", page: 4
43
+ end
44
+
45
+ rescue Nokogiri::XML::SyntaxError => e
46
+ l.error "Relationships (.rel) file is not a valid XML file: #{e.message}", page: 4
47
+ end
48
+ end
49
+ relationships
50
+ end
51
+ end
@@ -0,0 +1,3 @@
1
+ module Ruby3mf
2
+ VERSION = "0.1.0"
3
+ end
data/ruby3mf.gemspec ADDED
@@ -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 'ruby3mf/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "ruby3mf"
8
+ spec.version = Ruby3mf::VERSION
9
+ spec.authors = ["Mike Whitmarsh, Jeff Porter, and William Hertling"]
10
+ spec.email = ["mwhit@hp.com", "jeff.porter@hp.com", "william.hertling@hp.com"]
11
+
12
+ spec.summary = %q{Read, write and validate 3MF files with native Ruby}
13
+ spec.description = %q{Read, write and validate 3MF files with native Ruby easily.}
14
+ spec.homepage = "https://github.com/IPGPTP/ruby3mf"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
18
+ # delete this section to allow pushing this gem to any host.
19
+ # if spec.respond_to?(:metadata)
20
+ # spec.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
21
+ # else
22
+ # raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
23
+ # end
24
+
25
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_development_dependency "bundler", "~> 1.11"
31
+ spec.add_development_dependency "rake", "~> 10.0"
32
+ spec.add_development_dependency "rspec", "~> 3.0"
33
+
34
+ spec.add_runtime_dependency 'rubyzip'
35
+ spec.add_runtime_dependency 'nokogiri', '~>1.6.8'
36
+ spec.add_runtime_dependency 'mimemagic'
37
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby3mf
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mike Whitmarsh, Jeff Porter, and William Hertling
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-11-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubyzip
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: nokogiri
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 1.6.8
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 1.6.8
83
+ - !ruby/object:Gem::Dependency
84
+ name: mimemagic
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Read, write and validate 3MF files with native Ruby easily.
98
+ email:
99
+ - mwhit@hp.com
100
+ - jeff.porter@hp.com
101
+ - william.hertling@hp.com
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - ".gitignore"
107
+ - ".rspec"
108
+ - ".ruby-gemset"
109
+ - ".ruby-version"
110
+ - ".travis.yml"
111
+ - CODE_OF_CONDUCT.md
112
+ - Gemfile
113
+ - LICENSE.txt
114
+ - README.md
115
+ - Rakefile
116
+ - bin/console
117
+ - bin/setup
118
+ - lib/ruby3mf.rb
119
+ - lib/ruby3mf/Thumbnail3mf.rb
120
+ - lib/ruby3mf/content_types.rb
121
+ - lib/ruby3mf/document.rb
122
+ - lib/ruby3mf/hash.rb
123
+ - lib/ruby3mf/log3mf.rb
124
+ - lib/ruby3mf/model3mf.rb
125
+ - lib/ruby3mf/relationships.rb
126
+ - lib/ruby3mf/version.rb
127
+ - ruby3mf.gemspec
128
+ homepage: https://github.com/IPGPTP/ruby3mf
129
+ licenses:
130
+ - MIT
131
+ metadata: {}
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubyforge_project:
148
+ rubygems_version: 2.5.1
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: Read, write and validate 3MF files with native Ruby
152
+ test_files: []