|
<?xml version="1.0" encoding="UTF-8"?> |
|
<!--Xholon Workbook http://www.primordion.com/Xholon/gwt/ MIT License, Copyright (C) Ken Webb, Wed Sep 27 2023 11:53:30 GMT-0400 (Eastern Daylight Saving Time)--> |
|
<XholonWorkbook> |
|
|
|
<Notes><![CDATA[ |
|
Xholon |
|
------ |
|
Title: Transform ReactJS to Xholon |
|
Description: |
|
Url: http://www.primordion.com/Xholon/gwt/ |
|
InternalName: 62ee0cfeb3584c95dae72d4960fcb7e0 |
|
Keywords: |
|
|
|
My Notes |
|
-------- |
|
13 Sept 2023 |
|
|
|
In this workbook I explore how to transform ReactJS JSX to Xholon format. |
|
|
|
See my examples in: ~/nodespace/jsx2xh |
|
chatgptex01 |
|
chatgptex02 |
|
recast01 |
|
|
|
### Export result as XML |
|
- I used Export > Xml on board node |
|
Xml |
|
board |
|
{"xhAttrStyle":1,"nameTemplate":"^^C^^^","xhAttrReturnAll":false,"writeStartDocument":false,"writeXholonId":false,"writeXholonRoleName":false,"writePorts":false,"writeAnnotations":false,"shouldPrettyPrint":true,"writeAttributes":false,"writeStandardAttributes":false,"shouldWriteVal":false,"shouldWriteAllPorts":false,"shouldWriteLinks":false,"showComments":false} |
|
|
|
If I paste the resulting XML into the Xholon GUI HTML element <div id="xhappspecific"></div> |
|
- it shows the 3 x 3 grid |
|
|
|
### Event Handler |
|
button is written to console as: |
|
<button className="square" onClick="console.log('clicked ' + this.textContent)"> |
|
1 |
|
</button> |
|
|
|
If I paste this into the HTML in the browser, and click on that button, then "clicked N" is written to the Dev Tools console. |
|
|
|
### Edit HTML |
|
- paste or edit the following to appspecific element |
|
<Board> |
|
<div>Winner: Human</div> |
|
<div> |
|
<Square> |
|
<button className="square" onClick="console.log('clicked ' + this.textContent)">1</button> |
|
</Square> |
|
<Square> |
|
<button className="square" onClick="console.log('clicked ' + this.textContent)">2</button> |
|
</Square> |
|
<Square> |
|
<button className="square" onClick="console.log('clicked ' + this.textContent)">3</button> |
|
</Square> |
|
</div> |
|
<div> |
|
<Square> |
|
<button className="square" onClick="console.log('clicked ' + this.textContent)">4</button> |
|
</Square> |
|
<Square> |
|
<button className="square" onClick="console.log('clicked ' + this.textContent)">5</button> |
|
</Square> |
|
<Square> |
|
<button className="square" onClick="console.log('clicked ' + this.textContent)">6</button> |
|
</Square> |
|
</div> |
|
<div> |
|
<Square> |
|
<button className="square" onClick="console.log('clicked ' + this.textContent)">7</button> |
|
</Square> |
|
<Square> |
|
<button className="square" onClick="console.log('clicked ' + this.textContent)">8</button> |
|
</Square> |
|
<Square> |
|
<button className="square" onClick="console.log('clicked ' + this.textContent)">9</button> |
|
</Square> |
|
</div> |
|
</Board> |
|
|
|
### TODO |
|
27 Sept 2023 |
|
- the existing HtmlExporter is designed for Board |
|
- write a new HtmlExporter to handle Game |
|
- work on Game and Board to get the final app working |
|
- at this point, everything compiles and basically runs |
|
|
|
### References |
|
(1) https://react.dev/ |
|
I am using the initial simple example as a first test. |
|
see: ~/gwtspace/Xholon/Xholon/script/javascript/reactjs01.js |
|
|
|
(2) https://babeljs.io/ |
|
I will follow the general instructions here. |
|
|
|
(3) ChatGPT |
|
see: ~/A001_OpenAI/chatGPT48_babel.txt |
|
|
|
(4) https://babeljs.io/videos/ |
|
- maybe I should see some of these? But they are oldish. |
|
|
|
(5) https://astexplorer.net/ |
|
- has a good default example that reverses the names of Identifiers, using the followng visitor |
|
|
|
export default function (babel) { |
|
const { types: t } = babel; |
|
|
|
return { |
|
name: "ast-transform", // not required |
|
visitor: { |
|
Identifier(path) { |
|
path.node.name = path.node.name.split('').reverse().join(''); |
|
} |
|
} |
|
}; |
|
} |
|
|
|
(5b) https://github.com/fkling/astexplorer/blob/master/README.md |
|
AST explorer |
|
Paste or drop code into the editor and inspect the generated AST on https://astexplorer.net/ |
|
The AST explorer provides following code parsers: |
|
... |
|
|
|
(6) https://www.youtube.com/watch?v=VBscbcm2Mok&t=1253s |
|
Writing custom Babel and ESLint plugins with ASTs (Open West 2017), Kent C. Dodds |
|
|
|
(7) https://github.com/babel/babel |
|
The compiler for writing next generation JavaScript. |
|
Babel (pronounced "babble") is a community-driven project used by many companies and projects, |
|
and is maintained by a group of volunteers. |
|
|
|
(8) https://babel.dev/docs/babel-plugin-transform-react-jsx |
|
This plugin is included in @babel/preset-react |
|
- the page includes a bunch of examples |
|
|
|
(9) https://github.com/jamiebuilds/babel-handbook/tree/master |
|
Babel Handbook, Written by Jamie Kyle |
|
A guided handbook on how to use Babel and how to create plugins for Babel. |
|
) https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/user-handbook.md |
|
This document covers everything you ever wanted to know about using Babel and related tooling. |
|
|
|
(10) https://babeljs.io/docs/babel-traverse |
|
We can use it alongside the babel parser to traverse and update nodes: |
|
|
|
(11) https://dev.to/pulkitnagpal/transpile-jsx-using-your-own-custom-built-babel-plugin-4888 |
|
pulkitnagpal, Posted on Aug 16, 2020 |
|
Transpile JSX using your own custom built babel plugin |
|
Ever wondered how does react jsx code (<div>Hello World</div>) gets compiled to React.createElement("div", null, "Hello World"). |
|
This blog is all about this compilation process by taking help from the source code of babel-preset-react and trying to build our own custom plugin. |
|
|
|
(12) https://www.digitalocean.com/community/tutorials/js-traversing-ast |
|
// Tutorial //, Read JavaScript Source Code, Using an AST, Published on February 8, 2019, Bart Ledoux |
|
The following example is small. |
|
Our mission, should you choose to accept it, will be to extract the names of all the functions exposed in the global scope. |
|
- has a list of parsers |
|
- the example is fairly detailed |
|
|
|
(13) https://github.com/acornjs/acorn-jsx |
|
) https://github.com/acornjs/acorn-jsx/blob/main/test/tests-jsx.js |
|
|
|
(14) https://legacy.reactjs.org/docs/jsx-in-depth.html |
|
Fundamentally, JSX just provides syntactic sugar for the React.createElement(component, props, ...children) function. The JSX code: |
|
|
|
<MyButton color="blue" shadowSize={2}> |
|
Click Me |
|
</MyButton> |
|
|
|
compiles into: |
|
|
|
React.createElement( |
|
MyButton, |
|
{color: 'blue', shadowSize: 2}, |
|
'Click Me' |
|
) |
|
) https://react.dev/learn#writing-markup-with-jsx |
|
|
|
(15) https://transform.tools/html-to-jsx |
|
- convert from HTML to JSX |
|
- this site has many similar transformers |
|
|
|
(16) https://github.com/acornjs/acorn |
|
A tiny, fast JavaScript parser, written completely in JavaScript. |
|
This repository holds three packages: |
|
acorn: The main parser |
|
acorn-loose: The error-tolerant parser |
|
acorn-walk: The syntax tree walker |
|
|
|
(17a) https://github.com/acornjs/acorn/tree/master/acorn/ |
|
(17b) https://github.com/acornjs/acorn/tree/master/acorn-loose/ |
|
(17c) https://github.com/acornjs/acorn/tree/master/acorn-walk/ |
|
|
|
(18) https://github.com/estree/estree |
|
) https://github.com/estree/estree/blob/master/es5.md |
|
The ESTree Spec |
|
|
|
Once upon a time, an unsuspecting Mozilla engineer created an API in Firefox that exposed the SpiderMonkey engine's JavaScript parser as a JavaScript API. |
|
Said engineer documented the format it produced, and this format caught on as a lingua franca for tools that manipulate JavaScript source code. |
|
|
|
Meanwhile JavaScript is evolving. |
|
This site will serve as a community standard for people involved in building and using these tools to help evolve this format |
|
to keep up with the evolution of the JavaScript language. |
|
|
|
(19) https://github.com/estree/formal |
|
ESTree machine-readable data (+generator) |
|
|
|
(20) https://github.com/benjamn/recast |
|
JavaScript syntax tree transformer, nondestructive pretty-printer, and automatic source map generator |
|
Recast exposes two essential interfaces, one for parsing JavaScript code (require("recast").parse) |
|
and the other for reprinting modified syntax trees (require("recast").print). |
|
- used in ref [12] |
|
|
|
(21) https://esprima.org/ |
|
Esprima is a high performance, standard-compliant ECMAScript parser written in ECMAScript (also popularly known as JavaScript). |
|
Features |
|
- Full support for ECMAScript 2019 (ECMA-262 10th Edition) |
|
- Sensible syntax tree format, with optional node location info |
|
- Experimental support for JSX, a syntax extension for React |
|
- Heavily tested (~1600 tests with full code coverage) |
|
) https://www.npmjs.com/package/esprima |
|
|
|
(22) https://www.npmjs.com/search?q=keywords:ast |
|
- lots of interesting projects |
|
|
|
(23) https://react.dev/learn/tutorial-tic-tac-toe |
|
Create a Xholonic version of this React tutorial. |
|
- use DefaultContent |
|
- perhaps use the GUI I created for my MeTTTa Xholon app. |
|
|
|
(24) ~/gwtspace/Xholon/Xholon/script/javascript/reactjsUseState01.js |
|
- how to implement react useState in vanilla javascript |
|
(24b) https://stackoverflow.com/questions/64744252/how-to-replicate-usestate-with-vanilla-js |
|
- I will implement some of what I found here |
|
- note that "count" in the example is a function rather than a constant value |
|
(24c) https://codesandbox.io/s/usestate-implementation-in-vanilla-js-ff4rx |
|
- good example that runs in browser |
|
|
|
]]></Notes> |
|
|
|
<_-.XholonClass> |
|
<PhysicalSystem/> |
|
|
|
<Square superClass="Script"/> |
|
<Board superClass="Script"/> |
|
<Game superClass="Script"/> |
|
|
|
<TestUseState superClass="Script"/> |
|
|
|
<HtmlExporter superClass="Script"/> |
|
|
|
</_-.XholonClass> |
|
|
|
<xholonClassDetails> |
|
|
|
<!-- from ref[23]: |
|
function Square({ value, onSquareClick }) { |
|
return ( |
|
<button className="square" onClick={onSquareClick}> |
|
{value} |
|
</button> |
|
); |
|
} |
|
--> |
|
<Square><DefaultContent><![CDATA[ |
|
var me, beh = { |
|
postConfigure: function() { |
|
me = this.cnode; |
|
//$wnd.console.log(me.name()); |
|
|
|
function Square({ value, onSquareClick }) { |
|
return ( |
|
`<button className="square" onClick="${onSquareClick}"> |
|
${value} |
|
</button>` |
|
); |
|
} |
|
|
|
var sq = Square({value: me.value, onSquareClick: "console.log('clicked ' + this.textContent); this.textContent = 'X';"}); |
|
//$wnd.console.log(sq); |
|
me.append(sq); |
|
} |
|
} |
|
//# sourceURL=SquareTest.js |
|
]]></DefaultContent></Square> |
|
|
|
<!-- from ref[23]: |
|
function Board({ xIsNext, squares, onPlay }) { |
|
function handleClick(i) { |
|
if (calculateWinner(squares) || squares[i]) { |
|
return; |
|
} |
|
const nextSquares = squares.slice(); |
|
if (xIsNext) { |
|
nextSquares[i] = 'X'; |
|
} else { |
|
nextSquares[i] = 'O'; |
|
} |
|
onPlay(nextSquares); |
|
} |
|
|
|
const winner = calculateWinner(squares); |
|
let status; |
|
if (winner) { |
|
status = 'Winner: ' + winner; |
|
} else { |
|
status = 'Next player: ' + (xIsNext ? 'X' : 'O'); |
|
} |
|
|
|
return ( |
|
<> |
|
<div className="status">{status}</div> |
|
<div className="board-row"> |
|
<Square value={squares[0]} onSquareClick={() => handleClick(0)} /> |
|
<Square value={squares[1]} onSquareClick={() => handleClick(1)} /> |
|
<Square value={squares[2]} onSquareClick={() => handleClick(2)} /> |
|
</div> |
|
<div className="board-row"> |
|
<Square value={squares[3]} onSquareClick={() => handleClick(3)} /> |
|
<Square value={squares[4]} onSquareClick={() => handleClick(4)} /> |
|
<Square value={squares[5]} onSquareClick={() => handleClick(5)} /> |
|
</div> |
|
<div className="board-row"> |
|
<Square value={squares[6]} onSquareClick={() => handleClick(6)} /> |
|
<Square value={squares[7]} onSquareClick={() => handleClick(7)} /> |
|
<Square value={squares[8]} onSquareClick={() => handleClick(8)} /> |
|
</div> |
|
</> |
|
); |
|
} |
|
--> |
|
<Board><DefaultContent><![CDATA[ |
|
var me, beh = { |
|
postConfigure: function() { |
|
me = this.cnode; |
|
|
|
function calculateWinner(squares) { |
|
return "Human"; |
|
} |
|
|
|
function Board({ xIsNext, squares, onPlay }) { |
|
function handleClick(i) { |
|
if (calculateWinner(squares) || squares[i]) { |
|
return; |
|
} |
|
const nextSquares = squares.slice(); |
|
if (xIsNext) { |
|
nextSquares[i] = 'X'; |
|
} else { |
|
nextSquares[i] = 'O'; |
|
} |
|
onPlay(nextSquares); |
|
} |
|
|
|
const winner = calculateWinner(squares); |
|
let status; |
|
if (winner) { |
|
status = 'Winner: ' + winner; |
|
} else { |
|
status = 'Next player: ' + (xIsNext ? 'X' : 'O'); |
|
} |
|
|
|
return ( |
|
`<_-.board> |
|
<div className="status">${status}</div> |
|
<div className="board-row"> |
|
<Square value="${squares[0]}" onSquareClick="${() => handleClick(0)}" /> |
|
<Square value="${squares[1]}" onSquareClick="${() => handleClick(1)}" /> |
|
<Square value="${squares[2]}" onSquareClick="${() => handleClick(2)}" /> |
|
</div> |
|
<div className="board-row"> |
|
<Square value="${squares[3]}" onSquareClick="${() => handleClick(3)}" /> |
|
<Square value="${squares[4]}" onSquareClick="${() => handleClick(4)}" /> |
|
<Square value="${squares[5]}" onSquareClick="${() => handleClick(5)}" /> |
|
</div> |
|
<div className="board-row"> |
|
<Square value="${squares[6]}" onSquareClick="${() => handleClick(6)}" /> |
|
<Square value="${squares[7]}" onSquareClick="${() => handleClick(7)}" /> |
|
<Square value="${squares[8]}" onSquareClick="${() => handleClick(8)}" /> |
|
</div> |
|
</_-.board>` |
|
); |
|
} |
|
|
|
var brd = Board({xIsNext: true, squares: [1,2,3,4,5,6,7,8,9], onPlay: "play"}); |
|
//$wnd.console.log(brd); |
|
me.append(brd); |
|
|
|
// the following writes the resulting XML to a Xholon GUI tab: |
|
//$wnd.xh.xport("Xml", me, '{"xhAttrStyle":1,"nameTemplate":"^^C^^^","xhAttrReturnAll":false,"writeStartDocument":false,"writeXholonId":false,"writeXholonRoleName":false,"writePorts":false,"writeAnnotations":false,"shouldPrettyPrint":true,"writeAttributes":false,"writeStandardAttributes":false,"shouldWriteVal":false,"shouldWriteAllPorts":false,"shouldWriteLinks":false,"showComments":false}'); |
|
} |
|
} |
|
//# sourceURL=BoardTest.js |
|
]]></DefaultContent></Board> |
|
|
|
<!-- from ref[23]: |
|
export default function Game() { |
|
const [history, setHistory] = useState([Array(9).fill(null)]); |
|
const [currentMove, setCurrentMove] = useState(0); |
|
const xIsNext = currentMove % 2 === 0; |
|
const currentSquares = history[currentMove]; |
|
|
|
function handlePlay(nextSquares) { |
|
const nextHistory = [...history.slice(0, currentMove + 1), nextSquares]; |
|
setHistory(nextHistory); |
|
setCurrentMove(nextHistory.length - 1); |
|
} |
|
|
|
function jumpTo(nextMove) { |
|
setCurrentMove(nextMove); |
|
} |
|
|
|
const moves = history.map((squares, move) => { |
|
let description; |
|
if (move > 0) { |
|
description = 'Go to move #' + move; |
|
} else { |
|
description = 'Go to game start'; |
|
} |
|
return ( |
|
<li key={move}> |
|
<button onClick={() => jumpTo(move)}>{description}</button> |
|
</li> |
|
); |
|
}); |
|
|
|
return ( |
|
<div className="game"> |
|
<div className="game-board"> |
|
<Board xIsNext={xIsNext} squares={currentSquares} onPlay={handlePlay} /> |
|
</div> |
|
<div className="game-info"> |
|
<ol>{moves}</ol> |
|
</div> |
|
</div> |
|
); |
|
} |
|
|
|
function calculateWinner(squares) { |
|
const lines = [ |
|
[0, 1, 2], |
|
[3, 4, 5], |
|
[6, 7, 8], |
|
[0, 3, 6], |
|
[1, 4, 7], |
|
[2, 5, 8], |
|
[0, 4, 8], |
|
[2, 4, 6], |
|
]; |
|
for (let i = 0; i < lines.length; i++) { |
|
const [a, b, c] = lines[i]; |
|
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { |
|
return squares[a]; |
|
} |
|
} |
|
return null; |
|
} |
|
--> |
|
|
|
<Game><DefaultContent><![CDATA[ |
|
var me, beh = { |
|
postConfigure: function() { |
|
me = this.cnode; |
|
$wnd.console.log(me.name()); |
|
|
|
const useState = (defaultValue) => { |
|
let value = defaultValue; |
|
const getValue = () => value |
|
const setValue = newValue => value = newValue |
|
return [getValue, setValue] |
|
} |
|
|
|
function Game() { |
|
const [history, setHistory] = useState([Array(9).fill(null)]); |
|
const [currentMove, setCurrentMove] = useState(0); |
|
const xIsNext = currentMove() % 2 === 0; |
|
const currentSquares = history()[currentMove()]; |
|
console.log(history()); |
|
console.log(currentMove()); |
|
console.log(xIsNext); |
|
console.log(currentSquares); |
|
|
|
function handlePlay(nextSquares) { |
|
const nextHistory = [...history().slice(0, currentMove() + 1), nextSquares]; |
|
setHistory(nextHistory); |
|
setCurrentMove(nextHistory.length - 1); |
|
} |
|
|
|
function jumpTo(nextMove) { |
|
setCurrentMove(nextMove); |
|
} |
|
|
|
const moves = history().map((squares, move) => { |
|
let description; |
|
if (move > 0) { |
|
description = 'Go to move #' + move; |
|
} else { |
|
description = 'Go to game start'; |
|
} |
|
return ( |
|
`<li key="${move}"> |
|
<button onClick="{() => jumpTo(move)}">${description}</button> |
|
</li>` |
|
); |
|
}); |
|
console.log(moves); |
|
|
|
return ( |
|
`<div className="game"> |
|
<div className="game-board"> |
|
<Board xIsNext="${xIsNext}" squares="${currentSquares}" onPlay="${handlePlay}" /> |
|
</div> |
|
<div className="game-info"> |
|
<ol>${moves}</ol> |
|
</div> |
|
</div>` |
|
); |
|
} // end function Game() |
|
|
|
function calculateWinner(squares) { |
|
const lines = [ |
|
[0, 1, 2], |
|
[3, 4, 5], |
|
[6, 7, 8], |
|
[0, 3, 6], |
|
[1, 4, 7], |
|
[2, 5, 8], |
|
[0, 4, 8], |
|
[2, 4, 6], |
|
]; |
|
for (let i = 0; i < lines.length; i++) { |
|
const [a, b, c] = lines[i]; |
|
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) { |
|
return squares[a]; |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
var game = Game(); |
|
console.log(game); |
|
me.append(game); |
|
|
|
} // end postConfigure |
|
} |
|
//# sourceURL=GameTest.js |
|
]]></DefaultContent></Game> |
|
|
|
<TestUseState><DefaultContent><![CDATA[ |
|
((initValues) => { |
|
const useState = (defaultValue) => { |
|
let value = defaultValue; |
|
const getValue = () => value |
|
const setValue = newValue => value = newValue |
|
return [getValue, setValue] |
|
} |
|
|
|
const test1 = (initValue) => { |
|
const [count, setCount] = useState(initValue); |
|
|
|
console.log(count()); |
|
setCount(count() + 1); |
|
console.log(count()); |
|
setCount(count() + 1); |
|
console.log(count()); |
|
setCount(count() + 1); |
|
console.log(count()); |
|
} |
|
|
|
const test2 = (initValue) => { |
|
const [history, setHistory] = useState(initValue); |
|
const [currentMove, setCurrentMove] = useState(0); |
|
console.log(history()[currentMove()]); |
|
console.log(history()[currentMove()][0]); |
|
} |
|
|
|
test1(initValues[0]); |
|
test1(initValues[1]); |
|
test2(initValues[2]); |
|
|
|
})([13, "21", [Array(9).fill(null)]]) |
|
|
|
// works for integers (addition) |
|
// and works for strings (concatenation) |
|
// and works for history array |
|
//# sourceURL=TestUseState.js |
|
]]></DefaultContent></TestUseState> |
|
|
|
<HtmlExporter><DefaultContent><![CDATA[ |
|
(() => { |
|
const board = this.xpath("../Board"); |
|
|
|
// write Board subtree as an HTML string |
|
const asHtmlStr = (node, indent) => { |
|
let str = ""; |
|
const nodename = node.xhc().name(); |
|
if (node.first()) { |
|
str += `${indent}<${nodename}>\n`; |
|
str += asHtmlStr(node.first(), indent + " "); |
|
str += `${indent}</${nodename}>\n`; |
|
} |
|
else { |
|
if (nodename === "button") { |
|
str += `${indent}<${nodename} className="square" onClick="${node.onClick};">${node.text()}</${nodename}>\n`; |
|
} |
|
else { |
|
str += `${indent}<${nodename}/>\n`; |
|
} |
|
} |
|
if (node.next()) { |
|
str += asHtmlStr(node.next(), indent); |
|
} |
|
return str; |
|
} |
|
|
|
const str = asHtmlStr(board, ""); |
|
|
|
// insert in the HTML page |
|
const xhappspecific = $doc.querySelector("#xhappspecific"); |
|
xhappspecific.innerHTML = str; |
|
|
|
})() |
|
//# sourceURL=HtmlExporter.js |
|
]]></DefaultContent></HtmlExporter> |
|
</xholonClassDetails> |
|
|
|
<PhysicalSystem> |
|
|
|
<!-- see Xml2Xholon - DefaultContent only works if I include RoomModel somewhere before I need to use DefaultContent --> |
|
<RoomModel/> |
|
|
|
<!-- put this node first, because a sequence of Script nodes is configured in reverse order --> |
|
<HtmlExporter/> |
|
|
|
<!-- simple test of a single Square --> |
|
<!--<Square/>--> |
|
|
|
<Board/> |
|
|
|
<!--<TestUseState/>--> |
|
|
|
<!--<Game/> this works, but HTML does not yet display --> |
|
|
|
</PhysicalSystem> |
|
|
|
<SvgClient><Attribute_String roleName="svgUri"><![CDATA[data:image/svg+xml, |
|
<svg width="100" height="50" xmlns="http://www.w3.org/2000/svg"> |
|
<g> |
|
<title>Board</title> |
|
<rect id="PhysicalSystem/Board" fill="#98FB98" height="50" width="50" x="25" y="0"/> |
|
<g> |
|
<title>Square</title> |
|
<rect id="PhysicalSystem/Square" fill="#6AB06A" height="50" width="10" x="80" y="0"/> |
|
</g> |
|
</g> |
|
</svg> |
|
]]></Attribute_String><Attribute_String roleName="setup">${MODELNAME_DEFAULT},${SVGURI_DEFAULT}</Attribute_String></SvgClient> |
|
|
|
</XholonWorkbook> |