Jump to orphaned target marks (MText) via scripting
![Guest](https://community.adobe.com/html/@9D6E3E2F9C43EC8C06F730688F2F178B/assets/anonymous_user.webp)
Copy link to clipboard
Copied
Hi,
I am currently working on a script for FrameMaker which should directly jump to the orphaned target markers (MText) inside the book. To identify them in the first place, I have already written a code where I collect all cross-references (mainly the ID numbers), both source markers (XRef) and target markers, to match them. This works for me so far. However, I am a bit stuck with the part where the according book components of the orphaned target markers should be directly accessed. I was thinking of running through the entire book again while trying to match with the ID numbers of the target markers but since I collected them all in an array, this does not work properly. Maybe my approach is not that elegant.
main();
function main()
{
var numXRefTextPaths = []
var numMTextPaths = []
var book = app.ActiveBook;
if(!book.ObjectValid())
{
book = app.FirstOpenBook;
}
var comp = book.FirstComponentInBook;
while(comp.ObjectValid())
{
comp.ComponentIsSelected = true;
var compType = comp.ComponentType;
if(compType == Constants.FV_BK_FILE)
{
var doc = OpenFile(comp.Name);
if(doc.ObjectValid() == true)
{
crXRefText = doc.FirstXRefInDoc;
while(crXRefText.ObjectValid())
{
var longXRefText = crXRefText.XRefSrcText.toString();
var id_number = longXRefText.substr(0, 5);
//alert("CR-XRef: " + id_number);
numXRefTextPaths.push(id_number);
crXRefText = crXRefText.NextXRefInDoc;
}
crMText = doc.FirstMarkerInDoc;
while(crMText.ObjectValid())
{
if (crMText.MarkerText.match(/^[0-9]{5}/))
{
var longMText = crMText.MarkerText.toString();
var cr_number = longMText.substr(0, 5);
//alert("CR-MText: " + cr_number);
numMTextPaths.push(cr_number);
}
crMText = crMText.NextMarkerInDoc;
}
}
else
{
alert("Book cannot be found.\nRun the script again.");
return;
}
}
comp.ComponentIsSelected = false;
comp = comp.NextBookComponentInDFSOrder;
}
alert("Following cross-references were found:\n\n"
+ "\n\nXRefs ID: " + numXRefTextPaths
+ "\n\nMText ID: "+ numMTextPaths
);
function getOne(arr1, arr2)
{
var uniqueOne = [];
var matches = false;
for ( var i = 0; i < arr1.length; i++ )
{
matches = false;
for ( var j = 0; j < arr2.length; j++ )
{
if (arr1[i] === arr2[j]) matches = true;
}
if(!matches) uniqueOne.push(arr1[i]);
}
return uniqueOne;
}
alert("Orphaned target marks: " + getOne(numMTextPaths, numXRefTextPaths));
var comp = book.FirstComponentInBook;
while(comp.ObjectValid())
{
comp.ComponentIsSelected = true;
var compType = comp.ComponentType;
if(compType == Constants.FV_BK_FILE)
{
var doc = OpenFile(comp.Name);
if(doc.ObjectValid() == true)
{
var test = getOne(numMTextPaths, numXRefTextPaths);
jumpMText = doc.FirstMarkerInDoc;
while(jumpMText.ObjectValid())
{
if (jumpMText.MarkerText.match(test))
{
alert("Orphaned target marks: " + jumpMText.MarkerText);
}
jumpMText = jumpMText.NextMarkerInDoc;
}
}
else
{
alert("Book cannot be found.\nRun the script again.");
return;
}
}
comp.ComponentIsSelected = false;
comp = comp.NextBookComponentInDFSOrder;
}
}
Thank you in advance.
Copy link to clipboard
Copied
Three suggestions:
- Instead of using two separate arrays for
numXRefTextPaths
andnumMTextPaths
, you can use an array of objects for the markers. This way, you can store both the ID and the marker object itself, which will make it easier to jump to the orphaned markers later. - Use the
indexOf
method to check if a marker ID exists in the cross-references array. - There's a potential issue. The variable
doc
is defined inside the loop where you're iterating through the book components. However, when you're trying to jump to the orphanedMarker later on, the doc variable might not be pointing to the correct document where the orphanedMarker is located. I suggest to store the document reference along with each marker.
main();
function main() {
var xRefIDs = [];
var mTextMarkers = [];
var book = app.ActiveBook;
if (!book.ObjectValid()) {
book = app.FirstOpenBook;
}
var comp = book.FirstComponentInBook;
while (comp.ObjectValid()) {
comp.ComponentIsSelected = true;
if (comp.ComponentType == Constants.FV_BK_FILE) {
var doc = OpenFile(comp.Name);
if (doc.ObjectValid() == true) {
var crXRefText = doc.FirstXRefInDoc;
while (crXRefText.ObjectValid()) {
var id_number = crXRefText.XRefSrcText.substr(0, 5);
xRefIDs.push(id_number);
crXRefText = crXRefText.NextXRefInDoc;
}
var crMText = doc.FirstMarkerInDoc;
while (crMText.ObjectValid()) {
if (crMText.MarkerText.match(/^[0-9]{5}/)) {
var cr_number = crMText.MarkerText.substr(0, 5);
mTextMarkers.push({
id: cr_number,
marker: crMText,
document: doc // Store the document reference
});
}
crMText = crMText.NextMarkerInDoc;
}
} else {
alert("Book cannot be found.\nRun the script again.");
return;
}
}
comp.ComponentIsSelected = false;
comp = comp.NextBookComponentInDFSOrder;
}
// Generate lists of IDs for the alert
var xRefIDList = xRefIDs.join(", ");
var mTextIDList = mTextMarkers.map(function(obj) { return obj.id; }).join(", ");
alert("Following cross-references were found:\n\n"
+ "\n\nXRefs ID: " + xRefIDList
+ "\n\nMText ID: " + mTextIDList
);
// Identify and jump to orphaned markers
for (var i = 0; i < mTextMarkers.length; i++) {
if (xRefIDs.indexOf(mTextMarkers[i].id) === -1) {
var orphanedMarker = mTextMarkers[i].marker;
alert("Orphaned target mark: " + orphanedMarker.MarkerText);
// Jump to the orphanedMarker
var orphanedDoc = mTextMarkers[i].document;
var textLoc = orphanedMarker.TextLoc;
var textSel = new TextSelection();
textSel.beg = textLoc;
textSel.end = textLoc;
orphanedDoc.TextSelection = textSel;
orphanedDoc.ScrollToText(textSel); // This will ensure the marker is visible in the FrameMaker window
// If you want to pause after each jump (e.g., to allow the user to review the marker),
// you can introduce a confirmation dialog here, like:
if (!confirm("Found an orphaned marker. Continue searching?")) {
break; // This will exit the loop if the user clicks "Cancel"
}
}
}
}
This approach should be more efficient in ExtendScript and should allow you to identify and jump to orphaned markers in Adobe FrameMaker.
Copy link to clipboard
Copied
I have edited my reply above to cover the doc var problem and also added code to jump to the orphaned marker.
Please try and let me know if it works.
![Guest](https://community.adobe.com/html/@9D6E3E2F9C43EC8C06F730688F2F178B/assets/anonymous_user.webp)
Copy link to clipboard
Copied
Thank you so much!
Unfortunately, the script stops running at the part "mTextMarkers.map(function(obj) { return obj.id; }).join(", ");" since this part is marked in red on my editor. I have no idea why.
![Guest](https://community.adobe.com/html/@9D6E3E2F9C43EC8C06F730688F2F178B/assets/anonymous_user.webp)
Copy link to clipboard
Copied
Maybe I should mention that I am using Adobe ExtendScript Toolkit CC.
![Guest](https://community.adobe.com/html/@9D6E3E2F9C43EC8C06F730688F2F178B/assets/anonymous_user.webp)
Copy link to clipboard
Copied
Okay, so I figured out why the script always stops at line "mTextMarkers.map(function(obj) { return obj.id; }).join(", ");" - the map() method is not a standard function in ECMAScript. I came up with a workaround, so this should be fine even if it is not the most elegant approach, as long as it works...
But now I have another problem: The script stops now at line "var textSel = new TextSelection();" and I feel like this is a very tricky one since this syntax should be actually supported by ExtendScript when scripting for FrameMaker. The code looks like this so far:
main();
function main()
{
var xRefIDs = [];
var mTextMarkers = [];
var book = app.ActiveBook;
if (!book.ObjectValid())
{
book = app.FirstOpenBook;
}
var comp = book.FirstComponentInBook;
while (comp.ObjectValid()) {
comp.ComponentIsSelected = true;
if (comp.ComponentType == Constants.FV_BK_FILE) {
var doc = OpenFile(comp.Name);
if (doc.ObjectValid() == true) {
var crXRefText = doc.FirstXRefInDoc;
while (crXRefText.ObjectValid()) {
var id_number = crXRefText.XRefSrcText.substr(0, 5);
xRefIDs.push(id_number);
crXRefText = crXRefText.NextXRefInDoc;
}
var crMText = doc.FirstMarkerInDoc;
while (crMText.ObjectValid()) {
if (crMText.MarkerText.match(/^[0-9]{5}/)) {
var cr_number = crMText.MarkerText.substr(0, 5);
mTextMarkers.push({
id: cr_number,
marker: crMText,
document: doc // Store the document reference
});
}
crMText = crMText.NextMarkerInDoc;
}
} else {
alert("Book cannot be found.\nRun the script again.");
return;
}
}
comp.ComponentIsSelected = false;
comp = comp.NextBookComponentInDFSOrder;
}
// Generate lists of IDs for the alert
var xRefIDList = "";
var mTextIDList = "";
for (var i = 0; i < xRefIDs.length; i++) {
xRefIDList = xRefIDList + xRefIDs[i].toString() + ", ";
}
for (var j = 0; j < mTextMarkers.length; j++) {
var obj = mTextMarkers[j];
mTextIDList = mTextIDList + obj.id.toString() + ", ";
}
alert("Following cross-references were found:\n\n"
+ "\n\nXRefs ID: " + xRefIDList
+ "\n\nMText ID: " + mTextIDList
);
// Identify and jump to orphaned markers
for (var i = 0; i < mTextMarkers.length; i++)
{
if (xRefIDs.indexOf(mTextMarkers[i].id) === -1)
{
var orphanedMarker = mTextMarkers[i].marker;
alert("Orphaned target mark: " + orphanedMarker.MarkerText);
// Jump to the orphanedMarker
var orphanedDoc = mTextMarkers[i].document;
var textLoc = orphanedMarker.TextLoc;
var textSel = new TextSelection();
textSel.beg = textLoc;
textSel.end = textLoc;
orphanedDoc.TextSelection = textSel;
orphanedDoc.ScrollToText(textSel); // This will ensure the marker is visible in the FrameMaker window
// If you want to pause after each jump (e.g., to allow the user to review the marker),
// you can introduce a confirmation dialog here, like:
if (!confirm("Found an orphaned marker. Continue searching?"))
{
break; // This will exit the loop if the user clicks "Cancel"
}
}
}
}
Copy link to clipboard
Copied
TextSelection is a document property and is a TextRange. What you need is this:
var textSel = new TextRange();
![Guest](https://community.adobe.com/html/@9D6E3E2F9C43EC8C06F730688F2F178B/assets/anonymous_user.webp)
Copy link to clipboard
Copied
Thank you so much! Now it works.
Copy link to clipboard
Copied
Hi, I want to make sure I understand the overall task. Are you trying to find Cross-Ref markers that don't have any XRef pointing to them? That is one of the purposes of my CrossrefReporter script. In that script, and others, I collect my data in a simple XML object instead of using arrays of objects. I can then save the XML object to a file for a later pass through the data. The key advantage is that I can open the XML file and see the data that I have collected, which makes development and troubleshooting much easier. Here is an example of data that is collected with my CrossrefReporter script:
Also, you have your code in a single main function, which makes it difficult to troubleshoot. Each task should be in its own function; this makes it easier to test and troubleshoot each part of the process. And small, general purpose functions can be reused in other scripts. Here is a list of functions in my CrossrefReporter script:
This script does a lot more that you are trying to do so you would have less functions, but I want you to see how granularly I make my scripts.
I realize that I haven't given you specific help for your issue, but I may record some YouTube videos that illustrate these scripting principles. Can you confirm the overall purpose of your script? It would make a good example for instruction. Thank you.
Copy link to clipboard
Copied
Here is a playlist that shows how to use XML objects and files to store FrameMaker data with ExtendScript. These are unrehearsed and unedited, but should be helpful for your script and others. I will add a couple more videos in the near future.
https://www.youtube.com/playlist?list=PLRPNZaAC4LoSvyfl6S5PJ0euqFSi4NDCR
Copy link to clipboard
Copied
Hi Rick, very helpful! I had heard of that you can store data in an XML file, but I had not known how to do this. Your videos give a very good start! Thank you very much!
Only one question: Can you post the script, which you develop in your videos, somewhere? There are a few parts which I might re-use.
Best regards, Winfried
Copy link to clipboard
Copied
I completed this playlist by adding 3 more videos. Questions and comments are welcome.
![Guest](https://community.adobe.com/html/@9D6E3E2F9C43EC8C06F730688F2F178B/assets/anonymous_user.webp)
Copy link to clipboard
Copied
Yes, this was exactly my question. Your approach of saving the XML object to a file sounds very interesting but I think I will stick with the arrays. However, I briefly looked into your videos and I will definitively keep them in my mind for the future. Thank you!
![](/skins/images/1C0738531DF1E4A26EA2BDD5C8C87B34/responsive_peak/images/icon_anonymous_message.png)
![](/skins/images/1C0738531DF1E4A26EA2BDD5C8C87B34/responsive_peak/images/icon_anonymous_message.png)