ruby3mf 0.1.15 → 0.1.17

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 49c3ed54c8588a2979b63822dbab8f3e339b5207
4
- data.tar.gz: 50e088646a7159cf293fb896ce6ce7a5284b1131
3
+ metadata.gz: 152e1cd32d11dadcadb62904f2942100278d059c
4
+ data.tar.gz: 3359551e080d311dbdbe6a8607f0336cbfedf298
5
5
  SHA512:
6
- metadata.gz: 9ecf6eaf61cbb478408212ef0741133b8c1d88d6891eaa6b330dc3102c752572da8b13f664f35067d5a167fbc25bc0333d07885a8dfb94ea29c6421a0f06e085
7
- data.tar.gz: f220c8fc02162b03dbfbb2fa66b4e380f19e7a684ca0d59a5ab8cba6342333b00c35060802b9a2758f7109ff823792f62847e9b61ac6f606049ccd5c222db9fc
6
+ metadata.gz: 670d9a203081c4da7d93cdb8dc81e8db53352a5bd6635511f028a29e073bc8ffb5325365f7539d9718227bd6646fb2d5b00b5c6b1cc3161dfe77528cea6ae242
7
+ data.tar.gz: 48bf7933386810d11e503953607545b3db168949c97123836fadec03fb10392530732b11b02b40d02c761eccac96fca9e62bb5716c2d289da3c32c46a5026bdb
data/bin/batch.rb ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/ruby3mf'
4
+
5
+ files = Dir["spec/ruby3mf-testfiles/#{ARGV[0] || "failing_cases"}/*.#{ARGV[1] || '3mf'}"]
6
+
7
+ files.each do |file|
8
+
9
+ Log3mf.reset_log
10
+ doc = Document.read(file)
11
+ errors = Log3mf.entries(:error, :fatal_error)
12
+
13
+ puts "=" * 100
14
+ puts "Validating file: #{file}"
15
+ errors.each do |ent|
16
+ h = { context: ent[0], severity: ent[1], message: ent[2] }
17
+ puts h
18
+ end
19
+
20
+ end
data/bin/cli.rb CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  require_relative '../lib/ruby3mf'
4
4
 
5
- doc = Document.read(ARGV.first)
5
+ filename = ARGV.first
6
+ doc = Document.read(filename)
6
7
 
7
8
  errors = Log3mf.entries(:error, :fatal_error)
8
- puts "Validating file: #{ARGV.first}"
9
9
  errors.each do |ent|
10
- h = { context: ent[0], severity: ent[1], message: ent[2] }
10
+ h = { file: filename, context: ent[0], severity: ent[1], message: ent[2] }
11
11
  puts h
