# Font Replacement using walkDownTree

**URL:** <https://forums.creativeclouddeveloper.com/t/font-replacement-using-walkdowntree/1200>\
**Category:** UXP Plugin API\
**Tags:** fonts, editcontext\
**Created:** [August 27, 2019, 9:02pm UTC](https://forums.creativeclouddeveloper.com/t/font-replacement-using-walkdowntree/1200 "2019-08-27T21:02:21Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![wzad](https://avatars.discourse-cdn.com/v4/letter/w/e8c25b/32.png) [@wzad](https://forums.creativeclouddeveloper.com/u/wzad)\
**Post date:** [August 27, 2019, 9:02pm UTC](https://forums.creativeclouddeveloper.com/t/font-replacement-using-walkdowntree/1200/1 "2019-08-27T21:02:21Z")

</div>

I am trying to create a plugin to replace all fonts in a document (or within a selection). It works fine sometimes but other times it fails with errors like:

```auto
Plugin Error: Plugin made a change outside the current edit context

```

here is my code:

```auto
function replaceFonts(selection,root) { 
    walkDownTree(root, onNode, {
        find:{
            fontFamily: selection.items[0].fontFamily,
            fontStyle: selection.items[0].fontStyle
        },
        replace:{
            fontFamily: selection.items[1].fontFamily,
            fontStyle: selection.items[1].fontStyle            
        }
    });
    
}

function onNode(node,styles){
    if (node.constructor.name == "Text"){
        if (node.fontFamily == styles.find.fontFamily && node.fontStyle == styles.find.fontStyle ){
            node.fontFamily = styles.replace.fontFamily;
            node.fontStyle = styles.replace.fontStyle;
        }
    }
}
function walkDownTree(node, command, value = null, selection) {
    command(node, value, selection);

    if (node.isContainer) {
        var childNodes = node.children;

        for (var i = 0; i < childNodes.length; i++) {
            let childNode = childNodes.at(i);

            walkDownTree(childNode, command, value);
        }
    } 
}

```

Any ideas why this wouldn’t work in some instances?

---

<div class="post-metadata">

**Author:** ![stevekwak](https://sea1.discourse-cdn.com/flex015/user_avatar/forums.creativeclouddeveloper.com/stevekwak/32/48_2.png) [@stevekwak](https://forums.creativeclouddeveloper.com/u/stevekwak)\
**Post date:** [August 28, 2019, 3:02pm UTC](https://forums.creativeclouddeveloper.com/t/font-replacement-using-walkdowntree/1200/2 "2019-08-28T15:02:33Z")

</div>

Hi @wzad. This is a limitation on XD’s end. Please refer to this: [https://adobexdplatform.com/plugin-docs/reference/core/edit-context.html?h=edit%20context](https://adobexdplatform.com/plugin-docs/reference/core/edit-context.html?h=edit%20context)

---

<div class="post-metadata">

**Author:** ![wzad](https://avatars.discourse-cdn.com/v4/letter/w/e8c25b/32.png) [@wzad](https://forums.creativeclouddeveloper.com/u/wzad)\
**Post date:** [August 28, 2019, 3:38pm UTC](https://forums.creativeclouddeveloper.com/t/font-replacement-using-walkdowntree/1200/3 "2019-08-28T15:38:09Z")

</div>

@stevekwak thanks for your response.

Is there a different way I could go about this? For example, programatically updating the edit context while iterating through nodes?

Or is replacing a font across a whole document simply not going to be possible?

Additional question; is there a way to know whether or not a node I am iterating over is within editable context before attempting a command that requires edit context? The plugin appears to fail completely once an error occurs, it would be helpful to check in advance.

---

<div class="post-metadata">

**Author:** ![stevekwak](https://sea1.discourse-cdn.com/flex015/user_avatar/forums.creativeclouddeveloper.com/stevekwak/32/48_2.png) [@stevekwak](https://forums.creativeclouddeveloper.com/u/stevekwak)\
**Post date:** [August 28, 2019, 4:31pm UTC](https://forums.creativeclouddeveloper.com/t/font-replacement-using-walkdowntree/1200/4 "2019-08-28T16:31:38Z")

</div>

Everything can be within the edit context coming from the root except for any special nodes, such as repeat grids, mask group, boolean group, and linked graphic. This is probably the biggest limitation we have for XD extensibility with no great workaround. In order to check, you can check if any of the children are any of these special nodes.

You can guide the user to click into the text nodes when they are placed under any of those special nodes. Then, the text nodes are within the edit context.

Hope this helps!

---

<div class="post-metadata">

**Author:** ![wzad](https://avatars.discourse-cdn.com/v4/letter/w/e8c25b/32.png) [@wzad](https://forums.creativeclouddeveloper.com/u/wzad)\
**Post date:** [August 28, 2019, 6:22pm UTC](https://forums.creativeclouddeveloper.com/t/font-replacement-using-walkdowntree/1200/5 "2019-08-28T18:22:23Z")

</div>

@stevekwak thanks. that helps.So, if I update my walkDownTree function… something like this in theory should avoid edit context errors…

```auto
function walkDownTree(node, command, value = null,selection) {
    command(node, value,selection);
    if (
        (!node.mask) &&
        (node.constructor.name!="RepeatGrid") &&
        (node.constructor.name!="BooleanGroup") &&
        (node.constructor.name!="LinkedGraphic")
    ) {
        node.children.forEach(childNode => {
            walkDownTree(childNode, command, value);
        });
    }
}

```

---

<div class="post-metadata">

**Author:** ![wzad](https://avatars.discourse-cdn.com/v4/letter/w/e8c25b/32.png) [@wzad](https://forums.creativeclouddeveloper.com/u/wzad)\
**Post date:** [August 29, 2019, 7:38pm UTC](https://forums.creativeclouddeveloper.com/t/font-replacement-using-walkdowntree/1200/6 "2019-08-29T19:38:17Z")

</div>

@stevekwak  
Just an update on this. The code in my previous post does in fact seem to work well at avoiding edit context errors.

Additionally, knowing at times there are going to be items that are outside of edit context, I figured out a way to select the artboards where there are nodes that the plugin was unable to update.

Basically, while we’re walking down the tree of children, once it encounters a node/container that will be out of context, it will continue on crawling, but pass an argument to the command so that we know not to perform unsafe actions.

In my plugin I also store the unsafe nodes in an array, then once I’m done walking the tree, I loop through all the nodes where I was not able to make any changes, and use a `getAncestors` function to find which artboard it’s on. Because the artboard is within edit context, I can add it to `selection.items` safely thereby giving some visual indication where the unaffected items are located.

Its not the prettiest, but it will have to do for now.

```auto
const { Artboard} = require("scenegraph"); 
var commands = require("commands");
let application = require("application");
let panel;
let actionData;

function replaceArialWithHelvetica(){
    walkDownTree(root, onReplaceArialWithHelveticaNode, selection);
}
function onReplaceArialWithHelveticaNode(node, value, safe=true) {

    if (node.constructor.name == "Text") {
        if (node.fontFamily == "Arial") {
            if (safe) {
                node.fontFamily = "Helvetica";
            } else {
                actionData.push(node);
            }
        } 
    }
}
function walkDownTree(node, command, value = null) {
    command(node, value);
    if (
        (!node.mask) &&
        (node.constructor.name != "RepeatGrid") &&
        (node.constructor.name != "BooleanGroup") &&
        (node.constructor.name != "LinkedGraphic")
    ) {
        node.children.forEach(childNode => {
            walkDownTree(childNode, command, value);
            
        });
    }else{
        node.children.forEach(childNode => {
            unsafeWalkDownTree(childNode, command, value);
        });       
    }
}
function unsafeWalkDownTree(node, command, value = null) {
    command(node, value, false); //Pass false to the command, so we can store the nodes that are out of edit context
        node.children.forEach(childNode => {
            unsafeWalkDownTree(childNode, command, value);

        });
}
function getNodeAncestry(node){
    let ancestors = [];
    let currentNode = node;
    while (currentNode.parent!=null) {
        ancestors.push(currentNode.parent);
        currentNode = currentNode.parent;
    }
    return ancestors;
}

function create() {
    const HTML =
        `<style>
            .break {
                flex-wrap: wrap;
            }
            .show {
                display: block;
            }
            .hide {
                display: none;
            }
        </style>
        <form method="dialog" id="main">
            <div class="row break">
                <button id="replaceArialWithHelvetica">Global Replace Arial with Helvetica</button>
            </div>
        </form>
        `

    panel = document.createElement("div");
    panel.innerHTML = HTML;
    panel.querySelector("#replaceArialWithHelvetica").addEventListener("click", event => {
        application.editDocument( function(selection, root){
            actionData = [];
            replaceArialWithHelvetica(selection, root);
            let unaffectedArtboards = [];
            actionData.forEach(actionDataNode => {
                const unaffectedNodeAncestry = getNodeAncestry(actionDataNode);
                const unaffectedNodeArtboard = unaffectedNodeAncestry[unaffectedNodeAncestry.length - 2]
                if (unaffectedArtboards.indexOf(unaffectedNodeArtboard) === -1) {
                    unaffectedArtboards.push(unaffectedNodeArtboard);
                }
               
            });
            selection.items = unaffectedArtboards;
        });
    });

    return panel;
}

function show(event) {
    if (!panel) event.node.appendChild(create());
}

module.exports = {
    panels: {
        pluginPanel: {
            show,
            hide,
            update
        }
    }
};

```

---

<div class="post-metadata">

**Author:** ![stevekwak](https://sea1.discourse-cdn.com/flex015/user_avatar/forums.creativeclouddeveloper.com/stevekwak/32/48_2.png) [@stevekwak](https://forums.creativeclouddeveloper.com/u/stevekwak)\
**Post date:** [August 29, 2019, 9:15pm UTC](https://forums.creativeclouddeveloper.com/t/font-replacement-using-walkdowntree/1200/7 "2019-08-29T21:15:21Z")

</div>

Doesn’t this still require user to select the special nodes?

---

<div class="post-metadata">

**Author:** ![wzad](https://avatars.discourse-cdn.com/v4/letter/w/e8c25b/32.png) [@wzad](https://forums.creativeclouddeveloper.com/u/wzad)\
**Post date:** [August 29, 2019, 11:49pm UTC](https://forums.creativeclouddeveloper.com/t/font-replacement-using-walkdowntree/1200/8 "2019-08-29T23:49:33Z")

</div>

Yea. I don’t have a solution for that because of the XD limitation you described previously.

The point is, if you have 40 artboards and replaced all the occurences of a font, but there were some additional occurences within, let’s say, a repeat grid; at least this way you can quickly see where to find those because the art board(s) will be highlighted

---

<div class="post-metadata">

**Author:** ![stevekwak](https://sea1.discourse-cdn.com/flex015/user_avatar/forums.creativeclouddeveloper.com/stevekwak/32/48_2.png) [@stevekwak](https://forums.creativeclouddeveloper.com/u/stevekwak)\
**Post date:** [August 30, 2019, 3:04pm UTC](https://forums.creativeclouddeveloper.com/t/font-replacement-using-walkdowntree/1200/9 "2019-08-30T15:04:57Z")

</div>

Neat. thanks for sharing your code!
