characterStyleGroups.remove() recursive loop crashing on last styleGroup

Explorer ,
Mar 08, 2021 Mar 08, 2021

Copy link to clipboard

Copied

Hi scripters!

I've written a recursive function to remove all styleGroups in a document, and whilst it works absolutely fine for object, table, cell, and paragraph groups, it crashes out on the last character group.
Is there something I'm missing about character style groups that's different from other style groups?

 

My script is called from within another function that removes the styles themselves, and if I run the script as is, it doesn't appear to do anything and ID freezes up. Using a series of alerts I've managed to discover that the script does infact sucessfully remove all the character styles, and all but the last style group, where it crashes.

 

Here is my code, I've included the alert and a snippet from earlier in the code to show the variables being defined:

 

 

//variables snippet       
case "character":
            //x defines which top level array to use in later loop
            var x = 4;
            //defines which doc properties being used
            styles = "allCharacterStyles";
            styleGroups = "characterStyleGroups";  
            break;

//actual function
function removeStyles(check, newList, styles)
    {
        // loop to remove styles
        for(i=doc[styles].length-1;i>=0;i--)
        {...}
        removeGroups();
    };

    function removeGroups()
    {
        var styleGroup;
        
        for (var p=doc[styleGroups].length-1; p>=0; p--) 
        {
            checkForGroups(doc[styleGroups][p]);
        }

        function checkForGroups(styleGroup) 
        {
            if (styleGroup[styles].length == 0) 
            {
                alert("without this alert the script does not appear to run")
                styleGroup.remove();
            }
            else if (styleGroup[styleGroups].length > 0) 
            {
                for (var g=styleGroup[styleGroups].length-1; g>=0; g--) 
                {
                    checkForGroups(styleGroup[styleGroups][g]);
                }
            }            
        }
    }

   

 

TOPICS
Scripting

Views

267

Likes

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

Adobe Community Professional , Mar 10, 2021 Mar 10, 2021
Not sure you needed to do separate functions for each style type. Just seprate the remove from check functions, and pass appropriate group and style object names that you can reference with bracket notation. This may not be exact, but you get the idea.   var checkForGroups = function(obj, styleGroupType, styleType) { //obj can accept Document or AnyStyleGroup object //styleType would be characterStyles, objectStyles, paragraphStyles, etc. if (obj[styleType].length == 0) { try...

Likes

Translate

Translate
Enthusiast ,
Mar 08, 2021 Mar 08, 2021

Copy link to clipboard

Copied

Hello Tim,

 

are you getting an error message? maybe you could try using .......

try {

} catch (e) {
}

inside your function

 

regards,

Mike

Likes

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
Explorer ,
Mar 09, 2021 Mar 09, 2021

Copy link to clipboard

Copied

No errors, just a total application freeze up requiring a restart.

I tried using a try/catch,but I don't think as far as ID is concerned it meets the try condition and then falls over.

Likes

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
Explorer ,
Mar 09, 2021 Mar 09, 2021

Copy link to clipboard

Copied

I can't seem to edit my last post, I meant to write:

"I think as far..." rather than "I don't think..."

Likes

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
Adobe Community Professional ,
Mar 09, 2021 Mar 09, 2021

Copy link to clipboard

Copied

Hi,

 

Just a couple of points:

 

1. What is the doc object, it doesn't appear to be declared in what you gave us and you appear to be accessing properties as if they where arrays (which is confusing)?

2. Is there are reason why checkForGroups is nested inside the removeGroups method?

3. the check ForGroups method also makes use of the styles vairable although it is not declared or passed in it is relying on the scoping of nested functions which it probably not the best way to go.

 

Are you able to share all the code ?

 

Malcolm

Likes

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
Explorer ,
Mar 09, 2021 Mar 09, 2021

Copy link to clipboard

Copied

Hi Malcolm,

I'll give a brief overview for context before adressing your points more specifically. The aforementiond function is part of a CEP panel that allows the end user to selectively "flatten" any style within the document (or more importantly, many at once). In short this is handled by building an array host side of all the styles, and passing it to the client to in turn populate a series of ul's with checkboxes. The client side array is updated anytime a checkbox is clicked to reflect the checked value, and is passed back to the host when a "flatten" button is clicked so that the host can replace the original array with the new client one and use it to determine which styles are to be removed.

When a flatten button is clicked it triggers a parent function host side and passes it the client array, and a reference for which button was clicked (there are 3 different flatten buttons - preserve checked, flatten checked, and flatten all), and a refernce to which general style type is being requested for deletion (e.g object styles, or paragraph styles).

That parent function contains two functions, one to remove styles, and then this one to remove groups once that has completed.

Like I say, this all works beautifully for everything except character styles, and if I put that alert in just before the remove() groups method it does appear to be working for character styles up until that last group (in the instance of flattening all).

 

1)The doc object is the active document. I'm using bracket notation so that I can pass variables as properties. The removeGroups function is intended to be reused for all style groups, so doc[stylesgroups] is referencing a variable that is just a string for the property. That variable is defined by a switch statement that evaluates the passed value of the general style type (see below).

