sirena 0.1.0
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 +7 -0
- data/.github/workflows/build_deploy.yml +59 -0
- data/.github/workflows/links.yml +85 -0
- data/.github/workflows/rake.yml +15 -0
- data/.github/workflows/release.yml +27 -0
- data/.gitignore +68 -0
- data/.rspec +3 -0
- data/.rubocop.yml +14 -0
- data/.rubocop_todo.yml +70 -0
- data/ARCHITECTURE.md +744 -0
- data/Gemfile +12 -0
- data/LICENSE +25 -0
- data/README.adoc +357 -0
- data/Rakefile +11 -0
- data/docs/.gitignore +1 -0
- data/docs/Gemfile +13 -0
- data/docs/_config.yml +182 -0
- data/docs/_diagram_types/architecture-diagram.adoc +314 -0
- data/docs/_diagram_types/block-diagram.adoc +345 -0
- data/docs/_diagram_types/c4-diagram.adoc +559 -0
- data/docs/_diagram_types/class-diagram.adoc +816 -0
- data/docs/_diagram_types/er-diagram.adoc +719 -0
- data/docs/_diagram_types/error-diagram.adoc +114 -0
- data/docs/_diagram_types/examples/flowchart-examples.adoc +29 -0
- data/docs/_diagram_types/flowchart.adoc +488 -0
- data/docs/_diagram_types/gantt-chart.adoc +502 -0
- data/docs/_diagram_types/git-graph.adoc +600 -0
- data/docs/_diagram_types/index.adoc +192 -0
- data/docs/_diagram_types/info-diagram.adoc +103 -0
- data/docs/_diagram_types/kanban-diagram.adoc +262 -0
- data/docs/_diagram_types/mindmap.adoc +603 -0
- data/docs/_diagram_types/packet-diagram.adoc +378 -0
- data/docs/_diagram_types/pie-chart.adoc +335 -0
- data/docs/_diagram_types/quadrant-chart.adoc +406 -0
- data/docs/_diagram_types/radar-chart.adoc +528 -0
- data/docs/_diagram_types/requirement-diagram.adoc +416 -0
- data/docs/_diagram_types/sankey-diagram.adoc +357 -0
- data/docs/_diagram_types/sequence-diagram.adoc +664 -0
- data/docs/_diagram_types/state-diagram.adoc +658 -0
- data/docs/_diagram_types/timeline.adoc +352 -0
- data/docs/_diagram_types/treemap-diagram.adoc +462 -0
- data/docs/_diagram_types/user-journey.adoc +602 -0
- data/docs/_features/index.adoc +129 -0
- data/docs/_guides/cli-reference.adoc +203 -0
- data/docs/_guides/index.adoc +56 -0
- data/docs/_guides/installation.adoc +100 -0
- data/docs/_guides/quick-start.adoc +132 -0
- data/docs/_pages/comparison.adoc +441 -0
- data/docs/_pages/compatibility.adoc +300 -0
- data/docs/_pages/index.adoc +39 -0
- data/docs/_references/index.adoc +103 -0
- data/docs/_tutorials/index.adoc +57 -0
- data/docs/index.adoc +166 -0
- data/docs/lychee.toml +54 -0
- data/examples/.gitignore +10 -0
- data/examples/README.adoc +196 -0
- data/examples/README.md +64 -0
- data/examples/architecture/01-basic-services.mmd +9 -0
- data/examples/architecture/01-basic-services.svg +37 -0
- data/examples/architecture/02-service-groups.mmd +16 -0
- data/examples/architecture/02-service-groups.svg +55 -0
- data/examples/architecture/README.adoc +79 -0
- data/examples/block/01-basic-blocks.mmd +13 -0
- data/examples/block/01-basic-blocks.svg +44 -0
- data/examples/block/02-block-shapes.mmd +13 -0
- data/examples/block/02-block-shapes.svg +47 -0
- data/examples/block/README.adoc +85 -0
- data/examples/c4/01-context-diagram.mmd +10 -0
- data/examples/c4/01-context-diagram.svg +45 -0
- data/examples/c4/02-container-diagram.mmd +24 -0
- data/examples/c4/02-container-diagram.svg +105 -0
- data/examples/c4/README.adoc +92 -0
- data/examples/class_diagram/01-basic-classes.mmd +61 -0
- data/examples/class_diagram/01-basic-classes.svg +117 -0
- data/examples/class_diagram/02-relationships.mmd +61 -0
- data/examples/class_diagram/02-relationships.svg +129 -0
- data/examples/class_diagram/README.adoc +93 -0
- data/examples/er_diagram/01-basic-entities.mmd +64 -0
- data/examples/er_diagram/01-basic-entities.svg +5 -0
- data/examples/er_diagram/02-cardinality.mmd +57 -0
- data/examples/er_diagram/02-cardinality.svg +125 -0
- data/examples/er_diagram/README.adoc +88 -0
- data/examples/error/01-basic-error.mmd +1 -0
- data/examples/error/01-basic-error.svg +13 -0
- data/examples/error/02-error-display.mmd +1 -0
- data/examples/error/02-error-display.svg +13 -0
- data/examples/error/README.adoc +71 -0
- data/examples/error_message_example.svg +13 -0
- data/examples/flowchart/00-original.mmd +13 -0
- data/examples/flowchart/00-original.svg +5 -0
- data/examples/flowchart/01-basic-flow.mmd +7 -0
- data/examples/flowchart/01-basic-flow.svg +52 -0
- data/examples/flowchart/01-basic-flow.yml +13 -0
- data/examples/flowchart/02*.svg +87 -0
- data/examples/flowchart/02-node-shapes.mmd +9 -0
- data/examples/flowchart/02-node-shapes.svg +33 -0
- data/examples/flowchart/03-edge-types.mmd +7 -0
- data/examples/flowchart/03-edge-types.svg +53 -0
- data/examples/flowchart/04-subgraphs.mmd +9 -0
- data/examples/flowchart/04-subgraphs.svg +33 -0
- data/examples/flowchart/05-styling.mmd +9 -0
- data/examples/flowchart/05-styling.svg +33 -0
- data/examples/flowchart/06-complex-flow.mmd +8 -0
- data/examples/flowchart/06-complex-flow.svg +59 -0
- data/examples/flowchart/README.adoc +167 -0
- data/examples/gantt/01-simple-timeline.* +14 -0
- data/examples/gantt/01-simple-timeline.mmd +6 -0
- data/examples/gantt/01-simple-timeline.svg +26 -0
- data/examples/gantt/02-task-dependencies.mmd +6 -0
- data/examples/gantt/02-task-dependencies.svg +26 -0
- data/examples/gantt/README.adoc +86 -0
- data/examples/git_graph/01-linear-history.mmd +12 -0
- data/examples/git_graph/01-linear-history.svg +26 -0
- data/examples/git_graph/02-branching.mmd +12 -0
- data/examples/git_graph/02-branching.svg +26 -0
- data/examples/git_graph/README.adoc +73 -0
- data/examples/info/02-showinfo.mmd +1 -0
- data/examples/info/02-showinfo.svg +10 -0
- data/examples/info/README.adoc +58 -0
- data/examples/info_showinfo_example.svg +10 -0
- data/examples/kanban/01-simple-board.mmd +8 -0
- data/examples/kanban/01-simple-board.svg +43 -0
- data/examples/kanban/02-workflow.mmd +8 -0
- data/examples/kanban/02-workflow.svg +43 -0
- data/examples/kanban/README.adoc +79 -0
- data/examples/mindmap/01-simple-tree.mmd +19 -0
- data/examples/mindmap/01-simple-tree.svg +61 -0
- data/examples/mindmap/02-knowledge-map.mmd +19 -0
- data/examples/mindmap/02-knowledge-map.svg +61 -0
- data/examples/mindmap/README.adoc +77 -0
- data/examples/packet/01-basic-packet.* +17 -0
- data/examples/packet/01-basic-packet.mmd +4 -0
- data/examples/packet/01-basic-packet.svg +82 -0
- data/examples/packet/README.adoc +58 -0
- data/examples/pie/01-simple-chart.mmd +5 -0
- data/examples/pie/01-simple-chart.svg +17 -0
- data/examples/pie/02-labeled-slices.mmd +6 -0
- data/examples/pie/02-labeled-slices.svg +19 -0
- data/examples/pie/README.adoc +75 -0
- data/examples/quadrant/01-basic-quadrant.mmd +13 -0
- data/examples/quadrant/01-basic-quadrant.svg +33 -0
- data/examples/quadrant/02-positioned-items.mmd +14 -0
- data/examples/quadrant/02-positioned-items.svg +35 -0
- data/examples/quadrant/README.adoc +84 -0
- data/examples/radar/01-simple-radar.* +5 -0
- data/examples/radar/01-simple-radar.mmd +3 -0
- data/examples/radar/01-simple-radar.svg +25 -0
- data/examples/radar/02-multiple-curves.mmd +4 -0
- data/examples/radar/02-multiple-curves.svg +43 -0
- data/examples/radar/README.adoc +75 -0
- data/examples/requirement/01-basic-requirements.mmd +23 -0
- data/examples/requirement/01-basic-requirements.svg +49 -0
- data/examples/requirement/02-risk-levels.mmd +23 -0
- data/examples/requirement/02-risk-levels.svg +49 -0
- data/examples/requirement/README.adoc +85 -0
- data/examples/sankey/01-simple-flow.mmd +7 -0
- data/examples/sankey/01-simple-flow.svg +34 -0
- data/examples/sankey/02-multi-stage.mmd +11 -0
- data/examples/sankey/02-multi-stage.svg +44 -0
- data/examples/sankey/README.adoc +74 -0
- data/examples/sequence/01-basic-sequence.mmd +27 -0
- data/examples/sequence/01-basic-sequence.svg +5 -0
- data/examples/sequence/02-activations.mmd +17 -0
- data/examples/sequence/02-activations.svg +78 -0
- data/examples/sequence/README.adoc +86 -0
- data/examples/state_diagram/01-simple-states.mmd +29 -0
- data/examples/state_diagram/01-simple-states.svg +5 -0
- data/examples/state_diagram/02-composite.mmd +19 -0
- data/examples/state_diagram/02-composite.svg +81 -0
- data/examples/state_diagram/README.adoc +90 -0
- data/examples/timeline/01-simple-timeline.mmd +11 -0
- data/examples/timeline/01-simple-timeline.svg +36 -0
- data/examples/timeline/02-periods.mmd +15 -0
- data/examples/timeline/02-periods.svg +47 -0
- data/examples/timeline/README.adoc +78 -0
- data/examples/treemap/01-basic-treemap.mmd +12 -0
- data/examples/treemap/01-basic-treemap.svg +59 -0
- data/examples/treemap/README.adoc +59 -0
- data/examples/user_journey/01-simple-journey.mmd +23 -0
- data/examples/user_journey/01-simple-journey.svg +5 -0
- data/examples/user_journey/02-multi-actor.mmd +18 -0
- data/examples/user_journey/02-multi-actor.svg +129 -0
- data/examples/user_journey/README.adoc +81 -0
- data/examples/xychart/01-line-chart.mmd +5 -0
- data/examples/xychart/01-line-chart.svg +43 -0
- data/examples/xychart/02-bar-chart.mmd +7 -0
- data/examples/xychart/02-bar-chart.svg +48 -0
- data/examples/xychart/README.adoc +80 -0
- data/exe/sirena +7 -0
- data/lib/sirena/cli.rb +138 -0
- data/lib/sirena/commands/batch.rb +117 -0
- data/lib/sirena/commands/render.rb +80 -0
- data/lib/sirena/commands/types.rb +29 -0
- data/lib/sirena/commands/version.rb +24 -0
- data/lib/sirena/diagram/architecture.rb +46 -0
- data/lib/sirena/diagram/base.rb +61 -0
- data/lib/sirena/diagram/block.rb +81 -0
- data/lib/sirena/diagram/c4.rb +328 -0
- data/lib/sirena/diagram/class_diagram.rb +385 -0
- data/lib/sirena/diagram/er_diagram.rb +238 -0
- data/lib/sirena/diagram/error.rb +38 -0
- data/lib/sirena/diagram/flowchart.rb +160 -0
- data/lib/sirena/diagram/gantt.rb +71 -0
- data/lib/sirena/diagram/git_graph.rb +36 -0
- data/lib/sirena/diagram/info.rb +38 -0
- data/lib/sirena/diagram/kanban.rb +178 -0
- data/lib/sirena/diagram/mindmap.rb +54 -0
- data/lib/sirena/diagram/packet.rb +79 -0
- data/lib/sirena/diagram/pie.rb +115 -0
- data/lib/sirena/diagram/quadrant.rb +138 -0
- data/lib/sirena/diagram/radar.rb +52 -0
- data/lib/sirena/diagram/requirement.rb +133 -0
- data/lib/sirena/diagram/sankey.rb +217 -0
- data/lib/sirena/diagram/sequence.rb +242 -0
- data/lib/sirena/diagram/state_diagram.rb +237 -0
- data/lib/sirena/diagram/timeline.rb +171 -0
- data/lib/sirena/diagram/treemap.rb +84 -0
- data/lib/sirena/diagram/user_journey.rb +149 -0
- data/lib/sirena/diagram/xy_chart.rb +76 -0
- data/lib/sirena/diagram.rb +8 -0
- data/lib/sirena/diagram_registry.rb +101 -0
- data/lib/sirena/engine.rb +292 -0
- data/lib/sirena/parser/architecture.rb +41 -0
- data/lib/sirena/parser/base.rb +41 -0
- data/lib/sirena/parser/block.rb +72 -0
- data/lib/sirena/parser/c4.rb +53 -0
- data/lib/sirena/parser/class_diagram.rb +63 -0
- data/lib/sirena/parser/er_diagram.rb +40 -0
- data/lib/sirena/parser/error.rb +49 -0
- data/lib/sirena/parser/flowchart.rb +71 -0
- data/lib/sirena/parser/gantt.rb +60 -0
- data/lib/sirena/parser/git_graph.rb +95 -0
- data/lib/sirena/parser/grammars/architecture.rb +145 -0
- data/lib/sirena/parser/grammars/block.rb +190 -0
- data/lib/sirena/parser/grammars/c4.rb +226 -0
- data/lib/sirena/parser/grammars/class_diagram.rb +284 -0
- data/lib/sirena/parser/grammars/common.rb +84 -0
- data/lib/sirena/parser/grammars/er_diagram.rb +114 -0
- data/lib/sirena/parser/grammars/error.rb +40 -0
- data/lib/sirena/parser/grammars/flowchart.rb +298 -0
- data/lib/sirena/parser/grammars/gantt.rb +252 -0
- data/lib/sirena/parser/grammars/git_graph.rb +167 -0
- data/lib/sirena/parser/grammars/info.rb +58 -0
- data/lib/sirena/parser/grammars/kanban.rb +83 -0
- data/lib/sirena/parser/grammars/mindmap.rb +115 -0
- data/lib/sirena/parser/grammars/packet.rb +73 -0
- data/lib/sirena/parser/grammars/pie.rb +128 -0
- data/lib/sirena/parser/grammars/quadrant.rb +199 -0
- data/lib/sirena/parser/grammars/radar.rb +150 -0
- data/lib/sirena/parser/grammars/requirement.rb +188 -0
- data/lib/sirena/parser/grammars/sankey.rb +104 -0
- data/lib/sirena/parser/grammars/sequence.rb +247 -0
- data/lib/sirena/parser/grammars/state_diagram.rb +172 -0
- data/lib/sirena/parser/grammars/timeline.rb +142 -0
- data/lib/sirena/parser/grammars/treemap.rb +120 -0
- data/lib/sirena/parser/grammars/xy_chart.rb +120 -0
- data/lib/sirena/parser/info.rb +49 -0
- data/lib/sirena/parser/kanban.rb +97 -0
- data/lib/sirena/parser/mindmap.rb +106 -0
- data/lib/sirena/parser/packet.rb +76 -0
- data/lib/sirena/parser/pie.rb +49 -0
- data/lib/sirena/parser/quadrant.rb +57 -0
- data/lib/sirena/parser/radar.rb +104 -0
- data/lib/sirena/parser/requirement.rb +70 -0
- data/lib/sirena/parser/sankey.rb +64 -0
- data/lib/sirena/parser/sequence.rb +51 -0
- data/lib/sirena/parser/state_diagram.rb +69 -0
- data/lib/sirena/parser/timeline.rb +57 -0
- data/lib/sirena/parser/transforms/architecture.rb +97 -0
- data/lib/sirena/parser/transforms/block.rb +254 -0
- data/lib/sirena/parser/transforms/c4.rb +347 -0
- data/lib/sirena/parser/transforms/class_diagram.rb +352 -0
- data/lib/sirena/parser/transforms/er_diagram.rb +169 -0
- data/lib/sirena/parser/transforms/error.rb +58 -0
- data/lib/sirena/parser/transforms/flowchart.rb +293 -0
- data/lib/sirena/parser/transforms/gantt.rb +215 -0
- data/lib/sirena/parser/transforms/git_graph.rb +160 -0
- data/lib/sirena/parser/transforms/info.rb +58 -0
- data/lib/sirena/parser/transforms/kanban.rb +176 -0
- data/lib/sirena/parser/transforms/mindmap.rb +227 -0
- data/lib/sirena/parser/transforms/packet.rb +63 -0
- data/lib/sirena/parser/transforms/pie.rb +143 -0
- data/lib/sirena/parser/transforms/quadrant.rb +177 -0
- data/lib/sirena/parser/transforms/radar.rb +126 -0
- data/lib/sirena/parser/transforms/requirement.rb +272 -0
- data/lib/sirena/parser/transforms/sankey.rb +122 -0
- data/lib/sirena/parser/transforms/sequence.rb +342 -0
- data/lib/sirena/parser/transforms/state_diagram.rb +292 -0
- data/lib/sirena/parser/transforms/timeline.rb +177 -0
- data/lib/sirena/parser/transforms/treemap.rb +81 -0
- data/lib/sirena/parser/transforms/xy_chart.rb +132 -0
- data/lib/sirena/parser/treemap.rb +98 -0
- data/lib/sirena/parser/user_journey.rb +120 -0
- data/lib/sirena/parser/xy_chart.rb +114 -0
- data/lib/sirena/parser.rb +8 -0
- data/lib/sirena/renderer/architecture.rb +251 -0
- data/lib/sirena/renderer/base.rb +251 -0
- data/lib/sirena/renderer/block.rb +286 -0
- data/lib/sirena/renderer/c4.rb +490 -0
- data/lib/sirena/renderer/class_diagram.rb +499 -0
- data/lib/sirena/renderer/er_diagram.rb +417 -0
- data/lib/sirena/renderer/error.rb +131 -0
- data/lib/sirena/renderer/flowchart.rb +301 -0
- data/lib/sirena/renderer/gantt.rb +331 -0
- data/lib/sirena/renderer/git_graph.rb +368 -0
- data/lib/sirena/renderer/info.rb +93 -0
- data/lib/sirena/renderer/kanban.rb +295 -0
- data/lib/sirena/renderer/mindmap.rb +396 -0
- data/lib/sirena/renderer/packet.rb +239 -0
- data/lib/sirena/renderer/pie.rb +235 -0
- data/lib/sirena/renderer/quadrant.rb +292 -0
- data/lib/sirena/renderer/radar.rb +323 -0
- data/lib/sirena/renderer/requirement.rb +371 -0
- data/lib/sirena/renderer/sankey.rb +255 -0
- data/lib/sirena/renderer/sequence.rb +424 -0
- data/lib/sirena/renderer/state_diagram.rb +328 -0
- data/lib/sirena/renderer/timeline.rb +304 -0
- data/lib/sirena/renderer/treemap.rb +152 -0
- data/lib/sirena/renderer/user_journey.rb +331 -0
- data/lib/sirena/renderer/xy_chart.rb +452 -0
- data/lib/sirena/renderer.rb +8 -0
- data/lib/sirena/svg/circle.rb +41 -0
- data/lib/sirena/svg/document.rb +103 -0
- data/lib/sirena/svg/element.rb +65 -0
- data/lib/sirena/svg/ellipse.rb +33 -0
- data/lib/sirena/svg/group.rb +71 -0
- data/lib/sirena/svg/line.rb +49 -0
- data/lib/sirena/svg/path.rb +76 -0
- data/lib/sirena/svg/polygon.rb +43 -0
- data/lib/sirena/svg/polyline.rb +35 -0
- data/lib/sirena/svg/rect.rb +57 -0
- data/lib/sirena/svg/style.rb +44 -0
- data/lib/sirena/svg/text.rb +72 -0
- data/lib/sirena/svg.rb +19 -0
- data/lib/sirena/text_measurement.rb +71 -0
- data/lib/sirena/theme/builtin/dark.yml +70 -0
- data/lib/sirena/theme/builtin/default.yml +80 -0
- data/lib/sirena/theme/builtin/high_contrast.yml +70 -0
- data/lib/sirena/theme/builtin/light.yml +70 -0
- data/lib/sirena/theme/color_palette.rb +48 -0
- data/lib/sirena/theme/effect_styles.rb +28 -0
- data/lib/sirena/theme/registry.rb +41 -0
- data/lib/sirena/theme/shape_styles.rb +28 -0
- data/lib/sirena/theme/spacing_config.rb +24 -0
- data/lib/sirena/theme/typography.rb +30 -0
- data/lib/sirena/theme.rb +69 -0
- data/lib/sirena/transform/architecture.rb +273 -0
- data/lib/sirena/transform/base.rb +199 -0
- data/lib/sirena/transform/block.rb +215 -0
- data/lib/sirena/transform/c4.rb +288 -0
- data/lib/sirena/transform/class_diagram.rb +296 -0
- data/lib/sirena/transform/er_diagram.rb +204 -0
- data/lib/sirena/transform/error.rb +39 -0
- data/lib/sirena/transform/flowchart.rb +161 -0
- data/lib/sirena/transform/gantt.rb +253 -0
- data/lib/sirena/transform/git_graph.rb +283 -0
- data/lib/sirena/transform/info.rb +39 -0
- data/lib/sirena/transform/kanban.rb +180 -0
- data/lib/sirena/transform/mindmap.rb +251 -0
- data/lib/sirena/transform/packet.rb +185 -0
- data/lib/sirena/transform/pie.rb +62 -0
- data/lib/sirena/transform/quadrant.rb +167 -0
- data/lib/sirena/transform/radar.rb +227 -0
- data/lib/sirena/transform/requirement.rb +233 -0
- data/lib/sirena/transform/sankey.rb +212 -0
- data/lib/sirena/transform/sequence.rb +143 -0
- data/lib/sirena/transform/state_diagram.rb +228 -0
- data/lib/sirena/transform/timeline.rb +139 -0
- data/lib/sirena/transform/treemap.rb +120 -0
- data/lib/sirena/transform/user_journey.rb +207 -0
- data/lib/sirena/transform/xy_chart.rb +273 -0
- data/lib/sirena/transform.rb +8 -0
- data/lib/sirena/version.rb +5 -0
- data/lib/sirena.rb +328 -0
- data/lib/tasks/benchmark.rake +532 -0
- data/lib/tasks/examples.rake +468 -0
- data/lib/tasks/generate_mermaid_fixtures.rake +363 -0
- data/lib/tasks/mermaid_fixtures.rake +46 -0
- data/scripts/extract_mermaid_tests.rb +493 -0
- data/scripts/rename_to_sirena.rb +73 -0
- data/sirena.gemspec +47 -0
- metadata +529 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "parslet"
|
|
4
|
+
|
|
5
|
+
module Sirena
|
|
6
|
+
module Parser
|
|
7
|
+
module Transforms
|
|
8
|
+
# Transform for Mindmap diagrams
|
|
9
|
+
class Mindmap < Parslet::Transform
|
|
10
|
+
# Helper class to build mindmap tree from indented nodes
|
|
11
|
+
class TreeBuilder
|
|
12
|
+
attr_reader :root, :all_nodes
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@all_nodes = []
|
|
16
|
+
@root = nil
|
|
17
|
+
@level_stack = []
|
|
18
|
+
@pending_icon = nil
|
|
19
|
+
@pending_classes = []
|
|
20
|
+
@min_indent = nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def add_node(node_data)
|
|
24
|
+
# Handle icon and class declarations - apply to PREVIOUS node
|
|
25
|
+
if node_data[:icon]
|
|
26
|
+
icon = node_data[:icon].to_s
|
|
27
|
+
if @all_nodes.last
|
|
28
|
+
@all_nodes.last[:icon] = icon
|
|
29
|
+
end
|
|
30
|
+
return
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
if node_data[:classes]
|
|
34
|
+
classes_str = node_data[:classes].to_s
|
|
35
|
+
classes = classes_str.split(/\s+/)
|
|
36
|
+
if @all_nodes.last
|
|
37
|
+
@all_nodes.last[:classes] = classes
|
|
38
|
+
end
|
|
39
|
+
return
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Track minimum indentation for relative level calculation
|
|
43
|
+
indent_size = get_indent_size(node_data[:indent])
|
|
44
|
+
@min_indent = indent_size if @min_indent.nil? || indent_size < @min_indent
|
|
45
|
+
|
|
46
|
+
# Calculate level from indentation (will be adjusted later)
|
|
47
|
+
level = calculate_level(node_data[:indent])
|
|
48
|
+
|
|
49
|
+
# Create the node
|
|
50
|
+
content = extract_content(node_data)
|
|
51
|
+
shape = extract_shape(node_data)
|
|
52
|
+
|
|
53
|
+
node = {
|
|
54
|
+
id: "node-#{@all_nodes.size}",
|
|
55
|
+
content: content,
|
|
56
|
+
level: level,
|
|
57
|
+
shape: shape,
|
|
58
|
+
icon: nil,
|
|
59
|
+
classes: [],
|
|
60
|
+
children: [],
|
|
61
|
+
_indent_size: indent_size # Store for later adjustment
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@all_nodes << node
|
|
65
|
+
|
|
66
|
+
# Build hierarchy
|
|
67
|
+
if level == 0
|
|
68
|
+
@root = node
|
|
69
|
+
@level_stack = [node]
|
|
70
|
+
else
|
|
71
|
+
# Find parent at previous level
|
|
72
|
+
parent = find_parent(level)
|
|
73
|
+
if parent
|
|
74
|
+
parent[:children] << node
|
|
75
|
+
node[:parent] = parent
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Update stack
|
|
79
|
+
@level_stack = @level_stack[0..level - 1] + [node]
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def finalize
|
|
84
|
+
# Adjust all levels to be relative to minimum indentation
|
|
85
|
+
return if @min_indent.nil?
|
|
86
|
+
|
|
87
|
+
@all_nodes.each do |node|
|
|
88
|
+
indent_size = node.delete(:_indent_size)
|
|
89
|
+
node[:level] = calculate_relative_level(indent_size, @min_indent)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Rebuild hierarchy with corrected levels
|
|
93
|
+
rebuild_hierarchy
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def rebuild_hierarchy
|
|
99
|
+
@root = nil
|
|
100
|
+
@level_stack = []
|
|
101
|
+
|
|
102
|
+
@all_nodes.each do |node|
|
|
103
|
+
level = node[:level]
|
|
104
|
+
|
|
105
|
+
# Clear old parent/children relationships
|
|
106
|
+
node[:children] = []
|
|
107
|
+
node.delete(:parent)
|
|
108
|
+
|
|
109
|
+
if level == 0
|
|
110
|
+
@root = node
|
|
111
|
+
@level_stack = [node]
|
|
112
|
+
else
|
|
113
|
+
# Find parent at previous level
|
|
114
|
+
parent = @level_stack[level - 1]
|
|
115
|
+
if parent
|
|
116
|
+
parent[:children] << node
|
|
117
|
+
node[:parent] = parent
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Update stack
|
|
121
|
+
@level_stack = @level_stack[0..level - 1] + [node]
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def get_indent_size(indent_data)
|
|
127
|
+
return 0 if indent_data.nil?
|
|
128
|
+
return 0 if indent_data.is_a?(Array) && indent_data.empty?
|
|
129
|
+
|
|
130
|
+
indent_str = if indent_data.is_a?(Array)
|
|
131
|
+
indent_data.join('')
|
|
132
|
+
else
|
|
133
|
+
indent_data.to_s
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
indent_str.length
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def calculate_relative_level(indent_size, min_indent)
|
|
140
|
+
relative_indent = indent_size - min_indent
|
|
141
|
+
return 0 if relative_indent <= 0
|
|
142
|
+
|
|
143
|
+
# Try 2-space indentation first
|
|
144
|
+
level = relative_indent / 2
|
|
145
|
+
# If not evenly divisible, try 4-space
|
|
146
|
+
level = relative_indent / 4 if relative_indent % 2 != 0
|
|
147
|
+
|
|
148
|
+
level
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
private
|
|
152
|
+
|
|
153
|
+
def calculate_level(indent_data)
|
|
154
|
+
# Handle empty array or nil
|
|
155
|
+
return 0 if indent_data.nil?
|
|
156
|
+
return 0 if indent_data.is_a?(Array) && indent_data.empty?
|
|
157
|
+
|
|
158
|
+
# Convert to string and count length
|
|
159
|
+
indent_str = if indent_data.is_a?(Array)
|
|
160
|
+
indent_data.join('')
|
|
161
|
+
else
|
|
162
|
+
indent_data.to_s
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
return 0 if indent_str.empty?
|
|
166
|
+
|
|
167
|
+
# Count spaces (2 or 4 spaces per level)
|
|
168
|
+
spaces = indent_str.length
|
|
169
|
+
# Try 2-space indentation first
|
|
170
|
+
level = spaces / 2
|
|
171
|
+
# If not evenly divisible, try 4-space
|
|
172
|
+
level = spaces / 4 if spaces % 2 != 0
|
|
173
|
+
|
|
174
|
+
level
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def extract_content(node_data)
|
|
178
|
+
return node_data[:content].to_s if node_data[:content]
|
|
179
|
+
""
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def extract_shape(node_data)
|
|
183
|
+
# Determine shape based on which parser rule matched
|
|
184
|
+
return "circle" if node_data[:shape_circle]
|
|
185
|
+
return "bang" if node_data[:shape_bang]
|
|
186
|
+
return "cloud" if node_data[:shape_cloud]
|
|
187
|
+
return "hexagon" if node_data[:shape_hexagon]
|
|
188
|
+
return "square" if node_data[:shape_square]
|
|
189
|
+
"default"
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def find_parent(level)
|
|
193
|
+
return nil if level == 0
|
|
194
|
+
# Parent is the last node at level - 1
|
|
195
|
+
@level_stack[level - 1]
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Transform the nodes array into a tree structure
|
|
200
|
+
rule(nodes: subtree(:nodes)) do
|
|
201
|
+
builder = TreeBuilder.new
|
|
202
|
+
nodes_array = Array(nodes)
|
|
203
|
+
|
|
204
|
+
nodes_array.each do |node_data|
|
|
205
|
+
next unless node_data.is_a?(Hash)
|
|
206
|
+
|
|
207
|
+
# Skip if no actual node data (just whitespace)
|
|
208
|
+
next unless node_data[:content] || node_data[:icon] || node_data[:classes] ||
|
|
209
|
+
node_data[:shape_circle] || node_data[:shape_bang] ||
|
|
210
|
+
node_data[:shape_cloud] || node_data[:shape_hexagon] ||
|
|
211
|
+
node_data[:shape_square]
|
|
212
|
+
|
|
213
|
+
builder.add_node(node_data)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# Finalize to adjust levels and rebuild hierarchy
|
|
217
|
+
builder.finalize
|
|
218
|
+
|
|
219
|
+
{
|
|
220
|
+
root: builder.root,
|
|
221
|
+
nodes: builder.all_nodes
|
|
222
|
+
}
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "parslet"
|
|
4
|
+
|
|
5
|
+
module Sirena
|
|
6
|
+
module Parser
|
|
7
|
+
module Transforms
|
|
8
|
+
# Transform for Packet diagrams
|
|
9
|
+
class Packet < Parslet::Transform
|
|
10
|
+
# Transform field definition
|
|
11
|
+
rule(
|
|
12
|
+
bit_start: simple(:bit_start),
|
|
13
|
+
bit_end: simple(:bit_end),
|
|
14
|
+
label: simple(:label)
|
|
15
|
+
) do
|
|
16
|
+
{
|
|
17
|
+
type: :field,
|
|
18
|
+
bit_start: bit_start.to_s.to_i,
|
|
19
|
+
bit_end: bit_end.to_s.to_i,
|
|
20
|
+
label: label.to_s
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Transform title
|
|
25
|
+
rule(title: simple(:title)) do
|
|
26
|
+
{ type: :title, title: title.to_s }
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Transform the entire diagram
|
|
30
|
+
rule(statements: subtree(:statements)) do
|
|
31
|
+
result = {
|
|
32
|
+
title: nil,
|
|
33
|
+
fields: []
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Handle nil or empty statements
|
|
37
|
+
stmts = statements.nil? ? [] : Array(statements)
|
|
38
|
+
|
|
39
|
+
stmts.each do |stmt|
|
|
40
|
+
next unless stmt.is_a?(Hash)
|
|
41
|
+
|
|
42
|
+
case stmt[:type]
|
|
43
|
+
when :title
|
|
44
|
+
result[:title] = stmt[:title]
|
|
45
|
+
when :field
|
|
46
|
+
result[:fields] << stmt
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
result
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Handle empty diagram (no statements)
|
|
54
|
+
rule(statements: nil) do
|
|
55
|
+
{
|
|
56
|
+
title: nil,
|
|
57
|
+
fields: []
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../diagram/pie'
|
|
4
|
+
|
|
5
|
+
module Sirena
|
|
6
|
+
module Parser
|
|
7
|
+
module Transforms
|
|
8
|
+
# Transform for converting Parslet parse tree to Pie diagram model.
|
|
9
|
+
#
|
|
10
|
+
# Converts the parse tree output from Grammars::Pie into a
|
|
11
|
+
# fully-formed Diagram::Pie object with slices and metadata.
|
|
12
|
+
class Pie
|
|
13
|
+
# Transform parse tree into Pie diagram.
|
|
14
|
+
#
|
|
15
|
+
# @param tree [Array, Hash] Parslet parse tree
|
|
16
|
+
# @return [Diagram::Pie] the pie chart diagram model
|
|
17
|
+
def apply(tree)
|
|
18
|
+
diagram = Diagram::Pie.new
|
|
19
|
+
|
|
20
|
+
# Tree structure: array with header and statements
|
|
21
|
+
if tree.is_a?(Array)
|
|
22
|
+
tree.each do |item|
|
|
23
|
+
next unless item.is_a?(Hash)
|
|
24
|
+
|
|
25
|
+
process_header(diagram, item) if item.key?(:header)
|
|
26
|
+
process_title(diagram, item) if item.key?(:title)
|
|
27
|
+
process_show_data(diagram, item) if item.key?(:show_data)
|
|
28
|
+
process_statement(diagram, item) if statement?(item)
|
|
29
|
+
end
|
|
30
|
+
elsif tree.is_a?(Hash)
|
|
31
|
+
process_header(diagram, tree) if tree.key?(:header)
|
|
32
|
+
process_title(diagram, tree) if tree.key?(:title)
|
|
33
|
+
process_show_data(diagram, tree) if tree.key?(:show_data)
|
|
34
|
+
|
|
35
|
+
if tree[:statements]
|
|
36
|
+
process_statements(diagram, tree[:statements])
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
diagram
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def statement?(item)
|
|
46
|
+
item.key?(:data_entry) ||
|
|
47
|
+
item.key?(:acc_title) ||
|
|
48
|
+
item.key?(:acc_descr) ||
|
|
49
|
+
item.key?(:standalone_title)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def process_header(diagram, item)
|
|
53
|
+
# Header is just the 'pie' keyword, nothing to extract
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def process_title(diagram, item)
|
|
57
|
+
title_data = item[:title]
|
|
58
|
+
return unless title_data
|
|
59
|
+
|
|
60
|
+
# Extract title text
|
|
61
|
+
title_text = if title_data.is_a?(Hash)
|
|
62
|
+
extract_text(title_data[:title])
|
|
63
|
+
else
|
|
64
|
+
extract_text(title_data)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
diagram.title = title_text unless title_text.empty?
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def process_show_data(diagram, item)
|
|
71
|
+
show_data = item[:show_data]
|
|
72
|
+
return unless show_data
|
|
73
|
+
|
|
74
|
+
# showData flag is present if this key exists
|
|
75
|
+
diagram.show_data = true
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def process_statements(diagram, statements)
|
|
79
|
+
Array(statements).each do |stmt|
|
|
80
|
+
process_statement(diagram, stmt) if stmt.is_a?(Hash)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def process_statement(diagram, stmt)
|
|
85
|
+
return unless stmt.is_a?(Hash)
|
|
86
|
+
|
|
87
|
+
if stmt[:data_entry]
|
|
88
|
+
add_data_entry(diagram, stmt)
|
|
89
|
+
elsif stmt[:acc_title]
|
|
90
|
+
diagram.acc_title = extract_text(stmt[:acc_title])
|
|
91
|
+
elsif stmt[:acc_descr]
|
|
92
|
+
diagram.acc_description = extract_text(stmt[:acc_descr])
|
|
93
|
+
elsif stmt[:standalone_title]
|
|
94
|
+
diagram.title = extract_text(stmt[:standalone_title])
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def add_data_entry(diagram, stmt)
|
|
99
|
+
label = extract_text(stmt[:label])
|
|
100
|
+
value = extract_numeric_value(stmt[:value])
|
|
101
|
+
|
|
102
|
+
return if label.empty?
|
|
103
|
+
|
|
104
|
+
slice = Diagram::PieSlice.new.tap do |s|
|
|
105
|
+
s.label = label
|
|
106
|
+
s.value = value
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
diagram.slices << slice
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def extract_text(value)
|
|
113
|
+
case value
|
|
114
|
+
when Hash
|
|
115
|
+
if value[:string]
|
|
116
|
+
value[:string].to_s
|
|
117
|
+
elsif value[:title]
|
|
118
|
+
value[:title].to_s
|
|
119
|
+
else
|
|
120
|
+
value.values.first.to_s
|
|
121
|
+
end
|
|
122
|
+
when String
|
|
123
|
+
value
|
|
124
|
+
else
|
|
125
|
+
value.to_s
|
|
126
|
+
end.strip
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def extract_numeric_value(value)
|
|
130
|
+
# Value can be a simple string or a complex structure
|
|
131
|
+
value_str = if value.is_a?(Hash)
|
|
132
|
+
value.values.first.to_s
|
|
133
|
+
else
|
|
134
|
+
value.to_s
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Parse as float to handle both integers and decimals
|
|
138
|
+
value_str.to_f
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../diagram/quadrant'
|
|
4
|
+
|
|
5
|
+
module Sirena
|
|
6
|
+
module Parser
|
|
7
|
+
module Transforms
|
|
8
|
+
# Transform for converting Parslet parse tree to QuadrantChart model.
|
|
9
|
+
#
|
|
10
|
+
# Converts the parse tree output from Grammars::Quadrant into a
|
|
11
|
+
# fully-formed Diagram::QuadrantChart object with points and labels.
|
|
12
|
+
class Quadrant
|
|
13
|
+
# Transform parse tree into QuadrantChart diagram.
|
|
14
|
+
#
|
|
15
|
+
# @param tree [Array, Hash] Parslet parse tree
|
|
16
|
+
# @return [Diagram::QuadrantChart] the quadrant chart diagram model
|
|
17
|
+
def apply(tree)
|
|
18
|
+
diagram = Diagram::QuadrantChart.new
|
|
19
|
+
|
|
20
|
+
# Tree structure: array with header and statements
|
|
21
|
+
if tree.is_a?(Array)
|
|
22
|
+
tree.each do |item|
|
|
23
|
+
next unless item.is_a?(Hash)
|
|
24
|
+
|
|
25
|
+
process_header(diagram, item) if item.key?(:header)
|
|
26
|
+
process_title(diagram, item) if item.key?(:title)
|
|
27
|
+
process_x_axis(diagram, item) if item.key?(:x_axis_left)
|
|
28
|
+
process_y_axis(diagram, item) if item.key?(:y_axis_bottom)
|
|
29
|
+
process_quadrant_label(diagram, item) if item.key?(:quadrant_label)
|
|
30
|
+
process_data_point(diagram, item) if item.key?(:data_point)
|
|
31
|
+
end
|
|
32
|
+
elsif tree.is_a?(Hash)
|
|
33
|
+
process_header(diagram, tree) if tree.key?(:header)
|
|
34
|
+
process_title(diagram, tree) if tree.key?(:title)
|
|
35
|
+
process_x_axis(diagram, tree) if tree.key?(:x_axis_left)
|
|
36
|
+
process_y_axis(diagram, tree) if tree.key?(:y_axis_bottom)
|
|
37
|
+
|
|
38
|
+
if tree[:statements]
|
|
39
|
+
process_statements(diagram, tree[:statements])
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
diagram
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def process_header(diagram, item)
|
|
49
|
+
# Header is just the 'quadrantChart' keyword
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def process_title(diagram, item)
|
|
53
|
+
title_data = item[:title]
|
|
54
|
+
return unless title_data
|
|
55
|
+
|
|
56
|
+
title_text = extract_text(title_data)
|
|
57
|
+
diagram.title = title_text unless title_text.empty?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def process_x_axis(diagram, item)
|
|
61
|
+
diagram.x_axis_left = extract_text(item[:x_axis_left])
|
|
62
|
+
diagram.x_axis_right = extract_text(item[:x_axis_right])
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def process_y_axis(diagram, item)
|
|
66
|
+
diagram.y_axis_bottom = extract_text(item[:y_axis_bottom])
|
|
67
|
+
diagram.y_axis_top = extract_text(item[:y_axis_top])
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def process_quadrant_label(diagram, item)
|
|
71
|
+
quadrant_num = item[:quadrant_number].to_s
|
|
72
|
+
label_text = extract_text(item[:quadrant_label])
|
|
73
|
+
|
|
74
|
+
case quadrant_num
|
|
75
|
+
when '1'
|
|
76
|
+
diagram.quadrant_1_label = label_text
|
|
77
|
+
when '2'
|
|
78
|
+
diagram.quadrant_2_label = label_text
|
|
79
|
+
when '3'
|
|
80
|
+
diagram.quadrant_3_label = label_text
|
|
81
|
+
when '4'
|
|
82
|
+
diagram.quadrant_4_label = label_text
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def process_statements(diagram, statements)
|
|
87
|
+
Array(statements).each do |stmt|
|
|
88
|
+
next unless stmt.is_a?(Hash)
|
|
89
|
+
|
|
90
|
+
process_title(diagram, stmt) if stmt.key?(:title)
|
|
91
|
+
process_x_axis(diagram, stmt) if stmt.key?(:x_axis_left)
|
|
92
|
+
process_y_axis(diagram, stmt) if stmt.key?(:y_axis_bottom)
|
|
93
|
+
process_quadrant_label(diagram, stmt) if stmt.key?(:quadrant_label)
|
|
94
|
+
process_data_point(diagram, stmt) if stmt.key?(:data_point)
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def process_data_point(diagram, item)
|
|
99
|
+
label = extract_text(item[:label])
|
|
100
|
+
coords = item[:coordinates]
|
|
101
|
+
styling = item[:styling]
|
|
102
|
+
|
|
103
|
+
return if label.empty? || coords.nil?
|
|
104
|
+
|
|
105
|
+
x = extract_float(coords[:x])
|
|
106
|
+
y = extract_float(coords[:y])
|
|
107
|
+
|
|
108
|
+
point = Diagram::QuadrantPoint.new.tap do |p|
|
|
109
|
+
p.label = label
|
|
110
|
+
p.x = x
|
|
111
|
+
p.y = y
|
|
112
|
+
|
|
113
|
+
# Process optional styling parameters
|
|
114
|
+
if styling
|
|
115
|
+
style_hash = extract_styling(styling)
|
|
116
|
+
p.radius = style_hash[:radius] if style_hash[:radius]
|
|
117
|
+
p.color = style_hash[:color] if style_hash[:color]
|
|
118
|
+
p.stroke_color = style_hash[:stroke_color] if style_hash[:stroke_color]
|
|
119
|
+
p.stroke_width = style_hash[:stroke_width] if style_hash[:stroke_width]
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
diagram.points << point
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def extract_styling(styling)
|
|
127
|
+
result = {}
|
|
128
|
+
|
|
129
|
+
# Styling can be a Hash or Array depending on parse tree structure
|
|
130
|
+
items = if styling.is_a?(Array)
|
|
131
|
+
styling
|
|
132
|
+
elsif styling.is_a?(Hash)
|
|
133
|
+
[styling]
|
|
134
|
+
else
|
|
135
|
+
[]
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
items.each do |item|
|
|
139
|
+
next unless item.is_a?(Hash)
|
|
140
|
+
|
|
141
|
+
result[:radius] = extract_float(item[:radius]) if item[:radius]
|
|
142
|
+
result[:color] = extract_text(item[:color]) if item[:color]
|
|
143
|
+
result[:stroke_color] = extract_text(item[:stroke_color]) if item[:stroke_color]
|
|
144
|
+
result[:stroke_width] = extract_float(item[:stroke_width]) if item[:stroke_width]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
result
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def extract_text(value)
|
|
151
|
+
case value
|
|
152
|
+
when Hash
|
|
153
|
+
if value[:string]
|
|
154
|
+
value[:string].to_s
|
|
155
|
+
else
|
|
156
|
+
value.values.first.to_s
|
|
157
|
+
end
|
|
158
|
+
when String
|
|
159
|
+
value
|
|
160
|
+
else
|
|
161
|
+
value.to_s
|
|
162
|
+
end.strip
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def extract_float(value)
|
|
166
|
+
value_str = if value.is_a?(Hash)
|
|
167
|
+
value.values.first.to_s
|
|
168
|
+
else
|
|
169
|
+
value.to_s
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
value_str.to_f
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|