/** * @return Locale-specific sample input string which meets datatype criteria for node. Used for * error reporting. */ String getSampleInputString() { /* Create the help-string showing allowable range of input values. Can be re-created (e.g. if range dynamically changes */ String min = null; String max = null; String other = null; String rangeStr = null; String s = null; s = Datum.getExampleFormatStr(triceps, mask, datumType); if (!(s == null || s.equals(""))) { rangeStr = " (e.g. " + s + ")"; } if (answerType == PASSWORD) { return ""; } if (minDatum == null && maxDatum == null && allowableDatumValues == null && pattern == null && rangeStr != null) { return rangeStr; } if (minDatum != null) { setMinDatum(minDatum); min = minDatum.stringVal(true, mask); } if (maxDatum != null) { setMaxDatum(maxDatum); max = maxDatum.stringVal(true, mask); } if (allowableDatumValues != null) { other = buildOrList(allowableDatumValues); } if (minDatum != null && maxDatum != null) { if ((new DatumMath()).lt(maxDatum, minDatum).booleanVal()) { setError(triceps.get("max_less_than_min") + "(" + minStr + " - " + maxStr + ")"); } } rangeStr = "(" + ((min != null) ? min : "") + " - " + ((max != null) ? max : "") + ")"; if (other != null) { rangeStr = " [" + rangeStr + other + "]"; } if (pattern != null) { rangeStr = "(e.g. m/" + pattern + "/"; } if (!rangeStr.equals("( - )")) { return " " + rangeStr; } return ""; }
/** * Helper function to build OR list of data values * * @param v vector of Nodes */ private String buildOrList(Vector v) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < v.size(); ++i) { Datum d = (Datum) v.elementAt(i); sb.append("," + ((i == v.size() - 1) ? (" " + triceps.get("or")) : "") + " " + d.stringVal()); } return sb.toString(); }
/** @return false if the selected value violates the validation criteria */ boolean isWithinRange(Datum d) { boolean err = false; if (minDatum != null) { if (!(new DatumMath()).ge(d, minDatum).booleanVal()) { err = true; } } if (maxDatum != null) { if (!(new DatumMath()).le(d, maxDatum).booleanVal()) { err = true; } } if (err && allowableDatumValues != null) { /* then not within valid range - so check if it is an outlying, but allowable value */ for (int i = 0; i < allowableDatumValues.size(); ++i) { if ((new DatumMath()).eq(d, (Datum) allowableDatumValues.elementAt(i)).booleanVal()) { err = false; break; } } } if (pattern != null) { if (!Pattern.matches(pattern, d.stringVal())) { err = true; } } if (err) { if (answerType == PASSWORD) { setError(triceps.get("incorrect_password")); } else { setError( triceps.get("please_enter_a") + Datum.getTypeName(triceps, datumType) + triceps.get("in_the_range") + getSampleInputString()); } } return !(err); }
/** * Create HTML input field for this node, given its currently selected value and possible error * messages * * @param datum the value * @param errMsg optional error messages * @param autogen whether to auto-number the options * @return HTML fragment */ String prepareChoicesAsHTML(Datum datum, String errMsg, boolean autogen) { /* errMsg is a hack - only applies to RADIO_HORIZONTAL */ StringBuffer sb = new StringBuffer(); String defaultValue = ""; AnswerChoice ac; Enumeration ans = null; Vector v = null; switch (answerType) { case RADIO: // will store integers ans = getAnswerChoices().elements(); while (ans.hasMoreElements()) { // for however many radio buttons there are ac = (AnswerChoice) ans.nextElement(); ac.parse(triceps); sb.append( "<input type='radio' name='" + getLocalName() + "' id ='" + getLocalName() + "' value='" + ac.getValue() + "'" + (isSelected(datum, ac) ? " checked " : " ") + ">" + ac.getMessage() + "<br>"); } break; case RADIO_HORIZONTAL: { // will store integers /* table underneath questions */ v = getAnswerChoices(); ans = v.elements(); int count = v.size(); if (count > 0) { Double pct = new Double(100. / (double) count); sb.append("<table cellpadding='0' cellspacing='1' border='1' width='100%'>"); sb.append("<tr>"); while (ans.hasMoreElements()) { // for however many radio buttons there are ac = (AnswerChoice) ans.nextElement(); ac.parse(triceps); sb.append("<td valign='top' width='" + pct.toString() + "%'>"); sb.append( "<input type='radio' name='" + getLocalName() + "' id='" + getLocalName() + "' value='" + ac.getValue() + "'" + (isSelected(datum, ac) ? " checked " : " ") + ">" + ac.getMessage()); sb.append("</td>"); } sb.append("</tr>"); sb.append("</table>"); } } break; case RADIO_HORIZONTAL2: { /* table underneath questions */ v = getAnswerChoices(); ans = v.elements(); int count = v.size(); int max_width = Integer.parseInt( triceps.getSchedule().getReserved(Schedule.ANSWER_OPTION_FIELD_WIDTH)); if (count > 0) { Double pct = new Double((double) max_width / (double) count); sb.append( "<table cellpadding='0' cellspacing='1' border='1' width='100%'>"); // oddly, 100% // means all of // the enclosing // <td>, but for // embedded // <td>s, need // actual // percent of // top-level // table! sb.append("<tr>"); while (ans.hasMoreElements()) { // for however many radio buttons there are ac = (AnswerChoice) ans.nextElement(); ac.parse(triceps); sb.append("<td valign='top' width='" + pct.toString() + "%'>"); sb.append( "<input type='radio' name='" + getLocalName() + "' id='" + getLocalName() + "' value='" + ac.getValue() + "'" + (isSelected(datum, ac) ? " checked " : " ") + ">" + ac.getMessage()); sb.append("</td>"); } sb.append("</tr>"); sb.append("</table>"); } } break; case CHECK: ans = getAnswerChoices().elements(); while (ans.hasMoreElements()) { // for however many radio buttons there are ac = (AnswerChoice) ans.nextElement(); ac.parse(triceps); sb.append( "<input type='checkbox' name='" + getLocalName() + "' id='" + getLocalName() + "' value='" + ac.getValue() + "'" + (isSelected(datum, ac) ? " checked " : " ") + ">" + ac.getMessage() + "<br>"); } break; case COMBO: // stores integers as value case COMBO2: case LIST2: case LIST: { StringBuffer choices = new StringBuffer(); ans = getAnswerChoices().elements(); int optionNum = 0; int totalLines = 0; boolean nothingSelected = true; while (ans.hasMoreElements()) { // for however many radio buttons there are ac = (AnswerChoice) ans.nextElement(); ac.parse(triceps); ++optionNum; String messageStr = ac.getMessage(); String prefix = "<option value='" + ac.getValue() + "'"; boolean selected = isSelected(datum, ac); if (selected) { nothingSelected = false; } int max_text_len = Integer.parseInt( triceps.getSchedule().getReserved(Schedule.MAX_TEXT_LEN_FOR_COMBO)); v = subdivideMessage(messageStr, max_text_len); for (int i = 0; i < v.size(); ++i) { choices.append(prefix); if (i == 0 && selected) { choices.append(" selected"); } choices.append(">"); if (i == 0) { // show selection number if (answerType == COMBO || answerType == LIST) { choices.append((autogen) ? String.valueOf(optionNum) : ac.getValue()); choices.append(") "); } } else { // indent to indicate that same as previous choices.append(" "); } choices.append((String) v.elementAt(i)); choices.append("</option>"); } totalLines += v.size(); } sb.append( "<select name='" + getLocalName() + "' id='" + getLocalName() + "' " + ((answerType == LIST || answerType == LIST2) ? (" size = '" + Math.min(MAX_ITEMS_IN_LIST, totalLines + 1) + "' ") : " ") + ">"); sb.append( "<option value=''" + ((nothingSelected) ? " selected" : "") + ">" + // so that focus is properly shifted on List box triceps.get("select_one_of_the_following") + "</option>"); // first choice is empty sb.append(choices); sb.append("</select>"); } break; case TEXT: // stores Text type if (datum != null && datum.exists()) { defaultValue = datum.stringVal(); } sb.append( "<input type='text' " + " name='" + getLocalName() + "' id='" + getLocalName() + "' value='" + (new XMLAttrEncoder()).encode(defaultValue) + "'>"); break; case MEMO: if (datum != null && datum.exists()) { defaultValue = datum.stringVal(); } sb.append( "<textarea rows='5'" + " name='" + getLocalName() + "' id='" + getLocalName() + "'>" + (new XMLAttrEncoder()).encode(defaultValue) + "</textarea>"); break; case PASSWORD: // stores Text type if (datum != null && datum.exists()) { defaultValue = datum.stringVal(); } sb.append( "<input type='password'" + " name='" + getLocalName() + "' id='" + getLocalName() + "' value='" + (new XMLAttrEncoder()).encode(defaultValue) + "'>"); break; case DOUBLE: // stores Double type if (datum != null && datum.exists()) { defaultValue = datum.stringVal(); } sb.append( "<input type='text'" + " name='" + getLocalName() + "' id='" + getLocalName() + "' value='" + defaultValue + "'>"); break; default: /* case DATE: case TIME: case YEAR: case MONTH: case DAY: case WEEKDAY: case HOUR: case MINUTE: case SECOND: case MONTH_NUM: case DAY_NUM: */ if (datum != null && datum.exists()) { defaultValue = datum.stringVal(); } sb.append( "<input type='text'" + " name='" + getLocalName() + "' id='" + getLocalName() + "' value='" + defaultValue + "'>"); break; case NOTHING: sb.append(" "); break; } return sb.toString(); }
/** * Helper function to parse Answer Options and populate Node object * * @param langNum which language vector to populate * @param src The string to be parsed * @return true if suceeds */ private boolean parseAnswerOptions(int langNum, String src) { /* Need to make sure that the answer type, order of answers, and internal values of answers are the same across all languages */ if (src == null) { if (AUTHORABLE) { setParseError(triceps.get("answerOptions_column_missing")); } else { setParseError("syntax error"); } return false; } StringTokenizer ans = new StringTokenizer(src, "|", true); // return '|' tokens too String token = ""; // Determine the question type (first token) try { token = ans.nextToken(); } catch (NoSuchElementException t) { Logger.getLogger(LoggerName).log(Level.SEVERE, "missing_display_type", t); if (AUTHORABLE) { setParseError(triceps.get("missing_display_type") + t.getMessage()); } else { setParseError("syntax error"); } } if (langNum == 0) { for (int z = 0; z < QUESTION_TYPES.length; ++z) { if (token.equalsIgnoreCase(QUESTION_TYPES[z])) { answerType = z; break; } } } else { if (!QUESTION_TYPES[answerType].equalsIgnoreCase(token)) { if (AUTHORABLE) { setParseError(triceps.get("mismatch_across_languages_in_answerType")); } else { setParseError("syntax error"); } } // don't change the known value for answerType } if (questionOrEvalType == EVAL) { answerType = NOTHING; // so no further processing } else if (answerType == BADTYPE) { if (AUTHORABLE) { setParseError(triceps.get("invalid_answerType")); } else { setParseError("syntax error"); } answerType = NOTHING; } if (datumType == Datum.INVALID) { /* so only if not set via datumTypeStr */ datumType = DATA_TYPES[answerType]; } switch (answerType) { case CHECK: case COMBO: case LIST: case RADIO: case RADIO_HORIZONTAL: case RADIO_HORIZONTAL2: case COMBO2: case LIST2: String val = null; String msg = null; int field = 0; Vector ansOptions = new Vector(); Vector prevAnsOptions = null; if (langNum > 0) { prevAnsOptions = getValuesAt(answerChoicesVector, 0); } int ansPos = 0; while (ans.hasMoreTokens()) { String s = null; s = ans.nextToken(); if ("|".equals(s)) { ++field; continue; } switch (field) { case 0: break; // discard the first token - answerType case 1: val = s; if (langNum > 0) { boolean err = false; String s2 = null; // previous answer try { s2 = ((AnswerChoice) prevAnsOptions.elementAt(ansPos++)).getValue(); if (!s2.equals(val)) { err = true; } } catch (NullPointerException t) { Logger.getLogger(LoggerName).log(Level.SEVERE, "", t); err = true; } catch (ArrayIndexOutOfBoundsException t) { Logger.getLogger(LoggerName).log(Level.SEVERE, "", t); err = true; } if (err) { if (AUTHORABLE) { setParseError( triceps.get( "mismatch_across_languages_in_return_value_for_answerChoice_num") + (ansPos - 1)); } else { setParseError("syntax error"); } val = s2; // reset it to the previously known return value for consistency (?) } } break; case 2: msg = s; field = 0; // so that cycle between val & msg; if (val == null || msg == null) { if (AUTHORABLE) { setParseError( triceps.get("missing_value_or_message_for_answerChoice_num") + (ansPos - 1)); } else { setParseError("syntax error"); } } else { AnswerChoice ac = new AnswerChoice(val, msg); ansOptions.addElement(ac); /* check for duplicate answer choice values */ if (langNum == 0) { // only for first pass if (answerChoicesHash.put(val, ac) != null) { if (AUTHORABLE) { setParseError(triceps.get("answerChoice_value_already_used") + val); } else { setParseError("syntax error"); } } } } val = null; msg = null; break; } } if (ansOptions.size() == 0) { if (AUTHORABLE) { setParseError(triceps.get("answerChoices_must_be_specified")); } else { setParseError("syntax error"); } } if (field == 1) { if (AUTHORABLE) { setParseError(triceps.get("missing_message_for_answerChoice_num") + (ansPos - 1)); } else { setParseError("syntax error"); } } if (langNum > 0) { if (prevAnsOptions.size() != ansOptions.size()) { if (AUTHORABLE) { setParseError( triceps.get("mismatch_across_languages_in_number_of_answerChoices") + prevAnsOptions.size() + " != " + ansOptions.size()); } else { setParseError("syntax error"); } } } answerChoicesVector.addElement(ansOptions); break; default: break; } return true; }
/** * Helper function to parse the Question or Eval field, which also includes optional validation * criteria */ private void parseQuestionOrEvalTypeField() { StringTokenizer ans; int z; if (questionOrEvalTypeField == null) { if (AUTHORABLE) { setParseError(triceps.get("questionOrEvalTypeField_must_exist")); } else { setParseError("syntax error"); } return; } ans = new StringTokenizer(questionOrEvalTypeField, ";", true); // return ';' tokens too for (int field = 0; ans.hasMoreTokens(); ) { String s = null; s = ans.nextToken(); if (";".equals(s)) { ++field; continue; } switch (field) { case 0: questionOrEvalTypeStr = s; for (z = 0; z < ACTION_TYPES.length; ++z) { if (questionOrEvalTypeStr.equalsIgnoreCase(ACTION_TYPES[z])) { questionOrEvalType = z; break; } } break; case 1: datumTypeStr = s; datumType = Datum.parseDatumType(s); if (datumType == -1) { if (AUTHORABLE) { setParseError(triceps.get("invalid_dataType") + datumTypeStr); } else { setParseError("syntax error"); } datumType = Datum.INVALID; } break; case 2: minStr = s; break; case 3: maxStr = s; break; case 4: /* FIXME: HACK -- does double duty -- either a formatting mask, OR a regex input mask */ if (s == null || s.trim().length() == 0 || !s.startsWith("PERL5")) { mask = s; pattern = null; } else { pattern = s.substring("PERL5".length()); try { Pattern.compile(pattern); } catch (PatternSyntaxException ex) { setParseError( "Invalid Regular Expression Pattern " + pattern + " " + ex.getMessage()); } } break; default: /* extra parameters are additional allowable values, as Strings that will be parsed */ if (allowableValues == null) { allowableValues = new Vector(); } allowableValues.addElement(s); break; } } }
/** * Create a new Item - reading the contents from the tab separated value set of columns. Loads it * into the Node object * * @param lang the Triceps context * @param sourceLine the line number within the source file (for debugging purposes) * @param sourceFile the name of the source file (for debugging purposes) * @param tsv the tab separated list of colums representing the node contents * @param numLanguage to know how many langauges to parsse? */ Node(Triceps lang, int sourceLine, String sourceFile, String tsv, int numLanguages) { triceps = /*(lang == null) ? new Triceps() :*/ lang; String token; int field = 0; if (numLanguages < 1) { if (AUTHORABLE) { setParseError(triceps.get("numLanguages_must_be_greater_than_zero") + numLanguages); } else { setParseError("syntax error"); } numLanguages = 1; // the default } this.sourceLine = sourceLine; this.sourceFile = sourceFile; this.numLanguages = numLanguages; // needs to be validated? int numLanguagesFound = 0; int numAnswersFound = 0; StringTokenizer ans = new StringTokenizer(tsv, "\t", true); while (ans.hasMoreTokens()) { String s = null; s = ans.nextToken(); if (s.equals("\t")) { ++field; if (field == 7) { ++numLanguagesFound; // since once that field has been entered, has successfully coded a // language as present } if (field == 9 && numLanguagesFound < numLanguages) { field = 5; // so that next element is readback for the next language } continue; } switch (field) { /* there should be one copy of each of these */ case 0: conceptName = (new ExcelDecoder()).decode(s); break; case 1: localName = (new ExcelDecoder()).decode(s); break; case 2: externalName = (new ExcelDecoder()).decode(s); break; case 3: dependencies = (new ExcelDecoder()).decode(s); break; case 4: questionOrEvalTypeField = (new ExcelDecoder()).decode(s); break; /* there are as many copies of each of these are there are languages */ case 5: readback.addElement((new ExcelDecoder()).decode(s)); break; case 6: questionOrEval.addElement((new ExcelDecoder()).decode(s)); break; case 7: answerChoicesStr.addElement((new ExcelDecoder()).decode(s)); break; case 8: helpURL.addElement((new ExcelDecoder()).decode(s)); break; /* there are as many copies of each of these are there are answers - rudimentary support for arrays? */ case 9: { int i = 0; try { i = Integer.parseInt((new ExcelDecoder()).decode(s)); } catch (NumberFormatException t) { Logger.getLogger(LoggerName).log(Level.SEVERE, "", t); if (AUTHORABLE) { setParseError(triceps.get("languageNum_must_be_an_integer") + t.getMessage()); } else { setParseError("syntax error"); } i = 0; // default language } if (i < 0 || i >= numLanguages) { if (AUTHORABLE) { setParseError( triceps.get("languageNum_must_be_in_range_zero_to") + (numLanguages - 1) + "): " + i); } else { setParseError("syntax error"); } i = 0; // default language } answerLanguageNum = i; } break; case 10: questionAsAsked = (new ExcelDecoder()).decode(s); break; case 11: answerGiven = (new ExcelDecoder()).decode(s); break; case 12: comment = (new ExcelDecoder()).decode(s); break; case 13: answerTimeStampStr = (new ExcelDecoder()).decode(s); break; default: break; // ignore extras } } if (dependencies == null || dependencies.trim().length() == 0) { if (AUTHORABLE) { setParseError(triceps.get("dependencies_column_is_missing")); } else { setParseError("syntax error"); } } if (localName != null && localName.trim().length() > 0) { localName = localName.trim(); if (Character.isDigit(localName.charAt(0))) { if (AUTHORABLE) { setNamingError(triceps.get("localName_may_not_begin_with_a_digit") + localName); } else { setParseError("syntax error"); } localName = "_" + localName; } if (!isNMTOKEN(localName)) { if (AUTHORABLE) { setNamingError( triceps.get("localName_should_only_contain_letters_digits_and_underscores") + localName); } else { setParseError("syntax error"); } } } else { setNamingError(triceps.get("localName_must_be_specified")); } parseQuestionOrEvalTypeField(); if (questionOrEvalType == BADTYPE) { if (AUTHORABLE) { setParseError(triceps.get("invalid_questionOrEvalType") + questionOrEvalTypeField); } else { setParseError("syntax error"); } } for (int i = 0; i < answerChoicesStr.size(); ++i) { parseAnswerOptions(i, (String) answerChoicesStr.elementAt(i)); } if (datumType == Datum.INVALID) { if (AUTHORABLE) { setParseError(triceps.get("invalid_dataType")); } else { setParseError("syntax error"); } } }