Module: Metanorma::Standoc::Validate::Image

Included in:
Metanorma::Standoc::Validate
Defined in:
lib/metanorma/validate/image.rb

Constant Summary collapse

SVG_NS =
"http://www.w3.org/2000/svg".freeze

Instance Method Summary collapse

Instance Method Details

#expand_path(loc) ⇒ Object



26
27
28
29
30
31
# File 'lib/metanorma/validate/image.rb', line 26

def expand_path(loc)
  relative_path = File.join(@localdir, loc)
  [loc, relative_path].detect do |p|
    File.exist?(p) ? p : nil
  end
end

#image_exists(doc) ⇒ Object



17
18
19
20
21
22
23
24
# File 'lib/metanorma/validate/image.rb', line 17

def image_exists(doc)
  doc.xpath("//image[@src] | //altmedia[@src]").each do |i|
    Vectory::Utils::url?(i["src"]) and next
    Vectory::Utils::datauri?(i["src"]) and next
    expand_path(i["src"]) and next
    @log.add("STANDOC_44", i.parent, params: [i["src"]])
  end
end

#image_toobig(doc) ⇒ Object



56
57
58
59
60
61
62
# File 'lib/metanorma/validate/image.rb', line 56

def image_toobig(doc)
  @dataurimaxsize.zero? and return
  doc.xpath("//image[@src]").each do |i|
    i["src"].size > @dataurimaxsize and
      @log.add("STANDOC_46", i.parent)
  end
end

#image_validate(doc) ⇒ Object



10
11
12
13
14
15
# File 'lib/metanorma/validate/image.rb', line 10

def image_validate(doc)
  image_exists(doc)
  image_toobig(doc)
  png_validate(doc)
  svg_validate(doc)
end

#png_validate(doc) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/metanorma/validate/image.rb', line 33

def png_validate(doc)
  doc.xpath("//image[@mimetype = 'image/png'][@src]").each do |i|
    Vectory::Utils::url?(i["src"]) and next
    uri = Vectory::Utils::datauri?(i["src"])
    path = uri ? save_dataimage(i["src"]) : expand_path(i["src"])
    path or next
    PngConform::Readers::StreamingReader.open(path) do |reader|
      v = PngConform::Services::ValidationService.new(reader)
      png_validate1(i, path, v)
    end
  end
end

#png_validate1(img, _path, validator) ⇒ Object



46
47
48
49
50
51
52
53
54
# File 'lib/metanorma/validate/image.rb', line 46

def png_validate1(img, _path, validator)
  ret = validator.validate
  ret.error_messages.each do |e|
    @log.add("STANDOC_45", img.parent, params: [e])
  end
  ret.validation_result.warning_messages.each do |e|
    @log.add("STANDOC_63", img.parent, params: [e])
  end
end

#save_dataimage(uri, _relative_dir = true) ⇒ Object



168
169
170
171
172
173
174
175
176
177
# File 'lib/metanorma/validate/image.rb', line 168

def save_dataimage(uri, _relative_dir = true)
  imgtype, imgdata = save_dataimage_prep(uri)
  Tempfile.open(["image", ".#{imgtype}"],
                mode: File::BINARY | File::SHARE_DELETE) do |f|
    f.binmode
    f.write(Base64.strict_decode64(imgdata))
    @files_to_delete << f # persist to the end
    f.path
  end
end

#save_dataimage_prep(uri) ⇒ Object



179
180
181
182
183
184
185
186
# File 'lib/metanorma/validate/image.rb', line 179

def save_dataimage_prep(uri)
  %r{^data:(?:image|application)/(?<imgtype>[^;]+);(?:charset=[^;]+;)?base64,(?<imgdata>.+)$} =~ uri
  # imgtype = "emf" if emf?("#{imgclass}/#{imgtype}")
  imgtype = imgtype.sub(/\+[a-z0-9]+$/, "") # svg+xml
  imgtype = "png" unless /^[a-z0-9]+$/.match? imgtype
  imgtype == "postscript" and imgtype = "eps"
  [imgtype, imgdata]
end

#svg_error(id, svg, errors, options = {}) ⇒ Object



148
149
150
151
152
153
154
155
156
# File 'lib/metanorma/validate/image.rb', line 148