2. I've always written recursive functions like that, never really questioned it! My logic being that it's only relevant to that function and so nested there rather than globally.

3. Yes, it is relying on scoping. Maybe the full parent function below will make that more obvious. Not sure it's a problem though?

I've tried a try/catch in various places in the function to no avail, although combined with ID freezing up and requiring a restart make me think that as far as ID is concerned it never meets that catch condition, it's getting stuck in an endless loop somehow?

What baffles me is that it works beautifully for all other styles. It also works for character styles if it is not deleting the last folder - e.g. it finds a group folder that still has style children; not if I change the loop to stop before the last element. It'll error on whatever the last empty folder is regardless of the index.

Here's that parent function in full:

//removes document styles and style folders based on clientList sent back from panel
function styleRemove(clientList, btnId, itemType)
{  
    newList = JSON.parse(clientList);
    
    //switch recieves id of styleBtn clicked to define document styles to be worked with. e.g paragraph, character, etc
    switch(itemType)
    {        
        case "object":
            //x defines which top level array to use in later loop
            var x = 0;
            //defines which doc properties being used
            styles = "allObjectStyles";
            styleGroups = "objectStyleGroups";  
            break;
        case "table":
            //x defines which top level array to use in later loop
            var x = 1;
            //defines which doc properties being used
            styles = "allTableStyles";
            styleGroups = "tableStyleGroups";  
            break;
        case "cell":
            //x defines which top level array to use in later loop
            var x = 2;
            //defines which doc properties being used
            styles = "allCellStyles";
            styleGroups = "cellStyleGroups";  
            break;
        case "paragraph":
            //x defines which top level array to use in later loop
            var x = 3;
            //defines which doc properties being used
            styles = "allParagraphStyles";
            styleGroups = "paragraphStyleGroups";  
            break;
        case "character":
            //x defines which top level array to use in later loop
            var x = 4;
            //defines which doc properties being used
            styles = "allCharacterStyles";
            styleGroups = "characterStyleGroups";  
            break;
    };

    //switch evalutes btnID to define what style type to work with, then loops through clientList delivered from client and evaluates each checked value, removing host style as appropriate
    switch(btnId)
    {        
        case "preserve":
            var check = false;
            removeStyles(check, newList, styles);
            break;
        case "flatten":
            var check = true;
            removeStyles(check, newList, styles);            
            break;
    };

    function removeStyles(check, newList, styles)
    {
        for(i=doc[styles].length-1;i>=0;i--)
        {
            var clientItem = newList[x].arr[i];
            var hostItem = doc[styles][i];
            try
            {
                if(clientItem.checked === check)
                {
                    hostItem.remove();                
                }  
            }
            catch(e)
            {
            }                        
        }  
        removeGroups();
    };

    function removeGroups()
    {
        var styleGroup;

            for (var p=doc[styleGroups].length-1; p>=1; p--) 
            {
                checkForGroups(doc[styleGroups][p]);
            }
    
            function checkForGroups(styleGroup) 
            {
                if (styleGroup[styles].length == 0) 
                {
                    try
                    {
                        styleGroup.remove();
                    }
                    catch (e)
                    {
                        alert("errored on: " + p + g);
                    }
                }
                else if (styleGroup[styleGroups].length > 0) 
                {
                    for (var g=styleGroup[styleGroups].length-1; g>=0; g--) 
                    {
                        checkForGroups(styleGroup[styleGroups][g]);
                    }
                }            
            }
    }
}

