/// <summary>
        /// Recursively through all fields in a form to find fields of type ExternalMediaField and
        /// checks whether the media sources referred by them exist. Returns the list of URLs of media
        /// sources that does not exist.
        /// </summary>
        /// <param name="model">The form or field.</param>
        /// <returns></returns>
        public List <string> CheckMedia(CFXmlModel model)
        {
            List <string> errorneousMedia = new List <string>();

            if (typeof(Form).IsAssignableFrom(model.GetType()))
            {
                Form form = model as Form;
                foreach (var child in form.Fields)
                {
                    errorneousMedia.AddRange(CheckMedia(child));
                }
            }
            else if (typeof(ExternalMediaField).IsAssignableFrom(model.GetType()))
            {
                if (!CheckMedia((model as ExternalMediaField).Source))
                {
                    errorneousMedia.Add((model as ExternalMediaField).Source);
                }
            }
            else if (typeof(CompositeFormField).IsAssignableFrom(model.GetType()))
            {
                CompositeFormField cfield = model as CompositeFormField;

                foreach (var child in cfield.Header)
                {
                    errorneousMedia.AddRange(CheckMedia(child));
                }

                foreach (var child in cfield.Fields)
                {
                    errorneousMedia.AddRange(CheckMedia(child));
                }

                foreach (var child in cfield.Footer)
                {
                    errorneousMedia.AddRange(CheckMedia(child));
                }
            }

            return(errorneousMedia);
        }
        public Form CreateForm(FormIngestionViewModel model)
        {
            try
            {
                model.ColumnHeadings = GetColumnHeadings(model.DataSheet);
                string lastColName = GetColumnName(model.ColumnHeadings.Count - 1);
                string dataRange   = string.Format("'{0}'!A2:{1}", model.DataSheet, lastColName);

                var request = SheetsService.Spreadsheets.Values.Get(Spreadsheet.SpreadsheetId, dataRange);
                var result  = request.Execute();

                if (string.IsNullOrEmpty(model.ListIdColumn))
                {
                    throw new Exception("Please identify the List ID column");
                }
                int listIdCol = model.ColumnHeadings.IndexOf(model.ListIdColumn);

                if (string.IsNullOrEmpty(model.BlockIdColumn))
                {
                    throw new Exception("Please identify the Block ID column");
                }
                int blockIdCol = model.ColumnHeadings.IndexOf(model.BlockIdColumn);

                List <int> preContextColIndicies = model.PreContextColumns.Select(s => model.ColumnHeadings.IndexOf(s)).ToList();

                if (string.IsNullOrEmpty(model.QuestionColumn))
                {
                    throw new Exception("Please idenfify the Question column");
                }
                int questionCol = model.ColumnHeadings.IndexOf(model.QuestionColumn);

                if (string.IsNullOrEmpty(model.AnswerTypeColumn))
                {
                    throw new Exception("Please idenfify the Answer Type column");
                }
                int answerTypeCol = model.ColumnHeadings.IndexOf(model.AnswerTypeColumn);

                if (string.IsNullOrEmpty(model.AnswerOptionsColumn))
                {
                    throw new Exception("Please idenfify the Answer Options column");
                }
                int answerOptionsCol = model.ColumnHeadings.IndexOf(model.AnswerOptionsColumn);

                if (string.IsNullOrEmpty(model.QuestionIdColumn))
                {
                    throw new Exception("Please idenfify the Question ID column");
                }
                int questionIdCol = model.ColumnHeadings.IndexOf(model.QuestionIdColumn);

                if (string.IsNullOrEmpty(model.IsRequiredIndicatorColumn))
                {
                    throw new Exception("Please idenfify the Required Question Indicator column");
                }
                int isRequiredCol = model.ColumnHeadings.IndexOf(model.IsRequiredIndicatorColumn);

                if (string.IsNullOrEmpty(model.AudioFileColumn))
                {
                    throw new Exception("Please idenfify the Audio File column");
                }
                int audioFileCol = model.ColumnHeadings.IndexOf(model.AudioFileColumn);

                string urlBase = string.IsNullOrEmpty(model.MediaFolderUrl) ? "/media/" : model.MediaFolderUrl.TrimEnd(new char[] { '/' }) + "/";

                //Iterating through all the data and creating numbered lists and list of blocks, list of headers and list of footers
                //The key of each dictionary element represents the list where each block, header or foter belongs to.
                List <CompositeFormField> listFields = new List <CompositeFormField>();
                Dictionary <int, List <CompositeFormField> > blockFieldSets = new Dictionary <int, List <CompositeFormField> >();
                Dictionary <int, CompositeFormField>         headers        = new Dictionary <int, CompositeFormField>();
                Dictionary <int, CompositeFormField>         footers        = new Dictionary <int, CompositeFormField>();

                var uniqueListIds = result.Values.Select(x => x[listIdCol] as string).Distinct().ToList();
                foreach (var listId in uniqueListIds.Where(x => x != "*").Select(x => int.Parse(x)))
                {
                    listFields.Add(new CompositeFormField()
                    {
                        Name = "Page " + listId, Page = listId
                    });
                    blockFieldSets.Add(listId, new List <CompositeFormField>());
                    headers.Add(listId, new CompositeFormField()
                    {
                        Name = "Footer"
                    });
                    footers.Add(listId, new CompositeFormField()
                    {
                        Name = "Header"
                    });
                }

                //Iterating through all the data and creating blocks, headers, footers and questions
                for (int index = 0; index < result.Values.Count; ++index)
                {
                    var           row    = result.Values[index];
                    List <string> values = row.Select(s => s.ToString().Trim()).ToList();

                    //Creating the question
                    List <string> preContexts   = preContextColIndicies.Select(i => values[i]).ToList();
                    string        questionText  = values[questionCol];
                    string        questionId    = values[questionIdCol];
                    string        answerType    = values[answerTypeCol];
                    string        answerOptions = answerOptionsCol < values.Count ? values[answerOptionsCol] : "";

                    if (string.IsNullOrEmpty(answerType))
                    {
                        if (string.IsNullOrEmpty(answerOptions))
                        {
                            answerType = "ShortText";
                        }
                        else
                        {
                            answerType = "RadioButtonSet";
                        }
                    }

                    ///
                    /// Each precontext and question are represented by a composite field inside the selected block.
                    ///
                    CompositeFormField surveyItem = new CompositeFormField()
                    {
                        Name = "Question " + questionId
                    };
                    foreach (string pc in preContexts)
                    {
                        if (!string.IsNullOrEmpty(pc.Trim()))
                        {
                            HtmlField html = new HtmlField()
                            {
                                Name = "Description"
                            };
                            html.SetDescription(pc);
                            surveyItem.InsertChildElement("./fields", html.Data);
                        }
                    }

                    //Adding the question text
                    HtmlField questionTextField = new HtmlField()
                    {
                        Name = "Text"
                    };
                    questionTextField.SetDescription(questionText);
                    surveyItem.InsertChildElement("./fields", questionTextField.Data);

                    //Adding the media file
                    string mediaFile = audioFileCol < values.Count ? values[audioFileCol].TrimStart(new char[] { '/' }) : null;
                    if (!string.IsNullOrEmpty(mediaFile))
                    {
                        ExternalMediaField mf = new ExternalMediaField()
                        {
                            Name       = "Media",
                            Source     = urlBase + mediaFile,
                            MediaType  = Models.Data.CFDataFile.MimeType.Audio,
                            PlayOnce   = true,
                            PlayOnShow = true
                        };
                        surveyItem.InsertChildElement("./fields", mf.Data);
                    }

                    FormField question = null;
                    if (answerType == "ShortText")
                    {
                        question = new TextField();
                    }
                    else if (answerType == "Number")
                    {
                        question = new NumberField();
                    }
                    else if (answerType == "Scale")
                    {
                        List <string> options = answerOptions.Split(new char[] { '|' }).Select(s => s.Trim()).ToList();
                        string[]      begin   = options[0].Split(new char[] { ':' });
                        string[]      end     = options[1].Split(new char[] { ':' });

                        question = new SliderField()
                        {
                            Min      = begin.Length > 0 ? int.Parse(begin[0]) : 0,
                            Max      = end.Length > 0 ? int.Parse(end[0]) : 1,
                            MinLabel = begin.Length > 1 ? begin[1] : "",
                            MaxLabel = end.Length > 1 ? end[1] : ""
                        };
                    }
                    else if (answerType == "RadioButtonSet" || answerType == "DropDown" || answerType == "CheckBoxSet")
                    {
                        List <string> optionStrings = answerOptions.Split(new char[] { '|' }).Select(s => s.Trim()).ToList();
                        List <Option> options       = new List <Option>();
                        foreach (var optVal in optionStrings)
                        {
                            Option opt = new Option();
                            opt.Value = new List <TextValue>()
                            {
                                new TextValue()
                                {
                                    Value = optVal, LanguageCode = "en"
                                }
                            };
                            options.Add(opt);
                        }

                        if (answerType == "RadioButtonSet")
                        {
                            question = new RadioButtonSet()
                            {
                                Options = options
                            }
                        }
                        ;
                        else if (answerType == "CheckBoxSet")
                        {
                            question = new CheckBoxSet()
                            {
                                Options = options
                            }
                        }
                        ;
                        else
                        {
                            question = new DropDownMenu()
                            {
                                Options = options
                            }
                        };
                    }
                    else if (answerType == "TextArea")
                    {
                        question = new TextArea();
                    }
                    else if (answerType == "Attachment")
                    {
                        question = new Attachment();
                    }
                    ////else if(answerType == "DropDown")
                    ////{
                    ////    List<string> optionStrings = answerOptions.Split(new char[] { '|' }).Select(s => s.Trim()).ToList();
                    ////    List<Option> options = new List<Option>();
                    ////    foreach (var optVal in optionStrings)
                    ////    {
                    ////        Option opt = new Option();
                    ////        opt.Value = new List<TextValue>() { new TextValue() { Value = optVal, LanguageCode = "en" } };
                    ////        options.Add(opt);
                    ////    }
                    ////    question = new DropDownMenu() { Options = options };
                    ////}
                    else
                    {
                        throw new Exception(string.Format("Answer type \"{0}\" is not implemented in survey form ingestion.", answerType));
                    }

                    question.Name           = "Answer";
                    question.ReferenceLabel = questionId;
                    question.IsRequired     = values[isRequiredCol] == "1";
                    question.SetAttribute("question-id", values[questionIdCol].ToString());
                    surveyItem.InsertChildElement("./fields", question.Data);

                    //Adding the question to the header, footer or the appropriate block of the
                    //selected list (or all the lists, if list number is *)
                    var    listIdStr       = values[listIdCol];
                    string blockIdStr      = values[blockIdCol].ToUpper();
                    var    applicableLists = listIdStr == "*" ? listFields : listFields.Where(x => x.Page == int.Parse(listIdStr));
                    foreach (var list in applicableLists)
                    {
                        if (blockIdStr == "H")
                        {
                            headers[list.Page].InsertChildElement("./fields", surveyItem.Data);
                        }
                        else if (blockIdStr == "F")
                        {
                            footers[list.Page].InsertChildElement("./fields", surveyItem.Data);
                        }
                        else
                        {
                            var blockSet = blockFieldSets[list.Page];
                            var block    = blockSet.Where(x => x.Page == int.Parse(blockIdStr)).FirstOrDefault();
                            if (block == null)
                            {
                                block = new CompositeFormField()
                                {
                                    Name = "Block " + blockIdStr, Page = int.Parse(blockIdStr)
                                };
                                blockSet.Add(block);
                            }
                            block.InsertChildElement("./fields", surveyItem.Data);
                        }
                    }
                }//END: foreach (var row in result.Values)

                ///
                /// By this point, we have all "lists" in the listFields array and all "blocks" corresponding to
                /// each of those lists in the blockFieldSets dictionary.
                ///

                //Inserting all blocks into each list entry
                foreach (var list in listFields)
                {
                    List <CompositeFormField> blocks = blockFieldSets[list.Page];
                    foreach (var block in blocks)
                    {
                        list.InsertChildElement("./fields", block.Data);
                    }

                    if (headers[list.Page].Fields.Any())
                    {
                        list.InsertChildElement("./header", headers[list.Page].Data);
                    }

                    if (footers[list.Page].Fields.Any())
                    {
                        list.InsertChildElement("./footer", footers[list.Page].Data);
                    }
                }

                Form form = new Form();
                form.SetName(model.FormName);
                form.SetDescription(model.FormDescription);

                foreach (var field in listFields)
                {
                    form.InsertChildElement("./fields", field.Data);
                }

                form.Serialize();
                return(form);



                //////    //If the block number is H or F, the question goes to the header or the footer section of a block.
                //////    if (blockIdCol.HasValue && values[blockIdCol.Value].ToUpper() == "H")
                //////    {
                //////        string lsitNum = listIdCol.HasValue ? values[listIdCol.Value] : "0";
                //////        if (!headers.ContainsKey(lsitNum))
                //////            headers.Add(lsitNum, new List<CompositeFormField>());

                //////    }
                //////    //Otherwise, this is a regular question.


                //////    string listNum = listIdCol.HasValue ? values[listIdCol.Value] : "0";
                //////    string blockNum = blockIdCol.HasValue ? values[blockIdCol.Value] : "0";
                //////    List<string> preContexts = preContextColIndicies.Select(i => values[i]).ToList();
                //////    string questionText = values[questionCol];

                //////    string answerType = answerOptionsCol.HasValue ? values[answerOptionsCol.Value] : "";
                //////    string answerOptions = answerOptionsCol.HasValue ? values[answerOptionsCol.Value] : "";

                //////    if (string.IsNullOrEmpty(answerType))
                //////    {
                //////        if (string.IsNullOrEmpty(answerOptions))
                //////            answerType = "TextField";
                //////        else
                //////            answerType = "RadioButtonSet";
                //////    }

                //////    ///
                //////    /// Each list is represented by a composite field at the top level of the form.
                //////    ///The list number is stored in the "page" property of the field.
                //////    ///Get the composite field representing the given list number, or create a new one if it doesn't exist
                //////    ///
                //////    CompositeFormField list = listFields.Where(field => field.Page == int.Parse(listNum)).FirstOrDefault();
                //////    if(list == null)
                //////    {
                //////        list = new CompositeFormField() { Page = int.Parse(listNum) };
                //////        listFields.Add(list);
                //////        blockFieldSets.Add(listNum, new List<CompositeFormField>()); //Placehoder for blocks of this list.
                //////    }

                //////    ///
                //////    /// Each block is represented by a composite field in the "list".
                //////    ///The block number is stored in the "page" propoerty of this composite field.
                //////    ///Get the composite field representinhg the give block number from the selected list. pr create a new one if it doesn't exist
                //////    ///
                //////    List<CompositeFormField> blocks = blockFieldSets[listNum];
                //////    CompositeFormField block = blocks.Where(field => field.Page == int.Parse(blockNum)).FirstOrDefault();
                //////    if(block == null)
                //////    {
                //////        block = new CompositeFormField() { Page = int.Parse(blockNum) };
                //////        blocks.Add(block);
                //////    }

                //////    ///
                //////    /// Each precontext and question are represented by a composite field inside the selected block.
                //////    ///
                //////    CompositeFormField surveyItem = new CompositeFormField();
                //////    foreach(string pc in preContexts)
                //////    {
                //////        if(!string.IsNullOrEmpty(pc.Trim()))
                //////        {
                //////            HtmlField html = new HtmlField();
                //////            html.SetDescription(pc);
                //////            surveyItem.InsertChildElement("./fields", html.Data);
                //////        }
                //////    }

                //////    FormField question = null;
                //////    if (answerType == "TextField")
                //////        question = new TextField();
                //////    else if (answerOptions == "RadioButtonSet")
                //////    {
                //////        List<string> optionStrings = answerOptions.Split(new char[] { '\n' }).Select(s => s.Trim()).ToList();
                //////        List<Option> options = new List<Option>();
                //////        foreach (var optVal in optionStrings)
                //////        {
                //////            Option opt = new Option();
                //////            opt.Value = new List<TextValue>() { new TextValue() { Value = optVal } };
                //////            options.Add(opt);
                //////        }

                //////        question = new RadioButtonSet()
                //////        {
                //////            Options = options
                //////        };
                //////    }
                //////    else
                //////        throw new Exception(string.Format("Answer type \"{0}\" is not implemented in survey form ingestion."));

                //////    question.SetName(questionText);
                //////    surveyItem.InsertChildElement("./fields", question.Data);

                //////    block.InsertChildElement("./fields", surveyItem.Data);
                //////}

                ////////    ///
                ////////    /// By this point, we have all "lists" in the listFields array and all "blocks" corresponding to
                ////////    /// each of those lists in the blockFieldSets dictionary.
                ////////    ///

                ////////    //Inserting all blocks into each list entry
                ////////    foreach(var list in listFields)
                ////////    {
                ////////        List<CompositeFormField> blocks = blockFieldSets[list.Page.ToString()];
                ////////        foreach (var block in blocks)
                ////////            list.InsertChildElement("./fields", block.Data);
                ////////    }

                ////////    Form form = new Form();
                ////////    form.SetName(model.FormName);
                ////////    form.SetDescription(model.FormDescription);

                ////////    foreach (var field in listFields)
                ////////        form.InsertChildElement("./fields", field.Data);

                ////////    form.Serialize();
                ////////    return form;
            }
            catch (Exception ex)
            {
                model.Error = ex.Message;
            }

            return(null);
        }