12
12
  end
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+
3
+ NAME=$1
4
+ echo "input: ${NAME}"
5
+ echo "Starting test on: ${NAME}"
6
+ echo "Starting test on: ${NAME}" >> ${NAME}_test.json
7
+
8
+ for filename in ${NAME}/*.3mf; do
9
+ echo "Validating ${filename}"
10
+ ~/src/ruby3mf/bin/cli.rb ${filename} >> ${NAME}_test.json
11
+ done
12
+
13
+ echo "All Done."
data/lib/ruby3mf.rb CHANGED
@@ -7,7 +7,7 @@ require_relative "ruby3mf/model3mf"
7
7
  require_relative "ruby3mf/relationships"
8
8
  require_relative "ruby3mf/thumbnail3mf"
9
9
  require_relative "ruby3mf/texture3mf"
10
- require_relative "ruby3mf/global_xml_validations"
10
+ require_relative "ruby3mf/xml_val"
11
11
 
12
12
  require 'zip'
13
13
  require 'nokogiri'
@@ -6,7 +6,7 @@ class ContentTypes
6
6
  Log3mf.context "parse" do |l|
7
7
  begin
8
8
 
9
- doc = GlobalXMLValidations.validate_parse(zip_entry)
9
+ doc = XmlVal.validate_parse(zip_entry)
10
10
 
11
11
  l.warning '[Content_Types].xml must contain exactly one root node' unless doc.children.size == 1
12
12
  l.warning '[Content_Types].xml must contain root name Types' unless doc.children.first.name == "Types"
@@ -6,6 +6,7 @@ class Document
6
6
  attr_accessor :thumbnails
7
7
  attr_accessor :textures
8
8
  attr_accessor :objects
9
+ attr_accessor :parts
9
10
  attr_accessor :zip_filename
10
11
 
11
12
  # Relationship schemas
@@ -22,6 +23,8 @@ class Document
22
23
  PRINT_TICKET_TYPE => {}
23
24
  }
24
25
 
26
+ TEXTURE_TYPES = %w[image/jpeg image/png application/vnd.ms-package.3dmanufacturing-3dmodeltexture]
27
+
25
28
  def initialize(zip_filename)
26
29
  self.models=[]
27
30
  self.thumbnails=[]
@@ -29,25 +32,56 @@ class Document
29
32
  self.objects={}
30
33
  self.relationships=[]
31
34
  self.types=[]
35
+ self.parts=[]
32
36
  @zip_filename = zip_filename
33
37
  end
34
38
 
39
+ #verify that each texture part in the 3MF is related to the model through a texture relationship in a rels file
40
+ def self.validate_texture_parts(document, log)
41
+
42
+ return unless document.types.size > 0
43
+ document.parts.each do |filename|
44
+ ext = File::extname(filename).delete '.'
45
+ content_type = document.types[ext]
46
+ next unless TEXTURE_TYPES.include?(content_type)
47
+
48
+ has_relationship = false
49
+ document.textures.each do |texture_file|
50
+ if texture_file[:target] == filename
51
+ has_relationship = true
52
+ break
53
+ end
54
+ end
55
+
56
+ next unless !has_relationship
57
+ document.thumbnails.each do |thumbnail_file|
58
+ if thumbnail_file[:target] == filename
59
+ has_relationship = true
60
+ break
61
+ end
62
+ end
63
+ log.context "part names /#{filename}" do |l|
64
+ l.error :texture_without_relationship, name: filename unless has_relationship
65
+ end
66
+ end
67
+ end
68
+
35
69
  def self.read(input_file)
36
70
 
37
71
  m = new(input_file)
38
72
  begin
39
- Log3mf.context "zip" do |l|
73
+ Log3mf.context 'zip' do |l|
40
74
  begin
41
75
  Zip.warn_invalid_date = false
42
76
 
43
77
  # check for the general purpose flag set - if so, warn that 3mf may not work on some systems
44
78
  if File.read(input_file)[6] == "\b"
45
- l.warning "File format: this file may not open on all systems"
79
+ l.warning 'File format: this file may not open on all systems'
46
80
  end
47
81
 
48
82
  Zip::File.open(input_file) do |zip_file|
49
83
 
50
- l.info "Zip file is valid"
84
+ l.info 'Zip file is valid'
51
85
 
52
86
  # check for valid, absolute URI's for each path name
53
87
 
@@ -56,7 +90,7 @@ class Document
56
90
  unless part.name.end_with? '[Content_Types].xml'
57
91
  begin
58
92
  u = URI part.name
59
- rescue ArgumentError
93
+ rescue ArgumentError, URI::InvalidURIError
60
94
  l.error :err_uri_bad
61
95
  next
62
96
  end
@@ -64,28 +98,29 @@ class Document
64
98
  u.path.split('/').each do |segment|
65
99
  l.error :err_uri_hidden_file if (segment.start_with? '.') && !(segment.end_with? '.rels')
66
100
  end
101
+ m.parts << '/' + part.name unless part.directory?
67
102
  end
68
103
  end
69
104
  end
70
105
 
71
- l.context "content types" do |l|
106
+ l.context 'content types' do |l|
72
107
  # 1. Get Content Types
73
108
  content_type_match = zip_file.glob('\[Content_Types\].xml').first
74
109
  if content_type_match
75
110
  m.types = ContentTypes.parse(content_type_match)
76
111
  else
77
- l.error "Missing required file: [Content_Types].xml", page: 4
112
+ l.error 'Missing required file: [Content_Types].xml', page: 4
78
113
  end
79
114
  end
80
115
 
81
- l.context "relationships" do |l|
116
+ l.context 'relationships' do |l|
82
117
  # 2. Get Relationships
83
118
  # rel_folders = zip_file.glob('**/_rels')
