Skip to content

Actions

Noah Baldwin edited this page Sep 13, 2026 · 25 revisions

Warning

Actions are under development

Overview

Actions are intended to be a set of commands that can be used by a scripting language or GUI to provide an editor-like experience. Actions have the following features, except where explicitly stated otherwise.

  • Replayable: Each action is a pure function over the document. If you "rewind" the history and then replay those actions, then the output will be identical.
  • Embedded State: Each action produces a document with an oxvg:state element that will be referenced by proceeding actions.
  • Pure SVG: Actions reflect what is achievable purely through SVG and doesn't include advanced graphics manipulation beyond that.
  • Headless: Actions do as much as possible without depending on the box-model/rendering.

Usage

Actions are usable from the CLI and will take a set of actions to produce a transformed SVG document.

cat my-file.svg | oxvg action -- -select "#main" -delete > my-file.transformed.svg

Actions can also be used to get info regarding the document's state.

cat my-file.svg | oxvg action --derive-state -- -select path
cat my-file.transformed.svg | oxvg action --derive-state
{
  "history": [{ "type": "Select", "arg0": "path" }],
  "selection": [10],
  "info": {
    "elementId": "Path",
    "contentModel": { /* ... */ },
    "attributes": { /* ... */ },
  },
  "issues": [
    { "type": "UnknownAttr", "selected": 10, "name": "path" },
    { "type": "UnknownElement", "selected": 10, "name": "path" }
  ]
}

Together, actions can be used to build useful generative programs

def generate-nested []: nothing -> string {
    mut svg = '<svg viewBox="-50 -50 100 100"><rect x="-40" y="-40" width="80" height="80" fill="purple"/></svg>'

    for _ in 1..5 {
        # Derive state from OXVG to inspect current document
        let state = ($svg | oxvg action --derive-state -- -select rect | from json)
        
        # Condition based on allocation ID from state
        let fill = if ($state.selection.0 % 2 == 0) { "gold" } else { "teal" }

        # Apply transformations
        $svg = ($svg | oxvg action -- 
            -select rect 
            -duplicate 
            -rotate 15 
            -scale 0.8 
            -style fill $fill
        )
    }

    # Return optimized SVG string
    $svg | oxvg action -- -optimise
}

SVG Output

Actions will be completely self-contained within OXVG. An application can pass an SVG to OXVG with a list of actions. OXVG will then respond with a transformed SVG attached with an oxvg:state element for selections, history, state, etc.

Actions

An action is essentially a method which takes in some arguments and changes the state of the application. Actions are pure functions; the application is expected to take the result and update it's own state to send to the next action.

All actions act upon a document and accept primitive arguments. For example, as a library the interface may look like this.

const actor = new Actor(svgString);
actor.select("#main").delete();

Or as a terminal program

cat example.svg | oxvg action -- -select #main -delete

Action interfaces here will be outlined as follows

.<action-name>(<list-of args>) -> <list of updated `oxvg:state`>

Effects

Each action has some effect, as specified by the return types given in this spec. An action that affected everything would look like this.

.<action-name>(<list-of args>) -> history, clipboard, document, state, selection, tree, ui
  • History: The command is significant enough to be recorded in the history property of the OXVG state
  • Clipboard: The current internal clipboard of the OXVG state will be replaced
  • Document: The content of the document will change
  • State: A property of the OXVG state will be mutated (usually implied by history, clipboard, selection)
  • Selection: The current selection of the OXVG state will be replaced
  • Tree: A document node will be added/removed/moved. Allocation IDs will be recalculated to maintain linearity.
  • UI: A signal is added to the OXVG state to indicate that the client should display some UI.

Responses

After all specified actions are completed, a response will be generated containing an oxvg:state element at the end of the document. The state should be treated as a read-only specification of information that the client can use to display information to the user. OXVG provides utilities to derive additional state information.

The state has a shape similar to the following.

struct State {
  /// List of actions that mutated the original document/state to
  /// reach the current document
  history: Vec<Action>,
  /// List of actions to be handled by the client 
  ui: Vec<UIAction>,
  /// List of selections by allocation ID
  selection: Vec<usize>,
  /// Elements selected at last `copy` command
  clipboard: Vec<Node>,
}

enum Action {
  Select(String),
  Delete,
  // ...etc as described below
}