示例#3
0
        ////private void EvaluateCompositeField(IEnumerable<FormField> fields)
        ////{
        ////    foreach(var field in fields)
        ////    {
        ////        if(field is CompositeFormField)
        ////        {
        ////            if (((CompositeFormField)field).Shuffle)
        ////            {
        ////                //TODO: Shuffle
        ////            }

        ////            EvaluateCompositeField(((CompositeFormField)field).Fields);
        ////        }
        ////    }
        ////}

        /// <summary>
        /// Create a form submission based on a specified form template.
        /// </summary>
        /// <param name="formTemplateId">The template to create the submission on.</param>
        /// <returns>The newly created submission.</returns>
        public Form CreateSubmissionForm(int formTemplateId, bool enforceLists, bool shuffleBlocks, bool shuffleQuestions, CompositeFormField.eStepState questionStepOption, CompositeFormField.eStepState questionPartsStepOption)
        {
            //Obtaining the template
            Form template = Db.FormTemplates.Where(m => m.Id == formTemplateId).FirstOrDefault();

            //Creating a clone of the template and returning it. We don't want to return the template
            //itself to avoid saving user data into the template.
            Form submission = new Form()
            {
                Data = template.Data
            };

            submission.Id = template.Id;

            Random rand = new Random();

            if (enforceLists)
            {
                //Assumes the top-level CompositeFormFields represents of the form sublists of fields in the form and selects only one of them
                //for the submission
                var listSet       = submission.Fields.Where(field => field is CompositeFormField);
                var listCount     = listSet.Count();
                int selectedIndex = rand.Next(0, listCount);
                CompositeFormField selectedList = listSet.Skip(selectedIndex).FirstOrDefault() as CompositeFormField;

                //replacing the fields of the submission with composite field which represents the elected list
                submission.Fields = submission.Fields.Where(field => field.Guid == selectedList.Guid || !(field is CompositeFormField)).ToList();
            }

            ////EvaluateCompositeField(submission.Fields);

            if (shuffleBlocks)
            {
                //Assumes that the top-level CompositeFormFields of the form represents sublists of felds and CompositeFormFields in each of those
                //sublists represent blocks, and shuffles those blocks
                var listSet = submission.Fields.Where(field => field is CompositeFormField).Select(field => field as CompositeFormField);
                foreach (var list in listSet)
                {
                    List <CompositeFormField> blockSet = (list as CompositeFormField).Fields
                                                         .Where(b => b is CompositeFormField)
                                                         .Select(b => b as CompositeFormField)
                                                         .ToList();

                    List <CompositeFormField> shuffledBlockSet = new List <CompositeFormField>();
                    int n = blockSet.Count;
                    while (n > 0)
                    {
                        var selectedIndex = rand.Next(0, n);
                        shuffledBlockSet.Add(blockSet[selectedIndex]);
                        blockSet.RemoveAt(selectedIndex);
                        --n;
                    }

                    //Replacing the fields with the shuffeled block set
                    list.Fields = shuffledBlockSet;
                }
            }

            if (shuffleQuestions)
            {
                //Assumes that the top-level CompositeFormFields of the form represents sublists of felds and CompositeFormFields in each of those
                //sublists represent blocks, and each block to contain quesitons. This code section shudffles  questions inside those blocks.
                var listSet = submission.Fields.Where(field => field is CompositeFormField).Select(field => field as CompositeFormField);
                foreach (var list in listSet)
                {
                    List <CompositeFormField> blockSet = (list as CompositeFormField).Fields
                                                         .Where(b => b is CompositeFormField)
                                                         .Select(b => b as CompositeFormField)
                                                         .ToList();

                    foreach (var block in blockSet)
                    {
                        List <FormField> sourceFieldSet   = block.Fields.ToList();
                        List <FormField> shuffledFieldSet = new List <FormField>();
                        int n = sourceFieldSet.Count;
                        while (n > 0)
                        {
                            var selectedIndex = rand.Next(0, n);
                            shuffledFieldSet.Add(sourceFieldSet[selectedIndex]);
                            sourceFieldSet.RemoveAt(selectedIndex);
                            --n;
                        }

                        //Replacing the fields with the shuffeled block set
                        block.Fields = shuffledFieldSet;
                    }
                }
            }

            //Activating stepping through
            if (questionStepOption == CompositeFormField.eStepState.None && questionPartsStepOption != CompositeFormField.eStepState.None)
            {
                questionStepOption = CompositeFormField.eStepState.StepThrough;
            }

            if (questionStepOption != CompositeFormField.eStepState.None)
            {
                //Assumes that the top-level CompositeFormFields of the form represents sublists of felds and CompositeFormFields in each of those
                //sublists represent blocks, and each block to contain quesitons. This code initializes the step-through-children property of
                //the blocks and the questions in order to enable shuffling questions and question-parts, respectively.
                foreach (var list in submission.Fields.Where(field => field is CompositeFormField).Select(field => field as CompositeFormField))
                {
                    foreach (var headerItem in list.Header.Where(h => h is CompositeFormField).Select(h => h as CompositeFormField))
                    {
                        headerItem.StepState = questionStepOption;
                        if (questionPartsStepOption != CompositeFormField.eStepState.None)
                        {
                            foreach (var question in headerItem.Fields.Where(field => field is CompositeFormField).Select(field => field as CompositeFormField))
                            {
                                question.StepState = questionPartsStepOption;
                            }
                        }
                    }

                    foreach (var block in list.Fields.Where(field => field is CompositeFormField).Select(field => field as CompositeFormField))
                    {
                        block.StepState = questionStepOption;

                        if (questionPartsStepOption != CompositeFormField.eStepState.None)
                        {
                            foreach (var question in block.Fields.Where(field => field is CompositeFormField).Select(field => field as CompositeFormField))
                            {
                                question.StepState = questionPartsStepOption;
                            }
                        }
                    }

                    foreach (var footerItem in list.Footer.Where(h => h is CompositeFormField).Select(h => h as CompositeFormField))
                    {
                        footerItem.StepState = questionStepOption;
                        if (questionPartsStepOption != CompositeFormField.eStepState.None)
                        {
                            foreach (var question in footerItem.Fields.Where(field => field is CompositeFormField).Select(field => field as CompositeFormField))
                            {
                                question.StepState = questionPartsStepOption;
                            }
                        }
                    }
                }

                //throw new NotImplementedException("Handle stepping through footer questions here ...");
            }


            //Removing the audit trail from the created form since the current trail contains info
            //from the form template creation.
            XElement audit = submission.Data.Element("audit");

            if (audit != null)
            {
                audit.Remove();
            }

            return(submission);
        }