84
119
  # l.fatal_error "Missing any _rels folder", page: 4 unless rel_folders.size>0
85
120
 
86
121
  # 2.1 Validate that the top level _rels/.rel file exists
87
122
  rel_file = zip_file.glob('_rels/.rels').first
88
- l.fatal_error "Missing required file _rels/.rels", page: 4 unless rel_file
123
+ l.fatal_error 'Missing required file _rels/.rels', page: 4 unless rel_file
89
124
 
90
125
  zip_file.glob('**/*.rels').each do |rel|
91
126
  m.relationships += Relationships.parse(rel)
@@ -135,6 +170,8 @@ class Document
135
170
  end
136
171
  end
137
172
  end
173
+
174
+ validate_texture_parts(m, l)
138
175
  end
139
176
 
140
177
  return m
@@ -168,7 +205,7 @@ class Document
168
205
  end
169
206
  end
170
207
 
171
- File.open(output_file, "wb") { |f| f.write(buffer.string) }
208
+ File.open(output_file, 'wb') { |f| f.write(buffer.string) }
172
209
 
173
210
  end
174
211
 
@@ -52,11 +52,14 @@ invalid_startpart_type:
52
52
  invalid_relationship_type:
53
53
  msg: "Invalid relationship type '%{type}' specified in .rels relationship file. Parts in the 3D payload MUST use one of the appropriate relationship types to establish that relationship between two parts in the payload."
54
54
  page: 10
55
+ invalid_thumbnail_file:
56
+ msg: "thumbnail file must be valid image file"
57
+ page: 10
55
58
  invalid_thumbnail_colorspace:
56
59
  msg: "CMYK JPEG images must not be used for the thumbnail"
57
60
  page: 36
58
61
  invalid_thumbnail_file_type:
59
- msg: "Expected a png or jpeg thumbnail but the thumbnail was of type image/gif"
62
+ msg: "Expected a png or jpeg thumbnail but the thumbnail was of type %{type}"
60
63
  page: 36
61
64
  invalid_texture_file_type:
62
65
  msg: "Expected a png or jpeg texture but the texture was of type image/gif"
@@ -117,4 +120,16 @@ unknown_required_extension:
117
120
  page: 14
118
121
  missing_extension_namespace_uri:
119
122
  msg: "Required extension '%{ns}' MUST refer to namespace with URI"
120
- page: 14
123
+ page: 14
124
+ texture_without_relationship:
125
+ msg: "%{name} appears to be a texture file but no rels file declares any relationship to the model"
126
+ page: 13
127
+ invalid_metadata_under_defaultns:
128
+ msg: "Metadata without a namespace name must only contain allowed name values"
129
+ page: 21
130
+ has_commas_for_floats:
131
+ msg: "numbers not formatted for the en-US locale"
132
+ page: 15
133
+ invalid_language_locale:
134
+ msg: "locale should be en-US"
135
+ page: 15
@@ -14,7 +14,7 @@ class Model3mf
14
14
 
15
15
  Log3mf.context "parsing model" do |l|
16
16
  begin
17
- model_doc = GlobalXMLValidations.validate_parse(zip_entry)
17
+ model_doc = XmlVal.validate_parse(zip_entry)
18
18
  rescue Nokogiri::XML::SyntaxError => e
19
19
  l.fatal_error "Model file invalid XML. Exception #{e}"
20
20
  end
@@ -108,6 +108,13 @@ class Model3mf
108
108
  metadata = model_doc.root.css("metadata")
109
109
  metadata_names = metadata.map { |met| met['name'] }
110
110
  l.error :metadata_elements_with_same_name unless metadata_names.uniq!.nil?
