Help with a complex filter needed

Hi all,

What I want to achieve is a “colspan” Map for TiddlyWiki tables in wikitext
I want to create a “rowspan” Map in a similar manner but let’s start with the colspan Map since that appears to be easier

I start with a TiddlyWiki table and put it in a variable wikitextTable:

|Cell1 |Cell2 |Cell3 |Cell4 |
|Cell5 |Cell6 |Cell7 |<|
|Cell5 |~|Cell7 |Cell8 |
|>|>|Cell10 |Cell11 |

I get the single rows:

[<wikitextTable>!is[blank]splitregexp[\n]!suffix[|h]!suffix[|c]!suffix[|f]!suffix[|k]]

I get the single cells:

[<wikitextRows>!is[blank]splitregexp[\|]]

Now I want to iterate over the single cells and create a map in the following style for the last row for example:

0 0 3 1

It can get more complex when the last row looks like this:

|>|>|Cell10 |<|

and the output should be

0 0 4 0

but it could also be “invalid”

|>|>|<|Cell11 |

also this would be

0 0 3 1

Does anybody have an idea how to create that filter?
Would be absolutely appreciated!

Thanks in advance,
Simon

Its not clear to me where the 3 and 1 map values come from. I assume they relate to the intended size (or order?) of those columns.

Nonetheless, something to remember when working with your “map” lists: since they can contain multiple items with the same value, you will probably want to use enlist:raw[...] when handling those map lists, so that the duplicate map values will be preserved rather than collapsed into single values by the default “dominant append” filter handling.

-e

Hi @EricShulman , thanks for replying

0 means it spans no cell (I need that value)
1 spans only one cell (standard)
2 spans two cells
3 spans three cells
4 spans 4 cells

Hi @BurningTreeC

I was wondering how @Charlie_Veniot would solve this one, and I think he would search-replace using unicode characters, so here’s a try at it:

  • Take your row (as a string)
  • Replace every || by |x|
  • Remove first and last | and split by |
  • Replace every cell value not matching > or < by a 1 (with a regexp)
  • Replace every > by a :arrow_right:
  • Replace every < by a :arrow_left:
  • Join values without separator
  • Find substrings matching regexp ➡️*1⬅️* and replace the 1 by the substring length
  • Replace every :arrow_right: or :arrow_left: by a 0
  • Split each char, join with space

Hope this helps,

Fred

Argh, it will only work if collapsed cells are <10… :frowning_face:

Hi @tw-FRed and thank you!
I took inspiration from you!

\function tf.get.colspan.map() [<wikitextTable>!is[blank]splitregexp[\n]!suffix[|h]!suffix[|c]!suffix[|f]!suffix[|k]nth<rowIndex>splitregexp[\|]!match[]] :map[!match[>]!match[<]then[1]else<currentTiddler>] :map[match[>]then[0]else<currentTiddler>] :map[match[<]then[x]else<currentTiddler>] +[join[ ]]

\function tf.get.rowspan.map() [<wikitextTable>!is[blank]splitregexp[\n]!suffix[|h]!suffix[|c]!suffix[|f]!suffix[|k]splitregexp[\|]!match[]] :filter[<index>remainder<columns>match<previousColIndex>] :map[!match[~]then[1]else[0]] +[join[ ]]

Like this I get a “map” of the form:

1 1 0 1 0 1 1 x

where 0 stands for > and x stands for < (I could also let that one be <)


I try doing this in two steps.
Now I’d need a filter that accumulates consecutive or single 0, adds them to the next 1 as amount of consecutive 0 + 1 and replaces those 0 with a marker, say _ (so that later on I can use the produced list to get indexes correctly)

In a similar way and in a second step, I think I’d need to reverse the produced list and look for consecutive or single x and do the same as before…

Any idea appreciated :slightly_smiling_face:

Yours is a precarious tip-toe across the mined and barb-wired landscape that is my sponge. Maybe not the safest place to travel.

(aside: your arrow emojis are semantically oh-so-much better; I only looked at your code after I finished mine.)

My snippet of code to explore:

<$let wikitextTable="""|Cell1 |Cell2 |Cell3 |Cell4 |
|Cell5 |Cell6 |Cell7 |<|
|Cell5 |~|Cell7 |Cell8 |
|>|>|Cell10 |Cell11 |
|>|>|Cell10 |<|
|>|>|<|Cell11 |""">

