vectory 0.8.1 → 0.8.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 29078da81dfd3def401e196f9c13654a5b2ccc4eefed851c261796cca0e8e9d2
4
- data.tar.gz: 602ba638f3ff426687d778c87d4f93bde391cd7aaaa565902374bede727e0bd5
3
+ metadata.gz: 1825d8ed689d1eb94da1c473ffd9671bc3237471f3f4b675d6f2d8ff6db4e2a7
4
+ data.tar.gz: 7d9ee417ac1b74b481589a83f45a359d556f1c3d2aa401149f57439c39085811
5
5
  SHA512:
6
- metadata.gz: 32c32817c07c2578914427b923f82f97cbfd71249d11d10c07dcab6ad051d647679bd7cffdcaf6b104a6bd928dc8fe71bba32e686ebb00d1fa2c2b3758b93cd0
7
- data.tar.gz: 3636e536af14c71d4efd99136c0408528412a20b7e50e12efd33147d8c7e5bea1bb70128c35af71b9b439febd2ab3636c29a58984e04b49ecb146c292891180e
6
+ metadata.gz: 03d2373df0d22c8168dddecb4e073a62974db0d00b898d3a9896d839390c9aba6c959553d2e172e84534ece21913d0aa503d6469f600fae15e7bf5f32cc7a5b7
7
+ data.tar.gz: 169c5f762f9eb0a9897e7277633a1e7181f17e918341198b8d420ec482a3c83425d1ad45de4bce6428970d1850bc916fd91231a6c22dbf01c7ef6660070a7e5b
data/README.adoc CHANGED
@@ -169,15 +169,49 @@ Vectory::Eps.from_path("img.eps").to_uri.content
169
169
  ==== SVG mapping (for the metanorma project)
170
170
 
171
171
  Vectory can integrate SVG files into XML or HTML, respecting internal id and
172
- link references:
172
+ link references. It supports ID disambiguation for multi-document and multi-svgmap
173
+ scenarios.
174
+
175
+ ===== Basic usage
173
176
 
174
177
  [source,ruby]
175
178
  ----
176
179
  xml_string = Vectory::SvgMapping.from_path("doc.xml").to_xml
177
180
  ----
178
181
 
179
- In order to do that an initial XML should support the `svgmap` tag with links
180
- mapping. For example, it can convert XML like this:
182
+ ===== ID suffixing for uniqueness
183
+
184
+ When processing multiple documents or multiple svgmaps within a document,
185
+ SVG IDs must be unique to avoid conflicts. Vectory applies two types of suffixes:
186
+
187
+ *ID suffix* (Cross-document uniqueness)::
188
+ An optional suffix derived from document or container identity (e.g., `_ISO_17301-1_2016`).
189
+ This provides uniqueness across documents or collections.
190
+
191
+ *Index suffix* (Multi-svgmap uniqueness)::
192
+ Automatically applied based on the svgmap's position in the document (0, 1, 2, ...).
193
+ Formatted as a 9-digit zero-padded number (e.g., `_000000000`, `_000000001`).
194
+ This provides uniqueness when a document contains multiple svgmaps.
195
+
196
+ .Final ID composition
197
+ [source]
198
+ ----
199
+ Original ID: fig1
200
+ With ID suffix only: fig1_ISO_17301-1_2016
201
+ With both suffixes: fig1_ISO_17301-1_2016_000000000
202
+ ----
203
+
204
+ ===== Usage with ID suffix
205
+
206
+ [source,ruby]
207
+ ----
208
+ mapping = Vectory::SvgMapping.new(doc, "", id_suffix: "_ISO_17301-1_2016")
209
+ xml_string = mapping.call.to_xml
210
+ ----
211
+
212
+ ===== Input format
213
+
214
+ The input XML must support the `svgmap` tag with link mapping.
181
215
 
182
216
  [source,xml]
183
217
  ----
@@ -187,8 +221,8 @@ mapping. For example, it can convert XML like this:
187
221
  <xref target="ref1">Computer</xref>
