How to get the Table inside a MS Word bookmark and add multiple rows based on its Mergefield cells using docx4j? - ms-word

Till now am able to parse a docx file using docx4j and find the bookmarks and all the tables in a docx file using below code:
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(new java.io.File(docxFile));
List<Object> paragraphs = getAllElementFromObject(template.getMainDocumentPart(), P.class);
for (Object p : paragraphs) {
RangeFinder rt = new RangeFinder("CTBookmark", "CTMarkupRange");
new TraversalUtil(p, rt);
for (CTBookmark content : rt.getStarts()) {
if (content.getName().equals("if_supdef")) {
List<Object> tbl = getAllElementFromObject(content, Tbl.class);
System.out.println("tbl==" + tbl.size());
}
}
}
TableFinder finder = new TableFinder();
new TraversalUtil(documentPart.getContent(), finder);
System.out.println("Found " + finder.tblList.size() + " tables");
I've got these lines of code from some blogs and answers from other questions.
Now I would like to find the table only inside a bookmark (here my bookmark name is if_supdef) rather than searching in entire document. Once I find the table, I would add rows based on number of data I receive from SQL table and MERGEFIELDS available.
Bookmark and its table look like something in below picture:
Once processed through docx4j it should look like:
In document.xml I see parent tag of w:tbl is body but not bookmark.
Is it possible to read the table inside bookmark? If so, how?
If not, what is the other alternative to uniquely identify a table and add contents to it?

Try something along the lines of the below.
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.docx4j.TraversalUtil;
import org.docx4j.TraversalUtil.CallbackImpl;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.wml.CTBookmark;
import org.docx4j.wml.CTMarkupRange;
import org.docx4j.wml.Tbl;
import jakarta.xml.bind.JAXBContext;
public class TableInBookmarkFinder {
public static JAXBContext context = org.docx4j.jaxb.Context.jc;
public static void main(String[] args) throws Exception {
String inputfilepath = System.getProperty("user.dir")
+ "/tbl_bookmarks.docx";
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage
.load(new java.io.File(inputfilepath));
MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();
// find
TableInBookmarkFinderCallback finder = new TableInBookmarkFinderCallback();
new TraversalUtil(documentPart.getContent(), finder);
List<TableInfo> tableInfos = finder.getTableInfos();
// result?
for (TableInfo ti : tableInfos) {
System.out.println("table contained in bookmarks:");
for (String s: ti.getBookmarkNames()) {
System.out.println("bookmark name: " + s);
}
}
}
public static class TableInfo {
TableInfo(Tbl tbl, List<String> bookmarkNames) {
this.tbl = tbl;
this.bookmarkNames = bookmarkNames;
}
private Tbl tbl;
public Tbl getTbl() {
return tbl;
}
private List<String> bookmarkNames;
public List<String> getBookmarkNames() {
return bookmarkNames;
}
}
public static class TableInBookmarkFinderCallback extends CallbackImpl {
public TableInBookmarkFinderCallback() {
}
/**
* Keep this set to true unless you don't
* want to traverse a table (eg a nested table).
* NB: If traversing from body level, you'll need to set it to true!
*/
private boolean traverseTables=true;
/**
* Track bookmarks encountered
*/
private Map<BigInteger, String> bookmarkInfos = new HashMap<BigInteger, String>();
/**
* What bookmarks are we currently in?
*/
private Set<BigInteger> currentBookmarks = new HashSet<BigInteger>();
/**
* What tables did we encounter?
*/
private List<TableInfo> tableInfos = new ArrayList<TableInfo>();
public List<TableInfo> getTableInfos() {
return tableInfos;
}
#Override
public List<Object> apply(Object o) {
System.out.println(o.getClass().getName());
if (o instanceof CTBookmark) {
CTBookmark bmStart = (CTBookmark)o;
bookmarkInfos.put(bmStart.getId(), bmStart.getName());
if (currentBookmarks.add(bmStart.getId()) ) {
// ok
System.out.println("added " + bmStart.getId());
} else {
System.out.println("ERROR: duplicate bookmarks with id " + bmStart.getId());
}
} else /* need this else because CTBookmark extends CTMarkupRange */
if (o instanceof CTMarkupRange) {
CTMarkupRange bmEnd = (CTMarkupRange)o;
if (currentBookmarks.remove(bmEnd.getId()) ) {
// ok
System.out.println("removed " + bmEnd.getId());
} else {
System.out.println("ERROR: no start element for bookmark with id " + bmEnd.getId());
}
}
if (o instanceof Tbl ) {
System.out.println("tbl");
List<String> bookmarkNames = new ArrayList<String>();
for (BigInteger bmId : currentBookmarks) {
bookmarkNames.add(bookmarkInfos.get(bmId));
}
tableInfos.add( new TableInfo( (Tbl)o, bookmarkNames));
}
return null;
}
#Override
public boolean shouldTraverse(Object o) {
if (traverseTables) {
return true;
} else {
// Yes, unless its a nested Tbl
return !(o instanceof Tbl);
}
}
}
}

Related

Selection in Notes Client not working (using Eclipse SWT)

I would like to create a sidebar in the IBM Notes Client which shows infos about an Email. Therefore I use the Eclipse Framework with SWT. The current Code looks like this:
package DisplayPlugin;
import java.util.Iterator;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.ISelectionListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.part.ViewPart;
import com.ibm.notes.java.ui.NotesUIElement;
import com.ibm.notes.java.ui.NotesUIWorkspace;
import com.ibm.notes.java.ui.documents.NotesUIDocument;
import com.ibm.notes.java.ui.views.NotesUIDocumentEntry;
import com.ibm.notes.java.ui.views.NotesUIView;
import com.ibm.notes.java.ui.views.NotesUIViewEntryCollection;
public class ShelfView extends ViewPart {
public static final String ID = ShelfView.class.getName();
private Label label;
private ISelectionListener _listener = new ISelectionListener() {
public void selectionChanged(IWorkbenchPart sourcePart, ISelection selection) {
NotesUIWorkspace ws = new NotesUIWorkspace();
NotesUIElement elem = ws.getCurrentElement();
if (elem instanceof NotesUIView) {
NotesUIView currentView = (NotesUIView) elem;
NotesUIViewEntryCollection collection = currentView.getActionableEntries();
Iterator docIterator = collection.documentIterator();
String txt = "";
while (docIterator.hasNext()) {
NotesUIDocumentEntry entry = (NotesUIDocumentEntry) docIterator.next();
for (int i = 0; i < entry.getColumnValues().size(); ++i) {
txt = txt + i + ": " + entry.getColumnValueString(i) + "\n";
}
txt = txt + "UNID: " + entry.getDocumentData().getUnid() + "\nURL: " + entry.getDocumentData().getEditUrl() + "\n";
}
label.setText(txt);
label.pack();
}
}
};
public ShelfView()
{
}
public void createPartControl(Composite parent)
{
Composite comp = new Composite( parent, SWT.NONE );
comp.setLayout(new GridLayout());
label = new Label(comp, SWT.NONE );
label.setText("Start...");
getViewSite().getPage().addPostSelectionListener(_listener);
}
public void setFocus()
{
}
}
The problem lies in the line: NotesUIElement elem = ws.getCurrentElement();
When you test the ViewPart in the Notes Client and mark a mail from the list, not the current entry is shown in the sidebar but the mail entry which has been marked before.
The following picture shows the IBM Notes Client with the sidebar.

Read content of repeating sections with apache POI

I Have a word document with a repeating section, containing other content controls.
In java project, I have a function that gets all sdts (content controls) from a word document in apache POI, in a List List.
When I inspect my repeating section in that list, I can get the text inside all content controls (inside my repeating section) but is apears as a long paragraph instead of other sdt nodes.
Is there a way to inspect content of repeating section sdt with Apache POI ? I can't find anything about it in the doc
function that gets all sdts
private static List
extractSDTsFromBodyElements(List<IBodyElement> elements) {
List<AbstractXWPFSDT> sdts = new ArrayList<AbstractXWPFSDT>();
for (IBodyElement e : elements) {
if (e instanceof XWPFSDT) {
XWPFSDT sdt = (XWPFSDT) e;
sdts.add(sdt);
} else if (e instanceof XWPFParagraph) {
XWPFParagraph p = (XWPFParagraph) e;
for (IRunElement e2 : p.getIRuns()) {
if (e2 instanceof XWPFSDT) {
XWPFSDT sdt = (XWPFSDT) e2;
sdts.add(sdt);
}
}
}
}
return sdts;
}
The XWPF part of apache poi is rudimentary until now and highly in development. In XWPFSDT is this mentioned also: "Experimental class to offer rudimentary read-only processing of of StructuredDocumentTags/ContentControl". So until now your code only gets the surrounding XWPFSDT of the repeating content control but not the inner controls. One could have seen that by having some debugging outputs in the code. See my System.out.println(...).
So to really get all XWPFSDTs we must go other ways using the underlaying XMLdirectly.
Lets have a complete example.
Look at this Worddocument:
As you see there is a single control to input the group name, then a repeating content control around three controls to input name, amount and date and then a single control to input the employee. All controls which shall be read have titles set. So whether the title is set, is the criterion whether a control is important for reading or not.
The following code now can read all controls and their content:
import java.io.FileInputStream;
import org.apache.poi.xwpf.usermodel.*;
import java.util.List;
import java.util.ArrayList;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.apache.xmlbeans.XmlCursor;
import javax.xml.namespace.QName;
public class ReadWordForm {
/*
private static List<AbstractXWPFSDT> extractSDTsFromBodyElements(List<IBodyElement> elements) {
List<AbstractXWPFSDT> sdts = new ArrayList<AbstractXWPFSDT>();
for (IBodyElement e : elements) {
if (e instanceof XWPFSDT) {
XWPFSDT sdt = (XWPFSDT) e;
System.out.println("block: " + sdt);
sdts.add(sdt);
} else if (e instanceof XWPFParagraph) {
XWPFParagraph p = (XWPFParagraph) e;
for (IRunElement e2 : p.getIRuns()) {
if (e2 instanceof XWPFSDT) {
XWPFSDT sdt = (XWPFSDT) e2;
System.out.println("inline: " + sdt);
sdts.add(sdt);
}
}
}
}
return sdts;
}
*/
private static List<XWPFSDT> extractSDTsFromBody(XWPFDocument document) {
XWPFSDT sdt;
XmlCursor xmlcursor = document.getDocument().getBody().newCursor();
QName qnameSdt = new QName("http://schemas.openxmlformats.org/wordprocessingml/2006/main", "sdt", "w");
List<XWPFSDT> allsdts = new ArrayList<XWPFSDT>();
while (xmlcursor.hasNextToken()) {
XmlCursor.TokenType tokentype = xmlcursor.toNextToken();
if (tokentype.isStart()) {
if (qnameSdt.equals(xmlcursor.getName())) {
if (xmlcursor.getObject() instanceof CTSdtRun) {
sdt = new XWPFSDT((CTSdtRun)xmlcursor.getObject(), document);
//System.out.println("inline: " + sdt);
allsdts.add(sdt);
} else if (xmlcursor.getObject() instanceof CTSdtBlock) {
sdt = new XWPFSDT((CTSdtBlock)xmlcursor.getObject(), document);
//System.out.println("block: " + sdt);
allsdts.add(sdt);
}
}
}
}
return allsdts;
}
public static void main(String[] args) throws Exception {
XWPFDocument document = new XWPFDocument(new FileInputStream("WordDataCollectingForm.docx"));
/*
List<IBodyElement> bodyelements = document.getBodyElements();
List<AbstractXWPFSDT> sdts = extractSDTsFromBodyElements(bodyelements);
*/
List<XWPFSDT> allsdts = extractSDTsFromBody(document);
for (XWPFSDT sdt : allsdts) {
//System.out.println(sdt);
String title = sdt.getTitle();
String content = sdt.getContent().getText();
if (!(title == null) && !(title.isEmpty())) {
System.out.println(title + ": " + content);
} else {
System.out.println("====sdt without title====");
}
}
document.close();
}
}

How to use antlr4 in Eclipse?

Since Antlr4 is new version of Antlr and this is the first time I use it. I have downloaded the Antlr4 plugin from eclipse marketplace.
I made new ANTLR 4 project and I got Hello.g4
Afterward I saw this small grammar:
/**
* Define a grammar called Hello
*/
grammar Hello;
r : 'hello' ID ; // match keyword hello followed by an identifier
ID : [a-z]+ ; // match lower-case identifiers
WS : [ \t\r\n]+ -> skip ; // skip spaces, tabs, newlines
Once it was saved, It was build and I saw it from the Antlr console, I wanted to test the program but I didn't know how, and I didn't know how to make a new file that can be compiled by the new grammar?
Thanks in advance for any help.
You need to create an instance of the generated parser in order to run it.
Create a Java project J next to your ANTLR project A.
Create a linked folder in project J referencing the
generated-sources/antlr4 folder from A, and make this linked folder a source
folder. Compile errors should appear.
Add the antlr4 jar to the build path of project J. This should remove the compile errors.
Write a main program in J which creates an instance of the generated
parser and feeds it with text. You can take the TestRig from the
ANTLR documentation, pasted below for convenience.
The TestRig must be invoked by passing the grammar name (Hello, in your example) and the starting rule (r).
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.DefaultErrorStrategy;
import org.antlr.v4.runtime.DiagnosticErrorListener;
import org.antlr.v4.runtime.InputMismatchException;
import org.antlr.v4.runtime.Lexer;
import org.antlr.v4.runtime.Parser;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.atn.PredictionMode;
import javax.print.PrintException;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/** Run a lexer/parser combo, optionally printing tree string or generating
* postscript file. Optionally taking input file.
*
* $ java org.antlr.v4.runtime.misc.TestRig GrammarName startRuleName
* [-tree]
* [-tokens] [-gui] [-ps file.ps]
* [-trace]
* [-diagnostics]
* [-SLL]
* [input-filename(s)]
*/
public class Test {
public static final String LEXER_START_RULE_NAME = "tokens";
protected String grammarName;
protected String startRuleName;
protected final List<String> inputFiles = new ArrayList<String>();
protected boolean printTree = false;
protected boolean gui = false;
protected String psFile = null;
protected boolean showTokens = false;
protected boolean trace = false;
protected boolean diagnostics = false;
protected String encoding = null;
protected boolean SLL = false;
public Test(String[] args) throws Exception {
if ( args.length < 2 ) {
System.err.println("java org.antlr.v4.runtime.misc.TestRig GrammarName startRuleName\n" +
" [-tokens] [-tree] [-gui] [-ps file.ps] [-encoding encodingname]\n" +
" [-trace] [-diagnostics] [-SLL]\n"+
" [input-filename(s)]");
System.err.println("Use startRuleName='tokens' if GrammarName is a lexer grammar.");
System.err.println("Omitting input-filename makes rig read from stdin.");
return;
}
int i=0;
grammarName = args[i];
i++;
startRuleName = args[i];
i++;
while ( i<args.length ) {
String arg = args[i];
i++;
if ( arg.charAt(0)!='-' ) { // input file name
inputFiles.add(arg);
continue;
}
if ( arg.equals("-tree") ) {
printTree = true;
}
if ( arg.equals("-gui") ) {
gui = true;
}
if ( arg.equals("-tokens") ) {
showTokens = true;
}
else if ( arg.equals("-trace") ) {
trace = true;
}
else if ( arg.equals("-SLL") ) {
SLL = true;
}
else if ( arg.equals("-diagnostics") ) {
diagnostics = true;
}
else if ( arg.equals("-encoding") ) {
if ( i>=args.length ) {
System.err.println("missing encoding on -encoding");
return;
}
encoding = args[i];
i++;
}
else if ( arg.equals("-ps") ) {
if ( i>=args.length ) {
System.err.println("missing filename on -ps");
return;
}
psFile = args[i];
i++;
}
}
}
public static void main(String[] args) throws Exception {
Test test = new Test(args);
if(args.length >= 2) {
test.process();
}
}
public void process() throws Exception {
//System.out.println("exec "+grammarName+" "+startRuleName);
String lexerName = grammarName+"Lexer";
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class<? extends Lexer> lexerClass = null;
try {
lexerClass = cl.loadClass(lexerName).asSubclass(Lexer.class);
}
catch (java.lang.ClassNotFoundException cnfe) {
System.err.println("1: Can't load "+lexerName+" as lexer or parser");
return;
}
Constructor<? extends Lexer> lexerCtor = lexerClass.getConstructor(CharStream.class);
Lexer lexer = lexerCtor.newInstance((CharStream)null);
Class<? extends Parser> parserClass = null;
Parser parser = null;
if ( !startRuleName.equals(LEXER_START_RULE_NAME) ) {
String parserName = grammarName+"Parser";
parserClass = cl.loadClass(parserName).asSubclass(Parser.class);
if ( parserClass==null ) {
System.err.println("Can't load "+parserName);
}
Constructor<? extends Parser> parserCtor = parserClass.getConstructor(TokenStream.class);
parser = parserCtor.newInstance((TokenStream)null);
}
if ( inputFiles.size()==0 ) {
InputStream is = System.in;
Reader r;
if ( encoding!=null ) {
r = new InputStreamReader(is, encoding);
}
else {
r = new InputStreamReader(is);
}
process(lexer, parserClass, parser, is, r);
return;
}
for (String inputFile : inputFiles) {
InputStream is = System.in;
if ( inputFile!=null ) {
is = new FileInputStream(inputFile);
}
Reader r;
if ( encoding!=null ) {
r = new InputStreamReader(is, encoding);
}
else {
r = new InputStreamReader(is);
}
if ( inputFiles.size()>1 ) {
System.err.println(inputFile);
}
process(lexer, parserClass, parser, is, r);
}
}
protected void process(Lexer lexer, Class<? extends Parser> parserClass, Parser parser, InputStream is, Reader r) throws IOException, IllegalAccessException, InvocationTargetException, PrintException {
try {
ANTLRInputStream input = new ANTLRInputStream(r);
lexer.setInputStream(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
tokens.fill();
if ( showTokens ) {
for (Object tok : tokens.getTokens()) {
System.out.println(tok);
}
}
if ( startRuleName.equals(LEXER_START_RULE_NAME) ) return;
if ( diagnostics ) {
parser.addErrorListener(new DiagnosticErrorListener());
parser.getInterpreter().setPredictionMode(PredictionMode.LL_EXACT_AMBIG_DETECTION);
}
if ( printTree || gui || psFile!=null ) {
parser.setBuildParseTree(true);
}
if ( SLL ) { // overrides diagnostics
parser.getInterpreter().setPredictionMode(PredictionMode.SLL);
}
parser.setTokenStream(tokens);
parser.setTrace(trace);
//parser.setErrorHandler(new BailErrorStrategy());
try {
Method startRule = parserClass.getMethod(startRuleName);
ParserRuleContext tree = (ParserRuleContext)startRule.invoke(parser, (Object[])null);
if ( printTree ) {
System.out.println(tree.toStringTree(parser));
}
if ( gui ) {
tree.inspect(parser);
}
if ( psFile!=null ) {
tree.save(parser, psFile); // Generate postscript
}
}
catch (NoSuchMethodException nsme) {
System.err.println("No method for rule "+startRuleName+" or it has arguments");
}
}
finally {
if ( r!=null ) r.close();
if ( is!=null ) is.close();
}
}
#SuppressWarnings("unused")
private static class BailErrorStrategy extends DefaultErrorStrategy {
/** Instead of recovering from exception e, rethrow it wrapped
* in a generic RuntimeException so it is not caught by the
* rule function catches. Exception e is the "cause" of the
* RuntimeException.
*/
#Override
public void recover(Parser recognizer, RecognitionException e) {
throw new RuntimeException(e);
}
/** Make sure we don't attempt to recover inline; if the parser
* successfully recovers, it won't throw an exception.
*/
#Override
public Token recoverInline(Parser recognizer)
throws RecognitionException
{
throw new RuntimeException(new InputMismatchException(recognizer));
}
/** Make sure we don't attempt to recover from problems in subrules. */
#Override
public void sync(Parser recognizer) { }
}
}
'Hope this helps!
Create a new ANTLR 4 Project
Convert the project to faceted form
Add Java project facet
Optionally you can add package names to be generated in the header of the grammar file
Use target/generated-sources/antlr4 as source folder
Edit and save the grammar file to re-generate everything
The antlr4 generated sources should now be packaged and imported as usual inside your project.

Intersystems Cache using XEP

I am trying to extract data from the Samples namespace that comes with Intersystems Cache install. Specifically, I am trying to retrieve Sample.Company global data using XEP. Inorder to achieve this, I created Sample.Company class like this -
package Sample;
public class Company {
public Long id;
public String mission;
public String name;
public Long revenue;
public String taxId;
public Company(Long id, String mission, String name, Long revenue,
String taxId) {
this.id = id;
this.mission = mission;
this.name = name;
this.revenue = revenue;
this.taxId = taxId;
}
public Company() {
}
}
XEP related code looks like this -
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import Sample.Company;
import com.intersys.xep.Event;
import com.intersys.xep.EventPersister;
import com.intersys.xep.EventQuery;
import com.intersys.xep.EventQueryIterator;
import com.intersys.xep.PersisterFactory;
import com.intersys.xep.XEPException;
#Service
public class CompanyService {
public List<Company> fetch() {
EventPersister myPersister = PersisterFactory.createPersister();
myPersister.connect("SAMPLES", "user", "pwd");
try { // delete any existing SingleStringSample events, then import
// new ones
Event.isEvent("Sample.Company");
myPersister.deleteExtent("Sample.Company");
String[] generatedClasses = myPersister.importSchema("Sample.Company");
for (int i = 0; i < generatedClasses.length; i++) {
System.out.println("Event class " + generatedClasses[i]
+ " successfully imported.");
}
} catch (XEPException e) {
System.out.println("import failed:\n" + e);
throw new RuntimeException(e);
}
EventQuery<Company> myQuery = null;
List<Company> list = new ArrayList<Company>();
try {
Event newEvent = myPersister.getEvent("Sample.Company");
String sql = "Select * from Sample.Company";
myQuery = newEvent.createQuery(sql);
newEvent.close();
myQuery.execute();
EventQueryIterator<Company> iterator = myQuery.getIterator();
while (iterator.hasNext()) {
Company c = iterator.next();
System.out.println(c);
list.add(c);
}
myQuery.close();
myPersister.close();
return list;
} catch (XEPException e) {
System.out.println("createQuery failed:\n" + e);
throw new RuntimeException(e);
}
}
}
When I try executing the fetch() method of the above class, I am seeing the following exception -
com.intersys.xep.XEPException: Cannot import - extent for Sample.Company not empty.
at com.intersys.xep.internal.Generator.generate(Generator.java:52)
at com.intersys.xep.EventPersister.importSchema(EventPersister.java:954)
at com.intersys.xep.EventPersister.importSchema(EventPersister.java:363)
I got the simple string example working. Does it mean, we can not read the existing data using XEP? If we can read, Could some please help me in resolving the above issue? Thanks in advance.
You are trying to create a new class named Sample.Company in your instance:
String[] generatedClasses = myPersister.importSchema("Sample.Company");
But you still have data and an existing class there.

GMail like file upload progress bar with GWT?

All Gmail users should have already noticed that file upload progress bar has been updated recently.
I'm wondering such effect is possible to implement with GWT.
I'm fairly new with GWT, so if any GWT source code that can help me test out the function would be very helpful.
Update
I ended up going with SWFUpload. However, other suggestions under this question are all valid. Just try different options and choose the one you like!
Take a look to this library: http://code.google.com/p/gwtupload/. It is really easy to to use and works fine in all browsers and OS I've checked. It uses ajax requests to calculate progress. BTW Swfupload doesn't do well in linux and Mac.
I've used this tool before:
http://code.google.com/p/gwt-fileapi/
Unlike the other suggestions here, not only does it give the proper API to show upload progress, it also gives the ability to do batch uploads by selecting multiple files, and it also gives drag and drop support. It also has a pre HTML5 fallback mechanism.
I've had had great luck with it gwt-fileap. Recently it broke in Firefox 7 and 8 and I had to apply this patch to it - but otherwise it works really great:
## -57,26 +57,33 ##
/**
* gets the filename
- *
+ *
* #return the filename
*/
public final native String getFileName() /*-{
- return this.fileName;
+ if(this.name)
+ return this.name;
+ else
+ return this.fileName;
+
}-*/;
/**
* gets the file size in bytes
- *
+ *
* #return the file size in bytes
*/
public final native int getFileSize() /*-{
- return this.fileSize;
+ if(this.size)
+ return this.size;
+ else
+ return this.fileSize;
}-*/;
/**
* gets the MIME type of the file, may be null if the browser cannot detect
* the type
I also had to add the following lines to http://code.google.com/p/gwt-fileapi/source/browse/trunk/gwt-fileapi/src/com/gwtpro/html5/fileapi/Html5FileApi.gwt.xml - these lines describe how the fallback mechanism works. You can do something similar if you want your code to fall back on the SWFUploader implementation shown below in case HTML5 is missing.
<define-property name="fileapi.support" values="yes,no" />
<property-provider name="fileapi.support"><![CDATA[
var input=document.createElement('input');
input.setAttribute('type','file');
return input.files==null?'no':'yes';
]]></property-provider>
<replace-with
class="com.gwtpro.html5.fileapi.client.ui.FileInput.FileInputImplHtml5">
<when-type-is
class="com.gwtpro.html5.fileapi.client.ui.FileInput.FileInputImpl" />
<when-property-is name="fileapi.support" value="yes" />
<any>
<when-property-is name="user.agent" value="ie8" />
<when-property-is name="user.agent" value="safari" />
<when-property-is name="user.agent" value="gecko1_8" />
<when-property-is name="user.agent" value="opera" />
<when-property-is name="user.agent" value="chrome" />
</any>
</replace-with>
This is how I use it in my application:
This is the interface that describes the abstraction:
public interface FileUpload {
public void uploadFiles();
public Widget getWidget();
public void initialize(Grid updateTable, Uploader uploader, String url, boolean createDropHandler);
public void setDisabled(boolean b);
public void readyToPaint();
public void reset();
}
The following is the gwt-fileapi implementation of the interface:
package com.hierarchycm.gxt.client.fileUpload;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.http.client.RequestException;
import com.google.gwt.http.client.Response;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;
import com.gwtpro.html5.fileapi.client.FileApiSupport;
import com.gwtpro.html5.fileapi.client.drop.DropHandler;
import com.gwtpro.html5.fileapi.client.file.File;
import com.gwtpro.html5.fileapi.client.file.FileEvent;
import com.gwtpro.html5.fileapi.client.file.FileEvent.FileEventHandler;
import com.gwtpro.html5.fileapi.client.ui.FileInput;
import com.gwtpro.html5.fileapi.client.upload.UploadRequest;
import com.gwtpro.html5.fileapi.client.upload.UploadRequestBuilder;
import com.gwtpro.html5.fileapi.client.upload.UploadRequestCallback;
public class FileUploadHtmlImpl extends FileInput implements FileUpload {
private Grid uploadTable;
int currentFile =0;
String url;
File[] files;
UploadRequestBuilder fileUploader;
Uploader uploader;
public FileUploadHtmlImpl() {
}
FileUploadHtmlImpl(Grid updateTable, Uploader uploader, String url) {
this(updateTable, uploader, url, true);
}
FileUploadHtmlImpl(Grid updateTable, Uploader uploader, String url, boolean createDropHandler) {
initialize(updateTable, uploader, url, createDropHandler);
//this.setCallback(getMyCallback());
}
public void initialize(Grid updateTable, Uploader uploader, String url, boolean createDropHandler){
this.url = url;
this.uploadTable = updateTable;
this.uploader = uploader;
this.setAllowMultipleFiles(true);
this.addChangeHandler(new ChangeHandler() {
#Override
public void onChange(ChangeEvent event) {
addFiles(FileUploadHtmlImpl.this.getFiles());
uploadFiles();
}
});
if (createDropHandler) {
createDropHandler();
}
}
private File[] jsArrToArr (JsArray<File> ipFiles) {
File [] result = new File [ipFiles.length()];
for (int i = 0; i < ipFiles.length(); ++i) {
result[i] = ipFiles.get(i);
}
return result;
}
private UploadRequestCallback getMyCallback() {
return new UploadRequestCallback() {
#Override
public void onError(UploadRequest request, Throwable exception) {
uploadTable.setText(currentFile + 1, 2, "failed: " + exception.getMessage());
uploadNextFile(currentFile + 1);
}
#Override
public void onResponseReceived(UploadRequest request, Response response) {
uploadTable.setText(currentFile + 1, 2, "success: " + response.getText());
uploadNextFile(currentFile + 1);
//If we just finished uploading do your thing
if (currentFile == files.length) {
setDisabled(false);
uploader.uploadDoneEventHandler();
}
}
#Override
public void onUploadProgress(UploadRequest request, int bytesUploaded) {
uploadTable.setText(currentFile + 1, 2, bytesUploaded + "");
}
};
}
public void createDropHandler() {
RootPanel rootPanel = RootPanel.get();
DropHandler dropHandler = new DropHandler(rootPanel);
this.fileUploader = new UploadRequestBuilder(url);
this.fileUploader.setCallback(getMyCallback());
dropHandler.addFileEventHandler(new FileEventHandler() {
#Override
public void onFiles(FileEvent event) {
addFiles(jsArrToArr(event.getFiles()));
uploadFiles();
}
});
}
private void addFiles (File[] ipFiles) {
files = ipFiles;
uploadTable.clear();
uploadTable.resize(files.length + 1, 3);
uploadTable.setText(0, 0, "File name");
uploadTable.setText(0, 1, "File size");
uploadTable.setText(0, 2, "Progress");
for (int i = 0; i < files.length; ++i) {
uploadTable.setText(i + 1, 0, files[i].getFileName());
uploadTable.setText(i + 1, 1, files[i].getFileSize() + "");
uploadTable.setText(i + 1, 2, "");
}
}
public void uploadNextFile(int index) {
for (String paramName : uploader.getPostParams().keySet()) {
fileUploader.setHeader(paramName, uploader.getPostParams().get(paramName));
}
currentFile = index;
this.setDisabled(true);
if (index < this.files.length) {
try {
this.fileUploader.setHeader("itemName", files[currentFile].getFileName());
this.fileUploader.sendFile(files[currentFile]);
} catch (RequestException e) {
this.uploadTable.setText(index + 1, 2, "failed: " + e.getMessage());
uploadNextFile(index + 1);
}
}
}
public void uploadFiles() {
uploadNextFile(0);
}
#Override
public Widget getWidget() {
return this;
}
#Override
public void readyToPaint() {
//no need to do anything - already painted for non swf
}
#Override
public void reset() {
// TODO Auto-generated method stub
}
private void showCapabilities() {
RootPanel
.get("status")
.getElement()
.setInnerHTML(
"Drag and Drop Support: "
+ (FileApiSupport.isDragDropSupported() ? "Yes"
: "No")
+ "<br/>HTTPXmlRequest Level 2: "
+ (FileApiSupport.isHttpXmlRequestLevel2() ? "Yes"
: "No")
+ "<br/>File input supports multiple files: "
+ (FileApiSupport
.isMultipleFileInputSupported() ? "Yes"
: "No")+"<br/><br/>");
}
}
This is the SWFUpload http://code.google.com/p/swfupload-gwt/ implementation of the same interface:
package com.hierarchycm.gxt.client.fileUpload;
import com.extjs.gxt.ui.client.widget.Html;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Widget;
public class FileUploadSwfImpl extends Html implements FileUpload {
SwfUploadUtil swfUploadUtil = null;
private Uploader uploader;
private String url;
private boolean createDropHandler;
private Grid updateTable;
static int uploadId = 0;
static String divTagId;
public FileUploadSwfImpl() {
divTagId = "swfupload" + uploadId++;
String divTag = "<div id=\"" + divTagId + "\"></div";
this.setHtml(divTag);
}
#Override
public void uploadFiles() {
swfUploadUtil.startUpload();
}
#Override
public Widget getWidget() {
return this;
}
public void readyToPaint() {
swfUploadUtil = new SwfUploadUtil(uploader, updateTable, divTagId, url);
}
#Override
public void initialize(Grid updateTable, Uploader uploader, String url, boolean createDropHandler) {
this.uploader = uploader;
this.url = url;
this.createDropHandler = createDropHandler;
this.updateTable = updateTable;
}
#Override
public void setDisabled(boolean b) {
swfUploadUtil.setDisabled(b);
this.disabled = true;
}
#Override
public void reset() {
swfUploadUtil.reset();
}
}
And this is the utility the FileUploadSwfImpl depends on:
package com.hierarchycm.gxt.client.fileUpload;
import java.util.HashMap;
import org.swfupload.client.File;
import org.swfupload.client.SWFUpload;
import org.swfupload.client.UploadBuilder;
import org.swfupload.client.SWFUpload.ButtonAction;
import org.swfupload.client.SWFUpload.ButtonCursor;
import org.swfupload.client.event.DialogStartHandler;
import org.swfupload.client.event.FileDialogCompleteHandler;
import org.swfupload.client.event.FileQueuedHandler;
import org.swfupload.client.event.UploadCompleteHandler;
import org.swfupload.client.event.UploadErrorHandler;
import org.swfupload.client.event.UploadProgressHandler;
import org.swfupload.client.event.UploadSuccessHandler;
import org.swfupload.client.event.FileDialogCompleteHandler.FileDialogCompleteEvent;
import org.swfupload.client.event.FileQueuedHandler.FileQueuedEvent;
import org.swfupload.client.event.UploadCompleteHandler.UploadCompleteEvent;
import org.swfupload.client.event.UploadErrorHandler.UploadErrorEvent;
import org.swfupload.client.event.UploadProgressHandler.UploadProgressEvent;
import org.swfupload.client.event.UploadSuccessHandler.UploadSuccessEvent;
import com.extjs.gxt.ui.client.widget.form.TextArea;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Grid;
public class SwfUploadUtil {
HashMap<String, Integer> filenameRowHm = new HashMap<String, Integer>();
private boolean resetIssued;
SWFUpload swfUpload = null;
private HashMap <String, File> files = new HashMap<String, File>();
int tableRow = 5;
Uploader uploader = null;
private Grid updateTable;
private String divName;
private String url;
synchronized private void removeFile(String id) {
files.remove(id);
}
public SwfUploadUtil(Uploader uploader, Grid updateTable, String divName, String url){
reset();
this.uploader = uploader;
this.updateTable = updateTable;
this.divName = divName;
this.url = url;
this.swfUpload = loadSWFUpload();
updateTable.resize(5, 5);
updateTable.setText(2, 0, "Upload URL:" );
updateTable.setText(2, 1, url );
updateTable.setText(4, 0, "File Name" );
updateTable.setText(4, 1, "Bytes In");
updateTable.setText(4, 2, "Status");
updateTable.setText(4, 3, "File Size" );
updateTable.setText(4, 4, "Server response" );
}
public SWFUpload loadSWFUpload() {
this.updateTable = updateTable;
if (swfUpload == null) {
final UploadBuilder builder1 = new UploadBuilder();
builder1.setHTTPSuccessCodes(200, 201);
builder1.setFileTypes("*.webm;*.asf;*.wma;*.wmv;*.avi;*.flv;*.swf;*.mpg;*.mpeg;*.mp4;*.mov;*.m4v;*.aac;*.mp3;*.wav;*.png;*.jpg;*.jpeg;*.gif");
builder1.setFileTypesDescription("Images, Video & Sound");
builder1.setButtonPlaceholderID(divName);
builder1.setButtonImageURL("./images/XPButtonUploadText_61x22.png");
builder1.setButtonCursor(ButtonCursor.HAND);
builder1.setButtonWidth(61);
builder1.setButtonHeight(22);
builder1.setButtonAction(ButtonAction.SELECT_FILES);
builder1.setUploadProgressHandler(new UploadProgressHandler() {
public void onUploadProgress(UploadProgressEvent e) {
File f = e.getFile();
updateTable.setText(getFilenameRow(f), 2, String.valueOf(e.getBytesComplete()));
}
});
builder1.setUploadSuccessHandler(new UploadSuccessHandler() {
public void onUploadSuccess(UploadSuccessEvent e) {
File f = e.getFile();
updateTable.setText(getFilenameRow(f), 4, e.getServerData());
}
});
builder1.setUploadErrorHandler(new UploadErrorHandler() {
public void onUploadError(UploadErrorEvent e) {
File ff = e.getFile();
String message = e.getMessage();
if (message == null || message.trim().length() == 0) {
message = "upload failed";
}
updateTable.setText(getFilenameRow(ff), 2, String.valueOf(message));
removeFile(ff.getId());
if (files.values().size() > 0) {
ff = files.values().iterator().next();
updateTable.setText(getFilenameRow(ff), 2, "Started");
swfUpload.startUpload(ff.getId());
}
}
});
builder1.setUploadURL(url);
builder1.setDialogStartHandler(new DialogStartHandler() {
#Override
public void onDialogStart() {
if(resetIssued == true) {
filenameRowHm.clear();
resetIssued = false;
}
}
}
);
builder1.setUploadCompleteHandler(new UploadCompleteHandler() {
public void onUploadComplete(UploadCompleteEvent e) {
File f = e.getFile();
updateTable.setText(getFilenameRow(f), 2, "Done");
removeFile(f.getId());
if (files.values().size() > 0) {
File ff = files.values().iterator().next();
updateTable.setText(getFilenameRow(ff), 2, "Started");
swfUpload.startUpload(ff.getId());
} else {
uploader.uploadDoneEventHandler();
}
}
});
builder1.setFileQueuedHandler(new FileQueuedHandler() {
public void onFileQueued(FileQueuedEvent event) {
File f = event.getFile();
updateTable.setText(getFilenameRow(f), 2, "Queued");
files.put(f.getId(), f);
}
});
builder1.setFileDialogCompleteHandler(new FileDialogCompleteHandler() {
public void onFileDialogComplete(FileDialogCompleteEvent e) {
updateTable.setText(2, 0, "Number of files");
updateTable.setText(2, 1, String.valueOf(files.values().size()));
for(File f : files.values()) {
getFilenameRow(f);
}
if (files.values().size() > 0) {
for (String paramName : uploader.getPostParams().keySet()) {
swfUpload.addPostParam(paramName,uploader.getPostParams().get(paramName));
}
}
}
});
swfUpload = builder1.build();
}
return swfUpload;
}
public int getFilenameRow (File f) {
Integer filenamerow = filenameRowHm.get(f.getId());
if (filenamerow == null) {
updateTable.resize(tableRow+1, 5);
filenamerow = new Integer(tableRow++);
updateTable.setText(filenamerow.intValue(), 0, f.getName());
updateTable.setText(filenamerow.intValue(), 3, String.valueOf(f.getSize()));
//updateTable.setText(filenamerow.intValue(), 3, String.valueOf(f));
filenameRowHm.put(f.getId(), filenamerow);
}
return filenamerow.intValue();
}
public void startUpload() {
uploader.uploadStartedEventHandler();
swfUpload.startUpload();
}
public void setDisabled(boolean disabled) {
swfUpload.setButtonDisabled(disabled);
}
public void reset() {
// TODO Auto-generated method stub
resetIssued = true;
}
}
Use SWFUpload via swfupload-gwt
The main advantage over the other methods is this does not require any special server code. You could even upload to another domain (if there is a crossdomain.xml which allows it).
Check out GWTC Upload, which has an implementation of exactly what you're looking for.
It's trivial to write your own if you have a java back end, you just start a file upload and then poll the server on a timer to see where it's up to (say every second or two). The java file upload binaries (the apache commons ones) support telling you the current progress so it's trivial to do.
Recently I started a project of my own called gwtupld
http://github.com/kompot/gwtupld/
The main goal is to provide best file upload experience for cutting edge browsers
and acceptable usability for all others. By the moment, following key features are present
multiple file selection
drag'n'drop
progress bars
slick and simple exterior
consistent behavior for all browsers
ease of visual customization
no external dependencies but GWT
Feel free to fork and submit bugs/feature proposals.
You can check out source code, then type
gradlew gwtcompile devmode
and get it will start a fully functional
sandbox (server side with real file saving should work)
You can use GwtSwfExt which is wrapper on top of SWFUpload (Its same as Swfupload-gwt lib ) you can download example and source code from http://code.google.com/p/gwtswfext.
When creating your own file upload progress, instead of pulling it form server at a small set time, you can have the client to display a indeterminate bar for 2 seconds and have the server calculate the estimated finish time the change back to determinate and pull new estimates every 5, 10 seconds instead. that should have little to no effect on the traffic.
There is custom multiupload plugin demo http://ext4all.com/post/extjs-4-multiple-file-upload