def svg_error(id, svg, errors, options = {})
  errors.each do |err|
    # reference violations are handled separately
    err.violation_type == :reference_violation and next
    elem, loc = svg_error_locations(err)
    @log.add(id, svg, params: [err.rule&.id, err.message, elem, loc],
                      **options)
  end
end

#svg_error_locations(err) ⇒ Object



158
159
160
161
162
163
164
165
166
# File 'lib/metanorma/validate/image.rb', line 158

def svg_error_locations(err)
  err.respond_to?(:element) && err.element and
    elem = " Element: #{err.element}"
  err.respond_to?(:location) && err.location and
    loc = " Location: #{err.location}"
  err.respond_to?(:node) && err.node.respond_to?(:path_id) and
    loc2 = " Location: #{err.node.path_id}"
  [elem, loc || loc2]
end

#svg_reference_violations(svg, result) ⇒ Object

Check for unresolved internal references



101
102
103
104
105
106
107
108
109
# File 'lib/metanorma/validate/image.rb', line 101

def svg_reference_violations(svg, result)
  result.unresolved_internal_references&.each do |ref|
    val = ref.value.sub(/^#/, "")
    @doc_ids.include?(val) and next
    @doc_anchors.include?(val) and next
    @log.add("STANDOC_59", svg,
             params: [ref.value, ref.line_number])
  end
end

#svg_remed_log(remeds, svg) ⇒ Object



138
139
140
141
142
143
144
145
146
# File 'lib/metanorma/validate/image.rb', line 138

def svg_remed_log(remeds, svg)
  remeds.each do |e|
    e.changes_made.each do |c|
      @log.add("STANDOC_58", svg,
               params: [e.remediation_id, e.message,
                        c[:type], c[:message], c[:node]])
    end
  end
end

#svg_remediate(validator, profile, engine, svg, result) ⇒ Object

Apply remediation if needed



112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/metanorma/validate/image.rb', line 112

def svg_remediate(validator, profile, engine, svg, result)
  id = svg["id"]
  svg.delete("id") # cache will be tripped up by unique @id
  key = Digest::MD5.hexdigest(svg.to_xml)
  unless ret = @svg_remediation_cache[key]
    ret = svg_validate_fix(validator, profile, engine, svg,
                           result)
    @svg_remediation_cache[key] = ret
  end
  svg.replace(ret.to_xml)
  svg["id"] = id
end

#svg_validate(doc) ⇒ Object

Use SAX for fast validation



65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/metanorma/validate/image.rb', line 65

def svg_validate(doc)
  profile = SvgConform::Profiles.get(@svg_conform_profile)
  validator = SvgConform::Validator.new(mode: :sax)
  engine = SvgConform::RemediationEngine.new(profile)
  @svg_remediation_cache = {}
  doc.xpath("//m:svg", "m" => SVG_NS).each do |svg_element|
    result = svg_validate1(validator, profile, svg_element)
    if profile.remediation_count.positive? && !result.valid?
      svg_remediate(validator, profile, engine, svg_element,
                    result)
    end
  end
end

#svg_validate1(validator, profile, svg) ⇒ Object



79
80
81
82
83
84
85
# File 'lib/metanorma/validate/image.rb', line 79

def svg_validate1(validator, profile, svg)
  result = validator.validate(svg, profile: profile)
  svg_error("STANDOC_55", svg, result.errors, display: false)
  svg_error("STANDOC_57", svg, result.warnings, display: false)
  svg_reference_violations(svg, result)
  result
end

#svg_validate_fix(validator, profile, engine, svg, result) ⇒ Object



125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/metanorma/validate/image.rb', line 125

def svg_validate_fix(validator, profile, engine, svg, result)
  # Load DOM only for remediation
  doc = SvgConform::Document.from_content(svg.to_xml)
  remeds = engine.apply_remediations(doc, result)
  svg_remed_log(remeds, svg)
  # Use root to avoid processing instructions that may break SAX parser
  remediated_xml = doc.root
  result = validator.validate(remediated_xml.to_xml,
                              profile: profile)
  svg_error("STANDOC_56", svg, result.errors) # we still have errors
  remediated_xml
end