NewUnhandledException
NewUnhandledException

Reputation: 743

Automate the process of saving Framemaker documents (*.fm) as Xml Files

We have a list of Framemaker document files (*.fm files) coming into a folder. We need to pick up these files and convert to xml format (same as the saveAs opertion from the File menu).

I have written the follwing function to Save fm files to xml

Code to Save fm files to xml files
function saveAsXml (doc) {
   // Get required parameters for the save function.
    var params = GetSaveDefaultParams();
    var returnParamsp = new PropVals();

    // Replace the .fm extension with .mif.
    var saveName = doc.Name.replace (/\.[^\.\\]+$/,".xml");


    var i = GetPropIndex(params, Constants.FS_FileType);
    params[i].propVal.ival = Constants.FV_SaveFmtXml;

    // Save the document as XML.
    doc.Save(saveName, params, returnParamsp);

}

How to automate this process so that code checks -

  1. New fm files in the folder
  2. Saves the fm file as xml
  3. Moves the saved fm file to a different folder

Thanks

Upvotes: 0

Views: 2364

Answers (2)

chrispitude
chrispitude

Reputation: 121

I needed to save a set of FrameMaker .book files as .xml files. To do this, I wrote a perl script that runs under Windows 10 WSL (Windows Subsystem for Linux). It takes two arguments: a .book file filename, and an XML output directory name:

fm2xml.pl /foo/bar/mybook.book ./xmldir/mybook/

This perl script writes out a temporary ExtendScript file into the Adobe Scripts directory in My Documents (to avoid security dialog boxes), then runs it, then prints the console output from the XML save. You'll need the ExtendScript Toolkit installed as well as FrameMaker.

Here's the fm2xml.pl script:

#!/usr/bin/perl
use strict;
use warnings;
use File::Basename;
use File::Path qw(make_path remove_tree);

my $book_file = shift;
my $bookname = (basename($book_file));
$bookname =~ s!\.book$!!;

my $xml_dir = (shift or "./${bookname}_xml");
my $xml_dir_temp = $xml_dir.'_TEMP';
my $xml_file_temp = $xml_dir_temp."/${bookname}.xml";

my $maker_ini = <<'EOS';
[Frame]
ProductInterface=Structured FrameMaker
[Preferences]
ThorMessageShown=On
UnavailableFontsDialog=Off
EOS
`echo "$maker_ini" > /mnt/c/Users/$ENV{USER}/AppData/Roaming/Adobe/FrameMaker/14/maker.ini`;

my $jsx_script = <<'EOS';
#target framemaker-14.0

var books = [
"BOOK_FILE",
];

main();

function main() {
  var count = books.length;
  for (i = 0; i < count; i+= 1) {
    Console("Converting " + books[i]);
    convertBook(books[i]);
  }
//  alert("Finished");
//  app.Close(Constants.FF_CLOSE_MODIFIED);
}

function convertBook (bookname) {
  Console("  Opening book " + bookname);
  SimpleOpen(bookname);
  var book = app.ActiveBook;
  Console("  Opened book " + book.Name);
  fixLinks(book);
  Console("  Saving as XML...");
  var xmlName = saveAsXml (book);
  Console("  Closing everything in " + book.Name);
  CloseAll();
}

function fixLinks (book) {
  var comp = book.FirstComponentInBook;
  var docs = [];
  while(comp.ObjectValid()) {
    Console("  Opening doc " + comp.Name);
    var doc = OpenFile(comp.Name);
    Console("  Opened doc " + doc.Name);
    docs.push(doc);
    comp = comp.NextBookComponentInDFSOrder;
  }
  Console("  Fixing links...");
  CallClient("XRefWizard", "SetIDs---" + book.Name + "---DoReporting");
  Console("  Fixed links...");
}

function saveAsXml (doc) {
  // Get required parameters for the save function.
  var params = GetSaveDefaultParams();
  var returnParamsp = new PropVals();
  // Replace the .fm extension with .xml
  var saveName = "XML_FILE";
  Console("  Saving to '" + saveName + "'...");
  var i = GetPropIndex(params, Constants.FS_FileType);
  params[i].propVal.ival = Constants.FV_SaveFmtXml;
  // Save the document as XML.
  doc.Save(saveName, params, returnParamsp);
}

function OpenFile(path) {
  props = new PropVals();
  props = GetOpenDefaultParams();
  props[GetPropIndex(props, Constants.FS_AlertUserAboutFailure)].propVal.ival = false;
  props[GetPropIndex(props, Constants.FS_FileIsInUse)].propVal.ival = Constants.FV_ResetLockAndContinue;
  props[GetPropIndex(props, Constants.FS_BookIsInUse)].propVal.ival = Constants.FV_ResetLockAndContinue;
  props[GetPropIndex(props, Constants.FS_LockCantBeReset)].propVal.ival = Constants.FV_DoOK;
  props[GetPropIndex(props, Constants.FS_FileIsOldVersion)].propVal.ival = Constants.FV_DoOK;
  props[GetPropIndex(props, Constants.FS_FontChangedMetric)].propVal.ival = Constants.FV_DoOK;
  props[GetPropIndex(props, Constants.FS_FontNotFoundInCatalog)].propVal.ival = Constants.FV_DoOK;
  props[GetPropIndex(props, Constants.FS_FontNotFoundInDoc)].propVal.ival = Constants.FV_DoOK;
  props[GetPropIndex(props, Constants.FS_LanguageNotAvailable)].propVal.ival = Constants.FV_DoOK;
  props[GetPropIndex(props, Constants.FS_UpdateXRefs)].propVal.ival = Constants.FV_DoNo;
  props[GetPropIndex(props, Constants.FS_UseRecoverFile)].propVal.ival = Constants.FV_DoNo;
  props[GetPropIndex(props, Constants.FS_UseAutoSaveFile)].propVal.ival = Constants.FV_DoNo;
  props[GetPropIndex(props, Constants.FS_OpenFileNotWritable)].propVal.ival = Constants.FV_DoOK;
  returnp = new PropVals();
  var file = Open(path, props, returnp);
  return file;
}

