All of documents comes out from a template in that order, and I are unable to change the template for reasons beyond my control.
As far as moving/copy the fig title above the graphics instead of the bottom of the previous page, I think that would work also. We would just need to locate that z_anchor tag that is right above the fig tag.
Russ -- I will be getting a sample created to send to you.
Thanks,
Henry
Lucky for Henry, I became intrigued by this task and had a little time to mess around with a sample script. I'm posting it here for the benefit of the community. Basically, it:
- Goes through the book or document and finds all instances of a "z_anchor" pgf followed by a "fig" pgf followed by a "v_num" pgf followed by a "z_notes" pgf. This test is to make sure we are at a place to do the text operation.
- Retrieves the text from the fig pgf
- Inserts a new pgf above the "z_anchor" pgf, gives it a specified pgf format and adds the text retrieved in the previous step.
It also has a function to clean out all old insertions, by simply deleting all pgfs with that format. In this way, it will only work correctly if the template is using a unique pgf tag for the auto-inserted Robohelp titles.
Hopefully it can be of some use to someone. At the very least, I hope it might inspire others to explore the capabilities of ExtendScript, because while it is tough to figure out, it is a marvelous thing. I have historically been an FDK buff, but I'm really starting to lean in the ES direction for tasks like this. Once you get a hold of it (and despite the clunky ESTK editor), it is much easier than the FDK.
Russ
//Define some tags that appear in the template.
//We do this here for convenience, to make them
//easier to change in the future, if necessary.
var roboHelpFormatTag = "RoboHelpTitle";
var anchoredFrameTag = "z_anchor";
var figCaptionTagPrefix = "fig";
var figNumTag = "v_num";
var notesHeadingTag = "z_notes";
//Get the active book or document object
var file = app.ActiveDoc;
if(!file.ObjectValid())
file = app.ActiveBook;
//If there is no active file, abort.
if(!file.ObjectValid())
{
alert("No active book or document. Cannot continue.");
}
//Otherwise, let's get started
else InitiateScript(file);
//Everything after this point is a set of functions that drive the
//main features of the script.
//This initation function is the front
//door that handles overall file iteration,
//reporting, etc. It also allows us
//to confirm with the user at the beginning.
function InitiateScript(file)
{
//Make sure we really want to do this.
if(!confirm("Run the RoboHelp title script " +
"on the following file?\n\n" + file.Name))
return;
//we'll keep counters of what we do.
var doc;
var doInsertions = false;
var totalCleanups = 0;
var totalInsertions = 0;
var totalFilesProcessed = 0;
//If we are processing a book, let's get the first open chapter.
//Otherwise, we'll process directly on the document object
//we retrieved earlier.
//GetNextOpenBookChapter is a custom function defined later.
if(file.constructor.name == "Book")
doc = GetNextOpenBookChapter(file, null);
else doc = file;
//Confirmation. We allow the option to just remove old
//titles without new insertions.
if(confirm("Would you like to do the insertions or " +
"just the cleanup phase? Click Yes to do both."))
doInsertions = true;
//Iterate through our documents for processing.
while(doc != null && doc.ObjectValid())
{
totalFilesProcessed++;
//Clear out the titles currently in the doc
totalCleanups += ClearOutRoboHelpTitles(doc);
//...and, do the insertions, if requested
if(doInsertions)
totalInsertions += InsertRoboHelpTitles(doc);
//If we are processing a book, get the next open
//chapter, otherwise null out the object to end the loop.
if(file.constructor.name == "Book")
doc = GetNextOpenBookChapter(file, doc);
else doc = null;
}
//Report what we did.
alert("Process complete.\n\n" + totalFilesProcessed +
" file(s) processed.\n" + totalCleanups +
" previous title(s) deleted.\n" + totalInsertions +
" new title(s) inserted.");
}
//This is the function that cleans out the
//old titles from a single document.
function ClearOutRoboHelpTitles(doc)
{
var deletedPgfs = 0;
var nextPgf;
var pgf;
//Get the first paragraph in the main flow
//to start the iteration.
pgf = doc.MainFlowInDoc.FirstTextFrameInFlow.FirstPgf;
//Iterate over all paragraphs in the main flow.
while(pgf.ObjectValid())
{
//Before we do anything, we need to
//get the next paragraph object, so we
//don't get lost if we end up deleting this one.
nextPgf = pgf.NextPgfInFlow;
//If the paragraph name (ie, the last assigned
//format tag) is the title tag, let's delete.
if(pgf.Name == roboHelpFormatTag)
{
pgf.Delete();
deletedPgfs++;
}
//Reassign our main loop variable
//and continue iteration.
pgf = nextPgf;
}
//Return the total count of deleted pgfs.
return deletedPgfs;
}
//This is the function that inserts new titles
//for a single document.
function InsertRoboHelpTitles(doc)
{
var totalInsertions = 0;
var pgf;
//Get the first paragraph in the main flow and begin iteration.
pgf = doc .MainFlowInDoc.FirstTextFrameInFlow.FirstPgf;
while(pgf.ObjectValid())
{
//Check to see if we are at a place where a title requires
//insertion. We'll use a custom function for this, defined
//later.
if(CheckIfInsertionIsRequired(doc, pgf))
{
//If we get here, an insertion is required. Let's use this custom
//function to do it.
InsertPgf(doc, roboHelpFormatTag,
pgf.PrevPgfInFlow,
GetPgfText(pgf.NextPgfInFlow));
totalInsertions++;
}
//get the next paragraph to continue the loop.
pgf = pgf.NextPgfInFlow;
}
return totalInsertions;
}
//This is the function that checks whether
//we are at an anchor paragraph tag, for which
//a title insertion is required before it.
//It basically checks a sequence of
//tags, which if they all match, we
//assume that it is a place for insertion.
function CheckIfInsertionIsRequired(doc, pgf)
{
var returnVal = false;
//Check if we are at the right anchored frame tag
if(pgf.Name == anchoredFrameTag)
{
//If so, see if the next paragraph is a figure caption
pgf = pgf.NextPgfInFlow;
if(pgf.ObjectValid() && pgf.Name.indexOf(figCaptionTagPrefix) == 0)
{
//If so, see if the next paragraph is a figure number
pgf = pgf.NextPgfInFlow;
if(pgf.ObjectValid() && pgf.Name == figNumTag)
{
//If so, see if the next paragraph is a notes heading
pgf = pgf.NextPgfInFlow;
if(pgf.ObjectValid() && pgf.Name == notesHeadingTag)
{
//if so, we have a winner!
returnVal = true;
} //end notes heading tag if
} //end fig num tag if
} //end fig caption tag if
} //end anchored frame tag if
return returnVal;
}
//A general purpose paragraph inserter. It will insert
//the new paragraph after the reference paragraph,
//using the format and text provided.
function InsertPgf(doc, formatName, referencePgf, newText)
{
var newPgf;
var everythingWorkedRight = false;
//Insert the new paragraph.
if(referencePgf.ObjectValid())
newPgf = doc.NewSeriesObject (Constants.FO_Pgf, referencePgf);
else
newPgf = doc.NewSeriesObject (Constants.FO_Pgf, 0);
//If it inserted OK, set the text and the format;
if(newPgf.ObjectValid())
{
//Here is how to add text
var tr = new TextRange();
tr.beg.obj = tr.end.obj = newPgf;
tr.beg.offset = 0;
doc.AddText(tr.beg, newText);
//Reset the text range offset to the end of the
//newly-inserted text to prepare for formatting.
tr.end.offset = Constants.FV_OBJ_END_OFFSET;
//Get the format tag object, then apply if it exists in the template
var formatObj =
doc.GetNamedObject(Constants.FO_PgfFmt, formatName);
if(formatObj.ObjectValid())
{
//This is how you apply a paragraph format, by
//copying the properties from the format object
//to the paragraph object.
var props = formatObj.GetProps();
newPgf.SetProps(props);
everythingWorkedRight = true;
}
}
return everythingWorkedRight;
}
//A simple function to retrieve the text of
//a paragraph.
function GetPgfText(pgf)
{
var text = "";
if(!pgf.ObjectValid()) return text;
var ti = pgf.GetText(Constants.FTI_String);
for(var i = 0; i < ti.length; i++)
text += ti.sdata;
return text;
}
//A function to allow stepping through a book, chapter
//by chapter. Send it the document you are currently at
//and it will send back the next one that is open. It will
//not open any closed files.
//Send null for currentDoc to get the first open chapter.
function GetNextOpenBookChapter(book, currentDoc)
{
var comp;
var returnDoc = null;
var foundReference = false;
//We will loop through all chapters (components).
//However, we don't really want to consider any
//until we've found the one we are currently at,
//which is the doc sent here. So, the loop will operate
//on this flag to know when to start considering
//components. In the case where we want the first
//open chapter, we can automatically assume that
//any component is a valid candidate.
if(currentDoc == null) foundReference = true;
//get the first component in the book.
comp = book.FirstComponentInBook;
//Iterate through the components.
while(comp != null && comp.ObjectValid())
{
//If we find that we have reached the reference point;
//that is, the current document, we can reset
//this flag to start considering components
//for return.
if(!foundReference && comp.Name == currentDoc.Name)
foundReference = true;
//Otherwise, if we are already considering components,
//let's see if there is an open document for this one.
else if(foundReference)
returnDoc = DocIsOpen(comp.Name);
//If we got ourselves the next chapter, we are done. Null
//out the loop variable to end iteration.
if(returnDoc != null)
comp = null;
//otherwise, get the next component and keep going.
else comp = comp.NextBookComponentInDFSOrder;
}
//Send back whatever we got, if anything.
return returnDoc;
}
//A simple function to find an open document
//based on its fully-qualified path.
function DocIsOpen(path)
{
var tempDoc;
var returnDoc = null;
//If our path is empty, no sense continuing.
if(path == "") return returnDoc;
//get the first open document in the session.
tempDoc = app.FirstOpenDoc;
//Loop through all open documents
//until we find the one we want or we just
//run out.
while(tempDoc.ObjectValid())
{
if(tempDoc.Name == path)
{
//if we found it, set our return
//variable and break out of the loop.
returnDoc = tempDoc;
break;
}
tempDoc = tempDoc.NextOpenDocInSession;
}
//send back whatever we got, if anything.
return returnDoc;
}