111
+
112
+ # metadata values allowed under default namespace (xmlns):
113
+ metadata_values = ['Title', 'Designer', 'Description', 'Copyright', 'LicenseTerms', 'Rating', 'CreationDate', 'ModificationDate' ]
114
+
115
+ unless model_doc.root.namespace.href.nil? || model_doc.root.namespace_definitions.count > 1
116
+ l.error :invalid_metadata_under_defaultns unless (metadata_names - metadata_values).empty?
117
+ end
111
118
  end
112
119
 
113
120
  MeshAnalyzer.validate(model_doc)
@@ -5,7 +5,7 @@ class Relationships
5
5
  Log3mf.context "parsing relationships" do |l|
6
6
  begin
7
7
  # Parse Relationships XML
8
- doc = GlobalXMLValidations.validate_parse(zip_entry)
8
+ doc = XmlVal.validate_parse(zip_entry)
9
9
 
10
10
  # Verify <Relationships><Relationship/></Relationships>
11
11
  root_element = doc.children[0]
@@ -14,11 +14,7 @@ class Relationships
14
14
  if relationship_elements.size > 0
15
15
  relationship_elements.each do |node|
16
16
  if node.is_a?(Nokogiri::XML::Element) && node.name == "Relationship"
17
- relationships.each do |previous_rel|
18
- if previous_rel[:target] == node['Target'] && previous_rel[:type] == node['Type']
19
- l.error :multiple_relationships
20
- end
21
- end
17
+ l.error :multiple_relationships if (relationships.select { |r| r[:target] == node['Target'] && r[:type] == node['Type'] }.size > 0)
22
18
  relationships << {target: node['Target'], type: node['Type'], id: node['Id']}
23
19
  l.info "adding relationship: #{relationships.last.inspect}"
24
20
  else
@@ -3,16 +3,14 @@ require 'mini_magick'
3
3
  class Thumbnail3mf
4
4
 
5
5
  def self.parse(doc, relationship_file)
6
- img_type = MimeMagic.by_magic(relationship_file.get_input_stream)
7
- img_colorspace = MiniMagick::Image.read(relationship_file.get_input_stream).colorspace
8
-
9
6
  Log3mf.context "Thumbnail3mf" do |l|
10
- l.fatal_error "thumbnail file must be valid image file", page: 10 unless img_type
11
- l.fatal_error "CMYK JPEG images must not be used for the thumbnail", page: 36 if img_colorspace.include? "CMYK"
12
- l.debug "thumbnail is of type: #{img_type}"
13
- 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
14
- end
15
7
 
16
- end
8
+ img_type = MimeMagic.by_magic(relationship_file.get_input_stream)
9
+ l.fatal_error :invalid_thumbnail_file unless img_type
10
+ l.error(:invalid_thumbnail_file_type, type: img_type) unless ['image/png', 'image/jpeg'].include? img_type.type
17
11
 
12
+ img_colorspace = MiniMagick::Image.read(relationship_file.get_input_stream).colorspace
13
+ l.fatal_error :invalid_thumbnail_colorspace if img_colorspace.include? "CMYK"
14
+ end
15
+ end
18
16
  end
@@ -1,3 +1,3 @@
1
1
  module Ruby3mf
2
- VERSION = "0.1.15"
2
+ VERSION = "0.1.17"
3
3
  end
