public Question PopQuestion(IQuestionKey keyToUseForReference) { ResolveDeletionsAndAdditions(); PhraseCustomization questionToInsert; try { questionToInsert = AdditionsAndInsertions.First(); } catch (Exception e) { throw new InvalidOperationException("PopQuestion should only be called for a customization that is not tied to any other question and has " + "at least one insertion or addition.", e); } AdditionsAndInsertions.RemoveAt(0); var newQ = new Question(keyToUseForReference.ScriptureReference, keyToUseForReference.StartRef, keyToUseForReference.EndRef, questionToInsert.ModifiedPhrase ?? questionToInsert.OriginalPhrase, questionToInsert.Answer); SetExcludedAndModified(newQ); if (AllAnswers != null && AllAnswers.Any()) // Note: If there are any, there are at least 2 { newQ.Answers = AllAnswers.ToArray(); } return(newQ); }
public void Add(PhraseCustomization pc) { switch (pc.Type) { case PhraseCustomization.CustomizationType.AdditionAfter: case PhraseCustomization.CustomizationType.InsertionBefore: if (AdditionsAndInsertions.Any(a => a.ModifiedPhrase == pc.ModifiedPhrase || a.Type == pc.Type)) { m_isResolved = false; } AdditionsAndInsertions.Add(pc); break; case PhraseCustomization.CustomizationType.Deletion: Deletions.Add(pc); if (Deletions.Count > 1) { m_isResolved = false; } break; case PhraseCustomization.CustomizationType.Modification: if (Modification != null) { throw new InvalidOperationException("Only one modified version of a question/phrase is permitted. Question/phrase '" + pc.OriginalPhrase + "' has already been modified as '" + Modification.ModifiedPhrase + "'. Value of subsequent modification attempt was: '" + pc.ModifiedPhrase + "'."); } Modification = pc; break; } }
public bool IsInsertionAtOrBeforeReference(IQuestionKey keyToUseForReference, IEnumerable <Question> existingQuestionsInCategory, bool inclusive) { var addition = AdditionsAndInsertions.FirstOrDefault(); return(addition != null && addition.Key.IsAtOrBeforeReference(keyToUseForReference, inclusive) && !existingQuestionsInCategory.Any(q => q.Matches(addition.Key))); }
private void FinishResolvingIfNoMorePairsCanBeDeleted() { if (Deletions.Any() && AdditionsAndInsertions.Count > Deletions.Count) { return; } if (AdditionsAndInsertions.Count <= 1) { if (Deletions.Count > 1) { // This should probably be an exception, but maybe we can recover... Deletions.RemoveRange(1, Deletions.Count - 1); Debug.Fail($"There were more deletions than additions for {Deletions.Single().Key}."); } } else if (Deletions.Count <= 1) { // REVIEW: We're assuming that the first (remaining) addition is the "base" one (i.e., any other non-duplicates will // be hanging off of it as an insertion or addition). If this is not true, we'll need to look through the list to find // the first one whose OriginalPhrase is not the ModifiedPhrase of any other addition/insertion in the list. var baseAddition = AdditionsAndInsertions[0].ModifiedPhrase; var i = 0; while (i + 1 < AdditionsAndInsertions.Count) { // We assume that earlier ones in the list are older versions whose answers are less likely to be the most desirable // one, so we delete ealier ones first so that the last one survives (and its answer will be inserted first in the // list. Sadly, this is probably the best we can do. var iNewBase = AdditionsAndInsertions.FindIndex(i + 1, a => a.ModifiedPhrase == baseAddition); if (iNewBase < 0) { break; } RemoveAddition(i); i = iNewBase; } } else { return; } if (AllAnswers.Any()) { var bestAnswer = AdditionsAndInsertions.First().Answer; if (!String.IsNullOrWhiteSpace(bestAnswer) && !AllAnswers.Any(a => a.Contains(bestAnswer))) { AllAnswers.Insert(0, bestAnswer); } else if (AllAnswers.Count == 1) { AdditionsAndInsertions.First().Answer = AllAnswers[0]; AllAnswers = null; } } m_isResolved = true; }
private void RemoveAddition(int iAdditionToRemove) { var answer = AdditionsAndInsertions[iAdditionToRemove].Answer; AdditionsAndInsertions.RemoveAt(iAdditionToRemove); if (!String.IsNullOrWhiteSpace(answer) && !AllAnswers.Any(a => a.Contains(answer)) && !AdditionsAndInsertions.Where(b => b.Answer != null).Any(c => c.Answer.Contains(answer))) { AllAnswers.Add(answer); } }
private void ResolveDeletionsAndAdditions() { if (m_isResolved) { return; } AllAnswers = new List <string>(); FinishResolvingIfNoMorePairsCanBeDeleted(); int iDel = 0; // Pass 1: Exact match between deletion and addition on ModifiedPhrase while (!m_isResolved && iDel < Deletions.Count) { iDel = Deletions.Skip(iDel).IndexOf(d => !String.IsNullOrEmpty(d.ModifiedPhrase)); if (iDel < 0) { break; } // Prefer to delete additions that don't have answers int iAdditionToRemove = AdditionsAndInsertions.IndexOf(a => a.ModifiedPhrase == Deletions[iDel].ModifiedPhrase && String.IsNullOrEmpty(a.Answer)); if (iAdditionToRemove < 0) { iAdditionToRemove = AdditionsAndInsertions.IndexOf(a => a.ModifiedPhrase == Deletions[iDel].ModifiedPhrase); } if (iAdditionToRemove >= 0) { RemoveDeletionAndAdditionPair(iDel, iAdditionToRemove); } } iDel = 0; var iDelOrig = iDel; // Pass 2: Neither deletion nor addition have ModifiedPhrase set while (!m_isResolved && iDel < Deletions.Count) { iDel = Deletions.Skip(iDel).IndexOf(d => String.IsNullOrEmpty(d.ModifiedPhrase)); if (iDel < 0) { break; } // Prefer to delete additions that don't have answers int iAdditionToRemove = AdditionsAndInsertions.IndexOf(a => (String.IsNullOrEmpty(a.ModifiedPhrase) || a.ModifiedPhrase == a.OriginalPhrase) && String.IsNullOrEmpty(a.Answer)); if (iAdditionToRemove < 0) { iAdditionToRemove = AdditionsAndInsertions.IndexOf(a => String.IsNullOrEmpty(a.ModifiedPhrase) || a.ModifiedPhrase == a.OriginalPhrase); } if (iAdditionToRemove >= 0) { RemoveDeletionAndAdditionPair(iDel, iAdditionToRemove); } else if (iDel == iDelOrig) { break; } iDelOrig = iDel; } // Pass 3: Pair 'em up and blow 'em away while (!m_isResolved) { // Prefer to delete additions that don't have answers int iAdditionToRemove = AdditionsAndInsertions.IndexOf(a => String.IsNullOrEmpty(a.Answer)); if (iAdditionToRemove < 0) { iAdditionToRemove = 0; } RemoveDeletionAndAdditionPair(0, iAdditionToRemove); } }
public void ApplyToQuestion(Question question) { ResolveDeletionsAndAdditions(); SetExcludedAndModified(question); var duplicate = AdditionsAndInsertions.SingleOrDefault(q => q.OriginalPhrase == q.ModifiedPhrase && q.OriginalPhrase == question.PhraseInUse); if (duplicate != null) { var newAnswer = duplicate.Answer; if (newAnswer.Length == 0) { newAnswer = null; } // Adding an exactly identical question. if (question.IsExcluded) { // Okay, so this is a true replacement, but if the replacement doesn't have an answer, it probably doesn't make sense, so let's ignore it if (newAnswer == null) { question.IsExcluded = false; AdditionsAndInsertions.Clear(); } } else { if (newAnswer != null) { // We don't allow exact duplicate questions, so this can't be treated as a real addition. We're just changing the answer. if (question.Answers.Length == 0) { question.Answers = new[] { newAnswer } } ; else { question.Answers[0] = newAnswer; } } AdditionsAndInsertions.Remove(duplicate); } } // TODO: Support adding (not just replacing) answers? // TODO Support modifying (and replacing?) notes. var insertion = AdditionsAndInsertions.SingleOrDefault(a => a.Type == PhraseCustomization.CustomizationType.InsertionBefore); if (insertion != null) { question.InsertedQuestionBefore = new Question(question.ScriptureReference, question.StartRef, question.EndRef, insertion.ModifiedPhrase, insertion.Answer); } var addition = AdditionsAndInsertions.SingleOrDefault(a => a.Type == PhraseCustomization.CustomizationType.AdditionAfter); if (addition != null) { question.AddedQuestionAfter = new Question(question.ScriptureReference, question.StartRef, question.EndRef, addition.ModifiedPhrase, addition.Answer); } }