enum UIAction {
  /// Display dialog with markdown content
  Dialog { content: String },
  /// Write content to clipboard
  Clipboard { content: String },
  /// Form to be completed by the user to apply to the next action.
  Form { fields: Vec<Field>, target: Action },
}
<oxvg:state>
  <!-- List of actions applied to the source document -->
  <oxvg:history>
    <oxvg:action oxvg:id="Select">
      <oxvg:arg>#main</oxvg:arg>
    </oxvg:action>
  </oxvg:history>
  <!-- List of actions to be handled by the client -->
  <oxvg:ui>
    <oxvg:dialog>Hello, world!</oxvg:dialog>
    <oxvg:clipboard>&lt;text&gt;Hello, world!&lt;/text&gt;</oxvg:clipboard>
  </oxvg:ui>
  <!-- List of selections by allocation-id -->
  <oxvg:selection oxvg:ids="1" />
  <!-- Elements selected at last `copy` command -->
  <oxvg:clipboard>
    <text>Hello, world!</text>
  </oxvg:clipboard>
</oxvg:state>

HTML elements may also be given attributes, which can be used for styling or displaying inline information.

<html-element oxvg:selected />

The Global Action

After OXVG acts upon an SVG, it will apply any state that it's set by embedding it in the document.

<!-- input -->
<svg>
  <path d="M0 0" />
</svg>

<!-- output -->
<svg xmlns:oxvg="oxvg.noahwbaldwin.me">
  <path d="M 0 0" />
  <oxvg:state>
    <selection ids="1" />
  </oxvg:state>
</svg>

Note

The allocation ID will always be the index of the node within the document in a DFS traversal. As long as modifications to the document are handled via OXVG, the state will match these IDs. External modifications to the document may invalidate existing allocation IDs.

.<init>() -> document, state

UI

UI actions take the state and produce some UI output to the user application.

About

Returns version and application info

.about() -> ui: dialog

Copy

Copy the selected element(s) to clipboard, separated by newline. Adds deep copy of selected elements to <oxvg:clipboard>.

.copy() -> history, clipboard, ui: clipboard

Manipulation

Manipulation actions directly change the contents of the SVG document.

Attr

Sets the attribute of selected elements to the given value. Has no effect if the element doesn't accept the attribute as part of their [content-model](https://svgwg.org/svg2-draft/eltindex.html).

.attr(<ATTR-ID>, <STRING>) -> history, document

Class

Toggles the class of the selected elements with the given class name by removing the class when present, or adding the class when not.

.class(<NAME>) -> history, document

Optimise

Caution

This will destroy the OXVG's state, and should only be used when done with actions to produce a clean SVG.