@@ -0,0 +1,47 @@
1
+ require 'nokogiri'
2
+
3
+ class XmlVal
4
+
5
+ def self.validate_parse(file)
6
+ doc = Nokogiri::XML(file.get_input_stream) do |config|
7
+ config.strict.nonet.noblanks
8
+ end
9
+ validate(file, doc)
10
+ doc
11
+ end
12
+
13
+ def self.validate(file, document)
14
+ Log3mf.context "validations" do |l|
15
+ l.error :invalid_language_locale if invalid_locale?(document)
16
+ l.error :has_xml_space_attribute if space_attribute_exists?(document)
17
+ l.error :wrong_encoding if xml_not_utf8_encoded?(document)
18
+ l.error :dtd_not_allowed if dtd_exists?(file)
19
+ l.error :has_commas_for_floats if bad_floating_numbers?(document)
20
+ end
21
+ end
22
+
23
+ def self.invalid_locale?(document)
24
+ !document.xpath('//@xml:lang').empty? && document.xpath('//@xml:lang').text != "en-US"
25
+ end
26
+
27
+ def self.bad_floating_numbers?(document)
28
+ !document.xpath('.//*[find_with_regex(., "[0-9]+\,[0-9]+")]', Class.new {
29
+ def find_with_regex node_set, regex
30
+ node_set.find_all { |node| node.values.any? {|v| v =~ /#{regex}/ } }
31
+ end
32
+ }.new).empty?
33
+ end
34
+
35
+ def self.space_attribute_exists?(document)
36
+ !(document.xpath('//*[@xml:space]').empty?)
37
+ end
38
+
39
+ def self.xml_not_utf8_encoded?(document)
40
+ !document.encoding.nil? && (document.encoding.to_s.downcase != 'utf-8')
41
+ end
42
+
43
+ def self.dtd_exists?(file)
44
+ found = file.get_input_stream.find { |line| line =~ /(!DOCTYPE\b)|(!ELEMENT\b)|(!ENTITY\b)|(!NOTATION\b)|(!ATTLIST\b)/ }
45
+ !found.nil?
46
+ end
47
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby3mf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.15
4
+ version: 0.1.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Whitmarsh, Jeff Porter, and William Hertling
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-01-11 00:00:00.000000000 Z
11
+ date: 2017-01-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -141,15 +141,16 @@ files:
141
141
  - LICENSE.txt
142
142
  - README.md
143
143
  - Rakefile
144
+ - bin/batch.rb
144
145
  - bin/cli.rb
145
146
  - bin/console
147
+ - bin/folder_test.sh
146
148
  - bin/setup
147
149
  - lib/ruby3mf.rb
148
150
  - lib/ruby3mf/content_types.rb
149
151
  - lib/ruby3mf/document.rb
150
152
  - lib/ruby3mf/edge_list.rb
151
153
  - lib/ruby3mf/errors.yml
152
- - lib/ruby3mf/global_xml_validations.rb
153
154
  - lib/ruby3mf/interpolation.rb
154
155
  - lib/ruby3mf/log3mf.rb
155
156
  - lib/ruby3mf/mesh_analyzer.rb
@@ -158,6 +159,7 @@ files:
158
159
  - lib/ruby3mf/texture3mf.rb
159
160
  - lib/ruby3mf/thumbnail3mf.rb
160
161
  - lib/ruby3mf/version.rb
162
+ - lib/ruby3mf/xml_val.rb
161
163
  - ruby3mf.gemspec
162
164
  homepage: https://github.com/IPGPTP/ruby3mf
163
165
  licenses:
@@ -1,36 +0,0 @@
1
- class GlobalXMLValidations
2
-
3
- def self.validate_parse(file)
4
- doc = Nokogiri::XML(file.get_input_stream) do |config|
5
- config.strict.nonet.noblanks
6
- end
7
- validate(file, doc)
8
- doc
9
- end
10
-
11
- def self.validate(file, document)
12
- Log3mf.context "global xml validations" do |l|
13
- l.error :has_xml_space_attribute if space_attribute_exists?(document)
14
- l.error :wrong_encoding if xml_not_utf8_encoded?(document)
15
- l.error :dtd_not_allowed if dtd_exists?(file)
16
- end
17
- end
18
-
19
- def self.space_attribute_exists?(document)
20
- !(document.xpath('//*[@xml:space]').empty?)
21
- end
22
-
23
- def self.xml_not_utf8_encoded?(document)
24
- !(document.encoding == 'UTF-8' || document.encoding == 'utf-8')
25
- end
26
-
27
- def self.dtd_exists?(file)
28
- found = file.get_input_stream.find { |line| line =~ /(!DOCTYPE\b)|(!ELEMENT\b)|(!ENTITY\b)|(!NOTATION\b)|(!ATTLIST\b)/ }
29
- !found.nil?
30
- end
31
- end
32
-
33
-
34
-
35
-
36
-