OK, to create the button list, here's the general process of what we need to do (this will be for either the cantrips or the spells... and obviously would have to be duplicated for the other): get the things (all of them of that type) into an array map them into the component parts of your output test for whether the length is empty to build components that reference the GM sheet, instead map them to join each record individually, building a button out of the component parts join the entire array, giving you your buttons Let's see if we can figure that out. let focusOutput = findObjs({ type: 'attribute', characterid: character.id}) .filter(s => /^repeating_spellfocus_/.test(s.get('name'))) ...pausing here in the code; I'll reprint these lines when I get back to code, but I wanted to point out that the reason you weren't getting anything before was because when you're referencing the repeating objects at the object level, they aren't stored using the '$' at all. That's a construct for the macro/ability, as I understand it. A single entry for "Magic Missile" might look like this (the names might be wrong, but this is the way sheets are structured: repeating_spellfocus_-M1234567890_spellname current = "Magic Missile" repeating_spellfocus_-M1234567890_damage_roll current= "[[1d20+2]]" repeating_spellfocus_-M1234567890_anothersubattribute current="some value" ... So if a character has 3 spells in a repeating section that has 5 sub-attributes each, your initial pull of findObjs for the repeating section "spellfocus" would bring you 15 attributes. We need to get that down to the 3 that we would reference from the sheet level. They will all share the element id (-M1234567890), but have different suffixes. We can do that 2 ways (at least). Option 1 - If you know the naming suffix You can build your regex to look for one of the field suffixes (preferably the naming suffix... so you are referring to the attribute whose value is the name of the spell/repeating element). If that sub-attribute has a suffix of "spellname", you'd use a regex like this: /^repeating_spellfocus_([^_]+?)_.spellname$/ ...That will give a cross section of the repeating spells, one entry each, where group1 of the regex is the ID we're looking for. (To see that at work, copy the above and paste it it at regex101.com... then type in the test box a sample attribute name like "repeating_spellfocus_-M1234567890_spellname". When the regex hits, you will see the matches at the right, including the capture group denoted by the parentheses.) The next step for this option would be to map the entry into a record containing this ID as well as the current value (which would be our button label, including the spaces. Here is what this option would look like: let spellrx = /^repeating_spellfocus_([^_]+?)_.spellname$/g; let focusSpells = findObjs({ type: 'attribute', characterid: character.id}) .filter(s => spellrx.test(s.get('name'))) .map(s => { return { id: s.id, lbl: s.get('current') }; }) // start creating the object of component parts That would leave you with an array of objects. Each object would have 2 properties so far: id and lbl. The next bit is a bit trickier, since we have to track down the "action" attribute -- that is, the one that executes when you click on "Magic Missile." More on that in a minute. For now, the other option, when you don't know the naming suffix. Option 2 - When you don't know the naming suffix When you don't know the naming suffix (or any suffix, for that matter), you can try this method to get your set of unique IDs. It starts the same, but in the middle we use a little spread operator and 'Set' magic to get a new array of unique values. In this case, we're using a similar regex with the same capture group, but we're not able to limit it to a particular sub-attribute to get our cross section. That's why we need to get our unique set. One more thing to understand... in regex matches, the full match is always group 0, and your capturing groups start numbering at 1. So when we .exec() a regex (or, from the string perspective, we could string.match()), we are left with a regex object where [1] would refer to our first capture group. Whew. All of that should help you to understand what is going on here: let spellrx = /^repeating_spellfocus_([^_]+?)_.*$/g; let attrs = findObjs({ type: 'attribute', characterid: character.id}) .filter(s => spellrx.test(s.get('name'))); let focusSpells = [...new Set(attrs.map(a => spellrx.exec(a.get('name'))[1]))] // gives us an array of unique ids .map(s => {return { id: s }; }); // start creating the object of component parts At this point we have an array of objects, and each object has one property (id). We don't have the button label (lbl) because we didn't have the naming attribute. In fact, at this point, if you don't have access to your sheet, you're going to need to make use of your log (or chat). (This is just development legwork... you own't have to include this in your runtime version.) You'll want to map an output of all the attributes that begin with 'repeating_spellfocus_' and then the id from ONE of your entries in focusSpells. That would look like this: findObjs({ type: 'attribute', characterid: character.id }) .filter(a => new Regexp(`^repeating_spellfocus_${focusSpells[0].id}_.*`).test(a.get('name'))) .forEach(a => log(a.get('name'))); That will output the name of every sub-attribute associated with a given element in the repeating section. The trick then is to figure out which to use. (I have a script that could do a lot of this dev legwork for you. I will be releasing either today or tomorrow!) Putting it all together Once you have the naming sub-attribute and action sub-attribute, you are in a position to pull out the pieces you want. This is when you would test whether the current value of the action attribute has no length (meaning you want to construct it from the GM sheet, instead). What we're aiming for is an array of objects (focusSpells) where each element is an object. Each object should look like: { id: RegexGroupExtraction, lbl: Naming Attribute Current,
cn: CharacterName or GMCharacter Name elem: @ (for attributes, meaning from THIS sheet) -- OR --
% (for abilities, meaning from the GM sheet) exec: ActionAttributeName (if from this sheet) -- OR --
NamingAttributeCurrent (that is, the lbl property, removing the spaces, if we can assume that it exists on the GM sheet) } If you have trouble bridging where I left the code, above, to this point, post back and I can try to connect the dots. Basically, in a map operation for each item in the array, you're going to test the current value of the action attribute (building its name using the ID you've already isolated and the action_suffix you'd append to the attribute name) to see if it's empty. If it is, the elem property gets the % value (HTML entity for %), the exec property gets the action attribute name, and the cn property gets filled with the GM Character Name. Otherwise (if the current value of the action element is NOT empty), elem gets @ (HTML entity for @), the exec property gets the lbl (which is the NAMING attribute's current value), but you have to remove the spaces, and the cn property gets the name of THIS character. Are you still with me? This gets a little weedy. When everything is in order, your output would be a map operation putting those pieces together: focusSpells = focusSpells.map(s => `[${s.lbl}](!&#13${elem}{${cn}|${exec}})`).join(''); As an example from my game (different system, different repeating sections, I know), the following text: [Sight Beyond](!
@{Heretic|repeating_powers_-M5sVh3oYbp20R7VZWwQ_use_power2_formula}) Produces a button to run the "use_power2_formula" sub-attribute of the element -M5sVh3oYbp20R7VZWwQ in the repeating section, 'powers' (the attribute appears on the character sheet for 'Heretic' as "Sight Beyond"): If you're not already exhausted I know this is a lot, but if you wanted to sort the output, you'd do that before that last map operation. You can sort of array of objects by a property of those objects like this: .sort((a,b) => a.lbl > b.lbl ? 1 : -1); Two items fed into that callback, and you're comparing the lbl property of each. The callback is executing a ternary comparison (a fancy way of doing a single line 'if' construction). It says if the lbl property of a is greater than the lbl property of b (meaning that's how it would sort), return 1. Otherwise, return -1. That tells the sort() operation how to order the elements. I'll see if I can post my script that does some of these same sort of things, if you want to use it for the legwork and/or to compare.