I have a very simple ExtendScript script which creates a new document out of a subset of the current active document:
var sourceDocument = app.activeDocument;
var i, j;
for(i = 0; i < sourceDocument.layers.length; i++) {
sourceDocument.layers.item(i).locked = false;
}
for(i = 0; i < sourceDocument.spreads.length; i++) {
for(j = 0; j < sourceDocument.spreads.item(i).textFrames.length; j++) {
if(sourceDocument.spreads.item(i).textFrames.item(j).locked) {
sourceDocument.spreads.item(i).textFrames.item(j).locked = false;
}
}
}
var destDocument = app.documents.add();
var firstPageIndex = 0; // In the actual script, this is chosen by the user.
var lastPageIndex = 5; // In the actual script, this is chosen by the user.
destDocument.importStyles(ImportFormat.paragraphStylesFormat, new File(sourceDocument.filePath + "/" + sourceDocument.name), GlobalClashResolutionStrategy.LOAD_ALL_WITH_OVERWRITE);
destDocument.importStyles(ImportFormat.characterStylesFormat, new File(sourceDocument.filePath + "/" + sourceDocument.name), GlobalClashResolutionStrategy.LOAD_ALL_WITH_OVERWRITE);
destDocument.viewPreferences.horizontalMeasurementUnits = sourceDocument.viewPreferences.horizontalMeasurementUnits;
destDocument.viewPreferences.verticalMeasurementUnits = sourceDocument.viewPreferences.verticalMeasurementUnits;
destDocument.documentPreferences.facingPages = sourceDocument.documentPreferences.facingPages;
destDocument.documentPreferences.pageHeight = sourceDocument.documentPreferences.pageHeight;
destDocument.documentPreferences.pageWidth = sourceDocument.documentPreferences.pageWidth;
destDocument.documentPreferences.pageSize = sourceDocument.documentPreferences.pageSize;
destDocument.documentPreferences.allowPageShuffle = true;
var range = sourceDocument.pages.itemByRange(firstPageIndex, lastPageIndex);
range.duplicate(LocationOptions.AFTER, destDocument.pages[destDocument.pages.length - 1]);
destDocument.pages[0].remove(); // An empty spread containing an empty page is added when the new document is created and we cannot remove it before other pages are inserted (Documents must have at least one page)
This script works perfectly on many documents. But when I execute it against one particular document (let's call it foo.indd), InDesign becomes unresponsive when executing the duplication: range.duplicate(LocationOptions.AFTER, destDocument.pages[destDocument.pages.length - 1]);. From then on, the only thing I can do is force InDesign to quit.
Is this an InDesign bug? How can I find which part of this particular document is creating the problem?
I can't really say what's wrong in your example but if indesign hangs, that might caused by the loops ( to infinity and beyond :) )
So you may try to avoid issues by outputting the loop limit to avoid InDesign re-calculation
var limit = …
for ( i = 0; i<limit ; i++)…
Additionally you could try to write info on the console to get info where InDesign is actually being stuck. So write informations on the fly on a report file and you might finally identify the issue area.
Also, you can try to interrogate every key items to see if the file has some issue.
Last but not least, try a manual export to idml of this file, re open and run again the script. Sometimes files become clunky and passing by idml fix most of them.
Give this script a try onto your probleamtic file. If it fails, please have a look at the report it should have generated onto the desktop.
http://www.loicaigon.com/downloads/cloneDocument.jsx
Loic
http://www.loicaigon.com
Related
The project I am currently working on is to export an Excel file to PDF.
The Excel file is a "Template" that allows the generation of graphs. The goal is to fill some cells of the Excel file so that the graphs are generated and then to export the file in PDF.
I use Qt in C++ with the QAxObject class and all the data writing process works well but it's the PDF export part that doesn't.
The problem is that the generated PDF file also contains the data of the graphs while these data are not included in the print area of the Excel template.
The PDF export is done with the "ExportAsFixedFormat" function which has as a parameter the possibility to ignore the print area that is "IgnorePrintAreas" at position 5. Even if I decide to set this parameter to "false", so not to ignore the print area and therefore to take into account the print area, this does not solve the problem and it produces the same result as if this parameter was set to "true".
I tried to vary the other parameters, to change the type of data passed in parameter or not to use any parameter but it does not change anything to the obtained result which is always the same.
Here is the link to the "documentation" of the export command "ExportAsFixedFormat":
https://learn.microsoft.com/en-us/office/vba/api/excel.workbook.exportasfixedformat
I give you a simplified version of the command suite that is executed in the code:
Rapport::Rapport(QObject *parent) : QObject(parent)
{
//Create the template from excel file
QString pathTemplate = "/ReportTemplate_FR.xlsx"
QString pathReporter = "/Report"
this->path = QDir(QDir::currentPath() + pathReporter + pathTemplate);
QString pathAbsolute(this->path.absolutePath().replace("/", "\\\\"));
//Create the output pdf file path
fileName = QString("_" + QDateTime::currentDateTime().toString("yyyyMMdd-HHmmssff") + "_Report");
QString pathDocument = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation).append("/").replace("/", "\\\\");
QString exportName(pathDocument + fileName + ".pdf");
//Create the QAxObjet that is linked to the excel template
this->excel = new QAxObject("Excel.Application");
//Create the QAxObject « sheet » who can accepte measure data
QAxObject* workbooks = this->excel->querySubObject("Workbooks");
QAxObject* workbook = workbooks->querySubObject("Add(const QString&)", pathAbsolute);
QAxObject* sheets = workbook->querySubObject("Worksheets");
QAxObject* sheet = sheets->querySubObject("Item(int)", 3);
//Get some data measure to a list of Inner class Measurement
QList<Measurement*> actuMeasure = this->getSomeMeasure() ; //no need to know how it’s work…
//Create a 2 dimentional QVector to be able to place data on the table where we want (specific index)
QVector<QVector<QVariant>> vCells(actuMeasure.size());
for(int i = 0; i < vCells.size(); i++)
vCells[i].resize(6);
//Fill the 2 dimentional QVector with data measure
int row = 0;
foreach(Measurement* m, actuMeasure)
{
vCells[row][0] = QVariant(m->x);
vCells[row][1] = QVariant(m->y1);
vCells[row][2] = QVariant(m->y2);
vCells[row][3] = QVariant(m->y3);
vCells[row][4] = QVariant(m->y4);
vCells[row][5] = QVariant(m->y5);
row++;
}
//Transform the 2 dimentional QVector on a QVariant object
QVector<QVariant> vvars;
QVariant var;
for(int i = 0; i < actuMeasure.size(); i++)
vvars.append(QVariant(vCells[i].toList()));
var = QVariant(vvars.toList());
//Set the QVariant object that is the data measure on the excel file
sheet->querySubObject("Range(QString)", "M2:AB501")->setProperty("Value", var);
//Set the fileName on the page setup (not relevant for this example)
sheet->querySubObject("PageSetup")->setProperty("LeftFooter", QVariant(fileName));
//Export to PDF file with options – NOT WORKING !!!
workbook->dynamicCall("ExportAsFixedFormat(const QVariant&, const QVariant&, const QVariant&, const QVariant&, const QVariant&)", QVariant(0), QVariant(exportName), QVariant(0), QVariant(false), QVariant(false));
//Close
workbooks->dynamicCall("Close()");
this->excel->dynamicCall("Quit()");
}
A this point I really need help to find a way to solve this problem.
I also wonder if this is not a bug of the QAxObject class.
I finally found a solution on another forum.
If anyone needs help, I'll leave the link to the answer.
I'm using a custom footer on my View Control; there I have for example:
Displaying 1 - 25 of 34200
My underlying View is Categorized so the total of entries should include categories as well.
So far the only way I'm able to find the total of entries including categories is using NotesViewNavigator; however, performance is not acceptable since it's taking around 27 seconds to compute this piece of code.
I'm sure the issue is with line var nav:NotesViewNavigator = view1.createViewNav(); because I added some debugger info:
start = new Date().getTime();
var viewPanel1:com.ibm.xsp.component.xp.XspViewPanel = getComponent("dataView1");
var nav:NotesViewNavigator = view1.createViewNav();
if (viewScope.VendorSrch == "" || viewScope.VendorSrch == null){
var total = nav.getCount();
}else{
var total = viewPanel1.getRowCount();// View can be filtered by user as well (using categoryFilter property)
}
var from =(viewPanel1.getFirst() < total? (viewPanel1.getFirst() + 1 ) : total);
var tmpTo = viewPanel1.getFirst() + viewPanel1.getRows();
var to = (tmpTo < total? tmpTo : total);
var elapsed = new Date().getTime() - start;
print(elapsed + " ms");
"</b>Displaying <b>"+ from +"</b> - <b>"+ to + "</b> of " + "<b>"+total+"</b>"
Does anyone know how can I improve this piece of code?
Please note documents in this View have Readers fields as well which may be impacting the
performance of this operation.
You are trapped in performance hell. Read access protection is only and only stored inside the document. So when you ask your view navigator to get the count its only option is to open all involved documents - hence the poor performance. Read protection and performance are natural enemies (just imagine: you have an office where every door is locked and to move around you have to check all your keys every time if you have one with the matching lock number).
The way out of reader field introduced performance hell is to read only the entries you actually need (as outlined). It could be a little tricky if a user has access to documents based on name, group-membership and role (that would make one read per access), but it is still very much feasible. In this case you would use a repeat control and a object data source or managed bean, so the multiple passes happen in the background.
Bonus trick: if you add a column with the formula 1 (just the number) and add it up, while categorizing it, then you can just jump from naventry to next sibling (that would be the next category) and add the numbers --> much less reads involved and NO documents opened.
To stress again: nav.count needs to open all documents and is a BAD idea, anything that requires read access to be checked is a bad idea, so using one (or more) viewNav based on access rights that actually only read documents the user can read is the way to go.
Let me know if you need more hints
I tried several approaches and using this loop reduced the time from 27 seconds average to 2.7 seconds:
start = new Date().getTime();
var viewPanel1:com.ibm.xsp.component.xp.XspViewPanel = getComponent("dataView1");
var nav:NotesViewNavigator = view1.createViewNav();
nav.setEntryOptions(NotesViewNavigator.VN_ENTRYOPT_NOCOLUMNVALUES);
// disable autoupdate
view1.setAutoUpdate(false);
if (viewScope.VendorSrch == "" || viewScope.VendorSrch == null){
nav.setBufferMaxEntries(400);
//var total = nav.getCount(); // This works but slow
// peform lookup
var vwentry:NotesViewEntry = nav.getFirst();
var vwentrytmp:NotesViewEntry = null;
count = 0;
while (vwentry != null){
count = count + 1;
// Get entry and go recycle
vwentrytmp = nav.getNext(vwentry);
vwentry.recycle();
vwentry = vwentrytmp;
}
total = count;
}else{
var total = viewPanel1.getRowCount();
}
var from =(viewPanel1.getFirst() < total? (viewPanel1.getFirst() + 1 ) : total);
var tmpTo = viewPanel1.getFirst() + viewPanel1.getRows();
var to = (tmpTo < total? tmpTo : total);
var elapsed = new Date().getTime() - start;
print(elapsed + " ms");
"</b>Displaying <b>"+ from +"</b> - <b>"+ to + "</b> of " + "<b>" + total + "</b>"
I have a Google Apps script which replaces placeholders in a copy of a template document with some text by calling body.replaceText('TextA', 'TextB');.
Now I want to extend it to contain images. Does anybody have idea how to do this?
Thank you,
Andrey
EDIT: Just to make it clear what my script does. I have a Google form created in a spreadsheet. I've created a script which runs upon form submission, traverses a sheet corresponding to the form, find unprocessed rows, takes values from corresponding cells and put them into a copy of a Google document.
Some fields in the Google form are multi-line text fields, that's where '\r\r' comes from.
Here's a workaround I've come up with by now, not elegant, but it works so far:
// replace <IMG src="URL"> with the image fetched from URL
function processIMG_(Doc) {
var totalElements = Doc.getNumChildren();
for( var j = 0; j < totalElements; ++j ) {
var element = Doc.getChild(j);
var type = element.getType();
if (type =='PARAGRAPH'){
var par_text = element.getText();
var start = par_text.search(new RegExp('<IMG'));
var end = par_text.search(new RegExp('>'));
if (start==-1)
continue;
// Retrieve an image from the web.
var url = getURL_(par_text.substring(start,end));
if(url==null)
continue;
// Before image
var substr = par_text.substring(0,start);
var new_par = Doc.insertParagraph(++j, substr);
// Insert image
var resp = UrlFetchApp.fetch(url);
new_par.appendInlineImage(resp.getBlob());
// After image
var substr = par_text.substring(end+1);
Doc.insertParagraph(++j, substr);
element.removeFromParent();
j -= 2; // one - for latter increment; another one - for increment in for-loop
totalElements = Doc.getNumChildren();
}
}
}
Here is a piece of code that does (roughly) what you want.
(there are probably other ways to do that and it surely needs some enhancements but the general idea is there)
I have chosen to use '###" in the doc to mark the place where the image will be inserted, the image must be in your google drive (or more accurately in 'some' google drive ).
The code below uses a document I shared and an image I shared too so you can try it.
here is the link to the doc, don't forget to remove the image and to put a ### somewhere before testing (if ever someone has run the code before you ;-)
function analyze() { // just a name, I used it to analyse docs
var Doc = DocumentApp.openById('1INkRIviwdjMC-PVT9io5LpiiLW8VwwIfgbq2E4xvKEo');
var image = DocsList.getFileById('0B3qSFd3iikE3cF8tSTI4bWxFMGM')
var totalElements = Doc.getNumChildren();
var el=[]
for( var j = 0; j < totalElements; ++j ) {
var element = Doc.getChild(j);
var type = element.getType();
Logger.log(j+" : "+type);// to see doc's content
if (type =='PARAGRAPH'){
el[j]=element.getText()
if(el[j]=='###'){element.removeFromParent();// remove the ###
Doc.insertImage(j, image);// 'image' is the image file as blob
}
}
}
}
EDIT : for this script to work the ### string MUST be alone in its paragraph, no other character before nor after... remember that each time one forces a new line with ENTER the Document creates a new paragraph.
my code :
object c = "d:\\1.doc";
if(File.Exists(c.ToString()))
{
File.Delete(c.ToString());
}
error :
The process cannot access the file 'd:\1.doc' because it is being used
by another process.
How close ? with code
first of all use string instead of object, so:
string c = "d:\\1.doc";
now as the message indicated the file being used by another process. either by windows process, or you are opening the file stream and forget to close it. check in your code where you are interacting with the file.
Edit: Since you are using Microsoft.Office.Interop.Word make sure you close the file(s) open first like:
Word.ApplicationClass word = new Word.ApplicationClass();
//after using it:
if (word.Documents.Count > 0)
{
word.Documents.Close(...);
}
((Word._Application)word.Application).Quit(..);
word.Quit(..);
I had the same type of issue when I wanted to Delete File after Open/Read it using Microsoft.Office.Interop.Word and I needed to close my document and the application like that :
private void parseFile(string filePath)
{
// Open a doc file.
Microsoft.Office.Interop.Word.Application application = new Microsoft.Office.Interop.Word.Application();
Document document = application.Documents.Open(filePath);
// Loop through all words in the document.
int count = document.Words.Count;
for (int i = 1; i <= count; i++)
{
// Write the word.
string text = document.Words[i].Text;
Console.WriteLine("Word {0} = {1}", i, text);
}
// Close document correctly
((_Document)document).Close();
((_Application)application).Quit();
}
You have that file actively open in this or another program, and Windows prevents you from deleting it in that case.
Check if the file still running (opened) by another application
1- Microsoft Word
2- WordPad
I save a node with images which is beeing filled by a service. I write the image with drupal_write_record and the pictures appear in the node already. But when - at the end of the script - I call a node_save the image disappears again.
My Code:
$file_drupal_path= $filedata['location'];
$file = new stdClass();
$file->filename = $filedata['name'];
$file->filepath = $file_drupal_path;
$file->filemime = $filedata['mime'];
$file->filesize = filesize($file_drupal_path);
$file->filesource = $filedata['name'];
$file->uid = 1;
$file->status = FILE_STATUS_PERMANENT;
$file->timestamp = time();
$file->list = 1;
// fid is populated by drupal_write_record
drupal_write_record('files', $file);
$imageData = field_file_load($file->fid, TRUE);
return $imageData;
and the node_save
function transport_service_save($node) {
$node = (object) ($node);
$node->promote = 1;
node_save(node_submit($node));
return print_r( $node , TRUE );
}
in the cck image field in the node there are keys with unset values as well.
Andreas,
Had the exact same problem.
Using drupal_execute() as described here fixed the problem immediately:
// Save the node, updated or new
// Get the node object as an array the node_form can use
$values = (array)$node;
// Save the node (this is like pushing the submit button on the node edit form)
drupal_execute('abc_node_form', $values, $node);
Source:
http://www.drupalisms.com/gregory-go/blog/use-drupalexecute-instead-of-nodesave-to-save-a-node-programmatically
But a fair warning: it ran like a charm for the first few rounds, but now I get tonnes of errors type:
warning: call_user_func_array() [function.call-user-func-array]:
First argument is expected to be a valid callback, 'node_form' was given in ...
Can't see what changed. All I did was call the page that did the saving a few times to test it.
And a final (hopefully!) edit to this reply. It seems that including the node.module file that contains node_form is needed, so adding this:
module_load_include('', 'node', 'node.pages.inc');
in your code (like in hook_init()) will do the trick.
Worked here, and now my nodes save with images intact.