From 774459fa73c578e25fc374bce3de5743f34f0a0a Mon Sep 17 00:00:00 2001 From: "jeremy@jermolene.com" Date: Wed, 11 May 2022 13:51:11 +0100 Subject: [PATCH] Transclude: replace paramNames/paramValues with more robust JSON payload More details at https://github.com/Jermolene/TiddlyWiki5/pull/6666#issuecomment-1123719153 --- core/modules/filters/json-ops.js | 181 ++++++++++++++++++ core/modules/widgets/transclude.js | 29 +-- core/ui/Components/VisibleTransclude.tid | 10 +- .../tests/data/genesis-widget/RedefineLet.tid | 2 +- .../transclude/Parameterised-Name-Values.tid | 6 +- .../test/tiddlers/tests/test-json-filters.js | 98 ++++++++++ 6 files changed, 293 insertions(+), 33 deletions(-) create mode 100644 core/modules/filters/json-ops.js create mode 100644 editions/test/tiddlers/tests/test-json-filters.js diff --git a/core/modules/filters/json-ops.js b/core/modules/filters/json-ops.js new file mode 100644 index 000000000..a44c95c7a --- /dev/null +++ b/core/modules/filters/json-ops.js @@ -0,0 +1,181 @@ +/*\ +title: $:/core/modules/filters/json-ops.js +type: application/javascript +module-type: filteroperator + +Filter operators for JSON operations + +\*/ +(function(){ + +/*jslint node: true, browser: true */ +/*global $tw: false */ +"use strict"; + +exports["jsonget"] = function(source,operator,options) { + var results = []; + source(function(tiddler,title) { + var data = $tw.utils.parseJSONSafe(title,{}); + if(data) { + var item = getDataItemValueAsStrings(data,operator.operands); + if(item !== undefined) { + results.push.apply(results,item); + } + } + }); + return results; +}; + +exports["jsonextract"] = function(source,operator,options) { + var results = []; + source(function(tiddler,title) { + var data = $tw.utils.parseJSONSafe(title,{}); + if(data) { + var item = getDataItem(data,operator.operands); + if(item !== undefined) { + results.push(JSON.stringify(item)); + } + } + }); + return results; +}; + +exports["jsonindexes"] = function(source,operator,options) { + var results = []; + source(function(tiddler,title) { + var data = $tw.utils.parseJSONSafe(title,{}); + if(data) { + var item = getDataItemKeysAsStrings(data,operator.operands); + if(item !== undefined) { + results.push.apply(results,item); + } + } + }); + return results; +}; + +exports["jsontype"] = function(source,operator,options) { + var results = []; + source(function(tiddler,title) { + var data = $tw.utils.parseJSONSafe(title,{}); + if(data) { + var item = getDataItemType(data,operator.operands); + if(item !== undefined) { + results.push(item); + } + } + }); + return results; +}; + +/* +Given a JSON data structure and an array of index strings, return an array of the string representation of the values at the end of the index chain, or "undefined" if any of the index strings are invalid +*/ +function getDataItemValueAsStrings(data,indexes) { + // Get the item + var item = getDataItem(data,indexes); + // Return the item as a string + return convertDataItemValueToStrings(item); +} + +/* +Given a JSON data structure and an array of index strings, return an array of the string representation of the keys of the item at the end of the index chain, or "undefined" if any of the index strings are invalid +*/ +function getDataItemKeysAsStrings(data,indexes) { + // Get the item + var item = getDataItem(data,indexes); + // Return the item keys as a string + return convertDataItemKeysToStrings(item); +} + +/* +Return an array of the string representation of the values of a data item, or "undefined" if the item is undefined +*/ +function convertDataItemValueToStrings(item) { + // Return the item as a string + if(item === undefined) { + return item; + } + if(typeof item === "object") { + if(item === null) { + return ["null"]; + } + var results = []; + if($tw.utils.isArray(item)) { + $tw.utils.each(item,function(value) { + results.push.apply(results,convertDataItemValueToStrings(value)); + }); + return results; + } else { + $tw.utils.each(Object.keys(item).sort(),function(key) { + results.push.apply(results,convertDataItemValueToStrings(item[key])); + }); + return results; + } + } + return [item.toString()]; +} + +/* +Return an array of the string representation of the keys of a data item, or "undefined" if the item is undefined +*/ +function convertDataItemKeysToStrings(item) { + // Return the item as a string + if(item === undefined) { + return item; + } else if(typeof item === "object") { + if(item === null) { + return []; + } + var results = []; + if($tw.utils.isArray(item)) { + for(var i=0; i> style="display: inline-block;">
- <$list filter="[enlist:raw]" counter="counter" emptyMessage="(none)"> + <$list filter="[<@params>jsonindexes[]]" emptyMessage="(none)">
- <$text text=<>/><$text text=": "/><$text text={{{ [enlist:rawnth] }}}/> + <$text text=<>/><$text text=": "/><$text text={{{ [<@params>jsonget] }}}/>
<$genesis $type="element" $tag=<> style="background:white;color:black;padding:4px;"> - <$list filter="[enlist:raw] :filter[prefix[$]] +[limit[1]]" variable="ignore" emptyMessage=""" + <$list filter="[<@params>jsonindexes[]] :filter[prefix[$]] +[limit[1]]" variable="ignore" emptyMessage=""" - <$genesis $type="transclude" $remappable="no" $names="[enlist:raw]" $values="[enlist:raw]" recursionMarker="no" mode=<>> + <$genesis $type="transclude" $remappable="no" $names="[<@params>jsonindexes[]]" $values="[<@params>jsonindexes[]] :map[<@params>jsonget]" recursionMarker="no" mode=<>> <$slot $name="ts-raw" $depth="2"/> """> - <$genesis $type="transclude" $remappable="no" $names="[enlist:raw]" $values="[enlist:raw]" $$recursionMarker="no" $$mode=<>> + <$genesis $type="transclude" $remappable="no" $names="[<@params>jsonindexes[]]" $values="[<@params>jsonindexes[]] :map[<@params>jsonget]" $$recursionMarker="no" $$mode=<>> <$slot $name="ts-raw" $depth="2"/> diff --git a/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid index 136b16fb8..5f2a70f58 100644 --- a/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid +++ b/editions/test/tiddlers/tests/data/genesis-widget/RedefineLet.tid @@ -8,7 +8,7 @@ title: Output \whitespace trim \procedure <$let> \whitespace trim -<$setmultiplevariables $names="[enlist:raw]" $values="[enlist:rawaddprefix[--]addsuffix[--]]"> +<$setmultiplevariables $names="[<@params>jsonindexes[]]" $values="[<@params>jsonindexes[]] :map[<@params>jsongetaddprefix[--]addsuffix[--]]"> <$slot $name="ts-body"/> \end diff --git a/editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid b/editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid index d47ea440b..a80abc00c 100644 --- a/editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid +++ b/editions/test/tiddlers/tests/data/transclude/Parameterised-Name-Values.tid @@ -18,8 +18,8 @@ title: TiddlerOne \whitespace trim \parameters(zero:'Jaguar',one:'Lizard',two:'Mole') -<$list filter="[enlist]" counter="counter"> -{<$text text={{{ [enlist:rawnth] }}}/>:<$text text={{{ [enlist:rawnth] }}}/>} +<$list filter="[<@params>jsonindexes[]]"> +{<$text text=<>/>: <$text text={{{ [<@params>jsonget] }}}/>} + title: TiddlerTwo @@ -30,4 +30,4 @@ title: TiddlerTwo + title: ExpectedResult -

{0:}{1:}{2:}

{0:Ferret}

{0:Butterfly}{1:Moth}

{0:Beetle}{1:Scorpion}{2:Snake}

({zero:Beetle}{one:Scorpion}{two:Snake})

\ No newline at end of file +

{0:}{1:}{2:}

{0:Ferret}

{0:Butterfly}{1:Moth}

{0:Beetle}{1:Scorpion}{2:Snake}

({one:Scorpion}{two:Snake}{zero:Beetle})

\ No newline at end of file diff --git a/editions/test/tiddlers/tests/test-json-filters.js b/editions/test/tiddlers/tests/test-json-filters.js new file mode 100644 index 000000000..c5be2333f --- /dev/null +++ b/editions/test/tiddlers/tests/test-json-filters.js @@ -0,0 +1,98 @@ +/*\ +title: test-json-filters.js +type: application/javascript +tags: [[$:/tags/test-spec]] + +Tests the JSON filters. + +\*/ +(function(){ + +/* jslint node: true, browser: true */ +/* eslint-env node, browser, jasmine */ +/* eslint no-mixed-spaces-and-tabs: ["error", "smart-tabs"]*/ +/* global $tw, require */ +"use strict"; + +describe("json filter tests", function() { + + var wiki = new $tw.Wiki(); + var tiddlers = [{ + title: "First", + text: '{"a":"one","b":"","c":1.618,"d": {"e": "four","f": ["five","six",true,false,null]}}', + type: "application/json" + },{ + title: "Second", + text: '["une","deux","trois"]', + type: "application/json" + }]; + wiki.addTiddlers(tiddlers); + + it("should support the getindex operator", function() { + expect(wiki.filterTiddlers("[{First}getindex[b]]")).toEqual([]); + }); + + it("should support the jsonget operator", function() { + expect(wiki.filterTiddlers("[{First}jsonget[]]")).toEqual(["one","","1.618","four","five","six","true","false","null"]); + expect(wiki.filterTiddlers("[{First}jsonget[a]]")).toEqual(["one"]); + expect(wiki.filterTiddlers("[{First}jsonget[b]]")).toEqual([""]); + expect(wiki.filterTiddlers("[{First}jsonget[missing-property]]")).toEqual([]); + expect(wiki.filterTiddlers("[{First}jsonget[d]]")).toEqual(["four","five","six","true","false","null"]); + expect(wiki.filterTiddlers("[{First}jsonget[d],[e]]")).toEqual(["four"]); + expect(wiki.filterTiddlers("[{First}jsonget[d],[f]]")).toEqual(["five","six","true","false","null"]); + expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[0]]")).toEqual(["five"]); + expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[1]]")).toEqual(["six"]); + expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[2]]")).toEqual(["true"]); + expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[3]]")).toEqual(["false"]); + expect(wiki.filterTiddlers("[{First}jsonget[d],[f],[4]]")).toEqual(["null"]); + }); + + it("should support the jsonextract operator", function() { + expect(wiki.filterTiddlers("[{First}jsonextract[]]")).toEqual([`{"a":"one","b":"","c":1.618,"d":{"e":"four","f":["five","six",true,false,null]}}`]); + expect(wiki.filterTiddlers("[{First}jsonextract[a]]")).toEqual([`"one"`]); + expect(wiki.filterTiddlers("[{First}jsonextract[b]]")).toEqual([`""`]); + expect(wiki.filterTiddlers("[{First}jsonextract[d]]")).toEqual([`{"e":"four","f":["five","six",true,false,null]}`]); + expect(wiki.filterTiddlers("[{First}jsonextract[missing-property]]")).toEqual([]); + expect(wiki.filterTiddlers("[{First}jsonextract[d],[e]]")).toEqual([`"four"`]); + expect(wiki.filterTiddlers("[{First}jsonextract[d],[f]]")).toEqual([`["five","six",true,false,null]`]); + expect(wiki.filterTiddlers("[{First}jsonextract[d],[f],[0]]")).toEqual([`"five"`]); + expect(wiki.filterTiddlers("[{First}jsonextract[d],[f],[1]]")).toEqual([`"six"`]); + expect(wiki.filterTiddlers("[{First}jsonextract[d],[f],[2]]")).toEqual([`true`]); + expect(wiki.filterTiddlers("[{First}jsonextract[d],[f],[3]]")).toEqual([`false`]); + expect(wiki.filterTiddlers("[{First}jsonextract[d],[f],[4]]")).toEqual([`null`]); + }); + + it("should support the jsonindexes operator", function() { + expect(wiki.filterTiddlers("[{Second}jsonindexes[]]")).toEqual(["0","1","2"]); + expect(wiki.filterTiddlers("[{First}jsonindexes[]]")).toEqual(["a","b","c","d"]); + expect(wiki.filterTiddlers("[{First}jsonindexes[a]]")).toEqual([]); + expect(wiki.filterTiddlers("[{First}jsonindexes[b]]")).toEqual([]); + expect(wiki.filterTiddlers("[{First}jsonindexes[d]]")).toEqual(["e","f"]); + expect(wiki.filterTiddlers("[{First}jsonindexes[d],[e]]")).toEqual([]); + expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f]]")).toEqual(["0","1","2","3","4"]); + expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[0]]")).toEqual([]); + expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[1]]")).toEqual([]); + expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[2]]")).toEqual([]); + expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[3]]")).toEqual([]); + expect(wiki.filterTiddlers("[{First}jsonindexes[d],[f],[4]]")).toEqual([]); + }); + + it("should support the jsontype operator", function() { + expect(wiki.filterTiddlers("[{First}jsontype[]]")).toEqual(["object"]); + expect(wiki.filterTiddlers("[{First}jsontype[a]]")).toEqual(["string"]); + expect(wiki.filterTiddlers("[{First}jsontype[b]]")).toEqual(["string"]); + expect(wiki.filterTiddlers("[{First}jsontype[c]]")).toEqual(["number"]); + expect(wiki.filterTiddlers("[{First}jsontype[d]]")).toEqual(["object"]); + expect(wiki.filterTiddlers("[{First}jsontype[d],[e]]")).toEqual(["string"]); + expect(wiki.filterTiddlers("[{First}jsontype[d],[f]]")).toEqual(["array"]); + expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[0]]")).toEqual(["string"]); + expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[1]]")).toEqual(["string"]); + expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[2]]")).toEqual(["boolean"]); + expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[3]]")).toEqual(["boolean"]); + expect(wiki.filterTiddlers("[{First}jsontype[d],[f],[4]]")).toEqual(["null"]); + }); + +}); + +})(); + \ No newline at end of file