<$list variable="row" filter="[<wikitextTable>splitregexp[\n]]"> 
<hr style="border:1px solid red">
row: <<row>>
<hr style="border:1px solid red">
(
<$list variable="cell" filter="[<row>search-replace:g[>|],[🟦]search-replace:g[|<],[🟩]split[|]butfirst[]butlast[]]"> 
<$let range🟦={{{ [<cell>split[🟦]count[]subtract[1]compare:number:gt[0]] }}}
          range🟩={{{ [<cell>split[🟩]count[]subtract[1]compare:number:gt[0]] }}}>
<$list filter="[range<range🟦>compare:number:gt[0]]">0 </$list>
            <$text text={{{ [[1]add<range🟦>add<range🟩>]  }}}/>
            <$list filter="[range<range🟩>compare:number:gt[0]]">0 </$list>
</$let>

</$list>
)
</$list>

</$let>


The result:

For the full guacamole (the “here” to “there” thinking):

<$let wikitextTable="""|Cell1 |Cell2 |Cell3 |Cell4 |
|Cell5 |Cell6 |Cell7 |<|
|Cell5 |~|Cell7 |Cell8 |
|>|>|Cell10 |Cell11 |
|>|>|Cell10 |<|
|>|>|<|Cell11 |""">

! wikitextTable as-is

<<wikitextTable>>

<hr>

! wikitextTable, text-ified rows

<$list filter="[<wikitextTable>splitregexp[\n]]">
{{!!title}} <br>
</$list>

<hr>

! wikitextTable, text-ified rows, prepping content


<$list variable="row" filter="[<wikitextTable>splitregexp[\n]]"> 
<hr style="border:1px solid red">
row: <<row>>
<hr style="border:1px solid red">
<$list variable="cell" filter="[<row>search-replace:g[>|],[🟦]search-replace:g[|<],[🟩]split[|]butfirst[]butlast[]]"> 
<<cell>>
<hr>
</$list>
</$list>

<hr>

! wikitextTable, text-ified rows, setting up numbers


<$list variable="row" filter="[<wikitextTable>splitregexp[\n]]"> 
<hr style="border:1px solid red">
row: <<row>>
<hr style="border:1px solid red">
(
<$list variable="cell" filter="[<row>search-replace:g[>|],[🟦]search-replace:g[|<],[🟩]split[|]butfirst[]butlast[]]"> 
<$let range🟦={{{ [<cell>split[🟦]count[]subtract[1]compare:number:gt[0]] }}}
          range🟩={{{ [<cell>split[🟩]count[]subtract[1]compare:number:gt[0]] }}}>
<$list filter="[range<range🟦>compare:number:gt[0]]">0 </$list>
            <$text text={{{ [[1]add<range🟦>add<range🟩>]  }}}/>
            <$list filter="[range<range🟩>compare:number:gt[0]]">0 </$list>
</$let>

</$list>
)
</$list>

</$let>

All of that should have some validation added:

  • check that the first cell in the row isn’t “|<|”, and handle that accordingly
  • check that the last cell in the row ins’t “|>|”, and handle that accordingly
2 Likes

Hi @Charlie_Veniot and thanks for your help!
I got some new insights from your approach!

I cannot use a list widget though, I need to use filter runs in functions and store the results in variables

Use of list widgets is just the quickest way (no fuss, no muss) to demonstrate a “process”. It is meant to be “descriptive”, not “prescriptive”.

That is how everybody should use any “solution”.

I expect any snippets I provide to be studied for the description of the process (the mental gymnastics of getting from “A” to “Z”), so that you can then implement your solution.

Well, life does have a way of occasionally confounding my expectations.

Hi!

Here is some code based on functions which should do the job, even though it’s certainly not very robust to malformed wikitext tables…

I was heavily inspired by previous solutions in this thread, thank you all! :wink:

My main disappointment is I couldn’t use fancy unicode chars, because the solution relies on strings length and the result is wrong when the input contains unicode, be it with length[] or with split[]count[] filters.

<!-- Replace every cell value except ">" or "<" by "1" -->
\function fn.value-to-1()
  [all[]]
 :map[<currentTiddler>!regexp[>|<]then[1]else<currentTiddler>]
 +[join[ ]]
\end

<!-- Counts the occurrences of "c" in string "s" -->
\function fn.nb(c,s)
  [<s>split<c>count[]subtract[1]]
