private static void MoveUnitBounded(MarkupPointer p, MoveDirection direction, MoveContextFilter continueFilter, MarkupPointer boundary) { MarkupPointer p1 = p.Clone(); MarkupPointer lastGoodPosition = p.Clone(); MarkupContext context = new MarkupContext(); MoveFilterResult result = MoveFilterResult.CONTINUE; while (CheckMoveBoundary(p1, boundary, direction) && result == MoveFilterResult.CONTINUE) { lastGoodPosition.MoveToPointer(p1); MovePointer(p1, direction, context); result = continueFilter(context); } if (result == MoveFilterResult.CONTINUE) { //we hit the boundary, so position pointer at the boundary p1.MoveToPointer(boundary); } else if (result == MoveFilterResult.STOP_BACK) { p1.MoveToPointer(lastGoodPosition); } p.MoveToPointer(p1); }
/// <summary> /// Find the most logic direction to move the cursor so that it matches the where has clicked /// </summary> /// <param name="Selection"></param> /// <returns></returns> public static Direction FindSelectionToLogicalPosition(MarkupRange Selection, IHTMLElement body, bool?forward) { // There is a selection, not just a click if (!Selection.Start.IsEqualTo(Selection.End)) { return(Direction.None); } Direction dir = ImageBreakout(Selection.Start); if (dir != Direction.None) { return(dir); } MarkupContext contextRight = Selection.Start.Right(false); MarkupContext contextLeft = Selection.Start.Left(false); // there is text or some other type of content around the click if (!((contextLeft.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope || contextLeft.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope) && (contextRight.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope || contextRight.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope))) { return(Direction.None); } // The click is not between two block elements, so it should be fine where it is if (!ElementFilters.IsBlockElement(contextLeft.Element) || !ElementFilters.IsBlockElement(contextRight.Element)) { return(Direction.None); } // </blockElement>|</postBody> if (contextLeft.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope && !IsSmartContent(contextLeft) && contextRight.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope && contextRight.Element.id == body.id) { return(Direction.Left); } // <postBody>|<blockElement> if (contextRight.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope && !IsSmartContent(contextRight) && contextLeft.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope && contextLeft.Element.id == body.id) { return(Direction.Right); } // </blockElement>|<blockElement> if (contextLeft.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope && !IsSmartContent(contextLeft) && contextRight.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope && !IsSmartContent(contextRight)) { return(forward == null || forward == true ? Direction.Right : Direction.Left); } return(Direction.None); }
/// <summary> /// Inspects the content of the container to the left of the markup pointer and optionally moves /// the pointer one position to the left. /// </summary> /// <param name="move">TRUE if the pointer is to move past the content to the left, or FALSE otherwise. /// If TRUE, the pointer will move either to the other side of the tag or text to its left, depending on /// the CONTEXT_TYPE to the pointer's left. /// </param> /// <returns>A MarkupContext object describing the content positioned to the pointer's left</returns> public MarkupContext Left(bool move) { MarkupContext context = new MarkupContext(); Left(move, context); return(context); }
/// <summary> /// Returns true if the the range contains an element that matches the filter /// </summary> /// <param name="filter"></param> /// <returns></returns> public bool ContainsElements(IHTMLElementFilter filter) { if (!IsEmpty()) { Hashtable usedElements = new Hashtable(); MarkupPointer p = MarkupServices.CreateMarkupPointer(Start); MarkupContext context = p.Right(false); //move p through the range to locate each the elements adding elements that pass the filter while (p.IsLeftOfOrEqualTo(End)) { if (context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope || context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope || context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_NoScope) { if (usedElements[context.Element] == null) { if (filter(context.Element)) { return(true); } } //cache the fact that we've already tested this element. usedElements[context.Element] = context.Element; } p.Right(true, context); } } return(false); }
/// <summary> /// Inspects the content of the container to the right of the markup pointer and optionally moves /// the pointer one position to the right. /// </summary> /// <param name="move">TRUE if the pointer is to move past the content to the right, or FALSE otherwise. /// If TRUE, the pointer will move either to the other side of the tag or text to its right, depending on /// the CONTEXT_TYPE to the pointer's right. /// </summary> /// <param name="move"></param> /// <returns>A MarkupContext object describing the content positioned to the pointer's left</returns> public MarkupContext Right(bool move) { MarkupContext context = new MarkupContext(); Right(move, context); return(context); }
private static MoveFilterResult StopAfterEnterBlock(MarkupContext mc) { if (mc.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope && ElementFilters.IsBlockElement(mc.Element)) { return(MoveFilterResult.STOP); } return(MoveFilterResult.CONTINUE); }
private static MoveFilterResult StopBeforeExitBlock(MarkupContext mc) { if (mc.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope && ElementFilters.IsBlockElement(mc.Element)) { return(MoveFilterResult.STOP_BACK); } return(MoveFilterResult.CONTINUE); }
private static MoveFilterResult StopBeforeEnterScope(MarkupContext mc) { if (mc.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope) { return(MoveFilterResult.STOP_BACK); } return(MoveFilterResult.CONTINUE); }
/// <summary> /// Walk through the markup range in reverse, 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 WalkRangeReverse(MarkupRangeWalker walker, bool inScopeContextsOnly) { MarkupPointer p1 = MarkupServices.CreateMarkupPointer(End); MarkupPointer p2 = MarkupServices.CreateMarkupPointer(End); p1.Cling = false; p2.Cling = false; MarkupContext context = new MarkupContext(); bool continueWalking = true; MarkupRange currentRange = null; while (continueWalking && p2.IsRightOf(Start)) { string text = null; bool isInScope = true; p2.Left(true, context); currentRange = new MarkupRange(p2.Clone(), p1.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.Start.IsLeftOf(Start)) { currentRange.Start.MoveToPointer(Start); } } } 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 void MovePointer(MarkupPointer p, MoveDirection d, MarkupContext context) { if (d == MoveDirection.LEFT) { p.Left(true, context); } else { p.Right(true, context); } }
public MoveFilterResult MergeContextFilters(MarkupContext mc) { foreach (MoveContextFilter filter in _filters) { MoveFilterResult result = filter(mc); if (result != MoveFilterResult.CONTINUE) { return(result); } } return(MoveFilterResult.CONTINUE); }
public override bool ShouldMoveDropLocationRight(MarkupPointer dropLocation) { MarkupContext mc = new MarkupContext(); dropLocation.Right(false, mc); if (mc.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope && ElementFilters.IsBlockElement(mc.Element) && !ContentSourceManager.IsSmartContent(mc.Element)) { dropLocation.Left(false, mc); if (mc.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope && ElementFilters.IsBlockElement(mc.Element)) return true; } return false; }
private static MoveFilterResult StopBeforeVisible(MarkupContext mc) { if (mc.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_Text) { return(MoveFilterResult.STOP_BACK); } if (mc.Element != null && ElementFilters.IsInlineElement(mc.Element) && !ElementFilters.IsVisibleEmptyElement(mc.Element)) { return(MoveFilterResult.CONTINUE); } return(MoveFilterResult.STOP_BACK); }
private static void PrintHtml(HtmlWriter writer, MshtmlMarkupServices MarkupServices, MarkupRange bounds) { //create a range to span a single position while walking the doc MarkupRange range = MarkupServices.CreateMarkupRange(); range.Start.MoveToPointer(bounds.Start); range.End.MoveToPointer(bounds.Start); //create a context that can be reused while walking the document. MarkupContext context = new MarkupContext(); //move the range.End to the right and print out each element along the way range.End.Right(true, context); while (context.Context != _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_None && range.Start.IsLeftOf(bounds.End)) { string text = null; if (context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_Text) { //if this is a text context, then get the text that is between the start and end points. text = range.HtmlText; //the range.HtmlText operation sometimes returns the outer tags for a text node, //so we need to strip the tags. //FIXME: if the Right/Left operations returned the available text value, this wouldn't be necessary. if (text != null) text = StripSurroundingTags(text); } else if (context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope) { string htmlText = range.HtmlText; if (context.Element.innerHTML == null && htmlText != null && htmlText.IndexOf(" ") != -1) { //HACK: Under these conditions, there was a was an invisible NBSP char in the //document that is not detectable by walking through the document with MarkupServices. //So, we force the text of the element to be the char to ensure that the //whitespace that was visible in the editor is visible in the final document. text = " "; } } //print the context. printContext(writer, context, text, range); //move the start element to the spot where the end currently is so tht there is //only ever a single difference in position range.Start.MoveToPointer(range.End); //move the end to the next position range.End.Right(true, context); } }
//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)); }
/// <summary> /// Retrieve the parent of a child element that is closest to an outer parent element. /// </summary> /// <param name="from">the position to move move out from</param> /// <param name="lookRight">if true, look right for the inner child to start from, otherwise look left</param> /// <param name="outerParent">parent element to move out to</param> /// <returns>the direct child of the outerparent that contains the innerChild</returns> IHTMLElement GetOuterMostChildOfParent(MarkupPointer from, bool lookRight, IHTMLElement outerParent) { MarkupContext lookContext = new MarkupContext(); if (lookRight) { from.Right(false, lookContext); } else { from.Left(false, lookContext); } //if there is a new element coming into scope, start the search from there, //otherwise, start from the currentScope. IHTMLElement innerChild; if (lookContext.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope) { innerChild = lookContext.Element; } else { innerChild = from.CurrentScope; } IHTMLElement parent = innerChild; IHTMLElement innerParent = innerChild; while (parent != outerParent && parent != null) { innerParent = parent; parent = parent.parentElement; } Debug.Assert(innerParent != null, "Parent not found"); if (innerParent == outerParent) //occurs when the from pointer is position directly in the parent. { return(null); } return(innerParent); }
public IHTMLElement SeekElementRight(IHTMLElementFilter filter, MarkupPointer boundaryPointer) { // initialize markup context used to track seeking MarkupContext markupContext = new MarkupContext(); markupContext.Context = _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_NoScope; while ((boundaryPointer == null || IsLeftOf(boundaryPointer)) && // apply boundary if one exists (markupContext.Context != _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_None)) // otherwise use end of document { Right(true, markupContext); IHTMLElement element = markupContext.Element; if (element != null && filter(element)) { return(element); } } // none found return(null); }
/// <summary> /// Gets the elements in the range that match the filter. /// </summary> /// <param name="filter">the delegate testing each element to determine if it should be added to the list of elements to return</param> /// <param name="inScopeElementsOnly">if true, the only</param> /// <returns></returns> public IHTMLElement[] GetElements(IHTMLElementFilter filter, bool inScopeElementsOnly) { ArrayList list = new ArrayList(); if (!IsEmpty()) { Hashtable usedElements = new Hashtable(); MarkupPointer p = MarkupServices.CreateMarkupPointer(Start); MarkupPointer end = MarkupServices.CreateMarkupPointer(End); MarkupContext context = p.Right(false); //move p through the range to locate each the elements adding elements that pass the filter while (p.IsLeftOfOrEqualTo(end)) { if (context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope || context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope || context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_NoScope) { if (usedElements[context.Element] == null) { if ((inScopeElementsOnly && isInScope(context.Element)) || !inScopeElementsOnly) { if (filter(context.Element)) { list.Add(context.Element); } } //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); }
/// <summary> /// Appends a description of a MarkupContext context. /// </summary> /// <param name="e"></param> /// <param name="sb"></param> private void AppendContextDetail(MarkupContext context, StringBuilder detail, bool isRightContext) { switch (context.Context) { case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope: if (isRightContext) { AppendStartTagString(context.Element, detail); } else { AppendEndTagString(context.Element, detail); } break; case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope: if (isRightContext) { AppendEndTagString(context.Element, detail); } else { AppendStartTagString(context.Element, detail); } break; case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_NoScope: AppendEndTagString(context.Element, detail); break; case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_Text: //not supported for now detail.Append("...text..."); break; } }
private static MoveFilterResult StopBeforeExitBlock(MarkupContext mc) { if (mc.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope && ElementFilters.IsBlockElement(mc.Element)) return MoveFilterResult.STOP_BACK; return MoveFilterResult.CONTINUE; }
/// <summary> /// Inspects the content of the container to the right of the markup pointer and optionally moves /// the pointer one position to the right. /// </summary> /// <param name="move">TRUE if the pointer is to move past the content to the right, or FALSE otherwise. /// If TRUE, the pointer will move either to the other side of the tag or text to its right, depending on /// the CONTEXT_TYPE to the pointer's right. /// </summary> /// <param name="move"></param> /// <param name="context">context object to populate with the context information</param> public void Right(bool move, MarkupContext context) { PointerRaw.Right(move, out context.Context, out context.Element, IntPtr.Zero, IntPtr.Zero); }
private static MoveFilterResult StopBeforeEnterScope(MarkupContext mc) { if (mc.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope) return MoveFilterResult.STOP_BACK; return MoveFilterResult.CONTINUE; }
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; }
private static void MovePointer(MarkupPointer p, MoveDirection d, MarkupContext context) { if (d == MoveDirection.LEFT) p.Left(true, context); else p.Right(true, context); }
public MoveFilterResult MergeContextFilters(MarkupContext mc) { foreach (MoveContextFilter filter in _filters) { MoveFilterResult result = filter(mc); if (result != MoveFilterResult.CONTINUE) return result; } return MoveFilterResult.CONTINUE; }
/// <summary> /// Retrieve the parent of a child element that is closest to an outer parent element. /// </summary> /// <param name="from">the position to move move out from</param> /// <param name="lookRight">if true, look right for the inner child to start from, otherwise look left</param> /// <param name="outerParent">parent element to move out to</param> /// <returns>the direct child of the outerparent that contains the innerChild</returns> IHTMLElement GetOuterMostChildOfParent(MarkupPointer from, bool lookRight, IHTMLElement outerParent) { MarkupContext lookContext = new MarkupContext(); if (lookRight) from.Right(false, lookContext); else from.Left(false, lookContext); //if there is a new element coming into scope, start the search from there, //otherwise, start from the currentScope. IHTMLElement innerChild; if (lookContext.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope) innerChild = lookContext.Element; else innerChild = from.CurrentScope; IHTMLElement parent = innerChild; IHTMLElement innerParent = innerChild; while (parent != outerParent && parent != null) { innerParent = parent; parent = parent.parentElement; } Debug.Assert(innerParent != null, "Parent not found"); if (innerParent == outerParent) //occurs when the from pointer is position directly in the parent. { return null; } return innerParent; }
/// <summary> /// Returns true if the current context is an ending tag, assuming the context was retrieved by moving forward. /// </summary> /// <param name="currentContext">The markup context to examine.</param> /// <returns>true if the current context is an ending tag and false otherwise.</returns> private bool IsEndTag(MarkupContext currentContext) { return currentContext.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope; }
/// <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); } }
/// <summary> /// Inspects the range if a new font tag need to be added to apply formatting for the range. /// Call this only for ranges that are inside a header element /// </summary> private void WrapRangeInFontIfNecessary(MarkupRange currentRange, bool turnBold) { // Check if there is an existing font/span tag that completely wraps this range, // we can just use that instead of inserting a new one MarkupContext workingContext = new MarkupContext(); bool wrapFont = true; MarkupPointer start = currentRange.Start.Clone(); start.Gravity = _POINTER_GRAVITY.POINTER_GRAVITY_Left; start.Right(false, workingContext); // Look to the right to see what we have there if (workingContext.Element != null && workingContext.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope && IsFontableElement(workingContext.Element)) { start.MoveAdjacentToElement(workingContext.Element, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterEnd); if (currentRange.End.IsEqualTo(start)) { // There is an existing <FONT>/<SPAN> enclosing the range, no need to wrap again wrapFont = false; // set its font-weight workingContext.Element.style.fontWeight = turnBold ? "bold" : "normal"; } } if (wrapFont) { string weightAttribute = String.Format(CultureInfo.InvariantCulture, "style=\"font-weight: {0}\"", turnBold ? "bold" : "normal"); HtmlStyleHelper.WrapRangeInElement(markupServices, currentRange, _ELEMENT_TAG_ID.TAGID_FONT, weightAttribute); } }
private static MoveFilterResult ContinueFilter(MarkupContext mc) { return MoveFilterResult.CONTINUE; }
/// <summary> /// Inspects the content of the container to the right of the markup pointer and optionally moves /// the pointer one position to the right. /// </summary> /// <param name="move">TRUE if the pointer is to move past the content to the right, or FALSE otherwise. /// If TRUE, the pointer will move either to the other side of the tag or text to its right, depending on /// the CONTEXT_TYPE to the pointer's right. /// </summary> /// <param name="move"></param> /// <returns>A MarkupContext object describing the content positioned to the pointer's left</returns> public MarkupContext Right(bool move) { MarkupContext context = new MarkupContext(); Right(move, context); return context; }
public IHTMLElement SeekElementRight(IHTMLElementFilter filter, MarkupPointer boundaryPointer) { // initialize markup context used to track seeking MarkupContext markupContext = new MarkupContext(); markupContext.Context = _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_NoScope; while ((boundaryPointer == null || IsLeftOf(boundaryPointer)) && // apply boundary if one exists (markupContext.Context != _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_None)) // otherwise use end of document { Right(true, markupContext); IHTMLElement element = markupContext.Element; if (element != null && filter(element)) return element; } // none found return null; }
/// <summary> /// Returns true if the current context is an opening tag, assuming the context was retrieved by moving forward. /// </summary> /// <param name="currentContext">The markup context to examine.</param> /// <returns>true if the current context is an opening tag and false otherwise.</returns> private bool IsBeginTag(MarkupContext currentContext) { return currentContext.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope; }
/// <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; } }
/// <summary> /// Returns true if the current currentContext is text. /// </summary> /// <param name="currentContext">The markup currentContext to examine.</param> /// <returns>true if the current currentContext is text and false otherwise.</returns> private bool IsText(MarkupContext currentContext) { return currentContext.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_Text; }
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 static bool IsSmartContent(MarkupContext ctx) { return ((IHTMLElement3)ctx.Element).contentEditable.ToUpperInvariant() == "FALSE"; }
/// <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 IsSmartContent(MarkupContext ctx) { return(((IHTMLElement3)ctx.Element).contentEditable.ToUpperInvariant() == "FALSE"); }
/// <summary> /// Returns the markup pointer to the position of the first exit scope of type tagId or type terminatingTagId which follows this markup range. /// Returns null if text exists between the range and such an exit scope, or if there is no such exit scope. /// </summary> /// <param name="terminatingTagId"></param> /// <returns></returns> internal MarkupPointer NextExitScopeWithoutInterveningText(MarkupRange selection, _ELEMENT_TAG_ID tagId, _ELEMENT_TAG_ID terminatingTagId, out bool primaryTagIdMatch) { MarkupContext context = new MarkupContext(); MarkupPointer pointer = selection.End.Clone(); primaryTagIdMatch = false; while (true) { pointer.Right(true, context); if (context.Element == null) return null; switch (context.Context) { case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_None: return null; case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope: { if (_markupServices.GetElementTagId(context.Element) == tagId) { primaryTagIdMatch = true; return pointer; } if (terminatingTagId != _ELEMENT_TAG_ID.TAGID_NULL && terminatingTagId == _markupServices.GetElementTagId(context.Element)) { return pointer; } } break; case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope: case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_NoScope: break; case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_Text: return null; } } }
/// <summary> /// Returns true if shift+tab focused region changing is supported for the current edit location /// </summary> /// <returns></returns> private bool ShiftTabFocusChangeSupported() { //Shift-tab is only supported if the caret is positioned at the beginning of the post //If the selection is currently inside a non-blockquote block element with no visible content //to the left of it, then focus change is supported. if (!EditorContext.Selection.SelectedMarkupRange.IsEmpty()) return false; MarkupRange range = ElementRange.Clone(); range.Start.MoveAdjacentToElement(HTMLElement, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterBegin); range.End = EditorContext.Selection.SelectedMarkupRange.Start; //if there is any text between the caret and the beginning of the post, //then ShiftTab focus changing is not allowed. string text = range.Text; if (text != null && range.Text.Trim() != String.Empty) return false; MarkupContext context = new MarkupContext(); range.Start.Right(true, context); int blockElementDepth = 0; while (range.Start.IsLeftOfOrEqualTo(range.End)) { if (context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope || context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_NoScope) { string tagName = context.Element.tagName; if (tagName.Equals("BLOCKQUOTE") || ElementFilters.IsListElement(context.Element) || ElementFilters.IsListItemElement(context.Element)) return false; //ShiftTab in a blockquote or list implies "un-blockquote" or "un-list" else if (ElementFilters.IsBlockElement(context.Element)) { blockElementDepth++; if (blockElementDepth > 1) return false; //there are multiple block elements, so this is not the beginning } else if (ElementFilters.IsVisibleEmptyElement(context.Element)) return false; //there is a visible empty element (like an image), so this is not the beginning } else if (context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope) { if (ElementFilters.IsVisibleEmptyElement(context.Element)) return false; //there is a visible empty element (like an image), so this is not the beginning } range.Start.Right(true, context); } return true; }
/// <summary> /// Hook allowing editor subclasses to override (and veto handlers for) the pre-handle event /// </summary> /// <param name="inEvtDispId"></param> /// <param name="pIEventObj"></param> /// <returns></returns> protected virtual int OnPreHandleEvent(int inEvtDispId, IHTMLEventObj pIEventObj) { switch (inEvtDispId) { case DISPID_HTMLELEMENTEVENTS2.ONMOUSEMOVE: //fix evil bug 435455 - removing invalid selections using the Selection.empty() //call during onSelectionChanged is known to cause the editor to crash during //mouseUp if a dragdrop operation had been started by the editor. To prevent //the editor from hitting this condition, we hook the mouseMove event and eat //it if the editor's selection state is invalid. This prevents the editor from //intiating a dragdrop while in an invalid selection state. if ((Control.MouseButtons & MouseButtons.Left) == MouseButtons.Left) { if (HasContiguousSelection && !IsValidContiguousSelection()) { return HRESULT.S_OK; } } break; case DISPID_HTMLELEMENTEVENTS2.ONKEYDOWN: // WinLive 245925: Because we inline some CSS into the font tag (specifically, the font-size // property), we need to make sure that when the user hits enter that the font-size persists // onto the next line. MSHTML does not handle this for us. if (Editable && pIEventObj.keyCode == (int)Keys.Enter && SelectedMarkupRange.IsEmptyOfText(false)) { bool restOfBlockElementIsEmpty = true; bool foundFontSizeElement = false; MarkupContext context; MarkupPointer p; for (p = SelectedMarkupRange.Start.Clone(), context = new MarkupContext(); !ElementFilters.IsBlockElement(p.CurrentScope); p.Right(true, context)) { if (context.Element != null) { if (context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope && _fontTagWithFontSizeFilter(context.Element)) { // We found the end tag of a font element that wraps our current selection. foundFontSizeElement = true; } else if (ElementFilters.IsVisibleEmptyElement(context.Element)) { // If there is any content to the right of the cursor in the current block // element, then it (and any font tags) will go down to the new line // automatically, so we don't have to worry about anything in this case. restOfBlockElementIsEmpty = false; break; } } else if (context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_Text) { // If there is any content to the right of the cursor in the current block // element, then it (and any font tags) will go down to the new line // automatically, so we don't have to worry about anything in this case. restOfBlockElementIsEmpty = false; break; } } if (foundFontSizeElement && restOfBlockElementIsEmpty && !ElementFilters.IsHeaderElement(p.CurrentScope)) { // Save this for later. It will be processed after the Enter key has been processed. _fontSizeBeforeEnter = GetFontSizeAt(SelectedMarkupRange.Start); } } break; } return HRESULT.S_FALSE; }
private static MoveFilterResult StopBeforeVisible(MarkupContext mc) { if (mc.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_Text) return MoveFilterResult.STOP_BACK; if (mc.Element != null && ElementFilters.IsInlineElement(mc.Element) && !ElementFilters.IsVisibleEmptyElement(mc.Element)) return MoveFilterResult.CONTINUE; return MoveFilterResult.STOP_BACK; }
private MarkupRange ApplyInlineTag(_ELEMENT_TAG_ID tagId, string attributes, MarkupRange selection) { MarkupRange newSelection = _markupServices.CreateMarkupRange(); // If the selection is empty, then just insert the tag if (selection.IsEmpty()) { newSelection.MoveToElement(WrapRangeInSpanElement(tagId, attributes, selection), false); return newSelection; } // Start at the beginning of the selection move forward until you hit a block start/exit context or the end of the selection bool keepApplying = true; MarkupContext contextStart = new MarkupContext(); MarkupRange blockFreeRange = _markupServices.CreateMarkupRange(selection.Start.Clone(), selection.Start.Clone()); MarkupPointer currentPointer = _markupServices.CreateMarkupPointer(blockFreeRange.Start); while (keepApplying) { // Check if moving right would be beyond the bounds of the selection. if (currentPointer.IsRightOfOrEqualTo(selection.End)) { // We've hit the end of the selection, so we're done. keepApplying = false; Debug.Assert(blockFreeRange.Start.IsLeftOfOrEqualTo(selection.End)); blockFreeRange.End.MoveToPointer(selection.End.IsLeftOf(currentPointer) ? selection.End : currentPointer); if (ShouldApplyInlineTagToBlockFreeSelection(blockFreeRange)) newSelection.ExpandToInclude(ApplyInlineTagToBlockFreeSelection(tagId, attributes, blockFreeRange)); break; } // Check if the next context is entering or exiting a block. currentPointer.Right(false, contextStart); if (contextStart.Element != null && ElementFilters.IsBlockElement(contextStart.Element)) { switch (contextStart.Context) { case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope: case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope: { blockFreeRange.End.MoveToPointer(selection.End.IsLeftOf(currentPointer) ? selection.End : currentPointer); if (ShouldApplyInlineTagToBlockFreeSelection(blockFreeRange)) newSelection.ExpandToInclude(ApplyInlineTagToBlockFreeSelection(tagId, attributes, blockFreeRange)); if (contextStart.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope) blockFreeRange.Start.MoveAdjacentToElement(contextStart.Element, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterBegin); else if (contextStart.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope) blockFreeRange.Start.MoveAdjacentToElement(contextStart.Element, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterEnd); blockFreeRange.Collapse(true); } break; case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_None: { keepApplying = false; blockFreeRange.End.MoveToPointer(selection.End.IsLeftOf(currentPointer) ? selection.End : currentPointer); if (ShouldApplyInlineTagToBlockFreeSelection(blockFreeRange)) newSelection.ExpandToInclude(ApplyInlineTagToBlockFreeSelection(tagId, attributes, blockFreeRange)); } break; default: break; } } // Finally, move our pointer currentPointer.Right(true); } if (newSelection.Positioned) newSelection.Trim(); return newSelection; }
private static MoveFilterResult StopAfterEnterBlock(MarkupContext mc) { if (mc.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope && ElementFilters.IsBlockElement(mc.Element)) return MoveFilterResult.STOP; return MoveFilterResult.CONTINUE; }
private static MoveFilterResult ContinueFilter(MarkupContext mc) { return(MoveFilterResult.CONTINUE); }
/// <summary> /// Appends a description of a MarkupContext context. /// </summary> /// <param name="e"></param> /// <param name="sb"></param> private void AppendContextDetail(MarkupContext context, StringBuilder detail, bool isRightContext) { switch (context.Context) { case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope: if (isRightContext) AppendStartTagString(context.Element, detail); else AppendEndTagString(context.Element, detail); break; case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope: if (isRightContext) AppendEndTagString(context.Element, detail); else AppendStartTagString(context.Element, detail); break; case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_NoScope: AppendEndTagString(context.Element, detail); break; case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_Text: //not supported for now detail.Append("...text..."); break; } }
/// <summary> /// Inspects the content of the container to the left of the markup pointer and optionally moves /// the pointer one position to the left. /// </summary> /// <param name="move">TRUE if the pointer is to move past the content to the left, or FALSE otherwise. /// If TRUE, the pointer will move either to the other side of the tag or text to its left, depending on /// the CONTEXT_TYPE to the pointer's left. /// </param> /// <returns>A MarkupContext object describing the content positioned to the pointer's left</returns> public MarkupContext Left(bool move) { MarkupContext context = new MarkupContext(); Left(move, context); return context; }