Script: Parser to Filter structured Markdown

This may be too specific to my use, but maybe someone generous may be willing to help.

When I used FoldingText, there was a great plugin for filtering structured Markdown by search terms–like, show me only content under headings with “XXX” in the name. I used this for structuring feedback to students. I would have content like this:

# Assignment 1

## Draft 1

### Billy

great paper

### Johnny 

bad paper

## Draft 2

### Billy

bad paper

### Johnny 

good paper

Then, I could filter and say, show me only the feedback I gave Billy.

I’d like to recreate that in Drafts. I am 80% of the way there with the following script:

// Student filter

let [loc, len] = editor.getSelectedLineRange(),
  str = editor.getTextInRange(loc, len),
  regex = /### /;
  ;

let split = str.split(/r?\n/,2);
let stu = split[0].trim();
stu = stu.replace(regex,"");

const fullContent = draft.content;
const fullStudent = fullContent.split('### ');
let student = "";

for (s in fullStudent)
{
    if (fullStudent[s].includes(stu))
    {
        student+=fullStudent[s] + "\n\n"        
    }
}

draft.setTemplateTag("student", student);

(In this script I don’t supply a search term, I just put the cursor on the line of the student I want to filter by and run the action.) After the script step, the “student” tag is sent to an HTML preview action.

However, what I don’t see here are the level 1 and 2 headings, which tell me the name of the assignment and draft version. In other words, I see:

Billy

great paper

Billy 

bad paper

But what I want to see is this:


Assignment 1
Draft 1
Billy
great paper
Draft 2
Billy
bad paper

How can I adjust my script to show those headings?

Thanks for any help!!!

1 Like

I don’t have time to write up an example this morning, but conceptually, you can do what you are already doing and first split the whole draft on the “#” headings, then loop over each of those sub-sections with the code you are using above to loop over the the “###” headings. Pseudo-logic:

sections = split on '#'
for section in sections
    // add section to string
    students = split on '###'
    for student in students 
        // add student to string

Hmm, I’m having a hard time figuring that out. I can’t tell how to grab the header name after splitting each section–after I split there’s so much additional text and I can’t tell how to strip out what I don’t need.

I wonder if it would be possible to use the navigation markers somehow to do this?

So, I’m getting stuck here but I’m probably approaching this wrong. I’m having a hard time wrapping my mind around the logic. I can grab the first level headers ok, but I can’t figure out how to get the second levels from there … the “jSplit” below pulls in the first level headers again but I don’t know what to do about that.

// Student filter

let [loc, len] = editor.getSelectedLineRange(),
  str = editor.getTextInRange(loc, len),
  student = "",
  regex = /### /;

let split = str.split(/r?\n/,2);
let stu = split[0].trim();
stu = stu.replace(regex,"");

const fullContent = draft.content;
let header1 = fullContent.split('\n# ');
for (let h in header1)
{
  let hSplit = header1[h].split("\n");
  student += hSplit[0] + "\n\n";

  let header2 = header1[h].split('\n## ');
  for (let j in header2)
  {
    let jSplit = header2[j].split("\n");
    alert(JSON.stringify(jSplit));
  }

}

const fullStudent = fullContent.split('### ');


for (s in fullStudent)
{
    if (fullStudent[s].includes(stu))
    {
        student+=fullStudent[s] + "\n\n"        
    }
}

draft.setTemplateTag("student", student);

Note: If you can import the Markdown into my filterCSV project you can match a string in eg a title and only keep that title and the children of it. Then you can export to Markdown (or some other things).

If you like the idea please play with it with test data - and raise an issue or feedback to me if need be.

var search_term = 'Bill'
var active_block = false
const student_layer = '###'
const flip = true

var xlines = '# Assignment 1\n\n## Draft 1\n\n### Billy\n\ngreat paper\n\n### Johnny \n\nbad paper\n## Draft 2\n\n### Billy\n\nbad paper\n\n### Johnny \n\ngood paper\n'

var line = ""

var filtered_lines =''
var prefix = ''
var array_lines = xlines.split(/\n/)

if(flip)
{
   filtered_lines = '# '+search_term+ '\n'
   prefix = '#'
}

//alert(array_lines)

for( var i=0 ; i < array_lines.length; i +=1 )
{
   line = array_lines[i].trim()
   if( line.length == 0 )
   { 
      continue;
   }
   if ( line[0] == '#' )
   {
      if(line.search(student_layer)==0)
      {
         if( line.search(search_term) > 0 )
         {
           active_block = true
           if(!flip)
           { 
             filtered_lines += line + '\n'
           }
         }
         else
         {
           active_block = false
         }
      }
      else 
      {
         filtered_lines += prefix+line+ '\n'
      }
   }
   else
   {
      if(active_block)
      {
         filtered_lines += line+ '\n'
      }
   }
}

alert(filtered_lines)
1 Like

change it to false to print your suggested order.

change this to your selection (draft.selection)

put this where you need it

1 Like

for your evaluation

wow! amazing. thanks so much Andreas! I’m going to pick this apart and learn how it works.

1 Like

the trick is to go line by line

Yes, I see, very clever. One thing I don’t understand: how does it know if a line comes after the line with the search term? I see that “active_block” becomes true if the search term is found on a line, but after the ### Billy line, how does “active_block” get set to true?

the full condition is:

  • line starts with “#”
  • line starts with “###”
  • line has search term

the flag (active_block) is only modified if the second condition is true.

i added an debug info to the action in the action directory this might help to understand it:

all headings in the first and second layer will be shown.
A third level heading will be shown if the search term was found. If the search term was found we activate the block to be shown.

If the search term is not in the third heading, the heading and the following block will not be shown

Thanks for the explanation. I’m sorry if I’m being dense, but I still am having trouble understanding :upside_down_face:. In the example image you posted, it shows this:

line 6, great paper, show=true,active_block=true

But this line doesn’t seem to meet any of the criteria you mentioned:

  • doesn’t start with ‘#’
  • doesn’t start with ‘###’
  • doesn’t have the search term

Why is it true in both “show” and “active_block”?

Oh, I just figured it out. The “active_block” boolean stays true until the the next header “#” triggers the conditionals to make it false. Nice. Thanks again for this, really helpful!!

yep. I was not clear i my definition

there is a second option / path for all lines that are non headings.

  • show if active_block is set.

the active_block is set or reset on the heading line

there is a third condition that skips empty rows (with the continue statement, that jumps to the next loop execution

I think asking questions until you get it is very important.

Thanks for your answers and your time to understand it.

Do you like the flipping of the name to level 1?

Yes, that was a very nice change. No reason to repeat the name in every block.