internal static void MarkToRemove(ICmBaseAnnotation cbaToRemove, IList<ReusableCbaItem> reusableItems) { foreach (ReusableCbaItem item in reusableItems) { if (item == cbaToRemove) { item.MarkedToRemove = true; return; } } }
/// <summary> /// Given a segment which points into your paragraph, return the string that should correspond to it in the BT. /// </summary> /// <param name="seg"></param> /// <returns></returns> string ConvertedBtLabel(ICmBaseAnnotation seg) { ITsString tssResult = m_scr.ConvertCVNumbersInStringForBT((seg as CmBaseAnnotation).TextAnnotated, m_wsBt); if (tssResult == null) return null; // paranoia. return tssResult.Text; }
ICmBaseAnnotation MakeWfic(StTxtPara para, ICmBaseAnnotation previous) { return MakeAnnotation(para, previous, CmAnnotationDefn.Twfic(Cache)); }
ICmBaseAnnotation MakeAnnotation(StTxtPara para, ICmBaseAnnotation previous, ICmAnnotationDefn type) { using (new UndoRedoTaskHelper(Cache, "ConstituentChartDatabaseTests - MakeAnnotation()", "ConstituentChartDatabaseTests - MakeAnnotation()")) { ICmBaseAnnotation result = (ICmBaseAnnotation)Cache.LangProject.AnnotationsOC.Add(new CmBaseAnnotation()); result.BeginObjectRA = para; int prevOffset = 0; if (previous != null) prevOffset = previous.EndOffset; result.BeginOffset = prevOffset + 1; result.EndOffset = prevOffset + 2; result.AnnotationTypeRA = type; return result; } }
/// <summary> /// /// </summary> /// <param name="cba"></param> /// <param name="segBreakBeginOffsetsBeforeEdit"></param> /// <param name="segBreakBeginOffsetsAfterEdit"></param> /// <param name="segmentsAfterEdit"></param> /// <param name="ichEditMin"></param> /// <param name="cvIns"></param> /// <param name="cvDel"></param> /// <param name="segsToDel"></param> /// <param name="segToMerge"> This starts as null on the first segment we handle. It is changed to cba (the current segment) when we /// handle the (one and only, if any) segment that has lost the trailing punctuation that separated it from the next segment. /// Subsequently, that segment continues as segToMerge until we come to one that is not completely deleted and so can merge with it. /// if we cant find a segment in the right place to merge it with, we set its end offset according to what was computed in segmentsAfterEdit. /// </param> /// <returns>true if we handled it (caller should make no further adjustments to this CBA)</returns> private bool HandleAnySubstantialSegmentChange(ICmBaseAnnotation cba, List<int> segBreakBeginOffsetsBeforeEdit, List<int> segBreakBeginOffsetsAfterEdit, List<TsStringSegment> segmentsAfterEdit, int ichEditMin, int cvIns, int cvDel, ref Set<int> segsToDel, ref ICmBaseAnnotation segToMerge) { // determine whether we deleted across a segment break character. int ichEditLim = ichEditMin + cvDel; int ichMinSegBreakBeforeEdit = AnnotationAdjuster.GetSegmentBreakInCbaRange(cba, segBreakBeginOffsetsBeforeEdit); // find the endpoint of the paragraph, since it may be the last segments endmarker. int ichLimParaBeforeEdit = m_anchorTextInfo.ParaText.Length; // If the edit range entirely contains the segment, clobber it. The following cases were originally designed // to handle this as a subcase, but don't do so adequately for verse number and similar segments that // don't actually have closing punctuation. Since this code is very tricky, it seemed safer to put in a // new case rather than try to patch up the old ones. if (cvDel > 0 && ichEditMin <= cba.BeginOffset && OnlyWhiteSpaceFollowsEditLimit(cba, ichEditLim, ichEditMin + cvIns)) { AnnotationAdjuster.MarkCbaAndLinkedObjsForDeletion(cba, ref segsToDel); return true; } // 1.2) detect deletions that occur across segment annotations // 1.2.1) merge adjacent indirect annotations // - TODO: mark indirect annotations as needing verification. // 1.2.2) delete first annotation if (segToMerge != null && ichEditLim <= ichMinSegBreakBeforeEdit) { // There's a previous segment to merge, and at least part of this one will survive // (the edit ended before its terminating punctuation). Typically we will merge the two. // The decisive thing is whether the re-segmenting of the paragraph requires one segment or // two. Since the beginning of segToMerge survived, and the end of this one, we should // typically find a new desired segment that matches. if (ThereIsAMatchingSegment(segmentsAfterEdit, segToMerge.BeginOffset, cba.EndOffset + cvIns - cvDel)) //if (ichEditLim > cba.BeginOffset || cba.BeginOffset == segToMerge.EndOffset) { m_fSegSeqChanged = true; // They are (or have become by the deletion) adjacent; merge them. // get the Indirect Annotations for SegToMerge FdoObjectSet<ICmIndirectAnnotation> targetFreeformAnns = AnnotationAdjuster.GetFreeformAnnotations(segToMerge); FdoObjectSet<ICmIndirectAnnotation> srcFreeformAnns = AnnotationAdjuster.GetFreeformAnnotations(cba); // make a dictionary for type of freeform annotation to freeform annotation. Dictionary<int, List<ICmIndirectAnnotation>> targetTypeToAnn = AnnotationAdjuster.MakeAnnTypeToAnnDictionary(targetFreeformAnns); // foreach freeform annotation in the src, merge them into the (first) of same type of annotation on the target. // if it's not found on the target, then just move the AppliesToRC item the target. foreach (ICmIndirectAnnotation srcFreeformAnn in srcFreeformAnns) { List<ICmIndirectAnnotation> targetAnns; // Exception: don't merge note annotations, just move their instanceOf. if (srcFreeformAnn.AnnotationTypeRAHvo != m_segDefn_note && targetTypeToAnn.TryGetValue(srcFreeformAnn.AnnotationTypeRAHvo, out targetAnns)) { targetAnns[0].Comment.MergeAlternatives(srcFreeformAnn.Comment, true); } else { // it's a note or not found on the target, just move the AppliesToRC item to the target. srcFreeformAnn.AppliesToRS.Remove(cba.Hvo); srcFreeformAnn.AppliesToRS.Append(segToMerge.Hvo); } } // finished with the merge, so change the end offset and mark the merged cba for deletion. segToMerge.EndOffset = cba.EndOffset; AnnotationAdjuster.MarkCbaAndLinkedObjsForDeletion(cba, ref segsToDel); AdjustOffsets(segToMerge, cvIns, cvDel, ichEditMin, true); } else { // We didn't find an output segment to confirm that we want to merge these two, // so try to adjust them for reasonable survival. Eventually re-parsing the paragraph // will straighten things out somehow. AdjustSegmentToNewSegmentBounds(segToMerge, segmentsAfterEdit); // make sure we adjust the current cba. AdjustOffsets(cba, cvIns, cvDel, ichEditMin, true); } segToMerge = null; return true; } // We get here if either there's no previous segment needing merging, or if the edit extends beyond // our closing punctuation. if (cvDel > 0 && (ichEditLim > ichMinSegBreakBeforeEdit || ichEditLim == ichLimParaBeforeEdit)) { // user deleted(and possibly inserted) across a segment boundary // 0123456789 // "sample. sentence" // "sample!? sentence" m_fSegSeqChanged = true; if (ichEditMin <= cba.BeginOffset) { // (Case 1) the user deleted across the beginning of the sentence, in addition to the end // just delete the segment annotation. AnnotationAdjuster.MarkCbaAndLinkedObjsForDeletion(cba, ref segsToDel); return true; } // since the user didn't delete across the begin offset of this cba, // we can be fairly certain we can find a dummy segment corresponding to its begin offset. TsStringSegment closestSegAfterEdit; int ichMinSegBreakAfterEdit; AnnotationAdjuster.GetClosestSegmentInfoAfterEdit(cba, segBreakBeginOffsetsAfterEdit, segmentsAfterEdit, out closestSegAfterEdit, out ichMinSegBreakAfterEdit); if (ichMinSegBreakAfterEdit < 0) { // There is no following segment break...possibly we're deleting the final punctuation, // or inserted a paragraph break within this segment. Since there is no following segment, // this one should extend to the end of the paragraph. cba.EndOffset = ((cba.BeginObjectRA) as StTxtPara).Contents.Length; return true; } if ((ichEditMin + cvIns) > ichMinSegBreakAfterEdit) { // (Case 2) The user inserted a new segment break within this segment, // So adjust the end offset accordingly. (Dont merge with subsequent annotation.) // Example 1 - simple replacement of segment break char. // 0123456789012345678901234567890 // >!< // segment one. seg two. // segment one! seg two. // (ichEditMin(11) + cvIns(1)) > ichMinSegBreakAfterEdit(11) // Example 2 - deletion includes twfics but insertion has a seg break char. // 0123456789012345678901234567890 // > three. -----< // segment one. seg two. // seg three. two. // (ichEditMin(3) + cvIns(8)) > ichMinSegBreakAfterEdit(9) AnnotationAdjuster.AdjustSegmentToNewSegmentBounds(cba, closestSegAfterEdit); } else { // (Case 3) The user just deleted the segment break marker, merging it with an existing segment. // Rationale: // since we are inside the branch for deleting the old segment boundary (ie. ichEditLim > ichMinSegBreakBeforeEdit) // and the selection deletion didn't delete the whole segment (Case 1) // and the user didn't insert a new segment break character (Case 2) // So the user must have just deleted the segment marker so that it extends into an existing segment. segToMerge = cba; } return true; } // we didn't delete the old segment boundary, but we still may need to adjust our offsets. if (cvIns > 0 && ichEditMin >= cba.BeginOffset && ichEditMin <= cba.EndOffset) { // There was an insertion in the segment, and there was no deletion (or it didn't delete our segment break character) // Adjust the end offset accordingly. TsStringSegment closestSegAfterEdit; int ichMinSegBreakAfterEdit; AnnotationAdjuster.GetClosestSegmentInfoAfterEdit(cba, segBreakBeginOffsetsAfterEdit, segmentsAfterEdit, out closestSegAfterEdit, out ichMinSegBreakAfterEdit); // Handle the special case of a complete segment (e.g., a verse number, or pasting a sentence) right before this segment. if (cba.BeginOffset == ichEditMin && closestSegAfterEdit != null && closestSegAfterEdit.EndOffset == cba.BeginOffset + cvIns - cvDel) { // We were about to change this segment's offsets to point at the newly inserted segment! // Instead, just adjust it as a segment that follows the edit. AdjustOffsets(cba, cvIns, cvDel, ichEditMin, false); return true; } // TODO: Special case: if the user inserted at the beginning of the sentence. // just move the whole segment, and depend upon the parser to adjust the rest when // the user switches to Interlinear. // The impact upon current tests is too drastic //if (ichEditMin == cba.BeginOffset) //{ // AdjustOffsets(cba, cvIns, cvDel, ichEditMin, false); // return true; //} AnnotationAdjuster.AdjustSegmentToNewSegmentBounds(cba, closestSegAfterEdit); return true; } // otherwise, we expect the change can be handled by the caller in AdjustOffsets. // probably just an insertion or deletion contained within the segment. return false; }
/// <summary> /// given a real cbaToAdjust, adjust its offsets to the closest matching dummy segment /// in a list of segsAfterEdit. /// </summary> /// <param name="cbaToAdjust"></param> /// <param name="segsAfterEdit"></param> private void AdjustSegmentToNewSegmentBounds(ICmBaseAnnotation cbaToAdjust, List<TsStringSegment> segsAfterEdit) { TsStringSegment newSeg = AnnotationAdjuster.GetClosestSegment(cbaToAdjust, segsAfterEdit); AnnotationAdjuster.AdjustSegmentToNewSegmentBounds(cbaToAdjust, newSeg); }
private static void AdjustSegmentToNewSegmentBounds(ICmBaseAnnotation cbaToAdjust, TsStringSegment newSeg) { if (newSeg == null) return; // or Assert.Fail? should not happen, but be defensive. if (cbaToAdjust.BeginOffset != newSeg.BeginOffset) cbaToAdjust.BeginOffset = newSeg.BeginOffset; if (cbaToAdjust.EndOffset != newSeg.EndOffset) cbaToAdjust.EndOffset = newSeg.EndOffset; }
/// <summary> /// given the bounds of cba, return the offset of the first character of the first segment break. /// If none was found, we return the EndOffset of the given cba. /// </summary> /// <param name="cba"></param> /// <param name="ichMinSegBreaksInPara"></param> /// <returns> the ichMin of the segment break marker falling within the offsets of cba. /// -1 if cba is null, but will return cba.EndOffset if it couldn't find a segment break in the cba range.</returns> private static int GetSegmentBreakInCbaRange(ICmBaseAnnotation cba, List<int> ichMinSegBreaksInPara) { if (cba == null) return -1; int ichMin = cba.BeginOffset; int ichLim = cba.EndOffset; return GetSegmentBreakInRange(ichMin, ichLim, ichMinSegBreaksInPara); }
/// <summary> /// /// </summary> /// <param name="cba"></param> /// <param name="ichMin"></param> /// <param name="tssWordAnn"></param> protected void AdjustCbaFields(ICmBaseAnnotation cba, int ichMin, ITsString tssWordAnn) { AdjustCbaFields(cba, ichMin, ichMin + tssWordAnn.Length); }
/// <summary> /// Returns the tss of the given annotation. /// </summary> /// <param name="cba">a paragraph annotation.</param> /// <returns></returns> static public ITsString TssSubstring(ICmBaseAnnotation cba) { IStTxtPara paragraph = cba.BeginObjectRA as IStTxtPara; Debug.Assert(paragraph != null, String.Format("We expect cba{0} to be a paragraph annotation.", cba.Hvo)); if (paragraph == null) return null; FdoCache cache = cba.Cache; return cache.MainCacheAccessor.get_StringProp(cba.Hvo, CmBaseAnnotation.StringValuePropId(cache)); }
private void GetAdjacentWficInfo(int hvoStartingAnnotation, out int hvoSeg, out int iSegForm, out ICmBaseAnnotation currentAnnotation, out ICmBaseAnnotation nextAnnotation, out int hvoOldWordform1, out int hvoOldWordform2) { TwficInfo twficInfo = new TwficInfo(Cache, hvoStartingAnnotation); hvoSeg = twficInfo.SegmentHvo; iSegForm = twficInfo.SegmentFormIndex; List<int> segForms = this.SegmentForms(hvoSeg); if (iSegForm < 0 || iSegForm + 1 >= segForms.Count) throw new ArgumentException(String.Format("Can't find adjacent annotation to merge with {0}.", hvoStartingAnnotation)); int hvoNextAnnotation = segForms[iSegForm + 1]; currentAnnotation = CmBaseAnnotation.CreateFromDBObject(Cache, hvoStartingAnnotation, false) as CmBaseAnnotation; nextAnnotation = CmBaseAnnotation.CreateFromDBObject(Cache, hvoNextAnnotation, false) as CmBaseAnnotation; int twficType = CmAnnotationDefn.Twfic(Cache).Hvo; if (currentAnnotation.AnnotationTypeRAHvo != twficType || nextAnnotation.AnnotationTypeRAHvo != twficType) { throw new ArgumentException(String.Format("Can only support merging annotations of twfic type{0}.\nAnnotation({1}).Type({2}) Annotation({3}).Type({4})", twficType, currentAnnotation.Hvo, currentAnnotation.AnnotationTypeRAHvo, nextAnnotation.Hvo, nextAnnotation.AnnotationTypeRAHvo)); } hvoOldWordform1 = WfiWordform.GetWfiWordformFromInstanceOf(Cache, currentAnnotation.Hvo); hvoOldWordform2 = WfiWordform.GetWfiWordformFromInstanceOf(Cache, nextAnnotation.Hvo); }
/// <summary> /// given a wfic annotation, return information about the current wfic and the adjacent wfic /// </summary> /// <param name="hvoWfic"></param> /// <param name="nextWfic"></param> /// <param name="hvoOldWordform1">the wordform of the given wfic</param> /// <param name="hvoOldWordform2">the wordform of the adjacent wfic</param> public void GetAdjacentWficInfo(int hvoWfic, out ICmBaseAnnotation nextWfic, out int hvoOldWordform1, out int hvoOldWordform2) { int hvoSeg = 0; int iSegForm = 0; ICmBaseAnnotation currentWfic = null; GetAdjacentWficInfo(hvoWfic, out hvoSeg, out iSegForm, out currentWfic, out nextWfic, out hvoOldWordform1, out hvoOldWordform2); }
protected virtual void CompareCbaInstanceOf(string context, ICmBaseAnnotation expectedCba, ICmBaseAnnotation actualCba, string msg) { // we expect these analyses to be identical. Assert.AreEqual(expectedCba.InstanceOfRAHvo, actualCba.InstanceOfRAHvo, String.Format(msg, "InstanceOf", context)); }
protected void CreateTestData() { // Create required virtual properties XmlDocument doc = new XmlDocument(); // Subset of Flex virtuals required for parsing paragraphs etc. doc.LoadXml( "<virtuals>" +"<virtual modelclass=\"StTxtPara\" virtualfield=\"Segments\">" +"<dynamicloaderinfo assemblyPath=\"ITextDll.dll\" class=\"SIL.FieldWorks.IText.ParagraphSegmentsVirtualHandler\"/>" +"</virtual>" +"<virtual modelclass=\"WfiWordform\" virtualfield=\"OccurrencesInTexts\" destinationClass=\"CmBaseAnnotation\">" +"<dynamicloaderinfo assemblyPath=\"ITextDll.dll\" class=\"SIL.FieldWorks.IText.OccurrencesInTextsVirtualHandler\"/>" +"</virtual>" +"<virtual modelclass=\"WfiWordform\" virtualfield=\"HumanApprovedAnalyses\" computeeverytime=\"true\">" +"<dynamicloaderinfo assemblyPath=\"FDO.dll\" class=\"SIL.FieldWorks.FDO.FDOSequencePropertyVirtualHandler\"/>" +"</virtual>" +"<virtual modelclass=\"WfiWordform\" virtualfield=\"HumanNoOpinionParses\" computeeverytime=\"true\" requiresRealParserGeneratedData=\"true\">" +"<dynamicloaderinfo assemblyPath=\"FDO.dll\" class=\"SIL.FieldWorks.FDO.FDOSequencePropertyVirtualHandler\"/>" +"</virtual>" +"<virtual modelclass=\"WfiWordform\" virtualfield=\"HumanDisapprovedParses\" computeeverytime=\"true\">" +"<dynamicloaderinfo assemblyPath=\"FDO.dll\" class=\"SIL.FieldWorks.FDO.FDOSequencePropertyVirtualHandler\"/>" +"</virtual>" +"<virtual modelclass=\"WfiWordform\" virtualfield=\"FullConcordanceCount\" depends=\"OccurrencesInTexts\" computeeverytime=\"true\">" +"<dynamicloaderinfo assemblyPath=\"FDO.dll\" class=\"SIL.FieldWorks.FDO.IntegerPropertyVirtualHandler\"/>" +"</virtual>" +"<virtual modelclass=\"WfiWordform\" virtualfield=\"UserCount\" bulkLoadMethod=\"LoadAllUserCounts\" computeeverytime=\"true\">" +"<dynamicloaderinfo assemblyPath=\"FDO.dll\" class=\"SIL.FieldWorks.FDO.IntegerPropertyVirtualHandler\"/>" +"</virtual>" +"<virtual modelclass=\"WfiWordform\" virtualfield=\"ParserCount\" bulkLoadMethod=\"LoadAllParserCounts\" computeeverytime=\"true\" requiresRealParserGeneratedData=\"true\">" +"<dynamicloaderinfo assemblyPath=\"FDO.dll\" class=\"SIL.FieldWorks.FDO.IntegerPropertyVirtualHandler\"/>" +"</virtual>" +"<virtual modelclass=\"WfiWordform\" virtualfield=\"ConflictCount\" computeeverytime=\"true\" requiresRealParserGeneratedData=\"true\">" +"<dynamicloaderinfo assemblyPath=\"FDO.dll\" class=\"SIL.FieldWorks.FDO.IntegerPropertyVirtualHandler\"/>" +"</virtual>" +"<virtual modelclass=\"WordformInventory\" virtualfield=\"ConcordanceWords\" destinationClass=\"WfiWordform\">" +"<dynamicloaderinfo assemblyPath=\"ITextDll.dll\" class=\"SIL.FieldWorks.IText.ConcordanceWordsVirtualHandler\"/>" +"</virtual>" +"</virtuals>"); BaseVirtualHandler.InstallVirtuals(doc.DocumentElement, Cache); m_text = new Text(); Cache.LangProject.TextsOC.Add(m_text); string para1 = "Axx simplexx testxx withxx axx lotxx ofxx wordsxx endingxx inxx xx"; string para2 = "axx sentencexx axx havingxx axx lotxx ofxx axx"; m_para1 = new StTxtPara(); m_stText = new StText(); m_text.ContentsOA = m_stText; m_para1 = MakePara(para1); m_para2 = MakePara(para2); m_wfAxx = WfiWordform.CreateFromDBObject(Cache, WfiWordform.FindOrCreateWordform(Cache, "axx", Cache.DefaultVernWs, true)); // Make one real annotation, which also serves to link the Axx to this. m_cbaAxx = CmBaseAnnotation.CreateUnownedCba(Cache); m_cbaAxx.InstanceOfRA = m_wfAxx; m_cbaAxx.BeginObjectRA = m_para1; m_cbaAxx.BeginOffset = 0; m_cbaAxx.EndOffset = 3; m_cbaAxx.Flid = (int)StTxtPara.StTxtParaTags.kflidContents; m_cbaAxx.AnnotationTypeRA = CmAnnotationDefn.Twfic(Cache); // Make another real annotation, which should get updated during Apply. IWfiWordform wf2 = WfiWordform.CreateFromDBObject(Cache, WfiWordform.FindOrCreateWordform(Cache, "lotxx", Cache.DefaultVernWs, true)); m_cba2 = CmBaseAnnotation.CreateUnownedCba(Cache); m_cba2.InstanceOfRA = wf2; m_cba2.BeginObjectRA = m_para2; m_cba2.BeginOffset = "axx sentencexx axx havingxx axx ".Length; m_cba2.EndOffset = m_cba2.BeginOffset + "lotxx".Length; m_cba2.AnnotationTypeRA = CmAnnotationDefn.Twfic(Cache); m_cba2.Flid = (int)StTxtPara.StTxtParaTags.kflidContents; ParagraphParser.ConcordTexts(Cache, new int[] { m_stText.Hvo }, new NullProgressState()); m_axxOccurrences = m_wfAxx.ConcordanceIds; m_para1Occurrences = OccurrencesInPara(m_para1.Hvo, m_axxOccurrences); m_para2Occurrences = OccurrencesInPara(m_para2.Hvo, m_axxOccurrences); // to improve test isolation, be sure to null things not always initialized. m_wfaAxe = m_wfaCut = m_wfaCutIt = m_wfaNotRude = null; m_cAnalyses = 0; }
private bool IsLabelSeg(ICmBaseAnnotation seg) { return SegmentBreaker.HasLabelText(m_para.Contents.UnderlyingTsString, seg.BeginOffset, seg.EndOffset); }
/// <summary> /// /// </summary> /// <param name="cba"></param> /// <param name="segBreakBeginOffsetsAfterEdit"></param> /// <param name="segmentsAfterEdit"></param> /// <param name="closestSegAfterEdit">the dummy segment corresponding to cba in a list of dummy segments marking the bounds after the edit.</param> /// <param name="ichMinSegBreakAfterEdit">the ichMin of the segment break marker falling within the offsets of cba. /// -1 if cba is null, but will return cba.EndOffset if it couldn't find a segment break in the cba range.</param> private static void GetClosestSegmentInfoAfterEdit(ICmBaseAnnotation cba, List<int> segBreakBeginOffsetsAfterEdit, List<TsStringSegment> segmentsAfterEdit, out TsStringSegment closestSegAfterEdit, out int ichMinSegBreakAfterEdit) { closestSegAfterEdit = GetClosestSegment(cba, segmentsAfterEdit); ichMinSegBreakAfterEdit = GetSegmentBreakInCbaRange(closestSegAfterEdit, segBreakBeginOffsetsAfterEdit); }
private static FdoObjectSet<ICmIndirectAnnotation> GetFreeformAnnotations(ICmBaseAnnotation cba) { List<int> freeformAnnHvos = new List<int>(); foreach (LinkedObjectInfo loi in cba.LinkedObjects) { if (loi.RelObjClass == CmIndirectAnnotation.kClassId) { freeformAnnHvos.Add(loi.RelObjId); } } FdoObjectSet<ICmIndirectAnnotation> freeformAnns = new FdoObjectSet<ICmIndirectAnnotation>(cba.Cache, freeformAnnHvos.ToArray(), false, typeof(CmIndirectAnnotation)); return freeformAnns; }
/// <summary> /// /// </summary> /// <param name="cba"></param> /// <param name="ichMin"></param> /// <param name="ichLim"></param> protected virtual void AdjustCbaFields(ICmBaseAnnotation cba, int ichMin, int ichLim) { SegmentServices.SetCbaFields(cba, m_para, ichMin, ichLim); }
/// <summary> /// given cbaOrig, find the corresponding dummy segment in a list of dummy segments marking the bounds after the edit. /// </summary> /// <param name="cbaOrig"></param> /// <param name="segsAfterEdit">list of dummy segments marking bounds of segments after edit. we assume this is in order.</param> /// <returns>the dummy segment nearest to cbaOrig</returns> private static TsStringSegment GetClosestSegment(ICmBaseAnnotation cbaOrig, List<TsStringSegment> segsAfterEdit) { FdoCache cache = cbaOrig.Cache; int ichMin = cbaOrig.BeginOffset; foreach (TsStringSegment seg in segsAfterEdit) { // we stop after we find a segment that equals or is greater than cbaOrig's offset. // (this assumes segsAfterEdit list is in order.) if (seg.BeginOffset >= ichMin) return seg; } return null; }
internal ReusableCbaItem(ICmBaseAnnotation cba) { Item = cba; }
private static void HandleAnySubstantialPunctuationChange(ICmBaseAnnotation cba, WordMaker wm, int ichEditMin, int cvIns, int cvDel, ref Set<int> punctsToDel) { int ichEditLim = ichEditMin + cvDel; // 1.2) detect deletions that occur across segment annotations // 1.2.1) merge adjacent indirect annotations // - TODO: mark indirect annotations as needing verification. // 1.2.2) delete first annotation if (cvDel > 0 && ichEditMin <= cba.BeginOffset && ichEditLim >= cba.EndOffset) { // user deleted entire punctuation cba // 0123456789 // "sample. sentence" // "sample sentence" punctsToDel.Add(cba.Hvo); return; } else if (cvIns > 0 && ichEditMin >= cba.BeginOffset) { // user inserted something inside the punctuation punctsToDel.Add(cba.Hvo); return; } }
/// <summary> /// /// </summary> /// <param name="reusableCbaItems"></param> /// <param name="ichMin">begin offset in actual text</param> /// <param name="ichLim">end offset in actual text</param> /// <param name="cbaFirstUnused">the first unused cba, whether or not we could reuse it.</param> /// <returns>true if we reused it.</returns> internal bool TryReuseFirstUnusedCbaMatchingText(IList<ReusableCbaItem> reusableCbaItems, int ichMin, int ichLim, out ICmBaseAnnotation cbaFirstUnused) { cbaFirstUnused = null; ReusableCbaItem unusedCbaItem = UnusedCbaItems(reusableCbaItems).FirstOrDefault(); if (unusedCbaItem != null) { ICmBaseAnnotation unusedCba = unusedCbaItem.Item; if (unusedCba.BeginOffset == ichMin && unusedCba.EndOffset == ichLim) { cbaFirstUnused = unusedCba; unusedCbaItem.Reuse(); return true; } } return false; }
/// <summary> /// Handle the general case for adjusting the offsets of an annotation based on the /// position of the edit and the number of chars inserted and deleted. /// </summary> /// <param name="cba"></param> /// <param name="cvIns"></param> /// <param name="cvDel"></param> /// <param name="ichBeginEdit"></param> /// <param name="fGrowAtBounds">if false, treat the insertion at the boundary as exterior rather than /// changing the character count between BeginOffset and EndOffset</param> void AdjustOffsets(ICmBaseAnnotation cba, int cvIns, int cvDel, int ichBeginEdit, bool fGrowAtBounds) { if (m_annsToMoveFromDeletedParagraph.Contains(cba.Hvo) || m_movedAnnsFromAfterInsertBreak.Contains(cba.Hvo)) { // make sure we don't adjust the offsets a second time. return; } int ichEndEdit = ichBeginEdit + cvDel; if (ichBeginEdit > cba.EndOffset) return; // no change in offsets needed. if (ichBeginEdit <= cba.BeginOffset && ichEndEdit >= cba.EndOffset) { int hvoType = cba.AnnotationTypeRAHvo; if (hvoType == m_hvoAnnDefnTwfic || hvoType == m_hvoPunctDefn || hvoType == m_hvoSegDefn) throw new ArgumentException("this cba has been replaced by the edit. can't adjust its offsets"); // Some other kind of annotation which we don't attempt to delete earlier, for example, // a TE check annotation (see TE-7747). The best we can do is make it an empty annotation // at the place where the contents were deleted. cba.BeginOffset = cba.EndOffset = ichBeginEdit; return; } // the cba needs to change in size, if the edit began within the cba int ichMinNew = cba.BeginOffset; int ichLimNew = cba.EndOffset; // if the entire edit occurred at/before our begin offset, shift both offsets the same amount if (ichEndEdit <= cba.BeginOffset && !(fGrowAtBounds && (cvDel == 0 && ichBeginEdit == cba.BeginOffset))) { // 0123456 // abc def // ----def // def ichMinNew += (cvIns - cvDel); ichLimNew += (cvIns - cvDel); } else { // edit occurred within the cba, so change its size // 0123456 // abc def // ab---ef // abef if (ichBeginEdit >= cba.BeginOffset) { if (ichBeginEdit == cba.EndOffset && !fGrowAtBounds) { // don't try to change this cba size, if the edit starts at its end offset } else if (ichEndEdit > cba.EndOffset) { // the end of the edit crosses the bounds of the cba // so we need to truncate the adjustment by its end offset. ichLimNew += (cvIns - (cba.EndOffset - ichBeginEdit)); } else { // edit is contained within the cba ichLimNew += (cvIns - cvDel); } } else if (ichEndEdit > cba.BeginOffset) { // if the end of the edit occurs within the cba // then we need to truncate the adjustment by its begin offset. // the end of the edit and the begin offset. ichMinNew += (cvIns - cvDel + ichEndEdit - cba.BeginOffset); ichLimNew += (cvIns - cvDel); } } int max = ((cba.BeginObjectRA) as StTxtPara).Contents.Length; // Make sure we don't do anything totally unreasonable in cases we didn't anticipate. if (ichMinNew < 0 || ichLimNew > max || ichLimNew < ichMinNew) return; // Review: would this be better? //ichMinNew = Math.Min(Math.Max(ichMinNew, 0), max); //ichLimNew = Math.Min(Math.Max(ichLimNew, 0), max); //if (ichLimNew < ichMinNew) // ichLimNew = ichMinNew; CmBaseAnnotation.SetCbaFields(cba.Cache, cba.Hvo, ichMinNew, ichLimNew, cba.BeginObjectRAHvo, false); }
/// <summary> /// /// </summary> /// <param name="reusableCbas"></param> /// <param name="ichMin"></param> /// <param name="ichLim"></param> /// <param name="cbaUsed"></param> /// <returns></returns> internal virtual bool TryReuseFirstUnusedAnnotation(IList<ReusableCbaItem> reusableCbas, int ichMin, int ichLim, out ICmBaseAnnotation cbaUsed) { cbaUsed = null; // see if we can reuse an annotation from the current paragraph. ReusableCbaItem rci = UnusedCbaItems(reusableCbas).FirstOrDefault(); if (rci != null) { rci.Reuse(); cbaUsed = rci.Item; } return cbaUsed != null; }
// We want to be able to test whether all the significant text in cba has been deleted. However, // label segments (verse numbers, footnotes, and the like) include following white space. // This routine is called only when we know that the start of the range deleted is at or // before CBA. ichEditLimOld is the end of the edit (in the original string); if cba ends // before that, it is certainly deleted. However, if it ends after that, but all the rest of it // is white space, also answer true. // That involves testing characters in the NEW version of the paragraph, so we are passed the // position in the new contents that corresponds to ichEditLimOld in the old. private bool OnlyWhiteSpaceFollowsEditLimit(ICmBaseAnnotation cba, int ichEditLimOld, int ichEditLimNew) { if (ichEditLimOld >= cba.EndOffset) return true; StTxtPara para = cba.BeginObjectRA as StTxtPara; if (para == null) return false; // paranoia string contents = para.Contents.Text; if (contents == null) return false; // paranoia. int len = cba.EndOffset - ichEditLimOld; // To succeed, this many characters (or all) must be white. for (int i = ichEditLimNew; i < ichEditLimNew + len && i < contents.Length; i++) if (!Char.IsWhiteSpace(contents[i])) return false; return true; }
/// <summary> /// find and delete affected real twfics (let the paragraph parser recompute virtual twfics?) /// 1.1) deletions & insertions that occur within or across twfic annotations. /// 1.1.1) delete affected twfic(s) /// 1.1.2) adjust offsets /// </summary> /// <param name="cba"></param> /// <param name="wm"></param> /// <param name="ichEditMin">the beginning offset of the selection used for editing.</param> /// <param name="cvIns"></param> /// <param name="cvDel"></param> /// <param name="twficsToDel"></param> private static void HandleAnySubstantialTwficChange(ICmBaseAnnotation cba, WordMaker wm, int ichEditMin, int cvIns, int cvDel, ref Set<int> twficsToDel) { int ichEditLim = ichEditMin + cvDel; // Determine whether this could possibly be a substantial change in a twfic if (ichEditMin < cba.EndOffset && ichEditLim > cba.BeginOffset) { // the selection used to edit the text overlaps this twfic. // 0123456789 // "sample sentence" // "sample ???tence" MarkCbaAndLinkedObjsForDeletion(cba, ref twficsToDel); return; } else if (cvIns > 0 && ichEditMin > cba.BeginOffset && ichEditMin < cba.EndOffset) { // user changed or broke up this twfic // 0123456789 // "sample sentence" // "sample sen?tence" MarkCbaAndLinkedObjsForDeletion(cba, ref twficsToDel); return; } else if (cvIns > 0 && ichEditMin == cba.BeginOffset) { // user has prepended characters. // delete twfic if insertion ends with a wordforming character. // 0123456789 // "sample sentence" // "sample ?sentence" // make sure the last character before this twfic // is a wordforming (non-whitespace) character before // we consider it to warrant deleting the twfic. if (wm.IsWordforming(ichEditMin + cvIns - 1)) { MarkCbaAndLinkedObjsForDeletion(cba, ref twficsToDel); return; } } else if (cvIns > 0 && ichEditMin == cba.EndOffset) { // user has appended characters. // if it begins with a wordbreaking character, don't delete the twfic // 0123456789 // "sample sentence" // "sample?xx sentence" // make sure the character after this twfic // is a wordforming (non-whitespace) character before // we consider it to warrant deleting the twfic. if (wm.IsWordforming(ichEditMin)) { MarkCbaAndLinkedObjsForDeletion(cba, ref twficsToDel); return; } } }
List<int> MakeNWfics(StTxtPara para, ICmBaseAnnotation previous, int n) { ICmBaseAnnotation current = previous; List<int> result = new List<int>(n); for (int i = 0; i < n; i++) { current = MakeWfic(para, current); result.Add(current.Hvo); } return result; }
private static void MarkCbaAndLinkedObjsForDeletion(ICmBaseAnnotation cba, ref Set<int> cbasToDel) { (cba as CmBaseAnnotation).CollectLinkedItemsForDeletion(ref cbasToDel, true); }
private int AssignMissingAnnotationWritingSystem(ICmBaseAnnotation cba) { return CmBaseAnnotation.GetAnnotationWritingSystem(m_cache, cba.BeginObjectRAHvo, cba.BeginOffset, cba.Flid); }
/// <summary> /// Get the segments of the paragraph. This is public static to allow others to use /// the same code. This will actually parse the text of the paragraph, create any /// segments that do not yet exist, and create any needed free translation annotations /// to go with them. It also sets the kflidSegments (virtual) property of the paragraph, /// and the kflidFT (virtual) property of the segments. /// </summary> /// <returns>array of ICmBaseAnnotation objects for the segments</returns> public static ICmBaseAnnotation[] GetMainParaSegments(IStTxtPara para, int wsBt, out int[] paraSegs) { FdoCache cache = EnsureMainParaSegments(para, wsBt); int kflidSegments = StTxtPara.SegmentsFlid(cache); paraSegs = cache.GetVectorProperty(para.Hvo, kflidSegments, true); ICmBaseAnnotation[] segments = new ICmBaseAnnotation[paraSegs.Length]; for (int i = 0; i < paraSegs.Length; i++) { // This prevents trying to really load it from the database, which is typically not // useful and actully causes failures of some tests when using a memory cache that considers all // objects to be non-dummies. cache.VwCacheDaAccessor.CacheIntProp(paraSegs[i], (int)CmObjectFields.kflidCmObject_Class, (int)CmBaseAnnotation.kclsidCmBaseAnnotation); segments[i] = new CmBaseAnnotation(cache, paraSegs[i]); } return segments; }