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.
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 have 3 multi-value fields and I have already inserted values in them. All of the fields are Text type, edible. What I'm trying to do is that I want to add functionality in xpages, so that I can add new values to those fields.
Here's what I got so far:
The code that triggers on the save button:
var statuss = document1.getItemValue("statuss");
var stat_vec:java.util.Vector = document1.getItemValue("statuss_update");
stat_vec.add(statuss);
document1.replaceItemValue("statuss_update", stat_vec);
var vards = session.getEffectiveUserName();
var vards_vec:java.util.Vector = document1.getItemValue("name_update");
vards_vec.add(vards);
document1.replaceItemValue("name_update", vards_vec);
var laiks = session.createDateTime("Today");
var laiks_vec:java.util.Vector = document1.getItemValue("time_update");
laiks_vec.add(laiks);
document1.replaceItemValue("time_update", laiks_vec);
document1.save();
The code that I have atteched to the computedField, where the values are displayed from the 3 multi value fields + it refreshes when I insert new values:
var x = document1.getItemValue("statuss_update");
var y = document1.getItemValue("name_update");
var z = document1.getItemValue("time_update");
var html = "<head><link rel=\"stylesheet\" type = \"text/css\" href=\"test.css\"></head><table id=\"tabula\">";
for (i = 0 ; i < x.size()-1; i++){
html= html + "<tr><td>" + x[i] + "</td><td>" + y[i] + "</td><td>" +z[i] + "</td></tr>";
}
html = html + "</table>";
I can insert the values and they get displayed in the HTML table but the problem is with saving the edits. Whenever I try to save the document (I have a save button that has save document event attached to it) I get the error:
Could not save the document 1B06 NotesException: Unknown or
unsupported object type in Vector
As far as I understand I'm trying to savesomething in a field, where the values type is not supported. Can anyone give me a hint what am I doing wrong or where to look for the problem? Been stuck with this for a pretty long time.
Is it this part?
var statuss = document1.getItemValue("statuss");
var stat_vec:java.util.Vector = document1.getItemValue("statuss_update");
stat_vec.add(statuss);
It looks like you're getting the statuss item's values (potentially a Vector??) and adding it to the Vector for statuss_update. If it's definitely just one value, getItemValueString() would work better.
I'm nnot sure if this is right, but you mention all fields are Text type, but it looks like you're passing a DateTime to the third one.
It might be worth analysing the contents of the Vectors before it's doing the save, just to make sure they contain what you expect.
How do you call a different function when a line of text from a TextField/TextArea is clicked?
I already have a function which retrieves a description when any point of the TextField is clicked:
list.text = "chicken";
list.addEventListener(MouseEvent.CLICK, getter);
var descriptionArray:Array = new Array();
descriptionArray[0] = ["potato","chicken","lemon"];//words
descriptionArray[1] = ["Round and Brown","Used to be alive","Yellow"];//descriptions
function getter(e:MouseEvent):void
{
for (var i:int = 0; i < descriptionArray.length; i++)
{
var str:String = e.target.text;//The text from the list textfield
if (str == descriptionArray[0][i]) //if the text from List is in the array
{
trace("found it at index: " + i);
description.text = descriptionArray[1][i];//displays "Used to be alive".
}
else
{
trace(str+" != "+descriptionArray[0][i]);
}
}
}
It works fine, and returns the correct description.
But I want it to instead retrieve a different description depending on what line in the TextField/TextArea was clicked, like, if I used list.text = "chicken\npotato"
I know I can use multiple textfields to contain each word, but the list might contain over 100 words, and I want to use the TextArea's scrollbar to scroll through the words in the list, and if I used multiple textfields/areas, each one would have its own scrollbar, which is pretty pointless.
So, how do I call a different function depending on what line I clicked?
PS: It's not technically a different function, it's detecting the string in the line that was clicked, I just put it that way for minimal confusion.
There are a few built-in methods that should make your life easier:
function getter(e:MouseEvent):void
{
// find the line index at the clicked point
var lineIndex:int = list.getLineIndexAtPoint(e.localX, e.localY);
// get the text at that line index
var itemText:String = list.getLineText(lineIndex).split("\n").join("").split("\r").join("");
// find the text in the first array (using indexOf instead of looping)
var itemIndex:int = descriptionArray[0].indexOf(itemText);
// if the item was found, you can use the sam index to
// look up the description in the second array
if(itemIndex != -1)
{
description.text = descriptionArray[1][itemIndex];
}
}
I have this code below and trying to populate my pdf with values from the database.
PdfPCell points = new PdfPCell(new Phrase("and is therefore entitled to ", arialCertify));
points.Colspan = 2;
points.Border = 0;
points.PaddingTop = 40f;
points.HorizontalAlignment = 1;//0=Left, 1=Centre, 2=Right
// code below needs attention
var cID = "ALFKI";
var xw = Customers.First(p => p.CustomerID ==cID);
table.AddCell(xw.CompanyName.ToString());
I can not figure out where I am going wrong. When I remove the code under 'code below needs attention' it works but I need the database values.
I am using webmatrix with ItextSharp. If to answer you need further code please let me know.
PdfPCell cell=new PdfPCell(new Phrase(xw.CompanyName.ToString()));
cell.setColspan(numColumns);
table.addCell(cell);
document.add(table);
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