Forum Replies Created
-
AuthorPosts
-
Kal Starkis
MemberYou’re very welcome Masood! Okay, I wasn’t sure what your level of experience was with regex, but I can see why you used the approach you did. I’ll shoot you an email now.
Kal Starkis
MemberHi Masood
Firstly, in response to your original question…
When working with regular expressions, you can be far more efficient with your code if you use quantifiers. For example, instead of writing
(\d\d\d)you can just write\d{3}. Since you’re not sure how many digits there will be in a row, you can just search for one or more with a plus character:\d+. Each group of digits may or may not have a comma after it—in other words, the comma is optional. We can match an optional character with a question mark:\d+,?. The complete number has one or more of these groups, so our regex now looks like this:(\d+,?)+. Now you only want to match currencies (not phone numbers, years, etc), so lets’s only match strings that have one of the currency symbols before it. We can achieve this with a ‘positive lookbehind’:(?<=\$|ÂŁ|€). We’re not really interested in everything after the decimal point, so we’ll just ignore it. So… here’s our complete regex:(?<=\$|ÂŁ|€)(\d+,?)+(For more help with regex basics, check out my article: [Getting started with regular expressions](https://inkwire.app/articles/getting-started-with-regular-expressions.html).)
So how do we combine this approach with capture groups, so you can reconstruct each number with $1, $2, etc? Unfortunately we can’t. Quantifiers don’t dynamically create groups as they go. This kind of problem isn’t one we can easily solve with regular expressions alone, so yeah… we need to write a script.
I’ve modified and simplified Ariel’s script to your purposes here. You can define a thousands separator (set to a comma by default) and whether you want to ignore 4-digit numbers (set to true by default). The script will search your selected text, or the whole story if you place your cursor anywhere in the text or select a text frame…
app.doScript (main, undefined, undefined, UndoModes.ENTIRE_SCRIPT, "Format numbers"); function main(){ // Script settings var delimiter = ","; // Thousands separator var ignore4 = true // Ignore 4-digit numbers? // Get selection var mySelection = textSelection(); if (!mySelection) { alert("No text selected."); return; } var mySourceText; var mySelectionType = mySelection.constructor.name; if (mySelectionType == "InsertionPoint" || mySelectionType == "TextFrame") { // Select whole story mySourceText = mySelection.hasOwnProperty("storyOffset") ? mySelection.storyOffset.parentStory : mySelection.insertionPoints[0].parentStory; } else { mySourceText = mySelection; } var mySearch = /(?<=\$|£|€)(\d+,?)+/; app.findGrepPreferences = null; app.findGrepPreferences.findWhat = mySearch.source; var myFinds = mySourceText.findGrep(); var f, n, t; while (f = myFinds.pop()){ n = f.contents.toString().replace(/,/g,''); // Remove existing commas from string // Add commas to thousands if (!ignore4 || (n && n.length > 4)){ t = n.split('').reverse().join('').match(/.{1,3}/g).join(delimiter).split('').reverse().join(''); } t && f.contents = t; } function textSelection() { // Returns text-based object if ( app.selection.length == 1 && app.selection[0].hasOwnProperty("findGrep") ) { return (app.selection[0]); } // Invalid selection return null; } }Let me know if you want me to explain anything about the script. Cheers!
Kal Starkis
MemberAh… so the problem isn’t with the destination but the source. The fix should be much the same. Just as a hyperlink destination can be of various types in InDesign (a page, text or a URL), so a hyperlink source can be of different types too (a page item, text or cross reference). Page items don’t have a
sourceTextproperty, so if you have these in your document the script will no-doubt fail with the kind of error you’re seeing. (A consequence of banging out code without thorough testing!)It sounds like you’re about to roll up your sleeves and get stuck into a scripting project of your own, so why don’t you have a go at fixing my script? Just apply the same logic I used before to catch the error, and you should be good to go. Let me know if you can’t get it to work.
I don’t have a lot of spare time ATM, but I’m sure I can find time to share my thoughts on your project. You can always shoot me a message via the feedback form on my website (inkwire.app). If you have more technical questions, probably best just to ask questions here, at the Adobe forums or on Stack Overflow.
Kal Starkis
MemberHi Khaled. It’s been a good while since I threw that script together, but looking back at it now, I can see that it only works if all the hyperlinks in the document have URL Destinations. If you have other types of destinations (like Text Destinations for example), the script will fail at the line that tries to get the
destinationURL. So let’s fix it…I think the easiest way is to just catch the error when it happens. Since we’re only interested in finding actual URLs, anything that isn’t a URL can safely be ignored. So, here’s an updated version of the script that does just that…
// Get all our document hyperlinks var myDocument = app.documents[0]; var myDocumentHyperlinks = myDocument.hyperlinks.everyItem().getElements(); // Create an empty object to store all the URLs (along with linked text) var indexOfURLs = {}; // Iterate over all the document hyperlinks and add each one to the object for (var i = 0; i < myDocumentHyperlinks.length; i++) { var hyperlink = myDocumentHyperlinks[i]; // Get linked text (title) and URL var title = hyperlink.source.sourceText.contents; var url; try { url = hyperlink.destination.destinationURL; } catch (err) { // The destination is not a URL. Ignore it. continue; } // Add title and URL to the list indexOfURLs[title] = url; } // Print out all the URLs for (var key in indexOfURLs) { $.writeln(key + ": " + indexOfURLs[key]); }Please let us know how you go. :-) (I find that it’s not uncommon to give up precious time on forums like this one to try and help others only to never hear back from people, as was the case with the OP on this thread. We’re all human and a little ‘thanks for your help’ goes a long way to keeping any community alive and helpful.)
Kal Starkis
MemberI believe ExtendScript is based on ye olde ES3, while default parameters are a feature of ES6, so you’re out of luck I’m afraid.
As for your workaround, that should work in JavaScript, but ExtendScript can be a strange and unpredictable beast at times. In your sample code there, I notice you have ‘smart’ quotes around ‘undefined’, but I’m guessing that’s not in your actual code? You could try testing your variables directly against undefined instead:
function doSomething (a, b, c) { b = (b !== undefined) ? b : null; c = (c !== undefined) ? c : 100; // do scripty things }Kal Starkis
MemberThe printer has agreed to print 1 copy x 500 unique artwork files?? That sounds like a crazy way to do it, even if you do work out how to automate the numbering. I’d be choosing a printer who can do the automatic numbering for you, with just one artwork file.
Kal Starkis
MemberMichael, try defining your problem in words like this… I’m looking for a number that occurs AFTER a character that is NOT a whitespace, and BEFORE a whitespace. From there, you can refer to a regex cheatsheet (https://www.rexegg.com/regex-quickstart.html) or even use InDesign’s built-in cheatsheet (the little dropdown menu to the right of the GREP search field) to convert your sentence into regex.
What you’re looking for occurs ‘after’ something. This indicates a positive lookbehind—in other words, something must occur BEFORE we start matching any text. A positive lookbehind looks like this:
(?<=…)One way to represent ‘a character that is NOT a whitespace’ is by
\S. So the positive lookbehind now looks like this:(?<=\S)‘a number’ is, as Mr Jongware pointed out, simply a string of digits: \d+
What we’re looking for occurs ‘BEFORE’ something. This indicates a positive lookahead—in other words, something must occur AFTER our match. A positive lookahead, as Jongware pointed out, looks like this:
(?=…). A whitespace is represented by, so our positive lookahead becomes(?=).Put it all together, and we have:
(?<=\S)\d+(?=)When you test this, you might find a problem. It matches the ‘234’ in ‘1234’. That’s because the digit ‘1’ also satisfies the criteria ‘NOT a whitespace’. So how would you fix this? You could change your sentence to ‘NOT a whitespace or a digit’. ‘OR’ logic can start getting confusing, but regex has a really nice way of matching a set of possible characters. You just put all the options in square brackets. For example, [abc] will match a, b, OR c. [\d] will match a whitespace OR a digit. But we want to match everything that is NOT one of these characters. We can do that by inserting ^ after the opening bracket like this: [^\d]. Here, the ^ means ‘NOT’.
So now our improved regex looks like this:
(?<=[^\d])\d+(?=)We could have done the same thing with a NEGATIVE lookbehind. The ! in a negative lookbehind also means ‘NOT’:
(?<![\d])\d+(?=)If you’re an absolute beginner with regex, I wrote a very basic tutorial which you might find useful: https://inkwire.app/articles/getting-started-with-regular-expressions.html
There are some links at the bottom of that page for more useful resources.
Kal Starkis
MemberYep, I was about to say that a positive lookahead is what you need… This means that the letter needs to be there, but it won’t be included in the matched text.
Kal Starkis
MemberHi Kathlyn
I don’t know if there’s an existing script, but here’s a little script I’ve thrown together to get you started. Even if you’ve never scripted before, have a read over the comments in my code and see if you can follow along with what it’s doing…
// Get all our document hyperlinks var myDocument = app.documents[0] var myDocumentHyperlinks = myDocument.hyperlinks.everyItem().getElements() // Create an empty object to store all the URLs (along with linked text) var indexOfURLs = {} // Iterate over all the document hyperlinks and add each one to the object for (var i = 0; i < myDocumentHyperlinks.length; i++) { var hyperlink = myDocumentHyperlinks[i] // Get linked text (title) and URL var title = hyperlink.source.sourceText.contents var url = hyperlink.destination.destinationURL // Add title and URL to the list indexOfURLs[title] = url } // Print out all the URLs for (var key in indexOfURLs) { $.writeln(key + ": " + indexOfURLs[key]); }The last part prints out the URLs (with titles) to the console (which opens in Adobe’s ExtendScript Toolkit app). You could copy and paste them from there if you want. Or you could modify the script to do something else with them. That’s really up to you. :-)
Kal Starkis
MemberAh I see. No worries.
Kal Starkis
MemberThe following one-liner will do what you’re asking:
app.selection[0].applyParagraphStyle(app.documents[0].paragraphStyles.itemByName("My Paragraph Style"));But I’m still at a loss as to how this is going to save you any work, since running the script isn’t any easier than clicking on the style in the Paragraph Styles panel?
Kal Starkis
MemberWell yes, but Adobe also calls it a ‘shortcut’. :-) I always give my styles these shortcuts, and have done for so many years, I forget that not everyone works with an extended keyboard.
Kal Starkis
MemberWhy do you need a script to do this, when it’s one click (or keyboard shortcut) to apply a style to selected text the usual way?
Kal Starkis
MemberOkay, reading Michel’s comment again, and actually checking the behaviour in InDesign, I think I get it. If you select multiple cells, say in column 1 of a two column table, then shift-click on ANY cell in column 2, all the rows get selected. (It doesn’t matter which row you target.) So I can see how SelectionOptions.ADD_TO just mimics this behaviour.
And I guess
app.selection[0].rows.everyItem().select()probably fails because there’s no guarantee of everyItem() delivering a contiguous selection (even though it does in this case). Since InDesign doesn’t support multiple selections, it makes sense that select() would require a single object. Jeremy, this may begin to explain why your selection (going back to your original question) was reported as a single object (Cell) and not a collection?? That seems like a bit of a hack on Adobe’s part though. Perhaps a genuine ExtendScript guru will stumble onto this thread and explain it to us in a way that makes sense.Kal Starkis
MemberWell, all this just reminds me why I don’t enjoy working with Adobe’s API. I mean, can anyone explain why
app.selection[0].rows.everyItem().remove()works, butapp.selection[0].rows.everyItem().select()fails?And more astonishing still,
app.selection[0].rows[0].select(SelectionOptions.ADD_TO)selects ALL the rows with a selected cell, not just the first one. I get that SelectionOptions.ADD_TO is supposed to add to the current selection, but still, it makes no sense to me that you target just one row, and the others magically get selected too. -
AuthorPosts
