Exporting dataviews to plain markdown with templater

What I’m trying to do

I need to export the result of several dataview as plain markdown, so I can use it as basis for articles. After a lot of googling, it seems the best/least worst way to do this is to use templater to run the dataview query, and dump the result in the current note. But what is the correct syntax???

Things I have tried

One of the queries I would need to export as plain markdown is below. No matter how I tweak this:

<%
dv = this.app.plugins.plugins["dataview"].api;
query = 'LIST aliases FROM [[]] AND #sometag WHERE aliases';
let out = await dv.queryMarkdown(query);
%>

I ALWAYS end up with Template syntax error: missing ) after argument list

What am I missing? What is wrong here, and why?

Thanks!

2 Likes

Try the following for starters:

<%*
dv = this.app.plugins.plugins["dataview"].api;
query = 'LIST aliases FROM [[]] AND #sometag WHERE aliases';
let tR = await dv.queryMarkdown(query);
%>

There are two vital changes here:

  • Changed the query to Templater command template by using the <%* syntax
  • Appended the output to tR, which is where to do that

I’m a little unsure if you also need to handle the result of that call, so that you need to do something like queryMarkdown(...).value
Even better would then be to keep the out, check it for out.successful, and then append out.value to tR.

Hi @holroy ,

If I change the template as you suggest, adding the asterisk yelds “Template syntax error: Unexpected token ‘*’”

The asterisk is one of the many sintax variants I alluded to in the original message.

FTR, if I remove that asterisk, I get this other error:

“Template syntax error: Identifier ‘tR’ has already been declared”

You do need to use Templater command syntax for this to work, as this is the way to execute javascript code through Templater. And when you use javascript you need to declare all the new variables, and not declare those predefined. So dv and query should have been declared, and you shouldn’t have written let tR. In addition the disclaimer I had at the end regarding what is returned, was indeed the case.

Accounting for all of this, and slightly modifying the query for me to get some result at all, I ended with this query:

<%*
const dv = this.app.plugins.plugins["dataview"].api;
const query = 'LIST aliases WHERE aliases LIMIT 5';
const result = await dv.queryMarkdown(query);

if ( result.successful ) {
 tR += result.value
} else {
  tR += "~~~~\n" + result.error + "\n~~~~"
}
%>

Here you see that I do declare the new variables, and add another variable to store the result of the query. (You can of course change the query back to yours, if that is a working query in your setup)

In addition I check that query executed successfully, and if so I present the result value into the tR variable. If it failed for some reason, the error message is presented into your file.

This time the query is tested and verified that it works! :smiley:

2 Likes

Hi @holroy ,

first of all yes , now it works. THANKS!

As far as I can see, it works even selecting on some tag, in this way:

LIST aliases FROM #sometag WHERE aliases etc...

At this point, what I’d still need is

  1. confirmation that adding “FROM #sometag” in THAT way and place is the right way to select only the notes that contain a certain tag in the frontmatter

  2. how to make the whole thing PARAMETRIC

That is, I now to modify that templater code so that I can create a new note whose frontmatter includes a line like e.g.:

wanted_tags: "X'

so that I can replace “X” with any ONE tag e.g. “linux” or possibly a boolean combination of some tag, i.e “debian OR ubuntu”, and then, when I run templater, it creates the plain markdown list of all and only the aliases in notes with the combination of tags defined in “wanted_tags”

Thanks in advance for examples or pointer to the right documentation!

When are you inserting this template? At creation of the note, or after the note has been populated with content?

If at creation you are getting kind of a race condition, where any query you put into the template execution can’t depend on frontmatter fields which are not populated yet. In this case you need to actually insert the query as a query into the note itself.

If after creation, so that the content has been created, and there are some values within the frontmatter tags you use, you can insert the template and do the query with those current values. Now it becomes more of a task on how you want to combine the tags within your query.

So to get you started on doing such a query here is a query I ran on my test data:

tagsToMatch:: #frog, #water 

```dataview
TABLE WITHOUT ID 
  task.tags,
  this.tagsToMatch,
  "<span class='" + choice(anyFlag, "cellOK", "cellWARN") + "'>" + anyFlag + "</span>" as Any,
  "<span class='" + choice(allFlag, "cellOK", "cellWARN") + "'>" + allFlag + "</span>" as All,
  "<span class='" + choice(noneFlag, "cellOK", "cellWARN") + "'>" + noneFlag + "</span>" as None

FLATTEN file.tasks as task

FLATTEN any(map(this.tagsToMatch, (m) => contains(task.tags, m))) as anyFlag
FLATTEN all(map(this.tagsToMatch, (m) => contains(task.tags, m))) as allFlag
FLATTEN none(map(this.tagsToMatch, (m) => contains(task.tags, m))) as noneFlag

WHERE file.folder = "ForumStuff/f57/f57484"
WHERE file.tasks AND task.tags
```

Where top part of the output is as follows:

(This also utilises some extra markup and CSS to color cells to make it easier to spot the various responses). So your result will most likely not look as colorful, and you do need to remove the WHERE file.folder ... line for it produce any results at all for you.

The query above is a TABLE, so to transform it back to a TASK query, you’ll need to remove the task. from any task references, and you don’t need some of the clauses related to singling out the tasks. So given you want it to match all the tags, it’ll look like:

```dataview
TASK
FLATTEN all(map(this.tagsToMatch, (m) => contains(tags, m))) as allFlag
WHERE file.folder = "ForumStuff/f57/f57484"
WHERE tags and allFlag
```

( You’d still need to remove the WHERE file.folder ... line, and if you want to use a FROM line instead it needs to go above the FLATTEN line.
Or you could potentially move the entire expression from FLATTEN directly instead of the allFlag use in the last line. Play around with it, until you get a feel for what the alternate options does)


In summary, if your template is run at creation of the new file it can’t reference any frontmatter tags which hasn’t gotten a value yet. And since the query is executed at template insertion time, that is that. In this case, you need to insert the query into your note, to await the entry of values into the tags to match.

If on the other hand, you insert the template after the tags has been populated with something, you still need to find the proper query to use for which I gave some option in the query above. And even now the query result will be that of the insertion time of the template.

1 Like

Hi again @holroy , and again thanks for your time!

I will study/test this latest explanation, but right now I have to leave in 20 minutes, and probably won’t have time until tomorrow evening to go back to this.

Meanwhile, I will try to explain myself better what I currently believe should be my main use of what I am learning in this thread.

First, I would prepare once and for all a standard template named something like alias-collector, which has in the frontmatter one line saying "wanted_tags: [“x”]

Then, EVERY time I need to collect all the aliases in all my notes that contain e.g. the tag #recipes in their frontmatter, I would:

  1. create a new empty note
  2. insert the alias-collector template into it
  3. change “x” in the wanted_tags key of the frontmatter to (in this case) “recipes”
  4. save the note
  5. click on the templater button in the sidebar, and select the templater we are talking about, which, I hope, would read “recipes” from the frontmatter of the note I called it into, and list all the aliases from the notes that have “recipes” among their tags
  6. start writing a new article, recipe, whatever I need to write… using the templater output as rough outline

Is it going to be one or possibly multiple tags, and if multiple should all be present?

(thanks for also helping me to clarify to myself my own requirements…)

I think “if multiple they should all be present” would fit best what I hope to do with this function (assuming that one tag only IS a sub/corner case of "multiple tags should all be present).

The requirement to do single or multiple values of the wanted_tags, do cause the query to be a little more hairy, but it seems like the following template should work:

<%*
const dv = app.plugins.plugins.dataview.api

const query = `
LIST aliases
FROM [[]]
FLATTEN all(map(choice(typeof(this.wanted_tags) = "string",
        list(this.wanted_tags),
        this.wanted_tags),
        (m) => econtains(file.etags, m))) as allFlag
WHERE file.path != this.file.path
WHERE aliases AND allFlag
`

const result = await dv.queryMarkdown(query, tp.config.target_file.path);
if ( result.successful ) {
 tR += result.value
} else {
  tR += "~~~~\n" + result.error + "\n~~~~"
}
%>

This require a frontmatter tag of either of the following (just one of them, though) :

---
wanted_tags: "#recipe"
wanted_tags: [ "#recipe" ]
wanted_tags: [ "#recipe", "#something" ]
---

You might want to verify the query in your environment as a normal query, just to verify that it’s correct. Then after that verification, you should be good to go.

Note that I’ve used econtains(file.etags, ... ) to match against the entire tag, so no partial tag matches will survice, like if you also had #recipelist, it wouldn’t match against that. This also requires the usage of "#tag" instead of just tag. So depending on your setup, and level of errors, the query/template could be modified.

But hopefully, this should get you going!

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.