188
222
  </target>
189
223
  <target href="http://www.example.com">
190
- <link target="http://www.example.com">Phone</link><
191
- /target>
224
+ <link target="http://www.example.com">Phone</link>
225
+ </target>
192
226
  </svgmap>
193
227
  ----
194
228
 
@@ -196,16 +230,12 @@ mapping. For example, it can convert XML like this:
196
230
  [source,xml]
197
231
  ----
198
232
  <?xml version="1.0" encoding="utf-8"?>
199
- <!-- Generator: Adobe Illustrator 25.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
200
- <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
201
- viewBox="0 0 595.28 841.89" style="enable-background:new 0 0 595.28 841.89;" xml:space="preserve">
233
+ <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
202
234
  <style type="text/css">
203
- #Layer_1 { fill:none }
204
- svg[id = 'Layer_1'] { fill:none }
235
+ #Layer_1 { fill:none }
236
+ svg[id = 'Layer_1'] { fill:none }
205
237
  .st0{fill:none;stroke:#000000;stroke-miterlimit:10;}
206
238
  </style>
207
- <image style="overflow:visible;" width="368" height="315" xlink:href="data:image/gif;base64,R0lG..ommited to save space" transform="matrix(1 0 0 1 114 263.8898)">
208
- </image>
209
239
  <a xlink:href="mn://action_schema" xlink:dummy="Layer_1">
210
240
  <rect x="123.28" y="273.93" class="st0" width="88.05" height="41.84"/>
211
241
  </a>
@@ -218,15 +248,14 @@ mapping. For example, it can convert XML like this:
218
248
  </svg>
219
249
  ----
220
250
 
221
- into XML containing inline SVG tags. Notice changes in the `id` attributes and
222
- the `a` tags:
251
+ ===== Output format
223
252
 
224
253
  [source,xml]
225
254
  ----
226
255
  <figure>
227
- <svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' id='Layer_1_000000001' x='0px' y='0px' viewBox='0 0 595.28 841.89' style='enable-background:new 0 0 595.28 841.89;' xml:space='preserve'>
228
- <style> ..ommited to save space </style>
229
- <image> ..ommited </image>
256
+ <svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1'
257
+ id='Layer_1_000000001' x='0px' y='0px' viewBox='0 0 595.28 841.89'>
258
+ <style> #Layer_1_000000001 { fill:none }</style>
230
259
  <a xlink:href='#ref1' xlink:dummy='Layer_1_000000001'>
231
260
  <rect x='123.28' y='273.93' class='st0' width='88.05' height='41.84'/>
232
261
  </a>
@@ -240,53 +269,39 @@ the `a` tags:
240
269
  </figure>
241
270
  ----
242
271
 
243
- It also supports SVG in a form of an inline tag:
272
+ ===== Inline SVG support
273
+
274
+ SVG can also be provided inline within the svgmap:
244
275
 
245
276
  [source,xml]
246
277
  ----
247
278
  <svgmap id="_60dadf08-48d4-4164-845c-b4e293e00abd">
248
279
  <figure>
249
- <svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' version='1.1' id='Layer_1' x='0px' y='0px' viewBox='0 0 595.28 841.89' style='enable-background:new 0 0 595.28 841.89;' xml:space='preserve'>
250
- <a href="mn://action_schema" >
251
- <rect x="123.28" y="273.93" class="st0" width="88.05" height="41.84"/>
252
- </a>
253
- <a href="mn://basic_attribute_schema" >
254
- <rect x="324.69" y="450.52" class="st0" width="132.62" height="40.75"/>
255
- </a>
256
- <a xlink:href="mn://support_resource_schema" >
257
- <rect x="324.69" y="528.36" class="st0" width="148.16" height="40.75"/>
280
+ <svg xmlns='http://www.w3.org/2000/svg' id='Layer_1'>
281
+ <a href="mn://action_schema">
282
+ <rect x="123.28" y="273.93" class="st0"/>
258
283
  </a>
259
284
  </svg>
260
285
  </figure>
261
286
  <target href="mn://action_schema">
262
287
  <xref target="ref1">Computer</xref>
263
288
  </target>
264
- <target href="http://www.example.com">
265
- <link target="http://www.example.com">Phone</link>
266
- </target>
267
289
  </svgmap>
268
290
  ----
269
291
 
270
- and datauri:
292
+ ===== Data URI support
293
+
294
+ Images can be provided as data URIs:
271
295
 
272
296
  [source,xml]
273
297
  ----
274
298
  <svgmap id="_60dadf08-48d4-4164-845c-b4e293e00abd">
275
299
  <figure>
276
- <image src='data:image/svg+xml;base64,PD94..ommited to save space' id='__ISO_17301-1_2016' mimetype='image/svg+xml' height='auto' width='auto' alt='Workmap1'/>
300
+ <image src='data:image/svg+xml;base64,PD94...'/>
277
301
  </figure>
278
302
  <target href="href1.htm">
279
303
  <xref target="ref1">Computer</xref>
280
304
  </target>
281
- <target href="mn://basic_attribute_schema">
282
- <link target="http://www.example.com">Phone</link>
283
- </target>
284
- <target href="mn://support_resource_schema">
285
- <eref type="express" bibitemid="express_action_schema" citeas="">
286
- <localityStack><locality type="anchor"><referenceFrom>action_schema.basic</referenceFrom></locality></localityStack>
287
- Coffee
288
- </eref>
289
- </target>
290
305
  </svgmap>
291
306
  ----
292
307
 
@@ -355,7 +370,7 @@ Vector#width
355
370
 
356
371
  == Development
357
372
 
358
- === Regenerating Test Fixtures
373
+ === Regenerating test fixtures
359
374
 
360
375
  Some test fixtures are generated by external tools (Ghostscript, Inkscape, cairo).
361
376
  When these tools are updated, the reference files may need to be regenerated.
@@ -339,7 +339,7 @@ rescue Vectory::ConversionError => e
339
339
  end
340
340
  ----
341
341
 
342
- === Dimension Query
342
+ === Dimension query
343
343
 
344
344
  [source,ruby]
345
345
  ----
@@ -348,7 +348,89 @@ width, height = eps.dimensions
348
348
  puts "Dimensions: #{width}x#{height}"
349
349
  ----
350
350
 
351
- ## See Also
351
+ == SVG mapping
352
+
353
+ Integrates SVG content into XML documents with ID disambiguation for multi-document scenarios.
354
+
355
+ === Vectory::SvgMapping
356
+
357
+ Processes XML documents containing `<svgmap>` elements, extracting and integrating SVG content.
358
+
359
+ [source,ruby]
360
+ ----
361
+ # Basic usage
362
+ xml_string = Vectory::SvgMapping.from_path("doc.xml").to_xml
363
+
364
+ # With ID suffix for cross-document uniqueness
365
+ mapping = Vectory::SvgMapping.new(doc, "", id_suffix: "_ISO_17301-1_2016")
366
+ xml_string = mapping.call.to_xml
367
+ ----
368
+
369
+ **Factory methods**:
370
+
371
+ * `Vectory::SvgMapping.from_path(path)` - Load from file path
372
+ * `Vectory::SvgMapping.from_xml(xml)` - Load from XML string
373
+ * `Vectory::SvgMapping.new(doc, local_directory, id_suffix: nil)` - Direct instantiation
374
+
375
+ **Parameters**:
376
+
377
+ * `doc` - Nokogiri XML Document containing svgmap elements
378
+ * `local_directory` - Directory for resolving relative file paths
379
+ * `id_suffix` - Optional suffix for cross-document ID uniqueness (e.g., "_ISO_17301-1_2016")
380
+
381
+ **Methods**:
382
+
383
+ * `#call` - Process all svgmap elements, returns processed document
384
+ * `#to_xml` - Process and return XML string
385
+
386
+ === Vectory::SvgDocument
387
+
388
+ Represents an SVG document and handles ID suffixing for uniqueness.
389
+
390
+ [source,ruby]
391
+ ----
392
+ # Create from SVG content
393
+ svg_doc = Vectory::SvgDocument.new(svg_content)
394
+
395
+ # Apply both ID suffix and index suffix
396
+ svg_doc.namespace(index, links, xpath_to_remove, id_suffix: "_ISO_17301-1_2016")
397
+ result = svg_doc.content
398
+ ----
399
+
400
+ **Constructor**:
401
+
402
+ * `Vectory::SvgDocument.new(content)` - Create from SVG XML string
403
+
404
+ **Methods**:
405
+
406
+ * `namespace(index_suffix, links, xpath_to_remove, id_suffix: nil)` - Apply transformations
407
+ ** `index_suffix` - Position-based suffix (0, 1, 2...) formatted as 9-digit zero-padded
408
+ ** `links` - Hash mapping hrefs to targets
409
+ ** `xpath_to_remove` - XPath for elements to remove (processing instructions)
410
+ ** `id_suffix` - Optional identity-based suffix for cross-document uniqueness
411
+
412
+ * `content` - Returns processed SVG as XML string
413
+ * `remap_links(map)` - Remap internal links
414
+ * `apply_id_suffix(id_suffix)` - Apply ID suffix
415
+ * `apply_index_suffix(index)` - Apply index suffix
416
+
417
+ === ID Suffixing
418
+
419
+ SVG IDs are suffixed in two stages:
420
+
421
+ [source]
422
+ ----
423
+ # Stage 1: ID suffix (cross-document uniqueness)
424
+ fig1 → fig1_ISO_17301-1_2016
425
+
426
+ # Stage 2: Index suffix (multi-svgmap uniqueness)
427
+ fig1_ISO_17301-1_2016 → fig1_ISO_17301-1_2016_000000000
428
+ ----
429
+
430
+ * **ID suffix** (`id_suffix` parameter): Derived from document/container identity. Optional.
431
+ * **Index suffix** (`index_suffix` parameter): Zero-padded position (0, 1, 2... → _000000000, _000000001). Automatically applied.
432
+
433
+ == See Also
352
434
 
353
435
  * link:../guides/error-handling/[Error Handling Guide] - Common errors and solutions
354
436
  * link:../understanding/architecture/[Architecture] - Design patterns
@@ -1,11 +1,30 @@
1
1
  require "nokogiri"
2
2
 
3
3
  module Vectory
4
+ # Represents and processes an SVG document within XML.
5
+ #
6
+ # SvgDocument handles SVG ID suffixing for uniqueness in multi-document
7
+ # and multi-svgmap scenarios. It applies two types of suffixes:
8
+ #
9
+ # 1. **ID suffix** - Derived from document/container identity
10
+ # Provides cross-document uniqueness
11
+ #
12
+ # 2. **Index suffix** - Derived from svgmap position
13
+ # Provides multi-svgmap uniqueness within a document
14
+ #
15
+ # Final ID format: <original_id><id_suffix><index_suffix>
16
+ # Example: fig1 + _ISO_17301-1_2016 + _000000000 = fig1_ISO_17301-1_2016_000000000
17
+ #
4
18
  class SvgDocument
5
19
  SVG_NS = "http://www.w3.org/2000/svg".freeze
6
20
 
7
21
  class << self
8
- # Update instances of id in style statements in a nokogiri document
22
+ # Updates ID references in CSS style statements within a document.
23
+ #
24
+ # @param document [Nokogiri::XML::Document] The SVG document
25
+ # @param ids [Array<String>] IDs to update
26
+ # @param suffix [Integer, String] The suffix to apply (integers are zero-padded)
27
+ # @return [void]
9
28
  def update_ids_css(document, ids, suffix)
10
29
  suffix = suffix.is_a?(Integer) ? sprintf("%09d", suffix) : suffix
11
30
  document.xpath(".//m:style", "m" => SVG_NS).each do |s|
@@ -17,7 +36,12 @@ module Vectory
17
36
  end
18
37
  end
19
38
 
20
- # Update instances of id in style statements in the string style
39
+ # Updates ID references in a CSS style string.
40
+ #
41
+ # @param style [String] The CSS style content
42
+ # @param ids [Array<String>] IDs to update
43
+ # @param suffix [Integer, String] The suffix to apply (integers are zero-padded)
44
+ # @return [String] Updated style content
21
45
  def update_ids_css_string(style, ids, suffix)
22
46
  ids.each do |i|
23
47
  style = style.gsub(%r[##{i}\b],
@@ -30,6 +54,12 @@ module Vectory
30
54
  style
31
55
  end
32
56
 
57
+ # Updates ID attributes in SVG elements.
58
+ #
59
+ # @param document [Nokogiri::XML::Document] The SVG document
60
+ # @param ids [Array<String>] IDs to update
61
+ # @param suffix [Integer, String] The suffix to apply (integers are zero-padded)
62
+ # @return [void]
33
63
  def update_ids_attrs(document, ids, suffix)
34
64
  suffix = suffix.is_a?(Integer) ? sprintf("%09d", suffix) : suffix
35
65
  document.xpath(". | .//*[@*]").each do |a|
@@ -42,20 +72,57 @@ module Vectory
42
72
  end
43
73
  end
44
74
 
75
+ # Creates a new SvgDocument from SVG content.
76
+ #
77
+ # @param content [String] SVG XML content
45
78
  def initialize(content)
46
79
  @document = Nokogiri::XML(content)
47
80
  end
48
81
 
82
+ # Returns the processed SVG content as XML string.
83
+ #
84
+ # @return [String] SVG XML content
49
85
  def content
50
86
  @document.root.to_xml
51
87
  end
52
88
 
53
- def namespace(suffix, links, xpath_to_remove)
89
+ # Applies namespace transformations to the SVG document.
90
+ #
91
+ # This method performs the following operations in order:
92
+ # 1. Remap internal links to their targets
93
+ # 2. Apply ID suffix (if provided) for cross-document uniqueness
94
+ # 3. Apply index suffix for multi-svgmap uniqueness
95
+ # 4. Remove processing instructions
96
+ #
97
+ # @param index_suffix [Integer] The position-based suffix (0, 1, 2...)
98
+ # Converted to 9-digit zero-padded string (e.g., "_000000000")
99
+ # @param links [Hash{String => String}] Mapping of link hrefs to their targets
100
+ # @param xpath_to_remove [String] XPath for elements to remove (processing instructions)
101
+ # @param id_suffix [String, nil] Optional suffix derived from document/container
102
+ # identity. Applied BEFORE index_suffix for two-stage disambiguation.
103
+ # Example: "_ISO_17301-1_2016"
104
+ #
105
+ # @return [self] The processed document
106
+ def namespace(index_suffix, links, xpath_to_remove, id_suffix: nil)
54
107
  remap_links(links)
55
- suffix_ids(suffix)
108
+
109
+ # Apply ID suffix first (cross-document uniqueness)
110
+ # Then apply index suffix (multi-svgmap uniqueness)
111
+ # Final format: <original_id><id_suffix><index_suffix>
112
+ if id_suffix
113
+ apply_id_suffix(id_suffix)
114
+ end
115
+
116
+ apply_index_suffix(index_suffix)
56
117
  remove_xpath(xpath_to_remove)
118
+
119
+ self
57
120
  end
58
121
 
122
+ # Remaps internal SVG links to their target definitions.
123
+ #
124
+ # @param map [Hash{String => String}] Mapping of expanded hrefs to targets
125
+ # @return [self] The document
59
126
  def remap_links(map)
60
127
  @document.xpath(".//m:a", "m" => SVG_NS).each do |a|
61
128
  href_attrs = ["xlink:href", "href"]
@@ -67,16 +134,54 @@ module Vectory
67
134
  self
68
135
  end
69
136
 
70
- def suffix_ids(suffix)
137
+ # Applies an ID suffix to all SVG IDs for cross-document uniqueness.
138
+ #
139
+ # This is called BEFORE index-based suffixing.
140
+ #
141
+ # @param id_suffix [String] The suffix to apply (e.g., "_ISO_17301-1_2016")
142
+ # @return [self] The document
143
+ def apply_id_suffix(id_suffix)
144
+ ids = collect_ids
145
+ return if ids.empty?
146
+
147
+ self.class.update_ids_attrs(@document.root, ids, id_suffix)
148
+ self.class.update_ids_css(@document.root, ids, id_suffix)
149
+
150
+ self
151
+ end
152
+
153
+ # Applies an index-based suffix to all SVG IDs.
154
+ #
155
+ # The index is converted to a 9-digit zero-padded string.
156
+ # This is called AFTER ID-based suffixing.
157
+ #
158
+ # @param index [Integer] The index suffix (converted to "_000000000" format)
159
+ # @return [self] The document
160
+ def apply_index_suffix(index)
71
161
  ids = collect_ids
72
162
  return if ids.empty?
73
163
 
74
- self.class.update_ids_attrs(@document.root, ids, suffix)
75
- self.class.update_ids_css(@document.root, ids, suffix)
164
+ self.class.update_ids_attrs(@document.root, ids, index)
165
+ self.class.update_ids_css(@document.root, ids, index)
76
166
 
77
167
  self
78
168
  end
79
169
 
170
+ # Applies an index-based suffix to all SVG IDs.
171
+ #
172
+ # @deprecated Use {#apply_index_suffix} instead for clarity.
173
+ # Maintained for backward compatibility.
174
+ #
175
+ # @param suffix [Integer, String] The suffix to apply (integers are zero-padded)
176
+ # @return [self] The document
177
+ def suffix_ids(suffix)
178
+ apply_index_suffix(suffix)
179
+ end
180
+
181
+ # Removes elements matching the given XPath.
182
+ #
183
+ # @param xpath [String] XPath expression for elements to remove
184
+ # @return [self] The document
80
185
  def remove_xpath(xpath)
81
186
  @document.xpath(xpath).remove
82
187
 
@@ -85,6 +190,9 @@ module Vectory
85
190
 
86
191
  private
87
192
 
193
+ # Collects all id attribute values from the SVG document.
194
+ #
195
+ # @return [Array<String>] All ID values
88
196
  def collect_ids
89
197
  @document.xpath("./@id | .//@id").map(&:value)
90
198
  end
@@ -2,12 +2,45 @@ require_relative "svg"
2
2
  require_relative "svg_document"
3
3
 
4
4
  module Vectory
5
+ # Processes SVG mapping in XML documents.
6
+ #
7
+ # SvgMapping integrates SVG content into XML documents by:
8
+ # - Extracting SVG from image tags or inline <svg> elements
9
+ # - Processing <target> elements to build link mappings
10
+ # - Applying ID suffixes for uniqueness in multi-document scenarios
11
+ # - Simplifying svgmap structure for final output
12
+ #
13
+ # == ID Suffixing
14
+ #
15
+ # SVG IDs are suffixed in two stages to ensure uniqueness:
16
+ #
17
+ # 1. **ID suffix** (optional) - Derived from document/container identity
18
+ # Provides cross-document uniqueness (e.g., <id>_ISO_17301-1_2016)
19
+ #
20
+ # 2. **Index suffix** - Derived from svgmap position in document
21
+ # Provides multi-svgmap uniqueness (e.g., <id>_000000000)
22
+ #
23
+ # Final ID: <original_id><id_suffix><index_suffix>
24
+ # Example: fig1_ISO_17301-1_2016_000000000
25
+ #
26
+ # @example Basic usage
27
+ # xml_string = Vectory::SvgMapping.from_path("doc.xml").to_xml
28
+ #
29
+ # @example With ID suffix for multi-document uniqueness
30
+ # mapping = Vectory::SvgMapping.new(doc, "", id_suffix: "_ISO_17301-1_2016")
31
+ # xml_string = mapping.call.to_xml
32
+ #
5
33
  class SvgMapping
34
+ # Namespace helper for XML namespace handling
6
35
  class Namespace
36
+ # @param xmldoc [Nokogiri::XML::Document] The XML document
7
37
  def initialize(xmldoc)
8
38
  @namespace = xmldoc.root.namespace
9
39
  end
10
40
 
41
+ # Converts XPath to use document namespace
42
+ # @param path [String] The XPath expression
43
+ # @return [String] XPath with namespace prefixes
11
44
  def ns(path)
12
45
  return path if @namespace.nil?
13
46
 
@@ -19,22 +52,45 @@ module Vectory
19
52
  end
20
53
 
21
54
  SVG_NS = "http://www.w3.org/2000/svg".freeze
55
+ private_constant :SVG_NS
56
+
57
+ # XPath to remove processing instructions during SVG processing
22
58
  PROCESSING_XPATH =
23
59
  "processing-instruction()|.//processing-instruction()".freeze
60
+ private_constant :PROCESSING_XPATH
24
61
 
62
+ # Creates an SvgMapping from an XML file path.
63
+ #
64
+ # @param path [String] Path to the XML file
65
+ # @return [Vectory::SvgMapping] New mapping instance
25
66
  def self.from_path(path)
26
67
  new(Nokogiri::XML(File.read(path)))
27
68
  end
28
69
 
70
+ # Creates an SvgMapping from an XML string.
71
+ #
72
+ # @param xml [String] XML content
73
+ # @return [Vectory::SvgMapping] New mapping instance
29
74
  def self.from_xml(xml)
30
75
  new(Nokogiri::XML(xml))
31
76
  end
32
77
 
33
- def initialize(doc, local_directory = "")
78
+ # Initializes a new SvgMapping processor.
79
+ #
80
+ # @param doc [Nokogiri::XML::Document] The document containing svgmap elements
81
+ # @param local_directory [String] Directory for resolving relative file paths
82
+ # @param id_suffix [String, nil] Optional suffix derived from document/container
83
+ # identity. Applied before the index suffix to provide cross-document
84
+ # uniqueness. Example: "_ISO_17301-1_2016"
85
+ def initialize(doc, local_directory = "", id_suffix: nil)
34
86
  @doc = doc
35
87
  @local_directory = local_directory
88
+ @id_suffix = id_suffix
36
89
  end
37
90
 
91
+ # Processes all svgmap elements in the document.
92
+ #
93
+ # @return [Nokogiri::XML::Document] The processed document
38
94
  def call
39
95
  @namespace = Namespace.new(@doc)
40
96
 
@@ -45,17 +101,24 @@ module Vectory
45
101
  @doc
46
102
  end
47
103
 
104
+ # Processes and returns XML string.
105
+ #
106
+ # @return [String] XML representation of processed document
48
107
  def to_xml
49
108
  call.to_xml
50
109
  end
51
110
 
52
111
  private
53
112
 
54
- def process_svgmap(svgmap, suffix)
113
+ # Processes a single svgmap element.
114
+ #
115
+ # @param svgmap [Nokogiri::XML::Element] The svgmap element to process
116
+ # @param index [Integer] Position of this svgmap in the document (0-indexed)
117
+ def process_svgmap(svgmap, index)
55
118
  image = extract_image_tag(svgmap)
56
119
  return unless image
57
120
 
58
- content = generate_content(image, svgmap, suffix)
121
+ content = generate_content(image, svgmap, index)
59
122
  return unless content
60
123
 
61
124
  image.replace(content)
@@ -63,6 +126,10 @@ module Vectory
63
126
  simplify_svgmap(svgmap)
64
127
  end
65
128
 
129
+ # Extracts the image element (SVG inline or image with src) from svgmap.
130
+ #
131
+ # @param svgmap [Nokogiri::XML::Element] The svgmap element
132
+ # @return [Nokogiri::XML::Element, nil] The image/svg element or nil
66
133
  def extract_image_tag(svgmap)
67
134
  image = svgmap.at(@namespace.ns(".//image"))
68
135
  return image if image && image["src"] && !image["src"].empty?
@@ -70,16 +137,27 @@ module Vectory
70
137
  svgmap.at(".//m:svg", "m" => SVG_NS)
71
138
  end
72
139
 
73
- def generate_content(image, svgmap, suffix)
140
+ # Generates processed SVG content for an svgmap.
141
+ #
142
+ # @param image [Nokogiri::XML::Element] The image/svg element
143
+ # @param svgmap [Nokogiri::XML::Element] The parent svgmap element
144
+ # @param index [Integer] Index suffix to apply
145
+ # @return [String, nil] Processed SVG content or nil if invalid
146
+ def generate_content(image, svgmap, index)
74
147
  document = build_svg_document(image)
75
148
  return unless document
76
149
 
77
150
  links_map = from_targets_to_links_map(svgmap)
78
- document.namespace(suffix, links_map, PROCESSING_XPATH)
151
+ # Pass nil for id_suffix here; it's already handled in SvgDocument.namespace
152
+ document.namespace(index, links_map, PROCESSING_XPATH, id_suffix: @id_suffix)
79
153
 
80
154
  document.content
81
155
  end
82
156
 
157
+ # Builds an SvgDocument from an image element.
158
+ #
159
+ # @param image [Nokogiri::XML::Element] The image element
160
+ # @return [Vectory::SvgDocument, nil] The SVG document or nil if invalid
83
161
  def build_svg_document(image)
84
162
  vector = build_vector(image)
85
163
  return unless vector
@@ -87,6 +165,10 @@ module Vectory
87
165
  SvgDocument.new(vector.content)
88
166
  end
89
167
 
168
+ # Builds a Vector from an image element.
169
+ #
170
+ # @param image [Nokogiri::XML::Element] The image element
171
+ # @return [Vectory::Vector, nil] The vector or nil if invalid
90
172
  def build_vector(image)
91
173
  return Vectory::Svg.from_content(image.to_xml) if image.name == "svg"
92
174
 
@@ -101,6 +183,10 @@ module Vectory
101
183
  Vectory::Svg.from_path(path)
102
184
  end
103
185
 
186
+ # Builds a mapping from target hrefs to their link targets.
187
+ #
188
+ # @param svgmap [Nokogiri::XML::Element] The svgmap element
189
+ # @return [Hash{String => String}] Mapping of href to target
104
190
  def from_targets_to_links_map(svgmap)
105
191
  targets = svgmap.xpath(@namespace.ns("./target"))
106
192
  targets.each_with_object({}) do |target_tag, m|
@@ -114,6 +200,10 @@ module Vectory
114
200
  end
115
201
  end
116
202
 
203
+ # Extracts link target from a target element.
204
+ #
205
+ # @param target_tag [Nokogiri::XML::Element] The target element
206
+ # @return [String, nil] The link target or nil
117
207
  def link_target(target_tag)
118
208
  xref = target_tag.at(@namespace.ns("./xref"))
119
209
  return "##{xref['target']}" if xref
@@ -124,6 +214,10 @@ module Vectory
124
214
  link["target"]
125
215
  end
126
216
 
217
+ # Simplifies svgmap after processing.
218
+ # Removes svgmap wrapper if no more target elements with eref.
219
+ #
220
+ # @param svgmap [Nokogiri::XML::Element] The svgmap element
127
221
  def simplify_svgmap(svgmap)
128
222
  return if svgmap.at(@namespace.ns("./target/eref"))
129
223
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vectory
4
- VERSION = "0.8.1"
4
+ VERSION = "0.8.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vectory
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.1
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-01-28 00:00:00.000000000 Z
11
+ date: 2026-02-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: base64