\end

<!-- Replaces sequences of "from" characters by "to" characters -->
\function fn.expand.cell(from,to)
  [fn.nb<from>,<cell>!match[0]]
  :map:flat[range<currentTiddler>]
  :map[<to>]
  +[join[]]
\end

<!-- Returns <<cell>> length -->
\function fn.cell.ln() [<cell>split[]count[]]

<!-- Computes current <<cell>> weight -->
\function fn.compute.cell(cell)
  =[fn.expand.cell[>],[0|]]
  =[fn.cell.ln[]]
  =[fn.expand.cell[<],[|0]]
 +[join[]]
\end

<!-- Computes current <<row>> weights -->
\function fn.compute.row(row)
  =[<row>split[|]]
  :map:flat[fn.compute.cell<currentTiddler>]
  +[join[|]split[|]join[ ]]
\end

<!-- Sanitizes input (not perfect, but you get the idea... -->
\function fn.sanitize()
  [all[]]
  :map[<currentTiddler>search-replace:g[||],[|x|]]
\end

<!-- Replaces actual cell values by "1"s -->
\function fn.get.ones()
  [all[]]
  :map[<currentTiddler>split[|]butfirst[]butlast[]trim[]fn.value-to-1[]enlist-input:raw[]join[|]]
\end

<!-- Collapse cells (">|1" becomes ">1" -->
\function fn.collapse()
  [all[]]
  :map[<currentTiddler>search-replace:g[>|],[>]search-replace:g[|<],[<]]
\end

<!-- Maps a table to its colspans -->
\function row.map(table)
   [<table>splitregexp[\n]]
  :map[<currentTiddler>fn.sanitize[]]
  :map[<currentTiddler>fn.get.ones[]]
  :map[<currentTiddler>fn.collapse[]]
  :map[fn.compute.row<currentTiddler>]
\end

And here’s an example:

<$let table="""|Cell1 |Cell2 |Cell3 ||
|Cell5 |Cell6 |Cell7 |<|
|Cell5 |~|Cell7 |Cell8 |
|>|>|Cell10 |Cell11 |"""
>

<$list filter="[row.map<table>]">
<$text text=<<currentTiddler>>/><br>
</$list> 
</$let>

A solution with functions.json (2.1 KB)

I tried to add some comments, but can explain more if asked :slightly_smiling_face:

Fred

1 Like

Thank you so much @tw-FRed !

This is amazing!

I’ll try it out in my project and let you know!

Thank you <3

1 Like

@tw-FRed

with some small modifications I can use your solution directly to get the colspans :partying_face:

now to the rowspans :slightly_smiling_face:
They use the ~
I get a column, let’s say the second one:

<$let columns="6" previousColIndex="1">

<$let column={{{ [<wikitextTable>splitregexp[\n]split[|]join[ ]enlist-input:raw[]] :map:flat[<index>remainder<columns>match<previousColIndex>then<currentTiddler>] :and[!match[]join[|]] :and[addprefix[|]addsuffix[|]] }}}>

<$text text=<<column>>/>

</$let>

</$let>

The column would then have the same form as a row
Then I believe I can reuse your functions somehow

@tw-FRed

Hi and thank you again for your solution!

Here you can see your solution in action:

And here are the procedures where I integrated your solution:

This community is great.

3 Likes

Wow! Your work on this plugin is impressive!
I’m happy I could contribute :blush:

Fred

1 Like

Hi @tw-FRed ,

do you have an idea how to accomplish this:

  • I have a row in the style 0 2 1 1 3 0 0 for example
  • The last cell is the “3” cell, the following cells are collapsed (not shown)
  • I need to check the 0 2 1 1 3 0 0 string for each cell and check if after it are only “0” exclusively
  • In this case this would be true for the “3” cell
  • I get the cell through splitting the string split[ ] and nth<colIndex>
[[0 2 1 1 3 0 0]split[ ]nth<colIndex>]

Hope this makes sense,
any help appreciated!

Simon

Hi Simon,

Looks like this would work:

[[0 2 1 1 3 0 0]split[ ]butfirst<colIndex>!match[0]count[]match[0]]

Edit: Result is 0 if current cell is only followed by zeros, else no result.
Edit 2: Result is also 0 if current cell is last of the row.

Fred

Thanks so much @tw-FRed !

I couldn’t wrap my head around this!

1 Like