Runs a set of optimisation jobs upon the document, accepts a JSON string matching the "optimisation" option in [oxvgrc.json](https://github.com/noahbald/oxvg/wiki/Configuration). Also has the effect of the "Forget" action.

# alias: optimize
.optimise(<CONFIG>) -> document, state

Paste

Warning

Paste is not checked for validity and may procure duplicate ids or other SVG violations.

Warning

May cause SVG content-model violation.

Uses the content in <oxvg:clipboard>. If empty, does nothing. Selects the root of the pasted tree.

.paste -> document, history, selection, tree

Path

For selected elements with path data, merges the paths by some operation. Each merge will be applied to the selections sequentially. Merges will be effective on the front-most element.

A selected element will be filtered if the element is not a path element, has no d attribute, or has children. The merge will keep only the front-most element's attributes, omitting any attributes applied to other elements.

Intersect

Merges the paths, producing a path that fills the area that the paths overlapped. Deselects any removed paths.

.pathIntersect() -> document, history, selection, tree
Union

Merges the paths, producing a path that fills the area that both the paths covered. Deselects any removed paths.

.pathUnion() -> document, history, selection, tree
Subtract

Merges the paths, producing a path that clips other paths. Deselects any removed paths.

.pathSubtract() -> document, history, selection, tree
XOR

Merges the paths, producing a path that fills the area that the paths hadn't overlapped. Deselects any removed paths.

.pathXor() -> document, history, selection, tree

Style

Appends the style to the selected elements' style list. Has no effect of elements that don't accept style as part of their [content-model](https://svgwg.org/svg2-draft/attindex.html).

.style(<PROPERTY>, <VALUE>) -> document, history

Transform

Each transform action appends to the selected elements' transform attribute. Has no effect on elements that don't accept transform as part of their [content-model](https://www.w3.org/TR/2011/REC-SVG11-20110816/attindex.html). Applies transforms in SVG 1.1 format, including units.

.matrix(<A>, <B>, <C>, <D>, <E>, <F>) -> document, history
.translate(<X>, <Y?>) -> document, history
.scale(<X>, <Y?>) -> document, history
.rotate(<DEG>, (<X>, <Y>?)) -> document, history
.skewX(<ANGLE>) -> document, history
.skewY(<ANGLE>) -> document, history

Structure

Structure actions semantically change the contents of the SVG document.

Creation

Insert

Creates a new SVG element and inserts it into the current selection. Selects the new elements. Has no effect if the parent doesn't accept the tag as a child of it's [content-model](https://svgwg.org/svg2-draft/eltindex.html).

# alias: createElement
.insert(<QUAL-NAME>) -> history, document, selection, tree
Insert Namespaced

Creates a new element and inserts it into the current selection. Selects the new elements. Has no effect if the parent doesn't accept the tag as a child of it's [content-model](https://svgwg.org/svg2-draft/eltindex.html).

# alias: createElementNS
.insertNS(<URI>, <QUAL-NAME>) -> history, document, selection, tree
Duplicate

Creates a deep copy of each selected element and puts it after the selected element. Selection moved to copies.

.duplicate() -> history, document, selection, tree
Wrap

Warning

May cause SVG content-model violation.

Wraps each selected element in the given element. Adjacent selections will be grouped within the same element. Selection moved to created elements.

.wrap(<QUAL-NAME>) -> history, document, selection, tree
Clone

Warning

May cause SVG content-model violation.

Wraps each element in <symbol> under the root <svg> and creates an adjacent <use> element, referencing <symbol> by a random id. Selects the new use elements.

.clone() -> history, document, selection, tree
Anchor Link

Warning

May cause SVG content-model violation.

Wraps each selected element in an [anchor link element](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/a). Adjacent selections will be grouped within the same link. Selection moved to links.

.anchorLink(<STRING>) -> history, document, selection, tree
Group

Warning

May cause SVG content-model violation.

Wraps each selected element in a [group element](https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/g). Adjacent selections will be grouped within the same element. Selection moved to groups.

.group() -> history, document, selection, tree

Deletion

Delete

Removes each selected element from the document. Deselects.

.delete() -> history, document, selection, tree
Flatten

Warning

May cause SVG content-model violation.

Removes the selected elements, replacing itself with it's children. Selection moved to children.

.flatten() -> history, document, selection, tree

Order

Moves the selected elements place within it's parent

Front

Moves the selected elements to be in front of all it's siblings.

.front() -> document, history, tree
Push

Moves the selected elements to be in front of their next sibling.

.push() -> document, history, tree
Pull

Moves the selected elements to be behind their previous sibling.

.pull() -> document, history, tree
Back

Moves the selected elements to be behind all of it's siblings.

.back() -> document, history, tree

Step

Moves the selected elements up or down the tree, unless it's at the root or leaf of the document, in which case it does nothing.

Step In

Warning

May cause SVG content-model violation.

Moves the selected element into the start of it's next sibling. Does nothing if it's the last child.

.stepIn() -> document, history, tree
Step Out

Warning

May cause SVG content-model violation.

Moves the selected element up to be behind it's parent.

.stepOut() -> document, history, tree

State

Forget

Caution

This will destroy the OXVG's history, and should only be used when done with actions to produce a clean SVG.

Removes all oxvg prefixed and unknown data from the document.

.forget() -> state

Select

Selects elements in the document by some given query selector by setting oxvg:selected on each element and setting oxvg:state > oxvg:selections. Selections are always applied from the root element (i.e. the document).

.select(<STRING|ALLOCATION-ID>) -> history, selection

Select More

Like "Select", but keeps the previous selection

.selectMore(<STRING|ALLOCATION-ID>) -> history, selection

First Child

Selects the first-child of the current selection. Does nothing if the selection has no children.

.firstChild() -> history, selection

Previous Sibling

Selects the previous-sibling of the current selection. Does nothing if the selection is the first-child.

.previousSibling() -> history, selection

Next Sibling

Selects the next-sibling of the current selection. Does nothing if the selection is the last-child.

.nextSibling() -> history, selection

Last Child

Selects the last-child of the current selection. Does nothing if the selection has no children.

.lastChild() -> history, selection

Parent

Selects the parent of the current selection. Does nothing if the selection is the root.

.parent() -> history, selection

Deselect

Deselects elements by allocation id. Deselects all if list is empty

.deselect(...<ALLOCATION-ID>) -> history, selection

Clone this wiki locally