function CloseAll()
{
  doc=app.FirstOpenDoc
  while(doc.id !=0)
  {
    doc2=doc.NextOpenDocInSession;
    Console("  Closing doc " + doc.Name + "...");
    doc.Close(Constants.FF_CLOSE_MODIFIED);
    doc = doc2;  
  }

  book=app.FirstOpenBook
  while(book.id !=0)
  {
    book2=book.NextOpenBookInSession;
    Console("  Closing book " + book.Name + "...");
    book.Close(Constants.FF_CLOSE_MODIFIED);
    book=book2
  }
}

EOS

print "Processing book '$book_file'...\n";
`echo -n '' > /mnt/c/users/$ENV{USER}/AppData/Roaming/Adobe/FrameMaker/14/consfile.txt`;  # empty out the log file

my $jsx_file = "/mnt/c/Users/$ENV{USER}/Documents/Adobe Scripts/fm_to_xml.jsx";  # script must be here to avoid Adobe security warnings
remove_tree($xml_dir, $xml_dir_temp);
make_path($xml_dir_temp);

chomp (my $book_file_win = `wslpath -a -w '$book_file'`);
chomp (my $xml_file_win = `wslpath -a -w '$xml_file_temp'`);
chomp (my $jsx_file_win = `wslpath -a -w '$jsx_file'`);

$book_file_win =~ s!\\!\\\\!g;
$xml_file_win =~ s!\\!\\\\!g;

$jsx_script =~ s!BOOK_FILE!$book_file_win!;
$jsx_script =~ s!XML_FILE!$xml_file_win!;

open(JSX_FILE, '>', "${jsx_file}") or die "Can't open '${jsx_file}': $!";
print JSX_FILE $jsx_script;
close JSX_FILE;

my $book_dir = dirname($book_file);
`find '$book_dir' -name '*.lck' -print0 | xargs -0r rm`;  # try to remove lingering lock files

my $cmd = "\"/mnt/c/Program\ Files\ \(x86\)/Adobe/Adobe\ ExtendScript\ Toolkit\ CC/ExtendScript\ Toolkit.exe\" -run '$jsx_file_win'";
print "Running:\n  $cmd\n";
`$cmd`;

my $start_time = time();
while (!-e "${book_file}.lck" && (time() - $start_time) < 10) {`sleep 2`;}

print "Waiting for book lock file to disappear...\n";
while (-e "${book_file}.lck") {`sleep 2`;}

print `cat /mnt/c/users/chrispy/AppData/Roaming/Adobe/FrameMaker/14/consfile.txt | egrep -v '(is not available|will be used in this session)'`;  # strip out missing font messages
if (-e $xml_file_temp) {
`sed -i 's!\\]\\]>!]]\\&gt;!' $xml_dir_temp/*.e*`;  # ']]>' must be escaped in XML, but FrameMaker doesn't
 print "\n*** XML write for '$bookname' succeeded.\n\n";
 rename($xml_dir_temp, $xml_dir);
} else {
 print "\n*** XML write for '$bookname' FAILED.\n\n";
 remove_tree($xml_dir_temp);
}

Caveat emptor: This whole approach is hacky. The "error-checking" is a ten-second timeout after which it throws up its hands if it sees no signs of life from FrameMaker.

Note that in my case, I use Russ Ward's excellent XRef Wizard plugin to fix duplicate IDs that mess up XML output. If you have this plugin, uncomment the fixLinks(book) line above.

If you're running a different version than FrameMaker 2017, you'll need to change the #target comment in the embedded JSX script.

If you're a glutton for punishment, you can also try the Makefile I used to automate this:

FM2XML = $(shell which fm2xml.pl)

fm_book_files := \
 some/dir/book1.book \
 /yet/another/book2.book

book_names := $(patsubst %.book, %, $(notdir $(fm_book_files)))
xml_dirs := $(foreach b, $(book_names), ./xml/$b)
dirs := $(dir ${fm_book_files})

vpath %.book $(dir $(fm_book_files))


all: $(xml_dirs)
print-%: ; @echo $* = $($*)

.SECONDEXPANSION:
$(xml_dirs): ./xml/%: %.book
        ${FM2XML} ${<} ${@}

But if you hold your breath and don't move, it all works. :)

Upvotes: 0

bbh
bbh

Reputation: 519

If you are on windows, you can write a simple batch file to run your extendscript in a loop via the batch file (.bat) from command line.

Upvotes: 0

Related Questions