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 +4 -4
- data/README.adoc +57 -42
- data/docs/reference/api.adoc +84 -2
- data/lib/vectory/svg_document.rb +115 -7
- data/lib/vectory/svg_mapping.rb +99 -5
- data/lib/vectory/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1825d8ed689d1eb94da1c473ffd9671bc3237471f3f4b675d6f2d8ff6db4e2a7
|
|
4
|
+
data.tar.gz: 7d9ee417ac1b74b481589a83f45a359d556f1c3d2aa401149f57439c39085811
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
180
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
204
|
-
|
|
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
|
-
|
|
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'
|
|
228
|
-
|
|
229
|
-
<
|
|
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
|
-
|
|
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'
|
|
250
|
-
<a href="mn://action_schema"
|
|
251
|
-
<rect x="123.28" y="273.93" class="st0"
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
data/docs/reference/api.adoc
CHANGED
|
@@ -339,7 +339,7 @@ rescue Vectory::ConversionError => e
|
|
|
339
339
|
end
|
|
340
340
|
----
|
|
341
341
|
|
|
342
|
-
=== Dimension
|
|
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
|
-
|
|
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
|
data/lib/vectory/svg_document.rb
CHANGED
|
@@ -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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
75
|
-
self.class.update_ids_css(@document.root, ids,
|
|
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
|
data/lib/vectory/svg_mapping.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
data/lib/vectory/version.rb
CHANGED
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.
|
|
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-
|
|
11
|
+
date: 2026-02-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|