sevgi-derender 0.95.0 → 0.98.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/CHANGELOG.md +125 -0
- data/README.md +24 -9
- data/bin/igves +1 -1
- data/lib/sevgi/binaries/igves.rb +46 -19
- data/lib/sevgi/derender/attributes.rb +6 -2
- data/lib/sevgi/derender/document.rb +10 -4
- data/lib/sevgi/derender/elements/any.rb +3 -3
- data/lib/sevgi/derender/elements/css.rb +17 -3
- data/lib/sevgi/derender/elements/root.rb +1 -4
- data/lib/sevgi/derender/evaluator.rb +17 -5
- data/lib/sevgi/derender/internal.rb +75 -4
- data/lib/sevgi/derender/node.rb +116 -81
- data/lib/sevgi/derender/version.rb +1 -1
- data/lib/sevgi/derender.rb +112 -94
- metadata +8 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9d9e616e1b10140f4e9477ca8cc23f7912d56a6169b4e686a74353c816fe9706
|
|
4
|
+
data.tar.gz: e32b3958b5270380a74f218d87377a6fca87db2cfd3c518cd28530fcb7f97746
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '058bc5a453580bc9c32fd86a1d6a64474868c18b1ffe0af32ceb40d52a60263cfa1a787e1fac9f521ef31817f8a81991ddb65a33503a8b24e3743996f2dcd468'
|
|
7
|
+
data.tar.gz: eb906f2cb811fbbe73abeec77b298f68ee3db1de17366996762f02f4e36c0e5d392139cf83b4bc686d7fd7a156838065313fe1e7110d918dc3b45fe2a081fc85
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,131 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
|
|
|
5
5
|
|
|
6
6
|
## Unreleased
|
|
7
7
|
|
|
8
|
+
## 0.98.2 - 2026-07-25
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Restored the `sevgi` CLI's main-object scope so bare toolkit calls remain available inside script helper classes.
|
|
13
|
+
|
|
14
|
+
## 0.98.1 - 2026-07-20
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Changed the `sevgi` CLI to use the executor's isolated scope by default.
|
|
19
|
+
|
|
20
|
+
### Removed
|
|
21
|
+
|
|
22
|
+
- Removed the redundant `-n` and `--nomain` CLI options; library consumers can still select `main: true` through
|
|
23
|
+
`Sevgi.execute` and `Sevgi.execute_file` when needed.
|
|
24
|
+
|
|
25
|
+
## 0.98.0 - 2026-07-20
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- Added standard-input support to `sevgi`, `igves`, and `igsev`; `sevgi --as NAME` and
|
|
30
|
+
`Sevgi.execute_file(..., as: NAME)` supply the logical source basename used by implicit SVG, PDF, and PNG
|
|
31
|
+
destinations without changing physical load identity.
|
|
32
|
+
|
|
33
|
+
## 0.97.0 - 2026-07-19
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- Added `sevgi-appendix`, containing the Sevgi agent skill and a RuboCop plugin for preserving readable `.sevgi` DSL
|
|
38
|
+
source alongside rubyfmt-formatted Ruby; the umbrella `sevgi` gem installs the matching Appendix version.
|
|
39
|
+
- Added `sevgi --skill` to report the validated path of the matching packaged agent skill for product-neutral setup.
|
|
40
|
+
- Added `igsev` to the umbrella gem for normalizing an SVG file through a complete SVG-to-Sevgi-to-SVG round trip.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- Made Homebrew the recommended complete CLI installation while retaining Bundler and focused component gems for Ruby
|
|
45
|
+
application dependencies.
|
|
46
|
+
|
|
47
|
+
## 0.96.0 - 2026-07-18
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- Added exact, subtree-wide attribute omission to Derender content/file conversion, evaluation, and inclusion APIs;
|
|
52
|
+
`igves --omit` exposes the same behavior from the command line.
|
|
53
|
+
- Added `Sevgi.SVG` as the explicit namespaced form of the top-level SVG document entrypoint.
|
|
54
|
+
- Added the opt-in recursive `SVG::Modules` contract for callable drawing namespaces.
|
|
55
|
+
- Added callable drawing-module `base` blocks, document and paper registry introspection, Canvas structural equality,
|
|
56
|
+
renderer options on `RenderChildren`, axis translation helpers, and a CC BY RDF helper.
|
|
57
|
+
- Formalized non-rendering `-` metadata and `+` attribute updates as `Attributes::META_PREFIX` and
|
|
58
|
+
`Attributes::UPDATE_SUFFIX`; repeated Array updates now concatenate into a stable flat value.
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
|
|
62
|
+
- Breaking: replaced the `SVG = Sevgi::Graphics` alias with an independent SVG facade. Facade operations now use
|
|
63
|
+
capitalized names such as `SVG.Canvas` and `SVG.Document`; lowercase component helpers remain on
|
|
64
|
+
`Sevgi::Graphics`, and the stuttering `SVG.SVG` form was removed.
|
|
65
|
+
- Breaking: made low-level `Executor.execute` and `execute_file` runners private; consumers use
|
|
66
|
+
`Sevgi.execute` and `Sevgi.execute_file` while retaining the public result and error types.
|
|
67
|
+
- Breaking: removed the abstract `Document::Base` layer from the selectable document-profile registry; `Minimal` and
|
|
68
|
+
`Default` are now sibling concrete profiles, and custom profile hierarchies should derive from `Base`.
|
|
69
|
+
- Breaking: renamed Parallelogram segment and constraint arguments by geometric role: `base`, `side`, and
|
|
70
|
+
`constraint` replace axis-implying constructor names.
|
|
71
|
+
- Made degree-based sine and cosine exact at integer quarter turns, eliminating cardinal Geometry residue.
|
|
72
|
+
- Documented complete file-system failure families for file comparison, output, touch, and upward location helpers.
|
|
73
|
+
- Formalized generated Tile ids, positional CSS classes, template placement, and per-use callback signatures.
|
|
74
|
+
- Formalized String and Symbol ids across Derender selection APIs and documented file-read failures consistently.
|
|
75
|
+
- Breaking: made top-level `Decompile`, `Derender`, `Evaluate`, and `EvaluateChildren` consume inline SVG/XML;
|
|
76
|
+
file inputs now use the corresponding `File`-suffixed entrypoints.
|
|
77
|
+
- Breaking: replaced callable drawing module `call` block registration with argument-independent `base` blocks;
|
|
78
|
+
inherited bases run parent-first, and modules with one drawing method conventionally name it `call`.
|
|
79
|
+
- Breaking: made bracket and call notation the canonical Geometry constructors. Among Data value types, bracket notation
|
|
80
|
+
is public only for Point, Segment, LengthAngle, Margin, and Paper; use `.new` for Location and result carriers, and
|
|
81
|
+
`Stay` for traversal stop tokens.
|
|
82
|
+
- Breaking: aligned Point and Segment comparison with Ruby `Comparable`; malformed or unrelated comparison operands now
|
|
83
|
+
return nil from `<=>`.
|
|
84
|
+
- Breaking: executor entrypoints now return immutable `Executor::Result` values and expose only `execute` and
|
|
85
|
+
`execute_file`; inspect `result.error`, `result.stack`, and `result.value` instead of executor scopes or lifecycle state.
|
|
86
|
+
- Breaking: wrapper attributes and callable arguments use distinct channels, `With` rejects parentless receivers, and
|
|
87
|
+
unsupported direct constructors are private; use the documented factories for Content, concrete Geometry lined
|
|
88
|
+
elements, and Grid query values.
|
|
89
|
+
- Breaking: Canvas uses `.new` for explicit fields, `.from_paper(paper, **overrides)` for paper conversion, and
|
|
90
|
+
`.call`/`Graphics.canvas` for dispatch; the misleading keyword-only `.from_paper(width:, height:)` form was removed.
|
|
91
|
+
- Breaking: `Save`, `Write`, `PNG`, `PDF`, and Sundries native export normalize successful destinations to expanded
|
|
92
|
+
String paths and create missing parent directories; change-aware SVG writes still return nil when unchanged.
|
|
93
|
+
- Breaking: renamed `Derender.evaluate_file_children` to `evaluate_children_file`; decompiled nodes now own immutable
|
|
94
|
+
public state while parser, strategy, and construction plumbing remain private.
|
|
95
|
+
- Expanded runtime/YARD parity checks and exact contracts for inherited, extended, generated, and dynamic API surfaces,
|
|
96
|
+
forwarded options, error channels, path ownership, whitespace, namespaces, nil behavior, and return values.
|
|
97
|
+
|
|
98
|
+
### Fixed
|
|
99
|
+
|
|
100
|
+
- Rejected directories from default file discovery while preserving custom locator matchers.
|
|
101
|
+
- Kept `Ancestral` context in non-rendering `-context` metadata instead of leaking it into SVG attributes.
|
|
102
|
+
- Preserved source Canvas units and names when deriving fitted Grid canvases.
|
|
103
|
+
- Prevented stale documentation assets from mixing old layouts with new HTML, and moved sidebar navigation into the
|
|
104
|
+
tablet menu at 1024px and below.
|
|
105
|
+
- Normalized numeric slots owned by path, shape, transform, tile, and Inkscape page helpers to finite SVG number spelling;
|
|
106
|
+
arbitrary user-supplied attributes remain untouched.
|
|
107
|
+
- Made named callable wrapper and symbol ids stable while omitting defaults for anonymous modules, and indexed every
|
|
108
|
+
rendered id value, including false and numeric values, through its serialized string.
|
|
109
|
+
- Preserved signed Geometry constraint directions, rejected invalid sweep/export channels through Sevgi error families,
|
|
110
|
+
and validated raw output paths before expansion or rendering.
|
|
111
|
+
- Made callable module configuration copy-owned and freeze-aware, document subclasses inherit their nearest profile, and
|
|
112
|
+
false executor boot receivers remain explicit rather than defaulting to an internal scope.
|
|
113
|
+
- Made element trees, attributes, identifiers, locator results, document profiles, Derender nodes, shell results, and
|
|
114
|
+
executor results retain owned immutable snapshots where their public contracts promise value semantics.
|
|
115
|
+
- Corrected Shell combined-output separators, nil export density errors, document render-option routing, executor source
|
|
116
|
+
snapshots, selected-node namespace/whitespace documentation, and Standard character-data validation.
|
|
117
|
+
|
|
118
|
+
### Removed
|
|
119
|
+
|
|
120
|
+
- Removed public access to pluralization tables and the internal SVG save extension; pluralization rules are now deeply
|
|
121
|
+
immutable.
|
|
122
|
+
- Removed public documentation and constant access for command-line implementation modules; the `sevgi` and `igves`
|
|
123
|
+
executables remain unchanged.
|
|
124
|
+
- Removed eager loading and public documentation of private Showcase build/test support; explicit support entrypoints
|
|
125
|
+
now keep the harness under the Showcase namespace.
|
|
126
|
+
- Removed public access to document profile name normalizers; registry operations retain them as private plumbing.
|
|
127
|
+
- Removed the redundant `Margin.margin` constructor; use canonical bracket notation.
|
|
128
|
+
- Removed public executor orchestration, obsolete callable-module hooks, the old public attribute syntax constants, and
|
|
129
|
+
direct construction of abstract or internally wired Content, Element, and Grid query types.
|
|
130
|
+
- Removed accidental public access to abstract Lined factories, internal element-name/export maps, result/location
|
|
131
|
+
bracket constructors, and direct Stop construction.
|
|
132
|
+
|
|
8
133
|
## 0.95.0 - 2026-07-11
|
|
9
134
|
|
|
10
135
|
### Security
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Sevgi Derender
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Sevgi Derender converts SVG or XML back into Sevgi DSL source.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -19,26 +19,41 @@ require "sevgi/derender"
|
|
|
19
19
|
```ruby
|
|
20
20
|
source = "<svg><rect width=\"3\" height=\"5\"/></svg>"
|
|
21
21
|
Sevgi::Derender.derender(source)
|
|
22
|
+
|
|
23
|
+
source = '<svg><rect id="mark" style="fill: red" width="3"/></svg>'
|
|
24
|
+
Sevgi::Derender.derender(source, omit: %i[id style])
|
|
22
25
|
```
|
|
23
26
|
|
|
24
27
|
## Executable
|
|
25
28
|
|
|
26
29
|
```sh
|
|
27
|
-
igves drawing.svg
|
|
30
|
+
igves --omit id --omit style drawing.svg
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Omit the file or pass `-` to read SVG from standard input:
|
|
34
|
+
|
|
35
|
+
```sh
|
|
36
|
+
igves --omit id < drawing.svg
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`igves` prints generated Sevgi source. The umbrella `sevgi` gem also installs `igsev`, which evaluates that source and
|
|
40
|
+
prints normalized SVG:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
igsev --omit id --omit style < drawing.svg
|
|
28
44
|
```
|
|
29
45
|
|
|
30
46
|
## Ruby compatibility
|
|
31
47
|
|
|
32
|
-
Requires Ruby 3.4.0 or newer. CI verifies Ruby 3.4
|
|
48
|
+
Requires Ruby 3.4.0 or newer. CI verifies the current Ruby 3.4 release and the development Ruby from `.ruby-version`.
|
|
33
49
|
|
|
34
50
|
## Native prerequisites
|
|
35
51
|
|
|
36
|
-
|
|
37
|
-
platform.
|
|
52
|
+
This gem needs no native libraries of its own. Nokogiri may use platform packages depending on the target Ruby platform.
|
|
38
53
|
|
|
39
54
|
## Links
|
|
40
55
|
|
|
41
|
-
- Documentation: https://sevgi.roktas.dev
|
|
42
|
-
- API documentation: https://www.rubydoc.info/gems/sevgi-derender
|
|
43
|
-
- Source: https://github.com/roktas/sevgi/tree/main/derender
|
|
44
|
-
- Changelog: https://github.com/roktas/sevgi/blob/main/CHANGELOG.md
|
|
56
|
+
- Documentation: <https://sevgi.roktas.dev>
|
|
57
|
+
- API documentation: <https://www.rubydoc.info/gems/sevgi-derender>
|
|
58
|
+
- Source: <https://github.com/roktas/sevgi/tree/main/derender>
|
|
59
|
+
- Changelog: <https://github.com/roktas/sevgi/blob/main/CHANGELOG.md>
|
data/bin/igves
CHANGED
data/lib/sevgi/binaries/igves.rb
CHANGED
|
@@ -3,8 +3,11 @@
|
|
|
3
3
|
require "sevgi"
|
|
4
4
|
|
|
5
5
|
module Sevgi
|
|
6
|
+
# Command-line entrypoint implementations shipped with Sevgi.
|
|
7
|
+
# @api private
|
|
6
8
|
module Binaries
|
|
7
9
|
# Implements the `igves` executable that converts SVG files into Sevgi DSL source.
|
|
10
|
+
# @api private
|
|
8
11
|
module Igves
|
|
9
12
|
extend self
|
|
10
13
|
|
|
@@ -14,16 +17,33 @@ module Sevgi
|
|
|
14
17
|
# Error raised for invalid command-line usage.
|
|
15
18
|
Error = Class.new(::Sevgi::Error)
|
|
16
19
|
|
|
20
|
+
FLAGS = {
|
|
21
|
+
"--exception" => :vomit,
|
|
22
|
+
"--help" => :help,
|
|
23
|
+
"--version" => :version,
|
|
24
|
+
"-h" => :help,
|
|
25
|
+
"-v" => :version,
|
|
26
|
+
"-x" => :vomit
|
|
27
|
+
}.freeze
|
|
28
|
+
private_constant :FLAGS
|
|
29
|
+
|
|
17
30
|
# Parsed command-line options for the `igves` executable.
|
|
18
31
|
# @api private
|
|
19
|
-
Options = Struct.new(:vomit, :help, :version) do
|
|
32
|
+
Options = Struct.new(:vomit, :help, :version, :omit) do
|
|
20
33
|
# Parses command-line options and removes them from the argv array.
|
|
21
34
|
# @param argv [Array<String>] mutable command-line argument array
|
|
22
35
|
# @return [Sevgi::Binaries::Igves::Options] parsed options
|
|
23
36
|
# @raise [Sevgi::Binaries::Igves::Error] when an option is not recognized
|
|
24
37
|
def self.parse(argv)
|
|
25
38
|
new.tap do |options|
|
|
26
|
-
argv.
|
|
39
|
+
until argv.empty? || argv.first == "-" || !argv.first.start_with?("-")
|
|
40
|
+
if argv.first == "--"
|
|
41
|
+
argv.shift
|
|
42
|
+
break
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
option(argv, options)
|
|
46
|
+
end
|
|
27
47
|
end
|
|
28
48
|
end
|
|
29
49
|
|
|
@@ -31,16 +51,11 @@ module Sevgi
|
|
|
31
51
|
private
|
|
32
52
|
|
|
33
53
|
def option(argv, options)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
when "-v", "--version"
|
|
40
|
-
options.version = true
|
|
41
|
-
else
|
|
42
|
-
Error.("Not a valid option: #{arg}")
|
|
43
|
-
end
|
|
54
|
+
arg = argv.shift
|
|
55
|
+
return options[FLAGS[arg]] = true if FLAGS.key?(arg)
|
|
56
|
+
return (options.omit ||= []) << (argv.shift || Error.("No attribute given for --omit")) if arg == "--omit"
|
|
57
|
+
|
|
58
|
+
Error.("Not a valid option: #{arg}")
|
|
44
59
|
end
|
|
45
60
|
end
|
|
46
61
|
end
|
|
@@ -53,11 +68,11 @@ module Sevgi
|
|
|
53
68
|
# @raise [Sevgi::ArgumentError] when the SVG file cannot be found
|
|
54
69
|
# @raise [Sevgi::PanicError] when generated Ruby source cannot be formatted
|
|
55
70
|
# @raise [StandardError] when `--exception` or `SEVGI_VOMIT` requests raw errors
|
|
56
|
-
# @raise [SystemExit] when command-line usage aborts
|
|
71
|
+
# @raise [SystemExit] when argv does not match `[options...] [--] [file|-]` or command-line usage aborts
|
|
57
72
|
def call(argv)
|
|
58
73
|
dispatch(Array(argv))
|
|
59
74
|
rescue Binaries::Igves::Error => e
|
|
60
|
-
abort(e.message)
|
|
75
|
+
abort("#{e.message}\n\n#{help}")
|
|
61
76
|
end
|
|
62
77
|
|
|
63
78
|
private
|
|
@@ -67,7 +82,7 @@ module Sevgi
|
|
|
67
82
|
return puts(help) if options.help
|
|
68
83
|
return puts(::Sevgi::VERSION) if options.version
|
|
69
84
|
|
|
70
|
-
print_file(argv
|
|
85
|
+
print_file(operand(argv), options)
|
|
71
86
|
rescue Binaries::Igves::Error
|
|
72
87
|
raise
|
|
73
88
|
rescue ::StandardError => e
|
|
@@ -92,22 +107,32 @@ module Sevgi
|
|
|
92
107
|
|
|
93
108
|
def help
|
|
94
109
|
<<~HELP
|
|
95
|
-
Usage: #{PROGNAME} [options...]
|
|
110
|
+
Usage: #{PROGNAME} [options...] [--] [SVG file|-]
|
|
96
111
|
|
|
97
112
|
See documentation for detailed help.
|
|
98
113
|
|
|
99
114
|
Options:
|
|
100
115
|
|
|
116
|
+
--omit ATTRIBUTE Omit an attribute (repeatable)
|
|
101
117
|
-x, --exception Raise exception instead of abort
|
|
118
|
+
-- Stop option parsing
|
|
119
|
+
|
|
102
120
|
-h, --help Show this help
|
|
103
121
|
-v, --version Display version
|
|
104
122
|
HELP
|
|
105
123
|
end
|
|
106
124
|
|
|
107
|
-
def
|
|
108
|
-
|
|
125
|
+
def operand(argv)
|
|
126
|
+
file = argv.shift
|
|
127
|
+
Error.("Unexpected argument: #{argv.first}") unless argv.empty?
|
|
109
128
|
|
|
110
|
-
|
|
129
|
+
file unless file == "-"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def run(file, options)
|
|
133
|
+
return Derender.derender_file(file, omit: options.omit) if file
|
|
134
|
+
|
|
135
|
+
Derender.derender($stdin.read, omit: options.omit)
|
|
111
136
|
end
|
|
112
137
|
|
|
113
138
|
def print_file(file, options)
|
|
@@ -125,4 +150,6 @@ module Sevgi
|
|
|
125
150
|
end
|
|
126
151
|
end
|
|
127
152
|
end
|
|
153
|
+
|
|
154
|
+
private_constant :Binaries
|
|
128
155
|
end
|
|
@@ -38,8 +38,12 @@ module Sevgi
|
|
|
38
38
|
key = Css.to_key(key) if key.is_a?(::String)
|
|
39
39
|
|
|
40
40
|
if key == "style"
|
|
41
|
-
style = Css.
|
|
42
|
-
|
|
41
|
+
style = Css.declarations(value)
|
|
42
|
+
if style
|
|
43
|
+
style.empty? ? "{}" : "{ #{Attributes.decompile(style)} }"
|
|
44
|
+
else
|
|
45
|
+
Ruby.literal(value)
|
|
46
|
+
end
|
|
43
47
|
elsif value.is_a?(::String)
|
|
44
48
|
Css.to_value(value)
|
|
45
49
|
elsif value.is_a?(::Hash)
|
|
@@ -5,6 +5,7 @@ require "nokogiri"
|
|
|
5
5
|
module Sevgi
|
|
6
6
|
module Derender
|
|
7
7
|
# Parsed SVG/XML document wrapper used by the derender pipeline.
|
|
8
|
+
# @api private
|
|
8
9
|
class Document
|
|
9
10
|
# Loads and parses an SVG/XML file.
|
|
10
11
|
#
|
|
@@ -13,7 +14,7 @@ module Sevgi
|
|
|
13
14
|
# @param path [String] path to the source file, with or without `.svg` extension
|
|
14
15
|
# @return [Sevgi::Derender::Document] document wrapper
|
|
15
16
|
# @raise [Sevgi::ArgumentError] when the file cannot be found or file content is malformed XML
|
|
16
|
-
# @raise [
|
|
17
|
+
# @raise [SystemCallError] when the file cannot be read
|
|
17
18
|
def self.load_file(path)
|
|
18
19
|
entry = ::File.expand_path(F.qualify(path, "svg"))
|
|
19
20
|
|
|
@@ -65,10 +66,11 @@ module Sevgi
|
|
|
65
66
|
end
|
|
66
67
|
|
|
67
68
|
# Converts the root or selected node into a derender node.
|
|
68
|
-
# @param id [String, nil] optional SVG id selecting a node inside the document
|
|
69
|
+
# @param id [String, Symbol, nil] optional SVG id selecting a node inside the document
|
|
70
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] attribute name or names omitted from the selected subtree
|
|
69
71
|
# @return [Sevgi::Derender::Node] selected node in the derender tree
|
|
70
72
|
# @raise [Sevgi::ArgumentError] when the document has no root element or the id is absent
|
|
71
|
-
def decompile(id = nil)
|
|
73
|
+
def decompile(id = nil, omit: nil)
|
|
72
74
|
if id
|
|
73
75
|
if (found = doc.xpath("//*[@id=#{xpath_literal(id)}]") || []).empty?
|
|
74
76
|
ArgumentError.("No such element with id '#{id}' in document")
|
|
@@ -81,7 +83,7 @@ module Sevgi
|
|
|
81
83
|
|
|
82
84
|
ArgumentError.("XML document has no root element") unless element
|
|
83
85
|
|
|
84
|
-
Node.new
|
|
86
|
+
Node.send(:new, element, pres, namespaces: namespace_scope(element), omit: omissions(omit))
|
|
85
87
|
end
|
|
86
88
|
|
|
87
89
|
# Returns XML declaration and pre-root nodes preserved for root decompilation. The result contains only String
|
|
@@ -109,6 +111,10 @@ module Sevgi
|
|
|
109
111
|
end
|
|
110
112
|
end
|
|
111
113
|
|
|
114
|
+
def omissions(value)
|
|
115
|
+
Array(value).to_h { [it.to_s.dup.freeze, true] }.freeze
|
|
116
|
+
end
|
|
117
|
+
|
|
112
118
|
def xpath_literal(value)
|
|
113
119
|
value = value.to_s
|
|
114
120
|
|
|
@@ -10,7 +10,7 @@ module Sevgi
|
|
|
10
10
|
# @return [Array<String>] unformatted Ruby source lines
|
|
11
11
|
def decompile(*)
|
|
12
12
|
if children.any?
|
|
13
|
-
children.one? && children.first.
|
|
13
|
+
children.one? && children.first.send(:text?) ? Array(leaf(has_attributes: attributes.any?)) : tree
|
|
14
14
|
else
|
|
15
15
|
Array(leaf(has_content: false))
|
|
16
16
|
end
|
|
@@ -19,7 +19,7 @@ module Sevgi
|
|
|
19
19
|
private
|
|
20
20
|
|
|
21
21
|
def leaf(has_content: true, has_attributes: true)
|
|
22
|
-
attributes =
|
|
22
|
+
attributes = all_attributes
|
|
23
23
|
args = []
|
|
24
24
|
args << Ruby.literal(content) if has_content
|
|
25
25
|
args << Attributes.decompile(attributes) if has_attributes && attributes.any?
|
|
@@ -40,7 +40,7 @@ module Sevgi
|
|
|
40
40
|
def tree
|
|
41
41
|
[
|
|
42
42
|
"#{leaf(has_content: false)} do",
|
|
43
|
-
*children.map(
|
|
43
|
+
*children.map { it.send(:decompile) }.flatten,
|
|
44
44
|
"end"
|
|
45
45
|
]
|
|
46
46
|
end
|
|
@@ -9,20 +9,27 @@ module Sevgi
|
|
|
9
9
|
# Converts a style node into unformatted Sevgi DSL lines.
|
|
10
10
|
# @return [Array<String>] unformatted Ruby source lines
|
|
11
11
|
def decompile(*)
|
|
12
|
-
return
|
|
12
|
+
return raw_style unless (lines = css_lines)
|
|
13
13
|
|
|
14
14
|
[
|
|
15
15
|
"css({",
|
|
16
16
|
*lines,
|
|
17
|
-
"})",
|
|
17
|
+
"}, #{css_attributes})",
|
|
18
18
|
""
|
|
19
19
|
]
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
private
|
|
23
23
|
|
|
24
|
+
def css_attributes
|
|
25
|
+
attributes = all_attributes
|
|
26
|
+
source = Attributes.decompile(attributes)
|
|
27
|
+
|
|
28
|
+
attributes.key?("type") ? source : [source, "type: nil"].reject(&:empty?).join(", ")
|
|
29
|
+
end
|
|
30
|
+
|
|
24
31
|
def css_lines
|
|
25
|
-
return unless (hash = Css.
|
|
32
|
+
return unless (hash = Css.rules(node.content))
|
|
26
33
|
|
|
27
34
|
hash
|
|
28
35
|
.map do |selector, declarations|
|
|
@@ -34,6 +41,13 @@ module Sevgi
|
|
|
34
41
|
end
|
|
35
42
|
.flatten
|
|
36
43
|
end
|
|
44
|
+
|
|
45
|
+
def raw_style
|
|
46
|
+
arguments = ["Sevgi::Graphics::Content.cdata(#{Ruby.literal(node.content)})"]
|
|
47
|
+
arguments << Attributes.decompile(all_attributes) if all_attributes.any?
|
|
48
|
+
|
|
49
|
+
["style #{arguments.join(", ")}", ""]
|
|
50
|
+
end
|
|
37
51
|
end
|
|
38
52
|
end
|
|
39
53
|
end
|
|
@@ -17,7 +17,7 @@ module Sevgi
|
|
|
17
17
|
|
|
18
18
|
lines.unshift(
|
|
19
19
|
[
|
|
20
|
-
"SVG.
|
|
20
|
+
"SVG.Document preambles: [",
|
|
21
21
|
*pres.map { "#{Ruby.literal(it)}," },
|
|
22
22
|
"]",
|
|
23
23
|
""
|
|
@@ -29,9 +29,6 @@ module Sevgi
|
|
|
29
29
|
# @return [String]
|
|
30
30
|
def element = "SVG"
|
|
31
31
|
|
|
32
|
-
# Returns root attributes merged with namespace declarations.
|
|
33
|
-
# @return [Hash{String => String}] attributes and namespaces
|
|
34
|
-
def attributes! = {**attributes, **namespaces}
|
|
35
32
|
end
|
|
36
33
|
end
|
|
37
34
|
end
|
|
@@ -14,7 +14,7 @@ module Sevgi
|
|
|
14
14
|
# @param node [Sevgi::Derender::Node] derender node
|
|
15
15
|
# @return [Sevgi::Graphics::Element, nil] included element, or nil when the node does not produce graphics output
|
|
16
16
|
def append(node)
|
|
17
|
-
case node.type
|
|
17
|
+
case node.send(:type)
|
|
18
18
|
when :CSS
|
|
19
19
|
append_css(node)
|
|
20
20
|
when :Text
|
|
@@ -29,21 +29,33 @@ module Sevgi
|
|
|
29
29
|
attr_reader :parent
|
|
30
30
|
|
|
31
31
|
def append_css(node)
|
|
32
|
-
|
|
32
|
+
content = if (hash = Css.rules(node.content))
|
|
33
|
+
Graphics::Content.css(hash)
|
|
34
|
+
else
|
|
35
|
+
Graphics::Content.cdata(node.content)
|
|
36
|
+
end
|
|
33
37
|
|
|
34
|
-
build(:style,
|
|
38
|
+
build(:style, content, **node.send(:all_attributes))
|
|
35
39
|
end
|
|
36
40
|
|
|
37
41
|
def append_element(node)
|
|
38
42
|
contents = contents(node)
|
|
39
43
|
|
|
40
|
-
build(node.name, *contents, **node
|
|
44
|
+
build(node.name, *contents, **attributes(node)).tap do |element|
|
|
41
45
|
node.children.each { self.class.new(element).append(it) } if contents.empty?
|
|
42
46
|
end
|
|
43
47
|
end
|
|
44
48
|
|
|
49
|
+
def attributes(node)
|
|
50
|
+
attributes = node.send(:all_attributes)
|
|
51
|
+
return attributes unless (style = attributes["style"])
|
|
52
|
+
return attributes unless (declarations = Css.declarations(style))
|
|
53
|
+
|
|
54
|
+
{**attributes, "style" => declarations}
|
|
55
|
+
end
|
|
56
|
+
|
|
45
57
|
def contents(node)
|
|
46
|
-
node.children.one? && node.children.first.
|
|
58
|
+
node.children.one? && node.children.first.send(:text?) ? [node.content] : []
|
|
47
59
|
end
|
|
48
60
|
|
|
49
61
|
def build(name, *contents, **attributes)
|
|
@@ -27,6 +27,30 @@ module Sevgi
|
|
|
27
27
|
hash ? hash.fetch("*", {}) : {}
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
# Returns CSS rules only when Hash conversion preserves the source declarations.
|
|
31
|
+
# @param css_string [String] CSS rule source
|
|
32
|
+
# @return [Hash, nil] losslessly representable rules, or nil
|
|
33
|
+
def rules(css_string)
|
|
34
|
+
parsed = to_h(css_string)
|
|
35
|
+
sourced = source_rules(css_string)
|
|
36
|
+
|
|
37
|
+
parsed if parsed == sourced
|
|
38
|
+
rescue ::StandardError
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Returns inline declarations only when Hash conversion preserves the source declarations.
|
|
43
|
+
# @param style_string [String] inline CSS declaration source
|
|
44
|
+
# @return [Hash, nil] losslessly representable declarations, or nil
|
|
45
|
+
def declarations(style_string)
|
|
46
|
+
parsed = to_h!(style_string)
|
|
47
|
+
sourced = source_declarations(style_string)
|
|
48
|
+
|
|
49
|
+
parsed if parsed == sourced
|
|
50
|
+
rescue ::StandardError
|
|
51
|
+
nil
|
|
52
|
+
end
|
|
53
|
+
|
|
30
54
|
# Converts a CSS key into a Ruby hash key.
|
|
31
55
|
# @param arg [String] CSS key
|
|
32
56
|
# @return [String] Ruby hash key source
|
|
@@ -43,6 +67,47 @@ module Sevgi
|
|
|
43
67
|
# @return [String] Ruby literal or numeric source
|
|
44
68
|
def to_value(arg) = (arg.to_f.to_s == arg) || (arg.to_i.to_s == arg) ? arg : arg.inspect
|
|
45
69
|
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def source_declarations(source)
|
|
73
|
+
declarations = source.split(";", -1).map(&:strip).reject(&:empty?)
|
|
74
|
+
pairs = declarations.map { source_declaration(it) }
|
|
75
|
+
return if pairs.any?(&:nil?)
|
|
76
|
+
return unless pairs.map(&:first).uniq.size == pairs.size
|
|
77
|
+
|
|
78
|
+
pairs.to_h
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def source_declaration(source)
|
|
82
|
+
key, value = source.split(":", 2).map(&:strip)
|
|
83
|
+
|
|
84
|
+
[key, value] if key && value && !key.empty? && !value.empty?
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def source_rules(source)
|
|
88
|
+
source = source.dup
|
|
89
|
+
rules = {}
|
|
90
|
+
|
|
91
|
+
until source.strip.empty?
|
|
92
|
+
selector, declarations, source = source_rule(source)
|
|
93
|
+
return unless selector && !rules.key?(selector)
|
|
94
|
+
|
|
95
|
+
rules[selector] = declarations
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
rules
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def source_rule(source)
|
|
102
|
+
match = /\A\s*([^{}]+?)\s*\{([^{}]*)\}\s*/m.match(source)
|
|
103
|
+
return [nil, nil, source] unless match
|
|
104
|
+
|
|
105
|
+
selector = match[1].strip
|
|
106
|
+
declarations = source_declarations(match[2])
|
|
107
|
+
selector = nil if selector.empty? || selector.start_with?("@") || declarations.nil?
|
|
108
|
+
[selector, declarations, match.post_match]
|
|
109
|
+
end
|
|
110
|
+
|
|
46
111
|
extend self
|
|
47
112
|
end
|
|
48
113
|
|
|
@@ -133,11 +198,17 @@ module Sevgi
|
|
|
133
198
|
private
|
|
134
199
|
|
|
135
200
|
def receiver_collision?(name)
|
|
136
|
-
|
|
201
|
+
documents = [Graphics::Document::Proto]
|
|
202
|
+
documents.concat(Graphics::Document.keys.map { Graphics::Document.fetch(it) })
|
|
137
203
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
204
|
+
documents.uniq.any? { effective_collision?(it, name) }
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def effective_collision?(document, name)
|
|
208
|
+
return false unless document.method_defined?(name) || document.private_method_defined?(name)
|
|
209
|
+
|
|
210
|
+
method = document.instance_method(name)
|
|
211
|
+
method.owner != Graphics::Element || !Graphics::Element.send(:element_method?, name)
|
|
141
212
|
end
|
|
142
213
|
|
|
143
214
|
extend self
|
data/lib/sevgi/derender/node.rb
CHANGED
|
@@ -19,121 +19,153 @@ module Sevgi
|
|
|
19
19
|
|
|
20
20
|
private_constant :Namespace
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
META_NAMESPACE = "_:"
|
|
23
|
+
private_constant :META_NAMESPACE
|
|
24
|
+
|
|
25
|
+
# Captures owned Node state during construction.
|
|
26
|
+
# @api private
|
|
27
|
+
module Capture
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def capture_context(pres, namespaces)
|
|
31
|
+
@pres = pres.map { it.to_s.dup.freeze }.freeze
|
|
32
|
+
@namespaces = snapshot(namespaces || local_namespaces)
|
|
33
|
+
@type = dispatch
|
|
34
|
+
singleton_class.send(:private, :decompile)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def capture_children
|
|
38
|
+
@children = node
|
|
39
|
+
.children
|
|
40
|
+
.map { self.class.send(:new, it, omit: @omit, top: false) }
|
|
41
|
+
.reject { ignorable_child?(it) }
|
|
42
|
+
.freeze
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def capture_values
|
|
46
|
+
@attributes = snapshot(
|
|
47
|
+
node.attribute_nodes.filter_map do |attribute|
|
|
48
|
+
key = attribute_key(attribute)
|
|
49
|
+
[key, attribute.value] unless @omit.key?(key)
|
|
50
|
+
end
|
|
51
|
+
)
|
|
52
|
+
@meta = snapshot(attributes.filter_map { |key, value| metadata(key, value) })
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def capture_identity
|
|
56
|
+
@content = (preserve_space? ? node.content : normalized_content).dup.freeze
|
|
57
|
+
@name = [node.namespace&.prefix, node.name].compact.join(":").freeze
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def metadata(key, value)
|
|
61
|
+
[key.delete_prefix(META_NAMESPACE), value] if key.start_with?(META_NAMESPACE)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def snapshot(pairs)
|
|
65
|
+
pairs
|
|
66
|
+
.each_with_object({}) do |(key, value), result|
|
|
67
|
+
result[key.to_s.dup.freeze] = value.to_s.dup.freeze
|
|
68
|
+
end
|
|
69
|
+
.freeze
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private_constant :Capture
|
|
74
|
+
|
|
75
|
+
# Immutable conversion result for one SVG/XML node.
|
|
76
|
+
#
|
|
77
|
+
# Attributes, namespaces, content, and descendants are owned snapshots; parser objects and dispatch strategies
|
|
78
|
+
# remain internal to Derender. Attributes omitted during decompilation are absent throughout the captured subtree.
|
|
23
79
|
class Node
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
80
|
+
include Capture
|
|
81
|
+
|
|
82
|
+
private_class_method :new
|
|
27
83
|
|
|
28
|
-
# Returns
|
|
29
|
-
# @return [
|
|
30
|
-
attr_reader :
|
|
84
|
+
# Returns immutable XML attributes.
|
|
85
|
+
# @return [Hash{String => String}] owned attribute snapshot
|
|
86
|
+
attr_reader :attributes
|
|
31
87
|
|
|
32
|
-
# Returns
|
|
33
|
-
# @return [
|
|
34
|
-
attr_reader :
|
|
88
|
+
# Returns immutable child nodes.
|
|
89
|
+
# @return [Array<Sevgi::Derender::Node>] owned child snapshot
|
|
90
|
+
attr_reader :children
|
|
35
91
|
|
|
36
|
-
#
|
|
92
|
+
# Returns immutable normalized text content. `xml:space="preserve"` and single-line mixed text retain their exact
|
|
93
|
+
# text. Other text trims surrounding whitespace; multiline mixed text removes only its outer indentation lines.
|
|
94
|
+
# @return [String] frozen owned text snapshot
|
|
95
|
+
attr_reader :content
|
|
96
|
+
|
|
97
|
+
# Returns the immutable qualified element name.
|
|
98
|
+
# @return [String] owned element name
|
|
99
|
+
attr_reader :name
|
|
100
|
+
|
|
101
|
+
# Returns immutable namespace declarations emitted for this node. A conversion root owns its local declarations;
|
|
102
|
+
# a separately selected node owns all declarations in scope; descendant snapshots own their local declarations.
|
|
103
|
+
# @return [Hash{String => String}] frozen owned namespace snapshot
|
|
104
|
+
attr_reader :namespaces
|
|
105
|
+
|
|
106
|
+
# Builds an owned derender result.
|
|
37
107
|
# @param node [Nokogiri::XML::Node] source XML node
|
|
38
108
|
# @param pres [Array<String>] preamble XML lines carried by the root node
|
|
39
|
-
# @param namespaces [Hash{String => String}, nil] namespace declarations to emit on this node
|
|
40
|
-
#
|
|
109
|
+
# @param namespaces [Hash{String => String}, nil] namespace declarations to emit on this node
|
|
110
|
+
# @param omit [Hash{String => Boolean}, nil] normalized attribute omission set shared by the captured subtree
|
|
41
111
|
# @param top [Boolean] true when this node is the root of the current conversion
|
|
42
112
|
# @return [void]
|
|
43
|
-
|
|
113
|
+
# @api private
|
|
114
|
+
def initialize(node, pres = [], namespaces: nil, omit: nil, top: true)
|
|
44
115
|
@node = node
|
|
45
|
-
@
|
|
46
|
-
@namespaces = namespaces
|
|
116
|
+
@omit = omit || {}.freeze
|
|
47
117
|
@top = top
|
|
48
|
-
|
|
118
|
+
capture_context(pres, namespaces)
|
|
119
|
+
capture_values
|
|
120
|
+
capture_children
|
|
121
|
+
capture_identity
|
|
122
|
+
freeze
|
|
49
123
|
end
|
|
50
124
|
|
|
51
|
-
# Attribute namespace prefix used for Sevgi metadata.
|
|
52
|
-
META_NAMESPACE = "_:"
|
|
53
|
-
|
|
54
125
|
# Returns Sevgi metadata attributes without the metadata namespace prefix.
|
|
55
|
-
# @return [Hash{String => String}] metadata
|
|
56
|
-
def _
|
|
57
|
-
@_ ||= attributes
|
|
58
|
-
.slice(
|
|
59
|
-
*attributes.keys.select { |key| key.start_with?(META_NAMESPACE) }
|
|
60
|
-
)
|
|
61
|
-
.transform_keys! { |key| key.delete_prefix(META_NAMESPACE) }
|
|
62
|
-
end
|
|
126
|
+
# @return [Hash{String => String}] immutable metadata snapshot
|
|
127
|
+
def _ = @meta
|
|
63
128
|
|
|
64
129
|
alias meta _
|
|
65
130
|
|
|
66
|
-
# Returns source XML attributes keyed with namespace prefixes when present.
|
|
67
|
-
# @return [Hash{String => String}] XML attributes
|
|
68
|
-
def attributes = @attributes ||= node.attribute_nodes.to_h { [attribute_key(it), it.value] }
|
|
69
|
-
|
|
70
|
-
# Returns source XML attributes and namespace declarations emitted on this node.
|
|
71
|
-
# @return [Hash{String => String}] XML attributes
|
|
72
|
-
def attributes! = {**attributes, **namespaces}
|
|
73
|
-
|
|
74
|
-
# Returns non-ignorable child derender nodes.
|
|
75
|
-
# @return [Array<Sevgi::Derender::Node>] child nodes
|
|
76
|
-
def children = @children ||= node.children.map { self.class.new(it, top: false) }.reject { ignorable_child?(it) }
|
|
77
|
-
|
|
78
|
-
# Returns node text content.
|
|
79
|
-
#
|
|
80
|
-
# `xml:space="preserve"` keeps content verbatim. Default-space text nodes are stripped for ordinary pretty-printed
|
|
81
|
-
# content; line-break indentation around mixed-content children is removed while inline boundary spaces and tabs are
|
|
82
|
-
# kept because they affect rendered SVG text.
|
|
83
|
-
# @return [String]
|
|
84
|
-
def content = @content ||= preserve_space? ? node.content : normalized_content
|
|
85
|
-
|
|
86
131
|
# Converts this node into formatted Sevgi DSL Ruby source.
|
|
87
132
|
# @return [String] formatted Sevgi DSL source
|
|
88
133
|
# @raise [Sevgi::PanicError] when generated Ruby source cannot be formatted
|
|
89
134
|
# @note Foreign namespace elements use the explicit `Element` DSL path, and nested `svg` nodes remain elements.
|
|
90
135
|
# @note Unsafe bare Ruby names are emitted through the explicit `Element` DSL word.
|
|
91
|
-
def derender = Ruby.format(decompile(pres).join("\n"))
|
|
92
|
-
|
|
93
|
-
# Returns the Sevgi DSL element name for this node.
|
|
94
|
-
# @return [String]
|
|
95
|
-
def element = name
|
|
136
|
+
def derender = Ruby.format(decompile(@pres).join("\n"))
|
|
96
137
|
|
|
97
138
|
# Evaluates this node under a graphics element.
|
|
98
139
|
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
99
|
-
# @
|
|
100
|
-
# @return [Sevgi::Graphics::Element, Array<Sevgi::Graphics::Element>, nil] included current element, included child
|
|
101
|
-
# elements when include_current is false, or nil when the node does not produce graphics output
|
|
140
|
+
# @return [Sevgi::Graphics::Element, nil] included current element, or nil when it produces no graphics output
|
|
102
141
|
# @note Namespace declarations, qualified attributes, significant text, and nested `svg` nodes are preserved.
|
|
103
|
-
def evaluate(element
|
|
104
|
-
return Evaluator.new(element).append(self) if include_current
|
|
105
|
-
|
|
106
|
-
evaluate_children(element)
|
|
107
|
-
end
|
|
142
|
+
def evaluate(element) = Evaluator.new(element).append(self)
|
|
108
143
|
|
|
109
144
|
# Evaluates only this node's children under a graphics element.
|
|
110
145
|
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
111
|
-
# @return [Array<Sevgi::Graphics::Element>] included
|
|
112
|
-
def evaluate_children(element) = children.
|
|
146
|
+
# @return [Array<Sevgi::Graphics::Element>] immutable included-child snapshot
|
|
147
|
+
def evaluate_children(element) = children.filter_map { it.evaluate(element) }.freeze
|
|
113
148
|
|
|
114
|
-
# Finds the first descendant whose attribute matches a value.
|
|
149
|
+
# Finds this node or the first descendant whose attribute matches a value.
|
|
115
150
|
# @param arg [String] attribute value to find
|
|
116
151
|
# @param by [String] attribute name used for lookup
|
|
117
|
-
# @return [Sevgi::Derender::Node, nil] matching node, or nil
|
|
152
|
+
# @return [Sevgi::Derender::Node, nil] matching immutable node, or nil
|
|
153
|
+
# @note Attributes omitted during decompilation are unavailable for later searches.
|
|
118
154
|
def find(arg, by: "id")
|
|
119
155
|
return self if attributes[by] == arg
|
|
120
156
|
|
|
121
157
|
children.lazy.map { it.find(arg, by:) }.find(&:itself)
|
|
122
158
|
end
|
|
123
159
|
|
|
124
|
-
#
|
|
125
|
-
# @return [
|
|
126
|
-
def
|
|
160
|
+
# Reports whether this node represents an SVG document root.
|
|
161
|
+
# @return [Boolean] true for the conversion root strategy
|
|
162
|
+
def root? = @type == :Root
|
|
127
163
|
|
|
128
|
-
|
|
129
|
-
# @return [Hash{String => String}] namespace declarations
|
|
130
|
-
def namespaces = (@namespaces ||= local_namespaces)
|
|
164
|
+
private
|
|
131
165
|
|
|
132
|
-
|
|
133
|
-
# @return [Boolean]
|
|
134
|
-
def root? = type == :Root
|
|
166
|
+
attr_reader :node, :type
|
|
135
167
|
|
|
136
|
-
|
|
168
|
+
def all_attributes = {**attributes, **namespaces}.freeze
|
|
137
169
|
|
|
138
170
|
def attribute_key(attribute) = [attribute.namespace&.prefix, attribute.name].compact.join(":")
|
|
139
171
|
|
|
@@ -153,13 +185,16 @@ module Sevgi
|
|
|
153
185
|
.tap { extend(Elements.const_get(it)) }
|
|
154
186
|
end
|
|
155
187
|
|
|
188
|
+
def element = name
|
|
189
|
+
|
|
156
190
|
def ignorable_child?(child)
|
|
157
|
-
child.type == :Junk ||
|
|
158
|
-
(child.
|
|
191
|
+
child.send(:type) == :Junk ||
|
|
192
|
+
(child.send(:text?) &&
|
|
193
|
+
child.content.strip.empty? &&
|
|
194
|
+
!child.send(:preserve_space?) &&
|
|
195
|
+
!child.send(:inline_text?))
|
|
159
196
|
end
|
|
160
197
|
|
|
161
|
-
protected
|
|
162
|
-
|
|
163
198
|
def preserve_space?
|
|
164
199
|
each_node do |current|
|
|
165
200
|
case xml_space(current)
|
|
@@ -175,8 +210,6 @@ module Sevgi
|
|
|
175
210
|
|
|
176
211
|
def inline_text? = mixed_text? && !node.content.match?(/[\r\n]/)
|
|
177
212
|
|
|
178
|
-
private
|
|
179
|
-
|
|
180
213
|
def normalized_content
|
|
181
214
|
return node.content if mixed_text? && inline_text?
|
|
182
215
|
return node.content.strip unless mixed_text?
|
|
@@ -203,6 +236,8 @@ module Sevgi
|
|
|
203
236
|
end
|
|
204
237
|
end
|
|
205
238
|
|
|
239
|
+
def text? = node.text?
|
|
240
|
+
|
|
206
241
|
def xml_space(current)
|
|
207
242
|
return unless current.respond_to?(:attribute_nodes)
|
|
208
243
|
|
data/lib/sevgi/derender.rb
CHANGED
|
@@ -11,7 +11,13 @@ require_relative "derender/node"
|
|
|
11
11
|
require_relative "derender/version"
|
|
12
12
|
|
|
13
13
|
module Sevgi
|
|
14
|
-
#
|
|
14
|
+
# Brings editor-authored SVG/XML into programmatic Sevgi workflows.
|
|
15
|
+
#
|
|
16
|
+
# Vector geometry such as a Bezier-heavy logo or hand-adjusted illustration may be better authored in a visual editor
|
|
17
|
+
# than reconstructed as Ruby. Derender preserves that SVG/XML tree as inspectable data, formatted Sevgi DSL source,
|
|
18
|
+
# or graphics elements under an existing document. This lets editor-authored geometry participate in programmatic
|
|
19
|
+
# composition, styling, layout, and output. Use it when SVG/XML is a real input artifact, not as an intermediate
|
|
20
|
+
# authoring format for ordinary Sevgi drawing code.
|
|
15
21
|
#
|
|
16
22
|
# Generated source uses bare DSL calls only for recognized element names that cannot dispatch to an existing Ruby or
|
|
17
23
|
# Sevgi method. Other XML names use the explicit `Element` DSL word so executing generated source preserves the XML
|
|
@@ -24,154 +30,166 @@ module Sevgi
|
|
|
24
30
|
# declarations, qualified attributes, significant text, and nested `svg` elements survive source generation and direct
|
|
25
31
|
# evaluation. CSS specialization applies only to unqualified `style` elements in no namespace or the default SVG
|
|
26
32
|
# namespace; the document-root strategy additionally requires an unqualified `svg` at the root of the conversion.
|
|
33
|
+
# Simple CSS rules use the readable `css({...})` DSL form. At-rules, duplicate declarations, and other CSS that cannot
|
|
34
|
+
# be represented losslessly as a Hash remain owned raw style content.
|
|
35
|
+
#
|
|
36
|
+
# Attribute omission uses exact, case-sensitive names across the selected subtree. ID selection happens first;
|
|
37
|
+
# namespace declarations remain intact, and omitting the `style` attribute does not omit `style` elements.
|
|
38
|
+
#
|
|
39
|
+
# @example Inspect, select, and convert an immutable result
|
|
40
|
+
# result = Sevgi::Derender.decompile('<svg><rect id="mark" width="10"/></svg>')
|
|
41
|
+
# mark = result.find("mark")
|
|
42
|
+
# mark.attributes #=> {"id"=>"mark", "width"=>"10"}
|
|
43
|
+
# mark.derender #=> "rect id: \"mark\", width: 10\n"
|
|
44
|
+
# @example Preserve an at-rule as raw style content
|
|
45
|
+
# Sevgi::Derender.derender("<style>@media print { rect { fill: black; } }</style>")
|
|
46
|
+
# #=> "style Sevgi::Graphics::Content.cdata(\"@media print { rect { fill: black; } }\")\n"
|
|
47
|
+
# @example Select a node while omitting editor-only attributes
|
|
48
|
+
# source = '<svg><g id="mark" style="fill: red"><rect/></g></svg>'
|
|
49
|
+
# Sevgi::Derender.derender(source, id: "mark", omit: %i[id style])
|
|
50
|
+
# #=> "g do\n rect\nend\n"
|
|
51
|
+
# @see https://sevgi.roktas.dev/derender/ Derender guide
|
|
27
52
|
module Derender
|
|
28
|
-
|
|
29
|
-
# Converts SVG/XML content into a derender node.
|
|
30
|
-
# @param content [String] SVG/XML source content
|
|
31
|
-
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
32
|
-
# @return [Sevgi::Derender::Node] selected node in the derender tree
|
|
33
|
-
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or when the id is absent
|
|
34
|
-
# @!method self.decompile_file(file, id: nil)
|
|
35
|
-
# Converts an SVG/XML file into a derender node.
|
|
36
|
-
# @param file [String] path to the source SVG/XML file
|
|
37
|
-
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
38
|
-
# @return [Sevgi::Derender::Node] selected node in the derender tree
|
|
39
|
-
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
40
|
-
# absent
|
|
41
|
-
# @!method self.derender(content, id: nil)
|
|
42
|
-
# Converts SVG/XML content into Sevgi DSL Ruby source.
|
|
43
|
-
# @param content [String] SVG/XML source content
|
|
44
|
-
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
45
|
-
# @return [String] formatted Sevgi DSL source
|
|
46
|
-
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or when the id is absent
|
|
47
|
-
# @raise [Sevgi::PanicError] when generated Ruby source cannot be formatted
|
|
48
|
-
# @note Namespace-aware dispatch preserves ordinary foreign/qualified nodes and nested SVG elements.
|
|
49
|
-
# @note Unsafe bare Ruby names are emitted through the explicit `Element` DSL word.
|
|
50
|
-
# @!method self.derender_file(file, id: nil)
|
|
51
|
-
# Converts an SVG/XML file into Sevgi DSL Ruby source.
|
|
52
|
-
# @param file [String] path to the source SVG/XML file
|
|
53
|
-
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
54
|
-
# @return [String] formatted Sevgi DSL source
|
|
55
|
-
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
56
|
-
# absent
|
|
57
|
-
# @raise [Sevgi::PanicError] when generated Ruby source cannot be formatted
|
|
58
|
-
# @note Namespace-aware dispatch preserves ordinary foreign/qualified nodes and nested SVG elements.
|
|
59
|
-
# @note Unsafe bare Ruby names are emitted through the explicit `Element` DSL word.
|
|
60
|
-
# @!method self.evaluate(content, element, id: nil)
|
|
61
|
-
# Evaluates SVG/XML content under a graphics element, including the selected node.
|
|
62
|
-
# @param content [String] SVG/XML source content
|
|
63
|
-
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
64
|
-
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
65
|
-
# @return [Sevgi::Graphics::Element, nil] included selected/root graphics element, or nil when the selected node
|
|
66
|
-
# produces no graphics output
|
|
67
|
-
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or when the id is absent
|
|
68
|
-
# @note Namespace-aware dispatch preserves ordinary foreign/qualified nodes and nested SVG elements.
|
|
69
|
-
# @!method self.evaluate_children(content, element, id: nil)
|
|
70
|
-
# Evaluates only the selected node's children under a graphics element.
|
|
71
|
-
# @param content [String] SVG/XML source content
|
|
72
|
-
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
73
|
-
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
74
|
-
# @return [Array<Sevgi::Graphics::Element>] included child graphics elements
|
|
75
|
-
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or when the id is absent
|
|
76
|
-
# @note Namespace-aware dispatch preserves ordinary foreign/qualified nodes and nested SVG elements.
|
|
77
|
-
# @!method self.evaluate_file(file, element, id: nil)
|
|
78
|
-
# Evaluates an SVG/XML file under a graphics element, including the selected node.
|
|
79
|
-
# @param file [String] path to the source SVG/XML file
|
|
80
|
-
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
81
|
-
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
82
|
-
# @return [Sevgi::Graphics::Element, nil] included selected/root graphics element, or nil when the selected node
|
|
83
|
-
# produces no graphics output
|
|
84
|
-
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
85
|
-
# absent
|
|
86
|
-
# @note Namespace-aware dispatch preserves ordinary foreign/qualified nodes and nested SVG elements.
|
|
87
|
-
# @!method self.evaluate_file_children(file, element, id: nil)
|
|
88
|
-
# Evaluates only the selected node's children from an SVG/XML file under a graphics element.
|
|
89
|
-
# @param file [String] path to the source SVG/XML file
|
|
90
|
-
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
91
|
-
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
92
|
-
# @return [Array<Sevgi::Graphics::Element>] included child graphics elements
|
|
93
|
-
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
94
|
-
# absent
|
|
95
|
-
# @note Namespace-aware dispatch preserves ordinary foreign/qualified nodes and nested SVG elements.
|
|
53
|
+
private_constant :Attributes, :Document, :Elements
|
|
96
54
|
|
|
97
|
-
# Converts SVG/XML content into
|
|
55
|
+
# Converts SVG/XML content into an immutable derender result.
|
|
98
56
|
# @param content [String] SVG/XML source content
|
|
99
|
-
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
100
|
-
# @
|
|
57
|
+
# @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
|
|
58
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
59
|
+
# subtree after id selection
|
|
60
|
+
# @return [Sevgi::Derender::Node] owned immutable selected node
|
|
101
61
|
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or when the id is absent
|
|
102
|
-
|
|
62
|
+
# @example Inspect a selected immutable node
|
|
63
|
+
# root = Sevgi::Derender.decompile('<svg><g id="mark"><rect width="4"/></g></svg>')
|
|
64
|
+
# mark = root.find("mark")
|
|
65
|
+
# mark.name #=> "g"
|
|
66
|
+
# mark.children.first.name #=> "rect"
|
|
67
|
+
# mark.attributes["id"] #=> "mark"
|
|
68
|
+
# @see Sevgi::Derender.decompile_file
|
|
69
|
+
# @see Sevgi.Decompile
|
|
70
|
+
def self.decompile(content, id: nil, omit: nil) = Document.new(content).decompile(id, omit:)
|
|
103
71
|
|
|
104
|
-
# Converts an SVG/XML file into
|
|
72
|
+
# Converts an SVG/XML file into an immutable derender result.
|
|
105
73
|
# @param file [String] path to the source SVG/XML file
|
|
106
|
-
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
107
|
-
# @
|
|
74
|
+
# @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
|
|
75
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
76
|
+
# subtree after id selection
|
|
77
|
+
# @return [Sevgi::Derender::Node] owned immutable selected node
|
|
108
78
|
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
109
79
|
# absent
|
|
110
|
-
|
|
80
|
+
# @raise [SystemCallError] when the file cannot be read
|
|
81
|
+
# @see Sevgi::Derender.decompile
|
|
82
|
+
# @see Sevgi.DecompileFile
|
|
83
|
+
def self.decompile_file(file, id: nil, omit: nil) = Document.load_file(file).decompile(id, omit:)
|
|
111
84
|
|
|
112
85
|
# Converts SVG/XML content into Sevgi DSL Ruby source.
|
|
113
86
|
# @param content [String] SVG/XML source content
|
|
114
|
-
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
87
|
+
# @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
|
|
88
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
89
|
+
# subtree after id selection
|
|
115
90
|
# @return [String] formatted Sevgi DSL source
|
|
116
91
|
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or when the id is absent
|
|
117
92
|
# @raise [Sevgi::PanicError] when generated Ruby source cannot be formatted
|
|
118
93
|
# @note Namespace-aware dispatch preserves ordinary foreign/qualified nodes and nested SVG elements.
|
|
119
94
|
# @note Unsafe bare Ruby names are emitted through the explicit `Element` DSL word.
|
|
120
|
-
|
|
95
|
+
# @example Select first, then omit attributes from generated source
|
|
96
|
+
# xml = '<svg><g id="mark" style="fill: red"><rect id="part"/></g></svg>'
|
|
97
|
+
# Sevgi::Derender.derender(xml, id: :mark, omit: [:id, "style"])
|
|
98
|
+
# #=> "g do\n rect\nend\n"
|
|
99
|
+
# @see Sevgi::Derender.derender_file
|
|
100
|
+
# @see Sevgi.Derender
|
|
101
|
+
def self.derender(content, id: nil, omit: nil) = Document.new(content).decompile(id, omit:).derender
|
|
121
102
|
|
|
122
103
|
# Converts an SVG/XML file into Sevgi DSL Ruby source.
|
|
123
104
|
# @param file [String] path to the source SVG/XML file
|
|
124
|
-
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
105
|
+
# @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
|
|
106
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
107
|
+
# subtree after id selection
|
|
125
108
|
# @return [String] formatted Sevgi DSL source
|
|
126
109
|
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
127
110
|
# absent
|
|
128
111
|
# @raise [Sevgi::PanicError] when generated Ruby source cannot be formatted
|
|
112
|
+
# @raise [SystemCallError] when the file cannot be read
|
|
129
113
|
# @note Namespace-aware dispatch preserves ordinary foreign/qualified nodes and nested SVG elements.
|
|
130
114
|
# @note Unsafe bare Ruby names are emitted through the explicit `Element` DSL word.
|
|
131
|
-
|
|
115
|
+
# @see Sevgi::Derender.derender
|
|
116
|
+
# @see Sevgi.DerenderFile
|
|
117
|
+
def self.derender_file(file, id: nil, omit: nil) = Document.load_file(file).decompile(id, omit:).derender
|
|
132
118
|
|
|
133
119
|
# Evaluates SVG/XML content under a graphics element, including the selected node.
|
|
134
120
|
# @param content [String] SVG/XML source content
|
|
135
121
|
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
136
|
-
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
122
|
+
# @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
|
|
123
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
124
|
+
# subtree after id selection
|
|
137
125
|
# @return [Sevgi::Graphics::Element, nil] included selected/root graphics element, or nil when the selected node
|
|
138
126
|
# produces no graphics output
|
|
139
127
|
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or when the id is absent
|
|
140
128
|
# @note Namespace-aware dispatch preserves ordinary foreign/qualified nodes and nested SVG elements.
|
|
141
|
-
|
|
129
|
+
# @example Include the selected node under an existing element
|
|
130
|
+
# drawing = Sevgi::Graphics.SVG(:minimal)
|
|
131
|
+
# included = Sevgi::Derender.evaluate('<circle id="mark" r="4"/>', drawing)
|
|
132
|
+
# included.name #=> :circle
|
|
133
|
+
# included[:id] #=> "mark"
|
|
134
|
+
# @see Sevgi::Derender.evaluate_file
|
|
135
|
+
# @see Sevgi.Evaluate
|
|
136
|
+
def self.evaluate(content, element, id: nil, omit: nil)
|
|
137
|
+
Document.new(content).decompile(id, omit:).evaluate(element)
|
|
138
|
+
end
|
|
142
139
|
|
|
143
140
|
# Evaluates only the selected node's children under a graphics element.
|
|
144
141
|
# @param content [String] SVG/XML source content
|
|
145
142
|
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
146
|
-
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
147
|
-
# @
|
|
143
|
+
# @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
|
|
144
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
145
|
+
# subtree after id selection
|
|
146
|
+
# @return [Array<Sevgi::Graphics::Element>] immutable included-child snapshot
|
|
148
147
|
# @raise [Sevgi::ArgumentError] when content is malformed or rootless, or when the id is absent
|
|
149
148
|
# @note Namespace-aware dispatch preserves ordinary foreign/qualified nodes and nested SVG elements.
|
|
150
|
-
|
|
149
|
+
# @example Include only children and retain an immutable result snapshot
|
|
150
|
+
# drawing = Sevgi::Graphics.SVG(:minimal)
|
|
151
|
+
# children = Sevgi::Derender.evaluate_children('<g><rect/><circle/></g>', drawing)
|
|
152
|
+
# children.map(&:name) #=> [:rect, :circle]
|
|
153
|
+
# children.frozen? #=> true
|
|
154
|
+
# @see Sevgi::Derender.evaluate_children_file
|
|
155
|
+
# @see Sevgi.EvaluateChildren
|
|
156
|
+
def self.evaluate_children(content, element, id: nil, omit: nil)
|
|
157
|
+
Document.new(content).decompile(id, omit:).evaluate_children(element)
|
|
158
|
+
end
|
|
151
159
|
|
|
152
160
|
# Evaluates an SVG/XML file under a graphics element, including the selected node.
|
|
153
161
|
# @param file [String] path to the source SVG/XML file
|
|
154
162
|
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
155
|
-
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
163
|
+
# @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
|
|
164
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
165
|
+
# subtree after id selection
|
|
156
166
|
# @return [Sevgi::Graphics::Element, nil] included selected/root graphics element, or nil when the selected node
|
|
157
167
|
# produces no graphics output
|
|
158
168
|
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
159
169
|
# absent
|
|
170
|
+
# @raise [SystemCallError] when the file cannot be read
|
|
160
171
|
# @note Namespace-aware dispatch preserves ordinary foreign/qualified nodes and nested SVG elements.
|
|
161
|
-
|
|
172
|
+
# @see Sevgi::Derender.evaluate
|
|
173
|
+
# @see Sevgi.EvaluateFile
|
|
174
|
+
def self.evaluate_file(file, element, id: nil, omit: nil)
|
|
175
|
+
Document.load_file(file).decompile(id, omit:).evaluate(element)
|
|
176
|
+
end
|
|
162
177
|
|
|
163
178
|
# Evaluates only the selected node's children from an SVG/XML file under a graphics element.
|
|
164
179
|
# @param file [String] path to the source SVG/XML file
|
|
165
180
|
# @param element [Sevgi::Graphics::Element] target graphics element
|
|
166
|
-
# @param id [String, nil] optional SVG id selecting a node inside the source
|
|
167
|
-
# @
|
|
181
|
+
# @param id [String, Symbol, nil] optional SVG id selecting a node inside the source
|
|
182
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
183
|
+
# subtree after id selection
|
|
184
|
+
# @return [Array<Sevgi::Graphics::Element>] immutable included-child snapshot
|
|
168
185
|
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
169
186
|
# absent
|
|
187
|
+
# @raise [SystemCallError] when the file cannot be read
|
|
170
188
|
# @note Namespace-aware dispatch preserves ordinary foreign/qualified nodes and nested SVG elements.
|
|
171
|
-
|
|
172
|
-
|
|
189
|
+
# @see Sevgi::Derender.evaluate_children
|
|
190
|
+
# @see Sevgi.EvaluateChildrenFile
|
|
191
|
+
def self.evaluate_children_file(file, element, id: nil, omit: nil)
|
|
192
|
+
Document.load_file(file).decompile(id, omit:).evaluate_children(element)
|
|
173
193
|
end
|
|
174
|
-
|
|
175
|
-
extend self
|
|
176
194
|
end
|
|
177
195
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sevgi-derender
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.98.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Recai Oktaş
|
|
@@ -15,28 +15,28 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.98.2
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: 0.98.2
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: sevgi-graphics
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - '='
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.
|
|
32
|
+
version: 0.98.2
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - '='
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.
|
|
39
|
+
version: 0.98.2
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: css_parser
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -79,7 +79,7 @@ dependencies:
|
|
|
79
79
|
- - "~>"
|
|
80
80
|
- !ruby/object:Gem::Version
|
|
81
81
|
version: '0.18'
|
|
82
|
-
description:
|
|
82
|
+
description: Converts SVG or XML trees into Sevgi DSL source.
|
|
83
83
|
email: roktas@gmail.com
|
|
84
84
|
executables:
|
|
85
85
|
- igves
|
|
@@ -126,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
126
126
|
- !ruby/object:Gem::Version
|
|
127
127
|
version: '0'
|
|
128
128
|
requirements: []
|
|
129
|
-
rubygems_version: 4.0.
|
|
129
|
+
rubygems_version: 4.0.16
|
|
130
130
|
specification_version: 4
|
|
131
|
-
summary:
|
|
131
|
+
summary: SVG-to-Sevgi source conversion.
|
|
132
132
|
test_files: []
|