Likes

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
Participant ,
Mar 09, 2021 Mar 09, 2021

Copy link to clipboard

Copied

hello and sorry, I don't read all posts, but I think you don't need recursion,
I tested below code, on my Win10 it works fine

function test(){
	var mygroup = app.activeDocument.characterStyleGroups

	for(var i = mygroup.length-1; i >=0; i--){
		mygroup[i].remove()
	}
}

app.doScript('test()', ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'test')

Likes

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
Explorer ,
Mar 09, 2021 Mar 09, 2021

Copy link to clipboard

Copied

I need a recursive function because I'm only removing groups if they're empty.

Likes

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
Participant ,
Mar 09, 2021 Mar 09, 2021

Copy link to clipboard

Copied

in this case, recursion is really needed:

function test(mobj){
	var mygroup = mobj.characterStyleGroups

	for(var i = mygroup.length-1; i >=0; i--){
		if(mygroup[i].characterStyleGroups.length != 0){test(mygroup[i])}
		else{
			if(mygroup[i].characterStyles.length == 0){
				mygroup[i].remove()
			}
		}
	}
}

app.doScript('test(app.activeDocument)', ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'test')

 

Likes

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
Explorer ,
Mar 09, 2021 Mar 09, 2021

Copy link to clipboard

Copied

Yes, that's why I wrote a recursive function.

Likes

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
Explorer ,
Mar 09, 2021 Mar 09, 2021

Copy link to clipboard

Copied

The question is why my function works for all style groups except characters.

Likes

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
Adobe Community Professional ,
Mar 09, 2021 Mar 09, 2021

Copy link to clipboard

Copied

Really hard to tell. I think you would benefit from breaking your functions out and passing arguments to them as opposed to this nested structure, but as far as I can tell, styleGroup is never defined when you set it as a variable, and may be confusing later parts of the function when you are referencing the passed styleGroup object. Maybe just try removing that variable declaration? Just a thought. Code's tough to follow. 

 

function removeGroups()
    {
        var styleGroup;
            for (var p=doc[styleGroups].length-1; p>=1; p--) 
            {
                checkForGroups(doc[styleGroups][p]);
            }

 

 Why that's only failing on charStyles may be due to the structure of the groups within charStyles that's triggering a function call that's not happening with the other style types. 

Likes

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
Explorer ,
Mar 10, 2021 Mar 10, 2021

Copy link to clipboard

Copied

I think you are correct about the bracket probably confusing things!
The bigger takeaway was a reminder to not try and do everything in one big "clever" nested function that can adapt to a host of different inputs. In the end I've split it all up into separate functions for each style type and it all works a-ok (even if a lot of repeated code).
Thanks!

Likes

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
Adobe Community Professional ,
Mar 10, 2021 Mar 10, 2021

Copy link to clipboard

Copied

Not sure you needed to do separate functions for each style type. Just seprate the remove from check functions, and pass appropriate group and style object names that you can reference with bracket notation. This may not be exact, but you get the idea.  

var checkForGroups = function(obj, styleGroupType, styleType) {
    //obj can accept Document or AnyStyleGroup object
    //styleType would be characterStyles, objectStyles, paragraphStyles, etc.
    if (obj[styleType].length == 0) {
        try
        {
            obj.remove();
        }
        catch (e)
        {
            //alert("errored on: " + p + g);
        }
    }
    else if (obj[styleGroupType].length > 0) 
    {
        for (var g=obj[styleGroupType].length-1; g>=0; g--) 
        {
            checkForGroups(obj[styleGroupType][g], styleGroupType, styleType);
        }
    }       
}

function removeGroups(obj, styleGroupType, styleType)
    {
    for (var p=obj[styleGroupType].length-1; p>=1; p--) 
    {
        checkForGroups(obj[styleGroupType][p], styleGroupType, styleType);
    }
}           

 

Likes

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
Explorer ,
Mar 11, 2021 Mar 11, 2021

Copy link to clipboard

Copied

LATEST

Thanks Brian, that's my eventual aim, I split it up to test each in isolation, and then once I was done and it worked, I just left it as it's a pretty low overhead in a small panel.

 

Likes

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