Batch rename to initial lower case

Hi,

I’d like to rename all my tags to purely lowercase, and I’ve been trying to use Batch Renaming to do it.

The following regex works in BBEdit:

Input: Translate
Search: ^([A-Z])(.*)
Replace: \l\1\2
Output: translate

But it doesn’t work in DT4, where:

results in \1Translate.

Is this just one of those features of regex that hasn’t been implemented, or am I doing something wrong.

Is there another way of doing this without using AppleScript?

Thanks!

Regular expressions are only used by the Scan Name/Text actions, all other actions like Change Name are only able to use the numbered results.

The Script with Input/Output action might be a good alternative, there are already scripts for lower and upper case (see ~/Library/Application Scripts/com.devon-technologies.think/Script Output/)

1 Like

Firstly, your RE is borken, even in BBEdit.
Example: “MyFunnyTag” will be converted to “myFunnyTag”.
It’s a serious case of overengineering: If you want to lowercase a string, just do it:
(.*) => \l\1

Or is that the intended behavior, as in the title of your post? Whereas in the text, you say that you want to rename tags to “purely lowercase”, which is not quite the same as “initial lower case”.

DT uses Apple’s framework for REs. And lowercasing in the replace function is not really standard anyway. I think Perl can do that, but JavaScript (for example) can’t.
You can find out more about the flags supported by NSRegularExpression here:

though I don’t think that DT has implemented all or even any of them.

So, I’d suggest that you use a tiny script doing what you want, something like

(() => {
  const app = Application("DEVONthink");
  const tg = app.currentDatabase.tagsGroup;
  tg.children().forEach(t => t.name = t.name().toLowerCase());
})()

Select the database for which you want the tags to be lowercased and run the script.
No Regular Expressions needed.

If you want only the first character lowercased, change the code in the forEach to something like

t.name = t.name().charAt(0).toLowerCase() + t.name().substr(1);

The code is written in JavaScript/JXA, but I’m confident that the same result can be achieved with AppleScript. There’s an eye-watering example over at Apple’s:

that only works for ASCII characters. Oh, well, the good old times …

1 Like

I’ll have a look at that action – thanks, Cris!

Works as intended – I was basically after the principle of what the rename action can do.

I’ve never really used JXA, but perhaps I should start… so thanks for your very helpful examples for another approach, as always!

@cgrunenberg

I’ve never used the script actions before, so it took a bit of trial and error, but now it works, thank you very much!

@chrillek

While I’ve gone with Cris’s suggestion for simplicity, I really appreciate the time you spent to give such a comprehensive answer – and when I’ve got a bit more free time, I shall look at it, and your other JXA posts in more details to understand more. Thank you again!

So, you’re now lowercasing the whole tag, not only the first letter?

If you have any nested tags this will only affect top level tags. I would suggest using a whose filter instead of database.tagsGroup.children.

// Ordinary tags only:
const tags = database.parents.whose({
	_match: [ObjectSpecifier().tagType, "ordinary tag"]
});

// All tags, including group tags:
const tags = database.parents.whose({
	_not: [
		{_match: [ObjectSpecifier().tagType, "no tag"]}
	]
});

You can even get all tags across open databases at once:

const tags = app.databases.parents.whose({
	_match: [ObjectSpecifier().tagType, "ordinary tag"]
});

That returns an array of arrays, i.e.

[
	[app.databases.byId(1).parents.byId(148),
	 app.databases.byId(1).parents.byId(4887),
	 ... ],
	[app.databases.byId(4).parents.byId(838),
	 app.databases.byId(4).parents.byId(975),
	 ... ]
]

… so you need to flatten it before proceeding:

/* Convert all ordinary tags in open databases to lowercase */
(() => {
	const app = Application("DEVONthink");
	const tags = app.databases.parents.whose({
		_match: [ObjectSpecifier().tagType, "ordinary tag"]
	});
	tags().flat().forEach(t =>
		t.name = t.name().toLowerCase()
	);
})
1 Like

Yes – as I said, I was mainly interested in whether I could use the regex output (using \l and its variants) at all. The specific details of the casing could wait till I’d got the principle sorted, and for the time being, all lowercase will do fine.

Great catch, thanks.