• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
12

GREP replace quotation marks within quotation marks

New Here ,
Nov 09, 2023 Nov 09, 2023

Copy link to clipboard

Copied

Hi, I am looking to find and replace quotation marks within quotation marks, as example:

He cited: «As someone said «I need to better understand grep» and he was right».

 

Sorry for the silly example, but I want to find and select the second « (in order to replace with "), so i guess the line should said something like: '«', after another '«' followed by whatever except '»'. But i really cannot do it.

 

After that, I should select the first » for same reason.

 

Thank you

TOPICS
Scripting

Views

295

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 1 Correct answer

Community Expert , Nov 09, 2023 Nov 09, 2023

It would be absolutely possible to do this one with a lookbehind, but that's not how I started figuring this one out. Can you go into more detail on what you're trying to do? It sounds like you simply want to replace all double chevron quotes with double quote marks. I'm not sure why you'd want to do it in multiple passes; it looks to me like it's best done in one pass. 

 

The "followed by whatever except" bit is easy enough to do in GREP. If there are multiple nested quotes in a single paragrap

...

Votes

Translate

Translate
Community Expert ,
Nov 09, 2023 Nov 09, 2023

Copy link to clipboard

Copied

This should help you a bit:

 

https://carijansen.com/positive-lookbehind-grep-for-designers/

 

But I'm not GREP guru and I'm on my phone.

 

@FRIdNGE or @Joel Cherney should be able to give you the correct expression on the spot.

 

▒► ID-Tasker / ID-Tasker Server - work smart not hard ◄▒

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Nov 09, 2023 Nov 09, 2023

Copy link to clipboard

Copied

It would be absolutely possible to do this one with a lookbehind, but that's not how I started figuring this one out. Can you go into more detail on what you're trying to do? It sounds like you simply want to replace all double chevron quotes with double quote marks. I'm not sure why you'd want to do it in multiple passes; it looks to me like it's best done in one pass. 

 

The "followed by whatever except" bit is easy enough to do in GREP. If there are multiple nested quotes in a single paragraph, we'd need to have an expression that wasn't greedy; we want it to stop at the first double chevron close quote, right? The expression for that is

.+?

So my "Find" expression would be

(«)(.+?)(«)(.+?)(»)(.+?)(»)

 and my "Change to" expression would be

"$2"$4"$6"

because enclosing an expression in parentheses allows you to handle each parenthetical expression separately in the "Change to" field by addressing each group with a dollar sign. So this one replaces all the double chevron quotes with typographer's quotes. I would assume that you'd want the nested quotes to be single quotes, not double quotes, so I'd suggest this "Change to" expression instead:

"$2'$4'$6"

which leads to this behavior:

quotes.gif

 

If that's not what you're looking for, please do post again and fill us in.  If you absolutely had to do one pass to replace the inner quotes, followed by another pass to replace the outer quotes, the first "Change to" expression would be more like 

$1$2"$4"$6$7

(Also I'm sure that my method is a bit of a brute force method, and there are far more elegant ways to pull it off.)

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
New Here ,
Nov 09, 2023 Nov 09, 2023

Copy link to clipboard

Copied

I'm sorry but this solution leads to a problem, that it finds and selects also three subsequent quotations

 

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Nov 09, 2023 Nov 09, 2023

Copy link to clipboard

Copied

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Nov 09, 2023 Nov 09, 2023

Copy link to clipboard

Copied

But Joel Cherney made it "not greedy" - by using "?".

 

It's because my second capture group  doesn't explicity exclude a close double chevron. Once you have identical nesting markers, then regex aren't really the best tool for sorting 'em out. It's why we're told Never Parse Tag Formats Using Regex, right? Because it makes more sense to parse a phrase with lots of nested tags by walking down to the innermost tag's content and working up from there; trying to parse the tag structure inline with regex just leads to false positives or false negatives. 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Nov 09, 2023 Nov 09, 2023

Copy link to clipboard

Copied

That's why I started with "can you go into more detail"; because ny expression works just fine when there's only nested quoted phrases inside of another. in your document. You apparently have phrases with no nested quotes, and you don't want to match those, correct? You'd think that this would be obvious, but actually the only way to figure out the correct expression is to have all of that kind of information.

 

For example, it makes a lot more sense to take two passes through, now. Are you going to leave all of the phrases with «no nested quoted phrases» with their double chevron quotes? Or are they too going to have their quotes changed? Because it'd be comparatively easy to do all of the no-nested-quotes phrases first, so that your second pass would only need to catch more complicated phrases.  Are any of those «phrases that «we» want to capture enclosing «more than one» nested quoted phrase»?

 

Also, I'm curious about the point at which it makes more sense to say: "Okay, these queries find all of these phrases, but we only want to perform the Change To statement on half of the phrases. We have maybe eight hundred such phrases in roughly three hundred pages, so hey Joel, why don't you sit down and spend the next four to six minutes stepping through the whole document, applying the Change To expression only to the roughly 50% of the phrases that need changes to be made?" 

 

to which I respond:

 

"But Boss, I would far rather spend ninety minutes cooking up The Perfect GREP than I would like to spend six minutes hitting Find Next or Change/Find as appropriate."

 

and Boss says:

 

"Well okay then Joel, feel free to spend as much time as you need cooking up The Perfect GREP. It's not like the number of minutes spent here is going to affect our bottom line, or your invoice to us, or anything that actually matters. The only thing that matters is a perfect regular expression."

 

 

 

 

 

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Nov 09, 2023 Nov 09, 2023

Copy link to clipboard

Copied

Still making me laugh, Joel.

 

~Barb

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Nov 09, 2023 Nov 09, 2023

Copy link to clipboard

Copied

Hi @giognis4789300, you already have a good answer, but I was intrigued by this problem and decided to write a script to solve it. Here is what I came up with:

/**
 * Example of "findChangeNestedBrackets" function:
 * Find and process bracketing in selected text
 * so that inner brackets (level 1 and above)
 * are changed from chevrons to quote marks.
 * Easily customizable via the `changer` function.
 * @author m1b
 * @discussion https://community.adobe.com/t5/indesign-discussions/grep-replace-quotation-marks-within-quotation-marks/m-p/14223446
 */
function main() {

    /**
     * This is the function that decides what
     * to change, based on the char and level.
     * Customize this to suit your needs.
     * Important note: the closing level is 1
     * higher than the opening level, so that
     * level 1 opening matches with level 2 closing.
     * @param {String} ch - a single character.
     * @param {Number} level - the hierachical level of bracketing.
     * @param {String} changeTo - private variable, only here for convenience.
     */
    function changeNestedChevronsToQuotes(ch, level, changeTo) {

        // opening bracket
        if (ch == '«') {
            if (level == 1) changeTo = '“';
            else if (level == 2) changeTo = '‘';
            return [changeTo, true];
        }

        // closing bracket
        else if (ch == '»') {
            if (level == 2) changeTo = '”';
            else if (level == 3) changeTo = '’';
            return [changeTo, false];
        }

        // this is just for our peace-of-mind:
        if (level == 4) throw Error('We did not plan for level 4 nested brackets!');

    }

    findChangeNestedBrackets({
        text: app.activeDocument.selection[0],
        changer: changeNestedChevronsToQuotes,
        showResults: true,
        warnIfUnbalanced: true,
    });

};
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Find/Change Brackets');


/**
 * Perform find/change operation on characters used
 * as opening/closing bracketing, with reference
 * to their hierachical level.
 *
 * Example `changer` function:
 *
 *    function (ch, level, changeTo) {
 *       // opening bracket
 *       if (ch == '«') {
 *           if (level == 1) changeTo = '“';
 *           else if (level == 2) changeTo = '‘';
 *           return [changeTo, true];
 *       }
 *       // closing bracket
 *       else if (ch == '»') {
 *           if (level - 1 == 1) changeTo = '”';
 *           else if (level - 1 == 2) changeTo = '’';
 *           return [changeTo, false];
 *       }
 *       // this is just for our peace-of-mind:
 *       if (level == 3) throw Error('We did not plan for level 3 nested brackets!');
 *    };
 *
 * The `changer` function returns:
 * [changeToCharacter, risenToNewLevel],
 * which is used to
 *   (a) know if the character needs changing
 *       (undefined means no change), and
 *   (b) know when the level needs to change.
 *
 * @author m1b
 * @version 2023-11-10
 * @param {Object} options
 * @param {Text|TextFrame|Story} options.text - the text to find in.
 * @param {Function} options.changer - a function that returns a replacement char, if any, given `ch` {char} and `level` {Number}.
 * @param {Boolean} options.showResults - whether to show count of home many changes.
 */
function findChangeNestedBrackets(options) {

    options = options || {};

    var changer = options.changer,
        text = options.text;

    if (
        changer == undefined
        || typeof changer !== 'function'
    )
        throw Error('findNestedBrackets: bad `changer` function supplied.');

    if (
        text == undefined
        || !text.hasOwnProperty('characters')
    )
        return [];

    var contents = text.contents,
        change,
        counter = 0,
        level = 0;

    for (var i = 0; i < contents.length; i++) {

        if (change = changer(contents[i], level)) {

            if (change[0]) {
                // make the change
                text.characters[i].contents = change[0];
                counter++;
            }

            // adjust level depending on whether
            // change was opening or closing
            level += change[1] === true ? 1 : -1;

        }

    }

    // some messages for user:

    if (
        options.warnIfUnbalanced
        && level !== 0
    )
        alert(
            'Warning\nBackets found in target text were unbalanced by ' + level
            + (level == 1
                ? ' level. This usually means a bracket was opened but never closed.'
                : ' levels. This usually means ' + level + ' brackets were opened but never closed.'
            )
        );

    if (options.showResults)
        alert('Changed ' + counter + ' brackets.');

    return counter;

};

 

As I wrote in the script documentation, you can customize the changes by editing the "changeNestedChevronsToQuotes" function.

 

Currently it is configured so that a first level of chevrons does nothing (where level is 0), and the next level of nested chevrons are changed to double-quotes, and then next level of nested chevrons are changed to single-quotes.

- Mark

 

demo.gif

 

P.S. @Joel Cherney, my middle name is literally HappyToTakeAnHourToScriptATwoMinuteJob. 🙂 What's wrong with me!

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Nov 12, 2023 Nov 12, 2023

Copy link to clipboard

Copied

That's great, Mark! I mean, not only does it address the OP's question, but I think it also proves the intro-CS-course point I was trying to make with the wrong terminology a few posts upthread. If you need to parse nested tags, you need to be able to address the innermost tag set. And if you can't...

 

        // this is just for our peace-of-mind:
        if (level == 4) throw Error('We did not plan for level 4 nested brackets!');

 

Well, I don't know about you, Giognis, but my mind is at peace, now. 

 

 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Nov 12, 2023 Nov 12, 2023

Copy link to clipboard

Copied

LATEST

Thanks @Joel Cherney! As you rightly mentioned, the parsing of brackets is a case where regex on it's own won't cover all cases.

 

Also, @giognis4789300, I put the if (level == 4) check just for a demonstration. In practice, you may prefer something like

if (level > 1) changeTo = '”';

which means it would change every level of nesting after the first to the same character.

- Mark 

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines