/** * Read serialized {@link FormDef} from file and recreate as object. * * @param formDef * serialized FormDef file * @return {@link FormDef} object */ public FormDef deserializeFormDef(File formDef) { // TODO: any way to remove reliance on jrsp? FileInputStream fis = null; FormDef fd = null; try { // create new form def fd = new FormDef(); fis = new FileInputStream(formDef); DataInputStream dis = new DataInputStream(fis); // read serialized formdef into new formdef fd.readExternal(dis, ExtUtil.defaultPrototypes()); dis.close(); } catch (FileNotFoundException e) { e.printStackTrace(); fd = null; } catch (IOException e) { e.printStackTrace(); fd = null; } catch (DeserializationException e) { e.printStackTrace(); fd = null; } catch (Exception e) { e.printStackTrace(); fd = null; } return fd; }
protected FieldSpecCollection createFieldSpecsFromXForms(FormEntryController formEntryController) throws Exception { FormDef formDef = formEntryController.getModel().getForm(); List<IFormElement> children = formDef.getChildren(); unGroupedUniqueId = 0; return recursivelyConvertXFormsFieldsToFieldSpecs(formEntryController, children, false); }
protected JrFormEntryController getController() { Vector<IPreloadHandler> preloaders = JRDemoContext._().getPreloaders(); Vector<IFunctionHandler> funcHandlers = JRDemoContext._().getFuncHandlers(); FormDefFetcher fetcher = new FormDefFetcher(new RMSRetreivalMethod(formID), preloaders, funcHandlers); FormDef form = fetcher.getFormDef(); JrFormEntryController controller = new JrFormEntryController(new JrFormEntryModel(form)); String title = form.getTitle(); if(title == null) { title = "Enter Data"; } controller.setView(new JRDemoFormEntryViewFactory(title).getFormEntryView(controller)); return controller; }
private String getRepetitionText(String type, FormIndex index, boolean newrep) { if (element instanceof GroupDef && ((GroupDef) element).getRepeat() && index.getElementMultiplicity() >= 0) { GroupDef g = (GroupDef) element; String title = getLongText(); int ix = index.getElementMultiplicity() + 1; int count = getNumRepetitions(); String caption = null; if ("header".equals(type)) { caption = g.entryHeader; } else if ("choose".equals(type)) { caption = g.chooseCaption; if (caption == null) { caption = g.entryHeader; } } if (caption == null) { return title + " " + ix + "/" + count; } HashMap<String, Object> vars = new HashMap<String, Object>(); vars.put("name", title); vars.put("i", Integer.valueOf(ix)); vars.put("n", Integer.valueOf(count)); vars.put("new", new Boolean(newrep)); return form.fillTemplateString(caption, index.getReference(), vars); } else { return null; } }
/** * Creates a FormEntryCaption for the element at the given index in the form. * * @param form * @param index */ public FormEntryCaption(FormDef form, FormIndex index) { this.form = form; this.index = index; this.element = form.getChild(index); this.viewWidget = null; this.textID = this.element.getTextID(); }
// TODO: this is explicitly missing integration with the new multi-media support // TODO: localize the default captions public String getRepeatText(String typeKey) { GroupDef g = (GroupDef) element; if (!g.getRepeat()) { throw new RuntimeException("not a repeat"); } String title = getLongText(); int count = getNumRepetitions(); String caption = null; if ("mainheader".equals(typeKey)) { caption = g.mainHeader; if (caption == null) { return title; } } else if ("add".equals(typeKey)) { caption = g.addCaption; if (caption == null) { return "Add another " + title; } } else if ("add-empty".equals(typeKey)) { caption = g.addEmptyCaption; if (caption == null) { caption = g.addCaption; } if (caption == null) { return "None - Add " + title; } } else if ("del".equals(typeKey)) { caption = g.delCaption; if (caption == null) { return "Delete " + title; } } else if ("done".equals(typeKey)) { caption = g.doneCaption; if (caption == null) { return "Done"; } } else if ("done-empty".equals(typeKey)) { caption = g.doneEmptyCaption; if (caption == null) { caption = g.doneCaption; } if (caption == null) { return "Skip"; } } else if ("delheader".equals(typeKey)) { caption = g.delHeader; if (caption == null) { return "Delete which " + title + "?"; } } HashMap<String, Object> vars = new HashMap<String, Object>(); vars.put("name", title); vars.put("n", Integer.valueOf(count)); return form.fillTemplateString(caption, index.getReference(), vars); }
public RepeatOptions getRepeatOptions() { RepeatOptions ro = new RepeatOptions(); boolean has_repetitions = (getNumRepetitions() > 0); ro.header = getRepeatText("mainheader"); ro.add = null; if (form.canCreateRepeat(form.getChildInstanceRef(index), index)) { ro.add = getRepeatText(has_repetitions ? "add" : "add-empty"); } ro.delete = null; ro.delete_header = null; if (has_repetitions) { ro.delete = getRepeatText("del"); ro.delete_header = getRepeatText("delheader"); } ro.done = getRepeatText(has_repetitions ? "done" : "done-empty"); return ro; }
public List<String> getRepetitionsText() { GroupDef g = (GroupDef) element; if (!g.getRepeat()) { throw new RuntimeException("not a repeat"); } int numRepetitions = getNumRepetitions(); List<String> reps = new ArrayList<String>(numRepetitions); for (int i = 0; i < numRepetitions; i++) { reps.add(getRepetitionText("choose", form.descendIntoRepeat(index, i), false)); } return reps; }
public void formEntrySaved(FormDef form, FormInstance instanceData, boolean formWasCompleted) { System.out.println("form is complete: " + formWasCompleted); //Warning, this might be null final SubmissionProfile profile = form.getSubmissionProfile(); if (formWasCompleted) { // We want to cache this send before we actually ask, otherwise the user could quit before it is // either sent _or_ saved. IStorageUtility storage = StorageManager.getStorage(FormInstance.STORAGE_KEY); try { Logger.log("formentry","writing data: " + instanceData.getName()); storage.write(instanceData); final int record = instanceData.getID(); TransportMessage message = JRDemoContext._().buildMessage(instanceData, profile); CompletedFormOptionsState completed = new CompletedFormOptionsState(message) { public void sendData(TransportMessage message) { JRDemoFormTransportState send = new JRDemoFormTransportState(message, record) { public void done() { JRDemoUtil.goToList(cameFromFormList); } public void sendToBackground() { JRDemoUtil.goToList(cameFromFormList); } }; send.start(); } public void skipSend(TransportMessage message) { // Message should already be cached. abort(); } }; completed.start(); } catch (StorageFullException e) { new RuntimeException("Storage full, unable to save data."); } } else { abort(); } }
/** * Write the FormDef to the file system as a binary blog. * * @param filepath * path to the form file */ public void serializeFormDef(FormDef fd, String filepath) { // calculate unique md5 identifier String hash = FileUtils.getMd5Hash(new File(filepath)); File formDef = new File(Collect.CACHE_PATH + File.separator + hash + ".formdef"); // formdef does not exist, create one. if (!formDef.exists()) { FileOutputStream fos; try { fos = new FileOutputStream(formDef); DataOutputStream dos = new DataOutputStream(fos); fd.writeExternal(dos); dos.flush(); dos.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
protected ItemsetWidget( Context context, FormEntryPrompt prompt, boolean readOnlyOverride, boolean derived) { super(context, prompt); mButtons = new RadioGroup(context); mButtons.setId(QuestionWidget.newUniqueId()); mReadOnly = prompt.isReadOnly() || readOnlyOverride; mAnswers = new HashMap<String, String>(); String currentAnswer = prompt.getAnswerText(); // the format of the query should be something like this: // query="instance('cities')/root/item[state=/data/state and county=/data/county]" // "query" is what we're using to notify that this is an // itemset widget. String nodesetStr = prompt.getQuestion().getAdditionalAttribute(null, "query"); // parse out the list name, between the '' String list_name = nodesetStr.substring(nodesetStr.indexOf("'") + 1, nodesetStr.lastIndexOf("'")); // isolate the string between between the [ ] characters String queryString = nodesetStr.substring(nodesetStr.indexOf("[") + 1, nodesetStr.lastIndexOf("]")); StringBuilder selection = new StringBuilder(); // add the list name as the first argument, which will always be there selection.append("list_name=?"); // check to see if there are any arguments if (queryString.indexOf("=") != -1) { selection.append(" and "); } // can't just split on 'and' or 'or' because they have different // behavior, so loop through and break them off until we don't have any // more // must include the spaces in indexOf so we don't match words like // "land" int andIndex = -1; int orIndex = -1; ArrayList<String> arguments = new ArrayList<String>(); while ((andIndex = queryString.indexOf(" and ")) != -1 || (orIndex = queryString.indexOf(" or ")) != -1) { if (andIndex != -1) { String subString = queryString.substring(0, andIndex); String pair[] = subString.split("="); if (pair.length == 2) { selection.append(pair[0].trim() + "=? and "); arguments.add(pair[1].trim()); } else { // parse error } // move string forward to after " and " queryString = queryString.substring(andIndex + 5, queryString.length()); andIndex = -1; } else if (orIndex != -1) { String subString = queryString.substring(0, orIndex); String pair[] = subString.split("="); if (pair.length == 2) { selection.append(pair[0].trim() + "=? or "); arguments.add(pair[1].trim()); } else { // parse error } // move string forward to after " or " queryString = queryString.substring(orIndex + 4, queryString.length()); orIndex = -1; } } // parse the last segment (or only segment if there are no 'and' or 'or' // clauses String pair[] = queryString.split("="); if (pair.length == 2) { selection.append(pair[0].trim() + "=?"); arguments.add(pair[1].trim()); } if (pair.length == 1) { // this is probably okay, because then you just list all items in // the list } else { // parse error } // +1 is for the list_name String[] selectionArgs = new String[arguments.size() + 1]; boolean nullArgs = false; // can't have any null arguments selectionArgs[0] = list_name; // first argument is always listname // loop through the arguments, evaluate any expressions // and build the query string for the DB for (int i = 0; i < arguments.size(); i++) { XPathExpression xpr = null; try { xpr = XPathParseTool.parseXPath(arguments.get(i)); } catch (XPathSyntaxException e) { e.printStackTrace(); TextView error = new TextView(context); error.setText("XPathParser Exception: \"" + arguments.get(i) + "\""); addView(error); break; } if (xpr != null) { FormDef form = Collect.getInstance().getFormController().getFormDef(); TreeElement mTreeElement = form.getMainInstance().resolveReference(prompt.getIndex().getReference()); EvaluationContext ec = new EvaluationContext(form.getEvaluationContext(), mTreeElement.getRef()); Object value = xpr.eval(form.getMainInstance(), ec); if (value == null) { nullArgs = true; } else { if (value instanceof XPathNodeset) { XPathNodeset xpn = (XPathNodeset) value; value = xpn.getValAt(0); } selectionArgs[i + 1] = value.toString(); } } } File itemsetFile = new File( Collect.getInstance().getFormController().getMediaFolder().getAbsolutePath() + "/itemsets.csv"); if (nullArgs) { // we can't try to query with null values else it blows up // so just leave the screen blank // TODO: put an error? } else if (itemsetFile.exists()) { ItemsetDbAdapter ida = new ItemsetDbAdapter(); ida.open(); // name of the itemset table for this form String pathHash = ItemsetDbAdapter.getMd5FromString(itemsetFile.getAbsolutePath()); try { Cursor c = ida.query(pathHash, selection.toString(), selectionArgs); if (c != null) { c.move(-1); while (c.moveToNext()) { String label = ""; String val = ""; // try to get the value associated with the label:lang // string if that doen't exist, then just use label String lang = ""; if (Collect.getInstance().getFormController().getLanguages() != null && Collect.getInstance().getFormController().getLanguages().length > 0) { lang = Collect.getInstance().getFormController().getLanguage(); } // apparently you only need the double quotes in the // column name when creating the column with a : // included String labelLang = "label" + "::" + lang; int langCol = c.getColumnIndex(labelLang); if (langCol == -1) { label = c.getString(c.getColumnIndex("label")); } else { label = c.getString(c.getColumnIndex(labelLang)); } // the actual value is stored in name val = c.getString(c.getColumnIndex("name")); mAnswers.put(label, val); RadioButton rb = new RadioButton(context); rb.setOnCheckedChangeListener(this); rb.setText(label); rb.setTextSize(mAnswerFontsize); mButtons.addView(rb); // have to add it to the radiogroup before checking it, // else it lets two buttons be checked... if (currentAnswer != null && val.compareTo(currentAnswer) == 0) { rb.setChecked(true); } } c.close(); } } finally { ida.close(); } addView(mButtons); } else { TextView error = new TextView(context); error.setText(getContext().getString(R.string.file_missing, itemsetFile.getAbsolutePath())); addView(error); } }
/** * Initialize {@link FormEntryController} with {@link FormDef} from binary or * from XML. If given an instance, it will be used to fill the {@link FormDef} * . */ @Override protected FECWrapper doInBackground(String... path) { FormEntryController fec = null; FormDef fd = null; FileInputStream fis = null; mErrorMsg = null; String formPath = path[0]; File formXml = new File(formPath); String formHash = FileUtils.getMd5Hash(formXml); File formBin = new File(Collect.CACHE_PATH + File.separator + formHash + ".formdef"); publishProgress(Collect.getInstance().getString(R.string.survey_loading_reading_form_message)); FormDef.EvalBehavior mode = AdminPreferencesActivity.getConfiguredFormProcessingLogic(Collect.getInstance()); FormDef.setEvalBehavior(mode); // FormDef.setDefaultEventNotifier(new EventNotifier() { // // @Override // public void publishEvent(Event event) { // Log.d("FormDef", event.asLogLine()); // } // }); if (formBin.exists()) { // if we have binary, deserialize binary Log.i( t, "Attempting to load " + formXml.getName() + " from cached file: " + formBin.getAbsolutePath()); fd = deserializeFormDef(formBin); if (fd == null) { // some error occured with deserialization. Remove the file, and make a // new .formdef // from xml Log.w(t, "Deserialization FAILED! Deleting cache file: " + formBin.getAbsolutePath()); formBin.delete(); } } if (fd == null) { // no binary, read from xml try { Log.i(t, "Attempting to load from: " + formXml.getAbsolutePath()); fis = new FileInputStream(formXml); fd = XFormUtils.getFormFromInputStream(fis); if (fd == null) { mErrorMsg = "Error reading XForm file"; } else { serializeFormDef(fd, formPath); } } catch (FileNotFoundException e) { e.printStackTrace(); mErrorMsg = e.getMessage(); } catch (XFormParseException e) { mErrorMsg = e.getMessage(); e.printStackTrace(); } catch (Exception e) { mErrorMsg = e.getMessage(); e.printStackTrace(); } finally { IOUtils.closeQuietly(fis); } } if (mErrorMsg != null || fd == null) { return null; } // set paths to /sdcard/odk/forms/formfilename-media/ String formFileName = formXml.getName().substring(0, formXml.getName().lastIndexOf(".")); File formMediaDir = new File(formXml.getParent(), formFileName + "-media"); externalDataManager = new ExternalDataManagerImpl(formMediaDir); // add external data function handlers ExternalDataHandler externalDataHandlerPull = new ExternalDataHandlerPull(externalDataManager); fd.getEvaluationContext().addFunctionHandler(externalDataHandlerPull); try { loadExternalData(formMediaDir); } catch (Exception e) { mErrorMsg = e.getMessage(); e.printStackTrace(); return null; } if (isCancelled()) { // that means that the user has cancelled, so no need to go further return null; } // create FormEntryController from formdef FormEntryModel fem = new FormEntryModel(fd); fec = new FormEntryController(fem); boolean usedSavepoint = false; try { // import existing data into formdef if (mInstancePath != null) { File instance = new File(mInstancePath); File shadowInstance = SaveToDiskTask.savepointFile(instance); if (shadowInstance.exists() && (shadowInstance.lastModified() > instance.lastModified())) { // the savepoint is newer than the saved value of the instance. // use it. usedSavepoint = true; instance = shadowInstance; Log.w(t, "Loading instance from shadow file: " + shadowInstance.getAbsolutePath()); } if (instance.exists()) { // This order is important. Import data, then initialize. try { importData(instance, fec); fd.initialize(false, new InstanceInitializationFactory()); } catch (RuntimeException e) { Log.e(t, e.getMessage(), e); // SCTO-633 if (usedSavepoint && !(e.getCause() instanceof XPathTypeMismatchException)) { // this means that the .save file is corrupted or 0-sized, so // don't use it. usedSavepoint = false; mInstancePath = null; fd.initialize(true, new InstanceInitializationFactory()); } else { // this means that the saved instance is corrupted. throw e; } } } else { fd.initialize(true, new InstanceInitializationFactory()); } } else { fd.initialize(true, new InstanceInitializationFactory()); } } catch (RuntimeException e) { Log.e(t, e.getMessage(), e); if (e.getCause() instanceof XPathTypeMismatchException) { // this is a case of // https://bitbucket.org/m.sundt/javarosa/commits/e5d344783e7968877402bcee11828fa55fac69de // the data are imported, the survey will be unusable // but we should give the option to the user to edit the form // otherwise the survey will be TOTALLY inaccessible. Log.w( t, "We have a syntactically correct instance, but the data threw an exception inside JR. We should allow editing."); } else { mErrorMsg = e.getMessage(); return null; } } // Remove previous forms ReferenceManager._().clearSession(); // for itemsets.csv, we only check to see if the itemset file has been // updated File csv = new File(formMediaDir.getAbsolutePath() + "/" + ITEMSETS_CSV); String csvmd5 = null; if (csv.exists()) { csvmd5 = FileUtils.getMd5Hash(csv); boolean readFile = false; ItemsetDbAdapter ida = new ItemsetDbAdapter(); ida.open(); // get the database entry (if exists) for this itemsets.csv, based // on the path Cursor c = ida.getItemsets(csv.getAbsolutePath()); if (c != null) { if (c.getCount() == 1) { c.moveToFirst(); // should be only one, ever, if any String oldmd5 = c.getString(c.getColumnIndex("hash")); if (oldmd5.equals(csvmd5)) { // they're equal, do nothing } else { // the csv has been updated, delete the old entries ida.dropTable(ItemsetDbAdapter.getMd5FromString(csv.getAbsolutePath()), csv.getAbsolutePath()); // and read the new readFile = true; } } else { // new csv, add it readFile = true; } c.close(); } ida.close(); if (readFile) { readCSV(csv, csvmd5, ItemsetDbAdapter.getMd5FromString(csv.getAbsolutePath())); } } // This should get moved to the Application Class if (ReferenceManager._().getFactories().length == 0) { // this is /sdcard/odk ReferenceManager._().addReferenceFactory(new FileReferenceFactory(Collect.ODK_ROOT)); } // Set jr://... to point to /sdcard/odk/forms/filename-media/ ReferenceManager._().addSessionRootTranslator( new RootTranslator("jr://images/", "jr://file/forms/" + formFileName + "-media/")); ReferenceManager._().addSessionRootTranslator( new RootTranslator("jr://image/", "jr://file/forms/" + formFileName + "-media/")); ReferenceManager._().addSessionRootTranslator( new RootTranslator("jr://audio/", "jr://file/forms/" + formFileName + "-media/")); ReferenceManager._().addSessionRootTranslator( new RootTranslator("jr://video/", "jr://file/forms/" + formFileName + "-media/")); // clean up vars fis = null; fd = null; formBin = null; formXml = null; formPath = null; FormController fc = new FormController(formMediaDir, fec, mInstancePath == null ? null : new File(mInstancePath)); if (mXPath != null) { // we are resuming after having terminated -- set index to this // position... FormIndex idx = fc.getIndexFromXPath(mXPath); fc.jumpToIndex(idx); } if (mWaitingXPath != null) { FormIndex idx = fc.getIndexFromXPath(mWaitingXPath); fc.setIndexWaitingForData(idx); } data = new FECWrapper(fc, usedSavepoint); return data; }
protected String substituteStringArgs(String templateStr) { if (templateStr == null) { return null; } return form.fillTemplateString(templateStr, index.getReference()); }
// this should probably be somewhere better public int getNumRepetitions() { return form.getNumRepetitions(index); }