/// <summary> /// Creates a WorkSession object that a host application can use to step through the process of presenting /// all the interviews and assembling all the documents that may result from the given template. /// /// Allows the default interview settings to be specified instead of being read from config file /// </summary> /// <param name="service">An object implementing the IServices interface, encapsulating the instance of /// HotDocs Server with which the host app is communicating.</param> /// <param name="template">The template upon which this WorkSession is based. The initial interview and/or /// document work items in the WorkSession will be based on this template (including its Switches property).</param> /// <param name="answers">A collection of XML answers to use as a starting point for the work session. /// The initial interview (if any) will be pre-populated with these answers, and the subsequent generation /// of documents will have access to these answers as well.</param> /// <param name="defaultInterviewSettings">The default interview settings to be used throughout the session</param> public WorkSession(IServices service, Template template, TextReader answers, InterviewSettings defaultInterviewSettings) { _service = service; AnswerCollection = new AnswerCollection(); if (answers != null) { AnswerCollection.ReadXml(answers); } DefaultAssemblySettings = new AssembleDocumentSettings(); if (defaultInterviewSettings != null) { DefaultInterviewSettings = defaultInterviewSettings; } else { DefaultInterviewSettings = new InterviewSettings(); } // add the work items _workItems = new List <WorkItem>(); if (template.HasInterview) { _workItems.Add(new InterviewWorkItem(template)); } if (template.GeneratesDocument) { _workItems.Add(new DocumentWorkItem(template)); } }
/// <summary> /// AssembleDocuments causes all contiguous pending document work items (from CurrentWorkItem onwards) /// to be assembled, and returns the assembled documents. /// </summary> /// <param name="preAssembleDocument">This delegate will be called immediately before each document is assembled.</param> /// <param name="postAssembleDocument">This delegate will be called immediately following assembly of each document.</param> /// <param name="userState">This object will be passed to the above delegates.</param> /// <include file="../Shared/Help.xml" path="Help/string/param[@name='logRef']"/> /// <returns>An array of Document, one item for each document that was assembled. Note that these items /// are of type Document, not AssemblyResult (see below).</returns> /// <remarks> /// <para>If AssembleDocuments is called when the current work item is not a document (i.e. when there are /// currently no documents to assemble), it will return an empty array of results without performing any work.</para> /// TODO: include a table that shows the relationship between members of Document, AssemblyResult, WorkSession and DocumentWorkItem. /// </remarks> public Document[] AssembleDocuments(PreAssembleDocumentDelegate preAssembleDocument, PostAssembleDocumentDelegate postAssembleDocument, object userState, string logRef) { var result = new List <Document>(); // skip past completed work items to get the current workItem WorkItem workItem = null; int itemIndex = 0; for (; itemIndex < _workItems.Count; itemIndex++) { workItem = _workItems[itemIndex]; if (!workItem.IsCompleted) { break; } workItem = null; } // while the current workItem != null && is a document (i.e. is not an interview) while (workItem != null && workItem is DocumentWorkItem) { var docWorkItem = workItem as DocumentWorkItem; // make a copy of the default assembly settings and pass it to the BeforeAssembleDocumentDelegate (if provided) AssembleDocumentSettings asmOpts = new AssembleDocumentSettings(DefaultAssemblySettings); asmOpts.Format = workItem.Template.NativeDocumentType; // if this is not the last work item in the queue, force retention of transient answers asmOpts.RetainTransientAnswers |= (workItem != _workItems[_workItems.Count - 1]); if (preAssembleDocument != null) { preAssembleDocument(docWorkItem.Template, AnswerCollection, asmOpts, userState); } // assemble the item using (var asmResult = _service.AssembleDocument(docWorkItem.Template, new StringReader(AnswerCollection.XmlAnswers), asmOpts, logRef)) { if (postAssembleDocument != null) { postAssembleDocument(docWorkItem.Template, asmResult, userState); } // replace the session answers with the post-assembly answers AnswerCollection.ReadXml(asmResult.Answers); // add pendingAssemblies to the queue as necessary InsertNewWorkItems(asmResult.PendingAssemblies, itemIndex); // store UnansweredVariables in the DocumentWorkItem docWorkItem.UnansweredVariables = asmResult.UnansweredVariables; // add an appropriate Document to a list being compiled for the return value of this method result.Add(asmResult.ExtractDocument()); } // mark the current workitem as complete docWorkItem.IsCompleted = true; // advance to the next workitem workItem = (++itemIndex >= _workItems.Count) ? null : _workItems[itemIndex]; } return(result.ToArray()); }
/// <summary> /// Called by the host application when answers have been posted back from a browser interview. /// </summary> /// <param name="interviewAnswers">The answers that were posted back from the interview.</param> public void FinishInterview(TextReader interviewAnswers) { // overlay interviewAnswers over the session answer set, AnswerCollection.OverlayXml(interviewAnswers); // skip past completed work items to get the current workItem WorkItem workItem = null; int itemIndex = 0; for (; itemIndex < _workItems.Count; itemIndex++) { workItem = _workItems[itemIndex]; if (!workItem.IsCompleted) { break; } workItem = null; } if (workItem != null && workItem is InterviewWorkItem) { // if the current template is an interview template if (workItem.Template.TemplateType == TemplateType.InterviewOnly) { // "assemble" it... AssembleDocumentSettings asmOpts = new AssembleDocumentSettings(DefaultAssemblySettings); asmOpts.Format = DocumentType.Native; // if this is not the last work item in the queue, force retention of transient answers asmOpts.RetainTransientAnswers |= (itemIndex < _workItems.Count - 1); // assemble the item using (var asmResult = _service.AssembleDocument(workItem.Template, new StringReader(AnswerCollection.XmlAnswers), asmOpts, "")) { // replace the session answers with the post-assembly answers AnswerCollection.ReadXml(asmResult.Answers); // add pendingAssemblies to the queue as necessary InsertNewWorkItems(asmResult.PendingAssemblies, itemIndex); } } // mark this interview workitem as complete. (This will cause the WorkSession to advance to the next workItem.) CurrentWorkItem.IsCompleted = true; } }
/// <summary> /// Assemble a document from the given template, answers and settings. /// </summary> /// <param name="template">An instance of the Template class.</param> /// <param name="answers">Either an XML answer string, or a string containing encoded /// interview answers as posted from a HotDocs browser interview.</param> /// <param name="settings">An instance of the AssembleDocumentResult class.</param> /// <include file="../Shared/Help.xml" path="Help/string/param[@name='logRef']"/> /// <returns>An AssemblyResult object containing all the files and data resulting from the request.</returns> public AssembleDocumentResult AssembleDocument(Template template, TextReader answers, AssembleDocumentSettings settings, string logRef) { // Validate input parameters, creating defaults as appropriate. string logStr = logRef == null ? string.Empty : logRef; if (template == null) throw new ArgumentNullException("template", string.Format(@"Local.Services.AssembleDocument: the ""template"" parameter passed in was null, logRef: {0}", logStr)); if (settings == null) settings = new AssembleDocumentSettings(); HotDocs.Server.AnswerCollection ansColl = new HotDocs.Server.AnswerCollection(); ansColl.OverlayXMLAnswers(answers == null ? "" : answers.ReadToEnd()); HotDocs.Server.OutputOptions outputOptions = ConvertOutputOptions(settings.OutputOptions); string docPath = CreateTempDocDirAndPath(template, settings.Format); _app.AssembleDocument( template.GetFullPath(),//Template path settings.UseMarkupSyntax ? hdsi.HDAssemblyOptions.asmOptMarkupView : hdsi.HDAssemblyOptions.asmOptNone, ansColl, docPath, outputOptions); //Prepare the post-assembly answer set (dropping transient ("don't save") answers when appropriate) HotDocs.Sdk.AnswerCollection resultAnsColl = new AnswerCollection(); resultAnsColl.ReadXml(new StringReader(ansColl.XmlAnswers)); string resultAnsXml = resultAnsColl.GetXMLString(false, settings.RetainTransientAnswers || _app.PendingAssemblyCmdLineStrings.Count > 0); //Build the list of pending assemblies. List<Template> pendingAssemblies = new List<Template>(); for (int i = 0; i < _app.PendingAssemblyCmdLineStrings.Count; i++) { string cmdLine = _app.PendingAssemblyCmdLineStrings[i]; string path, switches; Util.ParseHdAsmCmdLine(cmdLine, out path, out switches); pendingAssemblies.Add(new Template(Path.GetFileName(path), template.Location.Duplicate(), switches)); } //Prepare the document stream and image information for the browser. DocumentType docType = settings.Format; List<NamedStream> supportingFiles = new List<NamedStream>(); MemoryStream docStream; if (docType == DocumentType.Native) { docType = Document.GetDocumentType(docPath); docStream = LoadFileIntoMemStream(docPath); } else if (docType == DocumentType.HTMLwDataURIs) { //If the consumer requested both HTML and HTMLwDataURIs, they'll only get the latter. string content = Util.EmbedImagesInURIs(docPath); docStream = new MemoryStream(Encoding.UTF8.GetBytes(content)); } else if (docType == DocumentType.MHTML) { string content = Util.HtmlToMultiPartMime(docPath); docStream = new MemoryStream(Encoding.UTF8.GetBytes(content)); } else if (docType == DocumentType.HTML) { string targetFilenameNoExtention = Path.GetFileNameWithoutExtension(docPath); foreach (string img in Directory.EnumerateFiles(Path.GetDirectoryName(docPath))) { string ext = Path.GetExtension(img).ToLower(); if (Path.GetFileName(img).StartsWith(targetFilenameNoExtention) && (ext == ".jpg" || ext == ".jpeg" || ext == ".gif" || ext == ".png" || ext == ".bmp")) supportingFiles.Add(LoadFileIntoNamedStream(img)); } docStream = LoadFileIntoMemStream(docPath); } else { docStream = LoadFileIntoMemStream(docPath); } //Now that we've loaded all of the assembly results into memory, remove the assembly files. FreeTempDocDir(docPath); //Return the results. Document document = new Document(template, docStream, docType, supportingFiles.ToArray(), _app.UnansweredVariablesList.ToArray()); AssembleDocumentResult result = new AssembleDocumentResult(document, resultAnsXml, pendingAssemblies.ToArray(), _app.UnansweredVariablesList.ToArray()); return result; }
public void ReadXml() { AnswerCollection anss = new AnswerCollection(); // this test checks some answer XML generated by browser interviews. It was problematic originally because of its empty <RptValue> elements // (expressed as paired open/close elements with nothing between them, rather than single elements with open/close combined in the same element) anss.ReadXml(@"<?xml version=""1.0"" standalone=""yes""?> <AnswerSet title="""" version=""1.1"" useMangledNames=""false""> <Answer name=""Editor Full Name""> <TextValue unans=""true"" /> </Answer> <Answer name=""Author Full Name""> <RptValue> <RptValue> <TextValue>A</TextValue> <TextValue unans=""true"" /> </RptValue> <RptValue></RptValue> </RptValue> </Answer> <Answer name=""Book Title""> <RptValue> <RptValue> <RptValue> <TextValue>A</TextValue> <TextValue unans=""true"" /> </RptValue> <RptValue></RptValue> </RptValue> <RptValue></RptValue> </RptValue> </Answer> <Answer name=""Date Completed""> <DateValue unans=""true"" /> </Answer> </AnswerSet> "); Assert.IsTrue(anss.AnswerCount == 4); Answer ans; Assert.IsFalse(anss.TryGetAnswer("Editor Full Name", Sdk.ValueType.Number, out ans)); Assert.IsTrue(anss.TryGetAnswer("Editor Full Name", Sdk.ValueType.Text, out ans)); Assert.IsFalse(ans.IsRepeated); Assert.IsTrue(ans.Save); Assert.IsTrue(ans.UserExtendible); Assert.IsTrue(ans.Type == Sdk.ValueType.Text); Assert.IsFalse(ans.GetAnswered()); Assert.IsFalse(ans.GetValue <TextValue>().IsAnswered); Assert.IsTrue(ans.GetValue <TextValue>().Type == Sdk.ValueType.Text); Assert.IsTrue(ans.GetValue <TextValue>().UserModifiable); Assert.IsFalse(anss.TryGetAnswer("author full name", Sdk.ValueType.Text, out ans)); Assert.IsTrue(anss.TryGetAnswer("Author Full Name", Sdk.ValueType.Text, out ans)); Assert.IsTrue(ans.IsRepeated); Assert.IsTrue(ans.GetChildCount() == 1); Assert.IsTrue(ans.GetChildCount(0) == 1); Assert.IsTrue(ans.GetChildCount(1) == 0); Assert.IsTrue(ans.GetValue <TextValue>(0, 0).Value == "A"); Assert.IsFalse(ans.GetValue <TextValue>(0, 1).IsAnswered); Assert.IsFalse(ans.GetValue <TextValue>(1).IsAnswered); Assert.IsFalse(ans.GetValue <TextValue>(1, 0).IsAnswered); // unusual HotDocs indexing rules Assert.IsTrue(ans.GetValue <TextValue>().IsAnswered); Assert.IsTrue(ans.GetValue <TextValue>().Value == "A"); Assert.IsTrue(ans.GetValue <TextValue>(0).IsAnswered); Assert.IsTrue(ans.GetValue <TextValue>(0).Value == "A"); Assert.IsTrue(ans.GetValue <TextValue>(0, 0, 0).IsAnswered); Assert.IsTrue(ans.GetValue <TextValue>(0, 0, 0).Value == "A"); Assert.IsFalse(anss.TryGetAnswer("does not exist", Sdk.ValueType.Text, out ans)); }
public void ReadXml() { AnswerCollection anss = new AnswerCollection(); // this test checks some answer XML generated by browser interviews. It was problematic originally because of its empty <RptValue> elements // (expressed as paired open/close elements with nothing between them, rather than single elements with open/close combined in the same element) anss.ReadXml(@"<?xml version=""1.0"" standalone=""yes""?> <AnswerSet title="""" version=""1.1"" useMangledNames=""false""> <Answer name=""Editor Full Name""> <TextValue unans=""true"" /> </Answer> <Answer name=""Author Full Name""> <RptValue> <RptValue> <TextValue>A</TextValue> <TextValue unans=""true"" /> </RptValue> <RptValue></RptValue> </RptValue> </Answer> <Answer name=""Book Title""> <RptValue> <RptValue> <RptValue> <TextValue>A</TextValue> <TextValue unans=""true"" /> </RptValue> <RptValue></RptValue> </RptValue> <RptValue></RptValue> </RptValue> </Answer> <Answer name=""Date Completed""> <DateValue unans=""true"" /> </Answer> </AnswerSet> "); Assert.IsTrue(anss.AnswerCount == 4); Answer ans; Assert.IsTrue(anss.TryGetAnswer("Editor Full Name", out ans)); Assert.IsFalse(ans.IsRepeated); Assert.IsTrue(ans.Save); Assert.IsTrue(ans.UserExtendible); Assert.IsTrue(ans.Type == Sdk.ValueType.Text); Assert.IsFalse(ans.GetAnswered()); Assert.IsFalse(ans.GetValue<TextValue>().IsAnswered); Assert.IsTrue(ans.GetValue<TextValue>().Type == Sdk.ValueType.Text); Assert.IsTrue(ans.GetValue<TextValue>().UserModifiable); Assert.IsFalse(anss.TryGetAnswer("author full name", out ans)); Assert.IsTrue(anss.TryGetAnswer("Author Full Name", out ans)); Assert.IsTrue(ans.IsRepeated); Assert.IsTrue(ans.GetChildCount() == 1); Assert.IsTrue(ans.GetChildCount(0) == 1); Assert.IsTrue(ans.GetChildCount(1) == 0); Assert.IsTrue(ans.GetValue<TextValue>(0, 0).Value == "A"); Assert.IsFalse(ans.GetValue<TextValue>(0, 1).IsAnswered); Assert.IsFalse(ans.GetValue<TextValue>(1).IsAnswered); Assert.IsFalse(ans.GetValue<TextValue>(1, 0).IsAnswered); // unusual HotDocs indexing rules Assert.IsTrue(ans.GetValue<TextValue>().IsAnswered); Assert.IsTrue(ans.GetValue<TextValue>().Value == "A"); Assert.IsTrue(ans.GetValue<TextValue>(0).IsAnswered); Assert.IsTrue(ans.GetValue<TextValue>(0).Value == "A"); Assert.IsTrue(ans.GetValue<TextValue>(0, 0, 0).IsAnswered); Assert.IsTrue(ans.GetValue<TextValue>(0, 0, 0).Value == "A"); Assert.IsFalse(anss.TryGetAnswer("does not exist", out ans)); }
private static void LoadAnswerFileDataSource(AnswerFileDataSource answerFileDataSource) { Debug.Assert(s_readerWriterLock.IsWriteLockHeld); Stream answerFileStream = null; try { answerFileStream = new FileStream(answerFileDataSource.AnswerFilePath, FileMode.Open, FileAccess.Read); AnswerCollection answerSet = new AnswerCollection(); answerSet.ReadXml(answerFileStream); ResourceSet resourceSet = s_metadata.ResourceSets.Single(rs => rs.Name == answerFileDataSource.ResourceId); ResourceType resourceType = resourceSet.ResourceType; List <Answer> answers = new List <Answer>(resourceType.Properties.Count); int repeatCount = 0; Answer answer; foreach (var property in resourceType.Properties) { if (((property.Kind & ResourcePropertyKind.Key) != ResourcePropertyKind.Key) && answerSet.TryGetAnswer(answerFileDataSource.PropertyNameToSourceNameMap[property.Name], out answer)) { // Sanity check to be sure the property type the metadata is expecting is the same as the values in the answer file. Type type; switch (answer.Type) { case ValueType.Text: case ValueType.MultipleChoice: type = typeof(string); break; case ValueType.Number: type = typeof(double?); break; case ValueType.Date: type = typeof(DateTime?); break; case ValueType.TrueFalse: type = typeof(bool?); break; default: throw new Exception(string.Format("The value type '{0}' is not supported.", answer.Type.ToString())); } if (property.ResourceType.InstanceType != type) { throw new Exception(string.Format("The type of the metadata property '{0}' does not match the type of the " + "corresponding answer '{1}'.", answerFileDataSource.PropertyNameToSourceNameMap[property.Name], answer.Name)); } repeatCount = Math.Max(repeatCount, answer.GetChildCount()); answers.Add(answer); } else { answers.Add(null); } } // Populate the data source with data. IList <DSPResource> resourceList = s_context.GetResourceSetEntities(resourceSet.Name); resourceList.Clear(); for (int repeatIndex = 0; repeatIndex < repeatCount; repeatIndex++) { var resource = new DSPResource(resourceSet.ResourceType, s_readerWriterLock); for (int propertyIndex = 0; propertyIndex < resourceType.Properties.Count; propertyIndex++) { ResourceProperty property = resourceType.Properties[propertyIndex]; object value; if ((property.Kind & ResourcePropertyKind.Key) == ResourcePropertyKind.Key) { value = repeatIndex + 1; } else { IValue iValue = null; answer = answers[propertyIndex]; if ((answer != null) && (repeatIndex <= answer.GetChildCount())) { iValue = answer.GetValue(repeatIndex); } if (property.ResourceType.InstanceType == typeof(string)) { value = ((iValue != null) && iValue.IsAnswered) ? iValue.ToString(null) : string.Empty; } else if (property.ResourceType.InstanceType == typeof(double?)) { if ((iValue != null) && iValue.IsAnswered) { value = iValue.ToDouble(null); } else { value = null; } } else if (property.ResourceType.InstanceType == typeof(DateTime?)) { if ((iValue != null) && iValue.IsAnswered) { value = iValue.ToDateTime(null); } else { value = null; } } else { Debug.Assert(property.ResourceType.InstanceType == typeof(bool?)); if ((iValue != null) && iValue.IsAnswered) { value = iValue.ToBoolean(null); } else { value = null; } } } resource.SetValue(property.Name, value); } resourceList.Add(resource); } } catch (Exception e) { throw new DataServiceException(string.Format("Failed to read the answers for the data source key '{0}' from the answer file '{1}'.", answerFileDataSource.DataSourceId, answerFileDataSource.DataSourceName), e); } finally { if (answerFileStream != null) { answerFileStream.Close(); } } }
/// <summary> /// Assemble a document from the given template, answers and settings. /// </summary> /// <param name="template">An instance of the Template class.</param> /// <param name="answers">Either an XML answer string, or a string containing encoded /// interview answers as posted from a HotDocs browser interview.</param> /// <param name="settings">An instance of the AssembleDocumentResult class.</param> /// <include file="../Shared/Help.xml" path="Help/string/param[@name='logRef']"/> /// <returns>An AssemblyResult object containing all the files and data resulting from the request.</returns> public AssembleDocumentResult AssembleDocument(Template template, TextReader answers, AssembleDocumentSettings settings, string logRef) { // Validate input parameters, creating defaults as appropriate. string logStr = logRef == null ? string.Empty : logRef; if (template == null) { throw new ArgumentNullException("template", string.Format(@"Local.Services.AssembleDocument: the ""template"" parameter passed in was null, logRef: {0}", logStr)); } if (settings == null) { settings = new AssembleDocumentSettings(); } HotDocs.Server.AnswerCollection ansColl = new HotDocs.Server.AnswerCollection(); ansColl.OverlayXMLAnswers(answers == null ? "" : answers.ReadToEnd()); HotDocs.Server.OutputOptions outputOptions = ConvertOutputOptions(settings.OutputOptions); string docPath = CreateTempDocDirAndPath(template, settings.Format); _app.AssembleDocument( template.GetFullPath(), //Template path hdsi.HDAssemblyOptions.asmOptMarkupView, ansColl, docPath, outputOptions); //Prepare the post-assembly answer set. HotDocs.Sdk.AnswerCollection resultAnsColl = new AnswerCollection(); resultAnsColl.ReadXml(new StringReader(ansColl.XmlAnswers)); if (!settings.RetainTransientAnswers && _app.PendingAssemblyCmdLineStrings.Count == 0) { // Create a list of all "transient" answers to remove. IEnumerable <Answer> transAnswers = from a in resultAnsColl where !a.Save select a; List <string> answersToRemove = new List <string>(); foreach (Answer ans in transAnswers) { answersToRemove.Add(ans.Name); } // Iterate through the list of answers to remove and remove them from the result answer collection. // This is done as a separate step so we are not modifying the collecion over which we are iterating. foreach (string s in answersToRemove) { resultAnsColl.RemoveAnswer(s); } } //Build the list of pending assemblies. List <Template> pendingAssemblies = new List <Template>(); for (int i = 0; i < _app.PendingAssemblyCmdLineStrings.Count; i++) { string cmdLine = _app.PendingAssemblyCmdLineStrings[i]; string path, switches; Util.ParseHdAsmCmdLine(cmdLine, out path, out switches); pendingAssemblies.Add(new Template(Path.GetFileName(path), template.Location.Duplicate(), switches)); } //Prepare the document stream and image information for the browser. DocumentType docType = settings.Format; List <NamedStream> supportingFiles = new List <NamedStream>(); MemoryStream docStream; if (docType == DocumentType.Native) { docType = Document.GetDocumentType(docPath); docStream = LoadFileIntoMemStream(docPath); } else if (docType == DocumentType.HTMLwDataURIs) { //If the consumer requested both HTML and HTMLwDataURIs, they'll only get the latter. string content = Util.EmbedImagesInURIs(docPath); docStream = new MemoryStream(Encoding.UTF8.GetBytes(content)); } else if (docType == DocumentType.MHTML) { string content = Util.HtmlToMultiPartMime(docPath); docStream = new MemoryStream(Encoding.UTF8.GetBytes(content)); } else if (docType == DocumentType.HTML) { string targetFilenameNoExtention = Path.GetFileNameWithoutExtension(docPath); foreach (string img in Directory.EnumerateFiles(Path.GetDirectoryName(docPath))) { string ext = Path.GetExtension(img).ToLower(); if (Path.GetFileName(img).StartsWith(targetFilenameNoExtention) && (ext == ".jpg" || ext == ".jpeg" || ext == ".gif" || ext == ".png" || ext == ".bmp")) { supportingFiles.Add(LoadFileIntoNamedStream(img)); } } docStream = LoadFileIntoMemStream(docPath); } else { docStream = LoadFileIntoMemStream(docPath); } //Now that we've loaded all of the assembly results into memory, remove the assembly files. FreeTempDocDir(docPath); //Return the results. Document document = new Document(template, docStream, docType, supportingFiles.ToArray(), _app.UnansweredVariablesList.ToArray()); AssembleDocumentResult result = new AssembleDocumentResult(document, resultAnsColl.XmlAnswers, pendingAssemblies.ToArray(), _app.UnansweredVariablesList.ToArray()); return(result); }
/// <summary> /// Creates a WorkSession object that a host application can use to step through the process of presenting /// all the interviews and assembling all the documents that may result from the given template. /// </summary> /// <param name="service">An object implementing the IServices interface, encapsulating the instance of /// HotDocs Server with which the host app is communicating.</param> /// <param name="template">The template upon which this WorkSession is based. The initial interview and/or /// document work items in the WorkSession will be based on this template (including its Switches property).</param> /// <param name="answers">A collection of XML answers to use as a starting point for the work session. /// The initial interview (if any) will be pre-populated with these answers, and the subsequent generation /// of documents will have access to these answers as well.</param> public WorkSession(IServices service, Template template, TextReader answers) { _service = service; AnswerCollection = new AnswerCollection(); if (answers != null) AnswerCollection.ReadXml(answers); DefaultAssemblySettings = new AssembleDocumentSettings(); DefaultInterviewSettings = new InterviewSettings(); // add the work items _workItems = new List<WorkItem>(); if (template.HasInterview) _workItems.Add(new InterviewWorkItem(template)); if (template.GeneratesDocument) _workItems.Add(new DocumentWorkItem(template)); }
public void ReadXml() { AnswerCollection anss = new AnswerCollection(); // this test checks some answer XML generated by browser interviews. It was problematic originally because of its empty <RptValue> elements // (expressed as paired open/close elements with nothing between them, rather than single elements with open/close combined in the same element) // NOTE: added a lot of oddly formed and malformed answers to this answer collection, for purposes of // trying to ensure that we read answer XML in a more robust manner. Some 3rd parties generate XML // that is schema valid, but is otherwise (by HotDocs standards) quite odd. For example, answers marked // "unanswered" but which have answer data present, or which use collapsed XML elements in ways // HotDocs typically does not. anss.ReadXml(@"<?xml version=""1.0"" standalone=""yes""?> <AnswerSet title="""" version=""1.1"" useMangledNames=""false""> <Answer name=""Editor Full Name""> <TextValue unans=""true"" /> </Answer> <Answer name=""Text000""> <TextValue/> </Answer> <Answer name=""Text010""> <TextValue></TextValue> </Answer> <Answer name=""Text020""> <TextValue>Test Answer</TextValue> </Answer> <Answer name=""Text030""> <TextValue> Another Test Answer </TextValue> </Answer> <Answer name=""Text040""> <TextValue unans=""true""></TextValue> </Answer> <Answer name=""Text050""> <TextValue unans=""true"">An Invalid Test Answer</TextValue> </Answer> <Answer name=""Number000""> <NumValue/> </Answer> <Answer name=""Number010""> <NumValue unans=""true"" /> </Answer> <Answer name=""Number020""> <NumValue unans=""true""></NumValue> </Answer> <Answer name=""Number030""> <NumValue>5.0000000</NumValue> </Answer> <Answer name=""Number040""> <NumValue>123</NumValue> </Answer> <Answer name=""Number050""> <NumValue unans=""true"">234 or anything else</NumValue> </Answer> <Answer name=""Date000""> <DateValue/> </Answer> <Answer name=""Date010""> <DateValue></DateValue> </Answer> <Answer name=""Date020""> <DateValue unans=""true"" /> </Answer> <Answer name=""Date030""> <DateValue unans=""true"" ></DateValue> </Answer> <Answer name=""Date Completed""> <DateValue>31-10-2015</DateValue> </Answer> <Answer name=""TF000""> <TFValue/> </Answer> <Answer name=""TF010""> <TFValue></TFValue> </Answer> <Answer name=""TF020""> <TFValue unans=""true"" /> </Answer> <Answer name=""TF030""> <TFValue unans=""true""></TFValue> </Answer> <Answer name=""TF040""> <TFValue>true</TFValue> </Answer> <Answer name=""TF050""> <TFValue unans=""false"">False</TFValue> </Answer> <Answer name=""TF060""> <TFValue unans=""true"">Ignored</TFValue> </Answer> <Answer name=""MCVar000""> <MCValue/> </Answer> <Answer name=""MCVar010""> <MCValue unans=""true"" /> </Answer> <Answer name=""MCVar020""> <MCValue></MCValue> </Answer> <Answer name=""MCVar030""> <MCValue unans=""true""></MCValue> </Answer> <Answer name=""MCVar031""> <MCValue> <SelValue/> </MCValue> </Answer> <Answer name=""MCVar032""> <MCValue unans=""true""> <SelValue unans=""true""/> </MCValue> </Answer> <Answer name=""MCVar033""> <MCValue unans=""true""> <SelValue></SelValue> </MCValue> </Answer> <Answer name=""MCVar034""> <MCValue unans=""true""> <SelValue unans=""true""></SelValue> </MCValue> </Answer> <Answer name=""MCVar035""> <MCValue unans=""true""> <SelValue>sel1</SelValue> </MCValue> </Answer> <Answer name=""MCVar040""> <MCValue> <SelValue/> </MCValue> </Answer> <Answer name=""MCVar050""> <MCValue> <SelValue unans=""true""/> </MCValue> </Answer> <Answer name=""MCVar060""> <MCValue> <SelValue></SelValue> </MCValue> </Answer> <Answer name=""MCVar070""> <MCValue> <SelValue unans=""true""></SelValue> </MCValue> </Answer> <Answer name=""MCVar080""> <MCValue> <SelValue>sel1</SelValue> <SelValue>sel2</SelValue> </MCValue> </Answer> <Answer name=""MCVar090""> <MCValue> <SelValue>sel1</SelValue> <SelValue></SelValue> </MCValue> </Answer> <Answer name=""MCVar100""> <MCValue> <SelValue>sel1</SelValue> <SelValue/> </MCValue> </Answer> <Answer name=""MCVar110""> <MCValue> <SelValue>sel1</SelValue> <SelValue unans=""true""></SelValue> </MCValue> </Answer> <Answer name=""MCVar120""> <MCValue> <SelValue>sel1</SelValue> <SelValue unans=""true""/> </MCValue> </Answer> <Answer name=""MCVar130""> <MCValue> <SelValue>sel1</SelValue> <SelValue></SelValue> <SelValue>sel3</SelValue> </MCValue> </Answer> <Answer name=""MCVar140""> <MCValue> <SelValue>sel1</SelValue> <SelValue/> <SelValue>sel3</SelValue> </MCValue> </Answer> <Answer name=""MCVar150""> <MCValue> <SelValue>sel1</SelValue> <SelValue unans=""true""></SelValue> <SelValue>sel3</SelValue> </MCValue> </Answer> <Answer name=""MCVar160""> <MCValue> <SelValue>sel1</SelValue> <SelValue unans=""true""/> <SelValue>sel3</SelValue> </MCValue> </Answer> <Answer name=""MCVar170""> <MCValue> <SelValue>sel1</SelValue> <SelValue unans=""true"">sel2</SelValue> </MCValue> </Answer> <Answer name=""MCVar180""> <MCValue> <SelValue unans=""true"">sel1</SelValue> <SelValue unans=""true"">sel2</SelValue> </MCValue> </Answer> <Answer name=""MCVar190""> <MCValue> <SelValue unans=""true"">sel1</SelValue> <SelValue>sel2</SelValue> </MCValue> </Answer> <Answer name=""Author Full Name""> <RptValue> <RptValue> <TextValue>A</TextValue> <TextValue unans=""true"" /> </RptValue> <RptValue></RptValue> </RptValue> </Answer> <Answer name=""Book Title""> <RptValue> <RptValue> <RptValue> <TextValue>A</TextValue> <TextValue unans=""true"" /> </RptValue> <RptValue></RptValue> </RptValue> <RptValue></RptValue> </RptValue> </Answer> </AnswerSet> "); Assert.IsTrue(anss.AnswerCount == 52); Answer ans; // ensure that lookup of incorrect typed answer fails Assert.IsFalse(anss.TryGetAnswer("Editor Full Name", Sdk.ValueType.Number, out ans)); // test various attributes of an answer Assert.IsTrue(anss.TryGetAnswer("Editor Full Name", Sdk.ValueType.Text, out ans)); Assert.IsFalse(ans.IsRepeated); Assert.IsTrue(ans.Save); Assert.IsTrue(ans.UserExtendible); Assert.IsTrue(ans.Type == Sdk.ValueType.Text); Assert.IsFalse(ans.GetAnswered()); Assert.IsFalse(ans.GetValue <TextValue>().IsAnswered); Assert.IsTrue(ans.GetValue <TextValue>().Type == Sdk.ValueType.Text); Assert.IsTrue(ans.GetValue <TextValue>().UserModifiable); // ensure lookup of name with incorrect casing fails Assert.IsFalse(anss.TryGetAnswer("author full name", Sdk.ValueType.Text, out ans)); // check some repeated answer indexing rules Assert.IsTrue(anss.TryGetAnswer("Author Full Name", Sdk.ValueType.Text, out ans)); Assert.IsTrue(ans.IsRepeated); Assert.IsTrue(ans.GetChildCount() == 1); Assert.IsTrue(ans.GetChildCount(0) == 1); Assert.IsTrue(ans.GetChildCount(1) == 0); Assert.IsTrue(ans.GetValue <TextValue>(0, 0).Value == "A"); Assert.IsFalse(ans.GetValue <TextValue>(0, 1).IsAnswered); Assert.IsFalse(ans.GetValue <TextValue>(1).IsAnswered); Assert.IsFalse(ans.GetValue <TextValue>(1, 0).IsAnswered); // unusual HotDocs indexing rules Assert.IsTrue(ans.GetValue <TextValue>().IsAnswered); Assert.IsTrue(ans.GetValue <TextValue>().Value == "A"); Assert.IsTrue(ans.GetValue <TextValue>(0).IsAnswered); Assert.IsTrue(ans.GetValue <TextValue>(0).Value == "A"); Assert.IsTrue(ans.GetValue <TextValue>(0, 0, 0).IsAnswered); Assert.IsTrue(ans.GetValue <TextValue>(0, 0, 0).Value == "A"); // ensure that lookup of non-existing answer fails Assert.IsFalse(anss.TryGetAnswer("does not exist", Sdk.ValueType.Text, out ans)); }
public void ReadXml() { AnswerCollection anss = new AnswerCollection(); // this test checks some answer XML generated by browser interviews. It was problematic originally because of its empty <RptValue> elements // (expressed as paired open/close elements with nothing between them, rather than single elements with open/close combined in the same element) // NOTE: added a lot of oddly formed and malformed answers to this answer collection, for purposes of // trying to ensure that we read answer XML in a more robust manner. Some 3rd parties generate XML // that is schema valid, but is otherwise (by HotDocs standards) quite odd. For example, answers marked // "unanswered" but which have answer data present, or which use collapsed XML elements in ways // HotDocs typically does not. anss.ReadXml(@"<?xml version=""1.0"" standalone=""yes""?> <AnswerSet title="""" version=""1.1"" useMangledNames=""false""> <Answer name=""Editor Full Name""> <TextValue unans=""true"" /> </Answer> <Answer name=""Text000""> <TextValue/> </Answer> <Answer name=""Text010""> <TextValue></TextValue> </Answer> <Answer name=""Text020""> <TextValue>Test Answer</TextValue> </Answer> <Answer name=""Text030""> <TextValue> Another Test Answer </TextValue> </Answer> <Answer name=""Text040""> <TextValue unans=""true""></TextValue> </Answer> <Answer name=""Text050""> <TextValue unans=""true"">An Invalid Test Answer</TextValue> </Answer> <Answer name=""Number000""> <NumValue/> </Answer> <Answer name=""Number010""> <NumValue unans=""true"" /> </Answer> <Answer name=""Number020""> <NumValue unans=""true""></NumValue> </Answer> <Answer name=""Number030""> <NumValue>5.0000000</NumValue> </Answer> <Answer name=""Number040""> <NumValue>123</NumValue> </Answer> <Answer name=""Number050""> <NumValue unans=""true"">234 or anything else</NumValue> </Answer> <Answer name=""Date000""> <DateValue/> </Answer> <Answer name=""Date010""> <DateValue></DateValue> </Answer> <Answer name=""Date020""> <DateValue unans=""true"" /> </Answer> <Answer name=""Date030""> <DateValue unans=""true"" ></DateValue> </Answer> <Answer name=""Date Completed""> <DateValue>31-10-2015</DateValue> </Answer> <Answer name=""TF000""> <TFValue/> </Answer> <Answer name=""TF010""> <TFValue></TFValue> </Answer> <Answer name=""TF020""> <TFValue unans=""true"" /> </Answer> <Answer name=""TF030""> <TFValue unans=""true""></TFValue> </Answer> <Answer name=""TF040""> <TFValue>true</TFValue> </Answer> <Answer name=""TF050""> <TFValue unans=""false"">False</TFValue> </Answer> <Answer name=""TF060""> <TFValue unans=""true"">Ignored</TFValue> </Answer> <Answer name=""MCVar000""> <MCValue/> </Answer> <Answer name=""MCVar010""> <MCValue unans=""true"" /> </Answer> <Answer name=""MCVar020""> <MCValue></MCValue> </Answer> <Answer name=""MCVar030""> <MCValue unans=""true""></MCValue> </Answer> <Answer name=""MCVar031""> <MCValue> <SelValue/> </MCValue> </Answer> <Answer name=""MCVar032""> <MCValue unans=""true""> <SelValue unans=""true""/> </MCValue> </Answer> <Answer name=""MCVar033""> <MCValue unans=""true""> <SelValue></SelValue> </MCValue> </Answer> <Answer name=""MCVar034""> <MCValue unans=""true""> <SelValue unans=""true""></SelValue> </MCValue> </Answer> <Answer name=""MCVar035""> <MCValue unans=""true""> <SelValue>sel1</SelValue> </MCValue> </Answer> <Answer name=""MCVar040""> <MCValue> <SelValue/> </MCValue> </Answer> <Answer name=""MCVar050""> <MCValue> <SelValue unans=""true""/> </MCValue> </Answer> <Answer name=""MCVar060""> <MCValue> <SelValue></SelValue> </MCValue> </Answer> <Answer name=""MCVar070""> <MCValue> <SelValue unans=""true""></SelValue> </MCValue> </Answer> <Answer name=""MCVar080""> <MCValue> <SelValue>sel1</SelValue> <SelValue>sel2</SelValue> </MCValue> </Answer> <Answer name=""MCVar090""> <MCValue> <SelValue>sel1</SelValue> <SelValue></SelValue> </MCValue> </Answer> <Answer name=""MCVar100""> <MCValue> <SelValue>sel1</SelValue> <SelValue/> </MCValue> </Answer> <Answer name=""MCVar110""> <MCValue> <SelValue>sel1</SelValue> <SelValue unans=""true""></SelValue> </MCValue> </Answer> <Answer name=""MCVar120""> <MCValue> <SelValue>sel1</SelValue> <SelValue unans=""true""/> </MCValue> </Answer> <Answer name=""MCVar130""> <MCValue> <SelValue>sel1</SelValue> <SelValue></SelValue> <SelValue>sel3</SelValue> </MCValue> </Answer> <Answer name=""MCVar140""> <MCValue> <SelValue>sel1</SelValue> <SelValue/> <SelValue>sel3</SelValue> </MCValue> </Answer> <Answer name=""MCVar150""> <MCValue> <SelValue>sel1</SelValue> <SelValue unans=""true""></SelValue> <SelValue>sel3</SelValue> </MCValue> </Answer> <Answer name=""MCVar160""> <MCValue> <SelValue>sel1</SelValue> <SelValue unans=""true""/> <SelValue>sel3</SelValue> </MCValue> </Answer> <Answer name=""MCVar170""> <MCValue> <SelValue>sel1</SelValue> <SelValue unans=""true"">sel2</SelValue> </MCValue> </Answer> <Answer name=""MCVar180""> <MCValue> <SelValue unans=""true"">sel1</SelValue> <SelValue unans=""true"">sel2</SelValue> </MCValue> </Answer> <Answer name=""MCVar190""> <MCValue> <SelValue unans=""true"">sel1</SelValue> <SelValue>sel2</SelValue> </MCValue> </Answer> <Answer name=""Author Full Name""> <RptValue> <RptValue> <TextValue>A</TextValue> <TextValue unans=""true"" /> </RptValue> <RptValue></RptValue> </RptValue> </Answer> <Answer name=""Book Title""> <RptValue> <RptValue> <RptValue> <TextValue>A</TextValue> <TextValue unans=""true"" /> </RptValue> <RptValue></RptValue> </RptValue> <RptValue></RptValue> </RptValue> </Answer> </AnswerSet> "); Assert.IsTrue(anss.AnswerCount == 52); Answer ans; // ensure that lookup of incorrect typed answer fails Assert.IsFalse(anss.TryGetAnswer("Editor Full Name", Sdk.ValueType.Number, out ans)); // test various attributes of an answer Assert.IsTrue(anss.TryGetAnswer("Editor Full Name", Sdk.ValueType.Text, out ans)); Assert.IsFalse(ans.IsRepeated); Assert.IsTrue(ans.Save); Assert.IsTrue(ans.UserExtendible); Assert.IsTrue(ans.Type == Sdk.ValueType.Text); Assert.IsFalse(ans.GetAnswered()); Assert.IsFalse(ans.GetValue<TextValue>().IsAnswered); Assert.IsTrue(ans.GetValue<TextValue>().Type == Sdk.ValueType.Text); Assert.IsTrue(ans.GetValue<TextValue>().UserModifiable); // ensure lookup of name with incorrect casing fails Assert.IsFalse(anss.TryGetAnswer("author full name", Sdk.ValueType.Text, out ans)); // check some repeated answer indexing rules Assert.IsTrue(anss.TryGetAnswer("Author Full Name", Sdk.ValueType.Text, out ans)); Assert.IsTrue(ans.IsRepeated); Assert.IsTrue(ans.GetChildCount() == 1); Assert.IsTrue(ans.GetChildCount(0) == 1); Assert.IsTrue(ans.GetChildCount(1) == 0); Assert.IsTrue(ans.GetValue<TextValue>(0, 0).Value == "A"); Assert.IsFalse(ans.GetValue<TextValue>(0, 1).IsAnswered); Assert.IsFalse(ans.GetValue<TextValue>(1).IsAnswered); Assert.IsFalse(ans.GetValue<TextValue>(1, 0).IsAnswered); // unusual HotDocs indexing rules Assert.IsTrue(ans.GetValue<TextValue>().IsAnswered); Assert.IsTrue(ans.GetValue<TextValue>().Value == "A"); Assert.IsTrue(ans.GetValue<TextValue>(0).IsAnswered); Assert.IsTrue(ans.GetValue<TextValue>(0).Value == "A"); Assert.IsTrue(ans.GetValue<TextValue>(0, 0, 0).IsAnswered); Assert.IsTrue(ans.GetValue<TextValue>(0, 0, 0).Value == "A"); // ensure that lookup of non-existing answer fails Assert.IsFalse(anss.TryGetAnswer("does not exist", Sdk.ValueType.Text, out ans)); }
private static void LoadAnswerFileDataSource(AnswerFileDataSource answerFileDataSource) { Debug.Assert(s_readerWriterLock.IsWriteLockHeld); Stream answerFileStream = null; try { answerFileStream = new FileStream(answerFileDataSource.AnswerFilePath, FileMode.Open, FileAccess.Read); AnswerCollection answerSet = new AnswerCollection(); answerSet.ReadXml(answerFileStream); ResourceSet resourceSet = s_metadata.ResourceSets.Single(rs => rs.Name == answerFileDataSource.ResourceId); ResourceType resourceType = resourceSet.ResourceType; List <Answer> answers = new List <Answer>(resourceType.Properties.Count); int repeatCount = 0; Answer answer; foreach (var property in resourceType.Properties) { if ((property.Kind & ResourcePropertyKind.Key) != ResourcePropertyKind.Key) { ValueType valueType = ValueType.Unknown; // Infer from the CLR type what the ValueType is of the answer to be fetched. if (property.ResourceType.InstanceType == typeof(string)) { valueType = ValueType.Text; } else if (property.ResourceType.InstanceType == typeof(double?)) { valueType = ValueType.Number; } else if (property.ResourceType.InstanceType == typeof(DateTime?)) { valueType = ValueType.Date; } else if (property.ResourceType.InstanceType == typeof(bool?)) { valueType = ValueType.TrueFalse; } if (valueType == ValueType.Unknown) { throw new Exception(string.Format("The metadata property '{0}' with a CLR type of '{1}' does not correspond to a supported " + "HotDocs ValueType.", answerFileDataSource.PropertyNameToSourceNameMap[property.Name], property.ResourceType.InstanceType.FullName)); } if (answerSet.TryGetAnswer(answerFileDataSource.PropertyNameToSourceNameMap[property.Name], valueType, out answer) || ((valueType == ValueType.Text) && answerSet.TryGetAnswer(answerFileDataSource.PropertyNameToSourceNameMap[property.Name], ValueType.MultipleChoice, out answer))) { repeatCount = Math.Max(repeatCount, answer.GetChildCount()); answers.Add(answer); } else { answers.Add(null); } } } // Populate the data source with data. IList <DSPResource> resourceList = s_context.GetResourceSetEntities(resourceSet.Name); resourceList.Clear(); for (int repeatIndex = 0; repeatIndex < repeatCount; repeatIndex++) { var resource = new DSPResource(resourceSet.ResourceType, s_readerWriterLock); int answerIndex = 0; for (int propertyIndex = 0; propertyIndex < resourceType.Properties.Count; propertyIndex++) { ResourceProperty property = resourceType.Properties[propertyIndex]; object value; if ((property.Kind & ResourcePropertyKind.Key) == ResourcePropertyKind.Key) { value = repeatIndex + 1; } else { IValue iValue = null; answer = answers[answerIndex++]; if ((answer != null) && (repeatIndex <= answer.GetChildCount())) { iValue = answer.GetValue(repeatIndex); } if (property.ResourceType.InstanceType == typeof(string)) { value = ((iValue != null) && iValue.IsAnswered) ? iValue.ToString(null) : string.Empty; } else if (property.ResourceType.InstanceType == typeof(double?)) { if ((iValue != null) && iValue.IsAnswered) { value = iValue.ToDouble(null); } else { value = null; } } else if (property.ResourceType.InstanceType == typeof(DateTime?)) { if ((iValue != null) && iValue.IsAnswered) { value = iValue.ToDateTime(null); } else { value = null; } } else { Debug.Assert(property.ResourceType.InstanceType == typeof(bool?)); if ((iValue != null) && iValue.IsAnswered) { value = iValue.ToBoolean(null); } else { value = null; } } } resource.SetValue(property.Name, value); } resourceList.Add(resource); } } catch (Exception e) { throw new DataServiceException(string.Format("Failed to read the answers for the data source key '{0}' from the answer file '{1}'.", answerFileDataSource.DataSourceId, answerFileDataSource.DataSourceName), e); } finally { if (answerFileStream != null) { answerFileStream.Close(); } } }