示例#4
0
        /// <summary>
        /// Creates a basic form to be used for testing.
        /// </summary>
        /// <param name="title">The name of the form to be using.</param>
        /// <param name="sectionCount">how many sections will be shown.</param>
        /// <param name="questionTypesPerSection">The types of questions to be shown in each section.</param>
        /// <param name="sectionTitleFunc">How to define the title of each section.</param>
        /// <param name="questionTitleFunc">How to define the title of each question.</param>
        /// <param name="setSectionDetailsFunc">Adds any details to the section.</param>
        /// <param name="setQuestionDetailsFunc">Adds any details to the question.</param>
        /// <returns></returns>
        static Form GenerateTestForm(string name, int sectionCount, IEnumerable <eQuestionType> questionTypesPerSection, Func <int, string> sectionTitleFunc, Func <int, int, eQuestionType, string> questionTitleFunc, Action <int, CompositeFormField> setSectionDetailsFunc = null, Action <int, int, FormField> setQuestionDetailsFunc = null)
        {
            List <Form> sections = new List <Form>();

            for (int i = 0; i < sectionCount; ++i)
            {
                Form             form   = new Form();
                List <FormField> fields = new List <FormField>();
                int questionId          = 0;
                foreach (eQuestionType type in questionTypesPerSection)
                {
                    ++questionId;

                    FormField field;

                    switch (type)
                    {
                    default:
                        field = new TextField();
                        break;
                    }

                    field.Name = questionTitleFunc(i, questionId, type);
                    if (setQuestionDetailsFunc != null)
                    {
                        setQuestionDetailsFunc(i, questionId, field);
                    }

                    fields.Add(field);
                }

                form.Fields = fields.AsReadOnly();
                sections.Add(form);
            }

            if (sections.Count == 1)
            {
                sections[0].Name = name;
                return(sections[0]);
            }

            Form             result       = new Form();
            List <FormField> resultFields = new List <FormField>();

            for (int i = 0; i < sections.Count; ++i)
            {
                Form section = sections[i];

                CompositeFormField field = new CompositeFormField();
                field.Fields = section.Fields;
                field.Name   = sectionTitleFunc(i);

                if (setSectionDetailsFunc != null)
                {
                    setSectionDetailsFunc(i, field);
                }

                resultFields.Add(field);
            }

            result.Name   = name;
            result.Fields = resultFields.AsReadOnly();
            return(result);
        }