/// <summary> /// Return the parent element that 2 pointers share in common /// </summary> /// <returns></returns> private IHTMLElement GetSharedParent(MarkupPointer start, MarkupPointer end) { IHTMLElement startCurrentScope = start.CurrentScope; IHTMLElement endCurrentScope = end.CurrentScope; if (startCurrentScope == endCurrentScope) { //the start/end points share the same current scope, so return that element as the parent. return(startCurrentScope); } else { //find the parent element that these 2 pointers share in common //by locating the first parent endtag that the rangeEnd pointer //is contained within. MarkupPointer parentStart = MarkupServices.CreateMarkupPointer(); MarkupPointer parentEnd = MarkupServices.CreateMarkupPointer(); IHTMLElement sharedParent = startCurrentScope; if (sharedParent != null) { parentEnd.MoveAdjacentToElement(sharedParent, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterEnd); while (sharedParent != null && parentEnd.IsLeftOf(end)) { sharedParent = sharedParent.parentElement; parentEnd.MoveAdjacentToElement(sharedParent, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterEnd); } } return(sharedParent); } }
/// <summary> /// Walk through the markup range letting the walker visit each position. /// </summary> /// <param name="walker">the delegate walking navigating the the markup range</param> /// <param name="inScopeElementsOnly">if true, enter/exit notifications about out-of-scope elements will be suppressed.</param> /// <returns></returns> public void WalkRange(MarkupRangeWalker walker, bool inScopeContextsOnly) { MarkupPointer p1 = MarkupServices.CreateMarkupPointer(Start); MarkupPointer p2 = MarkupServices.CreateMarkupPointer(Start); p1.Cling = false; p2.Cling = false; MarkupContext context = new MarkupContext(); bool continueWalking = true; MarkupRange currentRange = null; while (continueWalking && p2.IsLeftOf(End)) { string text = null; bool isInScope = true; p2.Right(true, context); currentRange = new MarkupRange(p1.Clone(), p2.Clone(), MarkupServices); if (inScopeContextsOnly) { if (context.Element != null) { if (context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope) { p1.MoveAdjacentToElement(context.Element, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterEnd); isInScope = InRange(p1); } else if (context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope) { p1.MoveAdjacentToElement(context.Element, _ELEMENT_ADJACENCY.ELEM_ADJ_BeforeBegin); isInScope = InRange(p1); } } else if (context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_Text) { // It's possible part of the text is out of scope, so only return the in-scope text. if (currentRange.End.IsRightOf(End)) { currentRange.End.MoveToPointer(End); } } } if (context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_Text) { text = currentRange.Text; } if (!inScopeContextsOnly || isInScope) { continueWalking = walker(currentRange, context, text); } p1.MoveToPointer(p2); } }
/// <summary> /// Condenses this range into the smallest well-formed state that still contains the same /// text markup. /// </summary> /// <returns></returns> public bool Trim() { MarkupPointer newStart = MarkupServices.CreateMarkupPointer(Start); MarkupPointer newEnd = MarkupServices.CreateMarkupPointer(End); MarkupContext context = new MarkupContext(); //set newStart adjacent to the first text element to its right newStart.Right(true, context); while (!HasContentBetween(Start, newStart) && newStart.IsLeftOf(End)) { newStart.Right(true, context); } if (HasContentBetween(Start, newStart)) { newStart.Left(true); //we overstepped the text, so back up one step } //set newEnd adjacent to the first text element to its left newEnd.Left(true, context); while (!HasContentBetween(newEnd, End) && newEnd.IsRightOf(Start)) { newEnd.Left(true, context); } if (HasContentBetween(newEnd, End)) { newEnd.Right(true); //we overstepped the text, so back up one step } IHTMLElement sharedParent = GetSharedParent(newStart, newEnd); //span the start and end pointers as siblings by finding the parents of start and end //pointers that are direct children of the sharedParent IHTMLElement child = GetOuterMostChildOfParent(newStart, true, sharedParent); if (child != null) { newStart.MoveAdjacentToElement(child, _ELEMENT_ADJACENCY.ELEM_ADJ_BeforeBegin); } child = GetOuterMostChildOfParent(newEnd, false, sharedParent); if (child != null) { newEnd.MoveAdjacentToElement(child, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterEnd); } if (!HasContentBetween(newStart, Start) && !HasContentBetween(End, newEnd) && !(Start.IsEqualTo(newStart) && End.IsEqualTo(newEnd))) { Start.MoveToPointer(newStart); End.MoveToPointer(newEnd); return(true); } else { //the range didn't change, so return false. return(false); } }
private static bool CheckMoveBoundary(MarkupPointer p, MarkupPointer boundary, MoveDirection d) { if (d == MoveDirection.LEFT) { return(p.IsRightOf(boundary)); } else { return(p.IsLeftOf(boundary)); } }
public static void SplitBlockForInsertionOrBreakout(MshtmlMarkupServices markupServices, MarkupRange bounds, MarkupPointer insertAt) { IHTMLElement currentBlock = insertAt.GetParentElement(ElementFilters.BLOCK_OR_TABLE_CELL_ELEMENTS); if (currentBlock == null) { return; } if (ElementFilters.IsBlockQuoteElement(currentBlock) || ElementFilters.IsTableCellElement(currentBlock)) { return; } MarkupPointer blockStart = markupServices.CreateMarkupPointer(currentBlock, _ELEMENT_ADJACENCY.ELEM_ADJ_BeforeBegin); MarkupPointer blockEnd = markupServices.CreateMarkupPointer(currentBlock, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterEnd); if (bounds != null && (blockStart.IsLeftOf(bounds.Start) || blockEnd.IsRightOf(bounds.End))) { return; } // Don't split if at the beginning or end of the visible content in the block. // Instead just move the insertion point outside the block. MarkupRange testRange = markupServices.CreateMarkupRange(); testRange.Start.MoveToPointer(insertAt); testRange.End.MoveAdjacentToElement(currentBlock, _ELEMENT_ADJACENCY.ELEM_ADJ_BeforeEnd); if (testRange.IsEmptyOfContent()) { insertAt.MoveAdjacentToElement(currentBlock, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterEnd); return; } testRange.Start.MoveAdjacentToElement(currentBlock, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterBegin); testRange.End.MoveToPointer(insertAt); if (testRange.IsEmptyOfContent()) { insertAt.MoveAdjacentToElement(currentBlock, _ELEMENT_ADJACENCY.ELEM_ADJ_BeforeBegin); return; } MarkupPointer moveTarget = markupServices.CreateMarkupPointer(blockEnd); markupServices.Move(insertAt, blockEnd, moveTarget); insertAt.MoveAdjacentToElement(currentBlock, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterEnd); }
/// <summary> /// Returns null if ranges do not intersect /// </summary> /// <param name="range"></param> /// <returns></returns> public MarkupRange Intersect(MarkupRange range) { MarkupPointer maxStart = Start.IsRightOf(range.Start) ? Start : range.Start; MarkupPointer minEnd = End.IsLeftOf(range.End) ? End : range.End; if (minEnd.IsLeftOf(maxStart)) { return(null); } MarkupRange intersection = MarkupServices.CreateMarkupRange(); intersection.Start.MoveToPointer(maxStart); intersection.End.MoveToPointer(minEnd); return(intersection); }
//this is similar to the GetTopLevelElements except will also return table cells if correct filter // is set and recurse is equal to true public IHTMLElement[] GetTopLevelBlocksAndCells(IHTMLElementFilter filter, bool recurse) { ArrayList list = new ArrayList(); Hashtable usedElements = new Hashtable(); MarkupPointer p = MarkupServices.CreateMarkupPointer(Start); MarkupContext context = p.Right(false); //move p through the range to locate each of the top level elements while (p.IsLeftOf(End)) { if (context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope || context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_NoScope) { p.MoveAdjacentToElement(context.Element, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterEnd); if (usedElements[context.Element] == null) { if (p.IsLeftOfOrEqualTo(End) && (filter == null || filter(context.Element))) { list.Add(context.Element); } //special case--inside of a table element, want to get out the cells inside else if (recurse && ElementFilters.TABLE_ELEMENTS(context.Element)) { MarkupRange newRange = MarkupServices.CreateMarkupRange(context.Element); newRange.Start.MoveAdjacentToElement(context.Element, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterBegin); if (newRange.Start.IsLeftOf(Start)) { newRange.Start.MoveToPointer(Start); } if (newRange.End.IsRightOf(End)) { newRange.End.MoveToPointer(End); } //recursively check inside table element for table cells list.AddRange(newRange.GetTopLevelBlocksAndCells(filter, true)); } //cache the fact that we've already tested this element. usedElements[context.Element] = context.Element; } } p.Right(true, context); } return(HTMLElementHelper.ToElementArray(list)); }
private MarkupPointer GetFirstTextPoint(MarkupPointer from, bool forward) { MarkupPointer firstTextPoint = from.Clone(); MarkupContext context = new MarkupContext(); bool keepLooking = true; do { if (forward) { firstTextPoint.Right(false, context); } else { firstTextPoint.Left(false, context); } if (context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_Text) { break; } if (forward) { firstTextPoint.Right(true, context); keepLooking = context.Element != null && firstTextPoint.IsLeftOf(End); } else { firstTextPoint.Left(true, context); keepLooking = context.Element != null && firstTextPoint.IsRightOf(Start); } } while (keepLooking); return(firstTextPoint); }
private IHTMLElement GetNextElement(MarkupPointer start, MarkupRange boundaries, IHTMLElementFilter filter, bool forward) { start = start.Clone(); MarkupPointer boundary = forward ? boundaries.End : boundaries.Start; MarkupContext moveResult = new MarkupContext(); _MARKUP_CONTEXT_TYPE skipContext = _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope; //advance the pointer if (forward) start.Right(true, moveResult); else start.Left(true, moveResult); while (forward ? start.IsLeftOf(boundary) : start.IsRightOf(boundary)) { if (moveResult.Element != null && moveResult.Context != skipContext && filter(moveResult.Element)) { return moveResult.Element; } //advance the pointer if (forward) start.Right(true, moveResult); else start.Left(true, moveResult); } return null; }
/// <summary> /// Safely removes the content within this range without leaving the document badly formed. /// </summary> public void RemoveContent() { //delete the selection by moving a delete range right (from the start). //Each time that a tag that does not entirely exist within this selection //is encountered, the range content will be deleted, the deleteRange will //skip over the element. MarkupRange deleteRange = this.Clone(); Trace.Assert(deleteRange.Start.Positioned, "Trying to remove content from selection that contains pointers that are not positioned."); deleteRange.End.MoveToPointer(deleteRange.Start); MarkupPointer p = MarkupServices.CreateMarkupPointer(); MarkupPointer previousPosition = MarkupServices.CreateMarkupPointer(deleteRange.End); MarkupContext context = new MarkupContext(); deleteRange.End.Right(true, context); while (deleteRange.End.IsLeftOf(End)) { if (context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope) { p.MoveAdjacentToElement(context.Element, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterEnd); if (p.IsRightOf(End)) { //this element does not exist entirely in this selection, so we need to //ignore it in the delete. //save this position so that the delete range can be repositioned here p.MoveToPointer(deleteRange.End); //move the end left since we overstepped the valid delete range deleteRange.End.MoveToPointer(previousPosition); //delete the content in the deleteRange, and move it back to this position deleteRangeContentAndMoveToPosition(deleteRange, p); } else { //this element exists entirely in this selection, so skip to its end (since //we know it can be deleted) deleteRange.End.MoveToPointer(p); } } else if (context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope) { p.MoveAdjacentToElement(context.Element, _ELEMENT_ADJACENCY.ELEM_ADJ_BeforeBegin); if (p.IsLeftOf(Start)) { //this element does not exist entirely in this selection, so we need to //ignore it in the delete. //save this position so that the delete range can be repositioned here p.MoveToPointer(deleteRange.End); //move the end left since we overstepped the valid delete range deleteRange.End.MoveToPointer(previousPosition); //delete the content in the deleteRange, and move it back to this position deleteRangeContentAndMoveToPosition(deleteRange, p); } else { //this element exists entirely in this selection, so skip to its end (since //we know it can be deleted) deleteRange.End.MoveToPointer(p); } } previousPosition.MoveToPointer(deleteRange.End); deleteRange.End.Right(true, context); } //delete the last part of the range deleteRange.End.MoveToPointer(End); if (!deleteRange.Start.Equals(deleteRange.End)) { MarkupServices.Remove(deleteRange.Start, deleteRange.End); } }
private static bool CheckMoveBoundary(MarkupPointer p, MarkupPointer boundary, MoveDirection d) { if (d == MoveDirection.LEFT) return p.IsRightOf(boundary); else return p.IsLeftOf(boundary); }
private void MovePointerRightUntilRegionBreak(MarkupPointer p, IHTMLElementFilter regionBreakFilter, MarkupPointer rightBoundary) { MarkupContext moveContext = new MarkupContext(); while (p.IsLeftOf(rightBoundary)) { p.Right(true, moveContext); if (moveContext.Element != null && regionBreakFilter(moveContext.Element)) { p.Left(true); return; } } }