/* * Copyright (C) 2010-2012 Stichting Akvo (Akvo Foundation) * * This file is part of Akvo Flow. * * Akvo Flow is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Akvo Flow is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Akvo Flow. If not, see <http://www.gnu.org/licenses/>. */ package org.akvo.flow.serialization.form; import org.akvo.flow.domain.AltText; import org.akvo.flow.domain.Dependency; import org.akvo.flow.domain.Level; import org.akvo.flow.domain.Option; import org.akvo.flow.domain.Question; import org.akvo.flow.domain.QuestionGroup; import org.akvo.flow.domain.QuestionHelp; import org.akvo.flow.domain.ScoringRule; import org.akvo.flow.domain.Survey; import org.akvo.flow.domain.SurveyGroup; import org.akvo.flow.domain.ValidationRule; import org.akvo.flow.util.ConstantUtil; import org.akvo.flow.util.StringUtil; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import java.util.ArrayList; import java.util.List; import timber.log.Timber; /** * Handler for sax-based xml parser for Survey files * * @author Christopher Fagiani **/ public class SurveyHandler extends DefaultHandler { private static final String DEFAULT_LANG = "defaultLanguageCode"; private static final String QUESTION_GROUP = "questionGroup"; private static final String HEADING = "heading"; private static final String QUESTION = "question"; private static final String SURVEY = "survey"; private static final String APP = "app"; private static final String ORDER = "order"; private static final String MANDATORY = "mandatory"; private static final String TYPE = "type"; private static final String ID = "id"; private static final String DEPENDENCY = "dependency"; private static final String ANSWER = "answer-value"; private static final String TEXT = "text"; private static final String OPTION = "option"; private static final String VALUE = "value"; private static final String CODE = "code"; private static final String OPTIONS = "options"; private static final String ALLOW_OTHER = "allowOther"; private static final String VALIDATION_TYPE = "validationType"; private static final String VALIDATION_RULE = "validationRule"; private static final String MAX_LENGTH = "maxLength"; private static final String ALLOW_DEC = "allowDecimal"; private static final String ALLOW_SIGN = "signed"; private static final String RENDER_TYPE = "renderType"; private static final String ALLOW_MULT = "allowMultiple"; private static final String MIN_VAL = "minVal"; private static final String MAX_VAL = "maxVal"; private static final String ALT_TEXT = "altText"; private static final String LANG = "language"; private static final String LOCKED = "locked"; private static final String HELP = "help"; private static final String SCORING = "scoring"; private static final String SCORE = "score"; private static final String RANGE_MIN = "rangeLow"; private static final String RANGE_MAX = "rangeHigh"; private static final String STRENGTH_MIN = "strengthMin"; private static final String STRENGTH_MAX = "strengthMax"; private static final String NAME = "name"; private static final String VERSION = "version"; private static final String LOCALE_NAME = "localeNameFlag"; private static final String LOCALE_LOCATION = "localeLocationFlag"; private static final String SOURCE_QUESTION_ID = "sourceId"; private static final String SOURCE_SURVEY_ID = "sourceSurveyId"; private static final String DOUBLE_ENTRY = "requireDoubleEntry"; private static final String REPEATABLE = "repeatable"; private static final String SURVEY_GROUP_ID = "surveyGroupId"; private static final String SURVEY_GROUP_NAME = "surveyGroupName"; private static final String REGISTRATION_SURVEY = "registrationSurvey"; private static final String USE_EXTERNAL_SOURCE = "allowExternalSources"; private static final String CASCADE_RESOURCE = "cascadeResource"; private static final String CADDISFLY_RESOURCE = "caddisflyResourceUuid"; private static final String LEVELS = "levels"; private static final String LEVEL = "level"; private static final String ALLOW_POINTS = "allowPoints"; private static final String ALLOW_LINE = "allowLine"; private static final String ALLOW_POLYGON = "allowPolygon"; @SuppressWarnings("unused") private static final String TRANSLATION = "translation"; private Survey survey; private QuestionGroup currentQuestionGroup; private Question currentQuestion; private Option currentOption; private Dependency currentDependency; private ArrayList<Option> currentOptions; private ValidationRule currentValidation; private AltText currentAltText; private QuestionHelp currentHelp; private ScoringRule currentScoringRule; private String currentScoringType; private Level currentLevel; private List<Level> currentLevels; private StringBuilder builder; public Survey getSurvey() { return survey; } public void characters(char[] ch, int start, int length) throws SAXException { super.characters(ch, start, length); builder.append(ch, start, length); } /** * processes elements after the end tag is encountered */ public void endElement(String uri, String localName, String name) throws SAXException { super.endElement(uri, localName, name); if (currentQuestionGroup != null) { if (localName.equalsIgnoreCase(HEADING)) { currentQuestionGroup.setHeading(builder.toString().trim()); } else if (localName.equalsIgnoreCase(QUESTION)) { currentQuestionGroup.addQuestion(currentQuestion); currentQuestion = null; } else if (localName.equalsIgnoreCase(QUESTION_GROUP)) { survey.addQuestionGroup(currentQuestionGroup); currentQuestionGroup = null; } } if (currentQuestion != null) { // <text> can appear multiple places. We need to make sure we're not // in the context of an option or help here if (localName.equalsIgnoreCase(TEXT) && currentOption == null && currentHelp == null && currentLevel == null) { currentQuestion.setText(builder.toString().trim()); } else if (localName.equalsIgnoreCase(OPTIONS)) { currentQuestion.setOptions(currentOptions); currentOptions = null; } else if (localName.equalsIgnoreCase(LEVELS)) { currentQuestion.setLevels(currentLevels); currentLevels = null; } else if (localName.equalsIgnoreCase(VALIDATION_RULE)) { currentQuestion.setValidationRule(currentValidation); currentValidation = null; } else if (localName.equalsIgnoreCase(HELP)) { if (currentHelp.isValid()) { if (StringUtil.isNullOrEmpty(currentHelp.getType())) { currentHelp.setType(ConstantUtil.TIP_HELP_TYPE); } currentQuestion.addQuestionHelp(currentHelp); } currentHelp = null; } else if (localName.equalsIgnoreCase(SCORE)) { currentQuestion.addScoringRule(currentScoringRule); currentScoringRule = null; } else if (localName.equalsIgnoreCase(SCORING)) { currentScoringType = null; } } if (currentOption != null) { // the null check here is to handle "old" style options that don't // have a <text> element if (localName.equalsIgnoreCase(OPTION) && currentOption.getText() == null) { currentOption.setText(builder.toString().trim()); if (currentOptions != null) { currentOptions.add(currentOption); } currentOption = null; } // handle "new" style options that have a <text> element if (localName.equalsIgnoreCase(TEXT)) { currentOption.setText(builder.toString().trim()); if (currentOptions != null) { currentOptions.add(currentOption); } } // close the current option if (localName.equalsIgnoreCase(OPTION)) { currentOption = null; } } if (currentLevel != null) { if (localName.equalsIgnoreCase(TEXT)) { currentLevel.setText(builder.toString().trim()); if (currentLevels != null) { currentLevels.add(currentLevel); } } else if (localName.equalsIgnoreCase(LEVEL)) { // close the current option currentLevel = null; } } if (currentAltText != null) { if (localName.equalsIgnoreCase(ALT_TEXT)) { currentAltText.setText(builder.toString().trim()); if (currentHelp != null) { currentHelp.addAltText(currentAltText); } else if (currentOption != null) { currentOption.addAltText(currentAltText); } else if (currentLevel != null) { currentLevel.addAltText(currentAltText); } else if (currentQuestion != null) { currentQuestion.addAltText(currentAltText); } currentAltText = null; } } if (currentHelp != null) { if (localName.equalsIgnoreCase(TEXT)) { currentHelp.setText(builder.toString().trim()); } } builder.setLength(0); } /** * construct a new survey object and store as a member */ public void startDocument() throws SAXException { super.startDocument(); survey = new Survey(); builder = new StringBuilder(); } /** * read in the attributes of the new xml element and set the appropriate * values on the object(s) being hydrated. */ public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException { super.startElement(uri, localName, name, attributes); if (localName.equalsIgnoreCase(SURVEY)) { if (attributes.getValue(NAME) != null) { survey.setName(attributes.getValue(NAME)); } if (attributes.getValue(VERSION) != null) { survey.setVersion(Double.parseDouble(attributes.getValue(VERSION))); } if (attributes.getValue(DEFAULT_LANG) != null) { survey.setLanguage(attributes.getValue(DEFAULT_LANG)); } else { survey.setLanguage(ConstantUtil.ENGLISH_CODE); } if (attributes.getValue(SOURCE_SURVEY_ID) != null) { survey.setSourceSurveyId(attributes.getValue(SOURCE_SURVEY_ID)); } // SurveyGroup info, if exists if (attributes.getValue(SURVEY_GROUP_ID) != null && attributes.getValue(SURVEY_GROUP_NAME) != null) { long sgid = Long.valueOf(attributes.getValue(SURVEY_GROUP_ID)); String sgname = attributes.getValue(SURVEY_GROUP_NAME); String regform = attributes.getValue(REGISTRATION_SURVEY); survey.setSurveyGroup(new SurveyGroup(sgid, sgname, regform, regform != null)); } survey.setApp(attributes.getValue(APP)); } else if (localName.equalsIgnoreCase(QUESTION_GROUP)) { currentQuestionGroup = new QuestionGroup(); if (attributes.getValue(ORDER) != null) { currentQuestionGroup.setOrder(Integer.parseInt(attributes .getValue(ORDER))); } else { int count = 1; if (survey != null && survey.getQuestionGroups() != null) { count = survey.getQuestionGroups().size() + 2; } currentQuestionGroup.setOrder(count); } // Repeatable flag currentQuestionGroup.setRepeatable(Boolean.parseBoolean(attributes.getValue(REPEATABLE))); } else if (localName.equalsIgnoreCase(QUESTION)) { currentQuestion = new Question(); if (attributes.getValue(ORDER) != null) { currentQuestion.setOrder(Integer.parseInt(attributes .getValue(ORDER))); } else { int count = 1; if (currentQuestionGroup != null && currentQuestionGroup.getQuestions() != null) { count = currentQuestionGroup.getQuestions().size() + 2; } currentQuestion.setOrder(count); } if (attributes.getValue(MANDATORY) != null) { currentQuestion.setMandatory(Boolean.parseBoolean(attributes .getValue(MANDATORY))); } else { currentQuestion.setMandatory(false); } if (attributes.getValue(LOCKED) != null) { currentQuestion.setLocked(Boolean.parseBoolean(attributes .getValue(LOCKED))); } else { currentQuestion.setLocked(false); } // Double Entry flag if (attributes.getValue(DOUBLE_ENTRY) != null) { currentQuestion.setIsDoubleEntry(Boolean.parseBoolean(attributes .getValue(DOUBLE_ENTRY))); } else { currentQuestion.setIsDoubleEntry(false); } // 'allowMultiple' flag can be found at the <question> and <options> scopes. In option // questions, the latter will be used. For the rest, the flag will be set in <question> currentQuestion.setAllowMultiple(Boolean.parseBoolean(attributes.getValue(ALLOW_MULT))); currentQuestion.setType(attributes.getValue(TYPE)); currentQuestion.setId(attributes.getValue(ID)); String validation = attributes.getValue(VALIDATION_TYPE); if (validation != null && validation.trim().length() > 0) { currentQuestion .setValidationRule(new ValidationRule(validation)); } if (attributes.getValue(STRENGTH_MAX) != null && currentQuestion.getType().equalsIgnoreCase( ConstantUtil.STRENGTH_QUESTION_TYPE)) { currentQuestion.setUseStrength(true); try { currentQuestion.setStrengthMax(Integer.parseInt(attributes .getValue(STRENGTH_MAX).trim())); if (attributes.getValue(STRENGTH_MIN) != null) { currentQuestion.setStrengthMin(Integer .parseInt(attributes.getValue(STRENGTH_MIN) .trim())); } else { currentQuestion.setStrengthMin(0); } } catch (NumberFormatException e) { currentQuestion.setUseStrength(false); currentQuestion.setType(ConstantUtil.OPTION_QUESTION_TYPE); Timber.e(e, "Could not parse strength values"); } } else { currentQuestion.setUseStrength(false); } // Locale Flags if (attributes.getValue(LOCALE_NAME) != null) { currentQuestion.setIsLocaleName(Boolean.parseBoolean(attributes .getValue(LOCALE_NAME))); } if (attributes.getValue(LOCALE_LOCATION) != null) { currentQuestion.setIsLocaleLocation(Boolean.parseBoolean(attributes .getValue(LOCALE_LOCATION))); } if (attributes.getValue(SOURCE_QUESTION_ID) != null) { currentQuestion.setSourceQuestionId(attributes.getValue(SOURCE_QUESTION_ID)); } if (attributes.getValue(USE_EXTERNAL_SOURCE) != null) { currentQuestion.useExternalSource(Boolean.parseBoolean(attributes .getValue(USE_EXTERNAL_SOURCE))); } else { currentQuestion.useExternalSource(false); } // Question src. Added in cascading question implementation. currentQuestion.setSrc(attributes.getValue(CASCADE_RESOURCE)); currentQuestion.setCaddisflyRes(attributes.getValue(CADDISFLY_RESOURCE)); // Geoshape options (question scope) currentQuestion.setAllowPoints(Boolean.parseBoolean(attributes.getValue(ALLOW_POINTS))); currentQuestion.setAllowLine(Boolean.parseBoolean(attributes.getValue(ALLOW_LINE))); currentQuestion.setAllowPolygon(Boolean.parseBoolean(attributes.getValue(ALLOW_POLYGON))); } else if (localName.equalsIgnoreCase(OPTIONS)) { currentOptions = new ArrayList<Option>(); if (currentQuestion != null) { if (attributes.getValue(ALLOW_OTHER) != null) { currentQuestion.setAllowOther(Boolean .parseBoolean(attributes.getValue(ALLOW_OTHER))); } else { currentQuestion.setAllowOther(false); } currentQuestion.setRenderType(attributes.getValue(RENDER_TYPE)); if (attributes.getValue(ALLOW_MULT) != null) { currentQuestion.setAllowMultiple(Boolean .parseBoolean(attributes.getValue(ALLOW_MULT))); } else { currentQuestion.setAllowMultiple(false); } } } else if (localName.equalsIgnoreCase(OPTION)) { currentOption = new Option(); currentOption.setCode(attributes.getValue(CODE)); } else if (localName.equalsIgnoreCase(LEVELS)) { currentLevels = new ArrayList<>(); } else if (localName.equalsIgnoreCase(LEVEL)) { currentLevel = new Level(); } else if (localName.equalsIgnoreCase(DEPENDENCY)) { currentDependency = new Dependency(); currentDependency.setQuestion(attributes.getValue(QUESTION)); currentDependency.setAnswer(attributes.getValue(ANSWER)); if (currentQuestion != null) { currentQuestion.addDependency(currentDependency); } currentDependency = null; } else if (localName.equalsIgnoreCase(VALIDATION_RULE)) { currentValidation = new ValidationRule( attributes.getValue(VALIDATION_TYPE)); currentValidation.setAllowDecimal(attributes.getValue(ALLOW_DEC)); currentValidation.setAllowSigned(attributes.getValue(ALLOW_SIGN)); currentValidation.setMaxLength(attributes.getValue(MAX_LENGTH)); currentValidation.setMinVal(attributes.getValue(MIN_VAL)); currentValidation.setMaxVal(attributes.getValue(MAX_VAL)); } else if (localName.equalsIgnoreCase(ALT_TEXT)) { currentAltText = new AltText(); currentAltText.setLanguage(attributes.getValue(LANG)); currentAltText.setType(attributes.getValue(TYPE)); } else if (localName.equalsIgnoreCase(HELP)) { currentHelp = new QuestionHelp(); currentHelp.setType(attributes.getValue(TYPE)); currentHelp.setValue(attributes.getValue(VALUE)); } else if (localName.equalsIgnoreCase(SCORING)) { currentScoringType = attributes.getValue(TYPE); } else if (localName.equalsIgnoreCase(SCORE)) { currentScoringRule = new ScoringRule(currentScoringType, attributes.getValue(RANGE_MIN), attributes.getValue(RANGE_MAX), attributes.getValue(TEXT), attributes.getValue(VALUE)); } } }