/// <summary> /// Adjust the start and end of the range to match the offset/length, in characters. /// if the offset/length adjustment fails to produce the expected value, /// then the adjustment is cancelled and false is returned. /// </summary> public static bool AdjustMarkupRange(ref IHTMLTxtRange stagingTextRange, MarkupRange range, int offset, int length) { string currentText = GetRangeTextFast(range) ?? ""; if (offset == 0 && length == currentText.Length) { return(true); } string expectedText; try { expectedText = currentText.Substring(offset, length); } catch (ArgumentOutOfRangeException) { return(false); } MarkupRange testRange = range.Clone(); AdjustMarkupRangeCore(testRange, offset, length, currentText); if (GetRangeTextFast(testRange) != expectedText) { return(false); } range.MoveToRange(testRange); return(true); }
public TypographicCharacterHandler(MarkupRange currentSelection, InsertHtml insertHtml, IBlogPostImageEditingContext imageEditingContext, IHTMLElement postBodyElement, char c, string htmlText, MarkupPointer blockBoundary) { _currentSelection = currentSelection.Clone(); _currentSelection.Start.Gravity = _POINTER_GRAVITY.POINTER_GRAVITY_Right; _currentSelection.End.Gravity = _POINTER_GRAVITY.POINTER_GRAVITY_Left; _insertHtml = insertHtml; _imageEditingContext = imageEditingContext; _postBodyElement = postBodyElement; this.c = c; this.htmlText = htmlText; this.blockBoundary = blockBoundary; }
private void ApplyBlockStyle(_ELEMENT_TAG_ID styleTagId, MarkupRange selection, MarkupRange maximumBounds, MarkupRange postOpSelection) { Debug.Assert(selection != maximumBounds, "selection and maximumBounds must be distinct objects"); SelectionPositionPreservationCookie selectionPreservationCookie = null; //update the range cling and gravity so it will stick with the re-arranged block content selection.Start.PushCling(false); selection.Start.PushGravity(_POINTER_GRAVITY.POINTER_GRAVITY_Left); selection.End.PushCling(false); selection.End.PushGravity(_POINTER_GRAVITY.POINTER_GRAVITY_Right); try { if (selection.IsEmpty()) { //nothing is selected, so expand the selection to cover the entire parent block element IHTMLElementFilter stopFilter = ElementFilters.CreateCompoundElementFilter(ElementFilters.BLOCK_ELEMENTS, new IHTMLElementFilter(IsSplitStopElement)); MovePointerLeftUntilRegionBreak(selection.Start, stopFilter, maximumBounds.Start); MovePointerRightUntilRegionBreak(selection.End, stopFilter, maximumBounds.End); } using (IUndoUnit undo = _editor.CreateSelectionUndoUnit(selection)) { selectionPreservationCookie = SelectionPositionPreservationHelper.Save(_markupServices, postOpSelection, selection); if (selection.IsEmptyOfContent()) { ApplyBlockFormatToEmptySelection(selection, styleTagId, maximumBounds); } else { ApplyBlockFormatToContentSelection(selection, styleTagId, maximumBounds); } undo.Commit(); } } finally { selection.Start.PopCling(); selection.Start.PopGravity(); selection.End.PopCling(); selection.End.PopGravity(); } if (!SelectionPositionPreservationHelper.Restore(selectionPreservationCookie, selection, selection.Clone())) selection.ToTextRange().select(); }
public KeepSourceFormatting(MarkupRange sourceRange, MarkupRange destinationRange) { Debug.Assert(sourceRange.Start.Container.GetOwningDoc() == destinationRange.Start.Container.GetOwningDoc(), "Ranges must share an owning document!"); if (sourceRange == null) { throw new ArgumentNullException("sourceRange"); } if (!sourceRange.Positioned) { throw new ArgumentException("sourceRange must be positioned."); } if (sourceRange.Start.IsRightOf(sourceRange.End)) { throw new ArgumentException("sourceRange start must be before range end."); } if (destinationRange == null) { throw new ArgumentNullException("destinationRange"); } if (!destinationRange.Positioned) { throw new ArgumentException("destinationRange must be positioned."); } if (destinationRange.Start.IsRightOf(destinationRange.End)) { throw new ArgumentException("destinationRange start must be before range end."); } this.sourceDocument = sourceRange.Start.Container.Document; this.sourceMarkupServices = new MshtmlMarkupServices((IMarkupServicesRaw)this.sourceDocument); this.sourceRange = sourceRange.Clone(); this.sourceRange.Start.Gravity = _POINTER_GRAVITY.POINTER_GRAVITY_Left; this.sourceRange.End.Gravity = _POINTER_GRAVITY.POINTER_GRAVITY_Right; this.destinationDocument = destinationRange.Start.Container.Document; this.destinationMarkupServices = new MshtmlMarkupServices((IMarkupServicesRaw)this.destinationDocument); this.destinationRange = destinationRange.Clone(); this.destinationRange.Start.Gravity = _POINTER_GRAVITY.POINTER_GRAVITY_Left; this.destinationRange.End.Gravity = _POINTER_GRAVITY.POINTER_GRAVITY_Right; }
public FixupSegment(MarkupRange rangeToFixup, TextStyle sourceTextStyle) { if (rangeToFixup == null) { throw new ArgumentNullException("rangeToFixup"); } if (!rangeToFixup.Positioned) { throw new ArgumentException("rangeToFixup must be positioned!"); } if (sourceTextStyle == null) { throw new ArgumentNullException("sourceTextStyle"); } SourceTextStyle = sourceTextStyle; RangeToFixup = rangeToFixup.Clone(); // We want the RangeToFixup to stick with the text its wrapped around. RangeToFixup.Start.Gravity = _POINTER_GRAVITY.POINTER_GRAVITY_Right; RangeToFixup.End.Gravity = _POINTER_GRAVITY.POINTER_GRAVITY_Left; }
private bool IsRangeInUrl(MarkupRange range) { //must have this range cloned, otherwise in some cases IsInsideURL call // was MOVING the current word range! if "www.foo.com" was in the editor, // when the final "\"" was the current word, this call MOVED the current // word range BACK to www.foo.com, then nextWord would get "\"" and a loop // would occur (bug 411528) range = range.Clone(); IMarkupPointer2Raw p2StartRaw = (IMarkupPointer2Raw)range.Start.PointerRaw; bool insideUrl; p2StartRaw.IsInsideURL(range.End.PointerRaw, out insideUrl); return insideUrl; }
public BoldApplier(MshtmlMarkupServices markupServices, MarkupRange markupRange, IMshtmlCommand boldCommand) { this.markupServices = markupServices; this.markupRange = markupRange.Clone(); this.boldCommand = boldCommand; }
/// <summary> /// Fixes up a range that is contained in a single header element. /// </summary> /// <param name="range">A range that is contained in a single header element.</param> /// <param name="turnBold">Whether or not the text should be turning bold.</param> private void FixupHeaderRange(MarkupRange range, bool turnBold) { IHTMLElement parentHeaderElement = range.ParentBlockElement(); if (parentHeaderElement == null || !ElementFilters.IsHeaderElement(parentHeaderElement)) { Debug.Fail("Expected entire range to be inside a single header element."); return; } MarkupRange expandedRange = range.Clone(); // Make sure we expand the selection to include any <font> tags that may be wrapping us. MarkupPointerMoveHelper.MoveUnitBounded(expandedRange.Start, MarkupPointerMoveHelper.MoveDirection.LEFT, MarkupPointerAdjacency.BeforeVisible, parentHeaderElement); MarkupPointerMoveHelper.MoveUnitBounded(expandedRange.End, MarkupPointerMoveHelper.MoveDirection.RIGHT, MarkupPointerAdjacency.BeforeVisible, parentHeaderElement); // Walks in-scope elements and clears out any elements or styles that might affect the bold formatting. var elementsToRemove = new List<IHTMLElement>(); expandedRange.WalkRange( delegate (MarkupRange currentexpandedRange, MarkupContext context, string text) { IHTMLElement currentElement = context.Element; if (currentElement != null && context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope) { if (IsStrongOrBold(currentElement)) { elementsToRemove.Add(currentElement); } else if (IsFontableElement(currentElement) && HTMLElementHelper.IsBold((IHTMLElement2)currentElement) != turnBold) { currentElement.style.fontWeight = String.Empty; } } return true; }, true); elementsToRemove.ForEach(e => markupServices.RemoveElement(e)); // Walks the range to find any segments of text that need to be fixed up. var rangesToWrap = new List<MarkupRange>(); range.WalkRange( delegate (MarkupRange currentRange, MarkupContext context, string text) { if (context.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_Text) { TextStyles currentTextStyles = new TextStyles(currentRange.Start); if (currentTextStyles.Bold != turnBold) { rangesToWrap.Add(currentRange.Clone()); } } return true; }, true); rangesToWrap.ForEach(r => WrapRangeInFontIfNecessary(r, turnBold)); }
/// <summary> /// Returns the maximum range that can be safely considered equivalent to this range (without bringing new text into the range). /// </summary> /// <param name="range"></param> /// <returns></returns> private MarkupRange CreateMaxSafeRange(MarkupRange range) { MarkupRange maxRange = range.Clone(); SelectOuter(maxRange); return maxRange; }
private void ApplyBlockStyleToRange(_ELEMENT_TAG_ID styleTagId, MarkupRange range, MarkupRange maximumBounds) { //update the range cling and gravity so it will stick with the re-arranged block content range.Start.PushCling(false); range.Start.PushGravity(_POINTER_GRAVITY.POINTER_GRAVITY_Left); range.End.PushCling(false); range.End.PushGravity(_POINTER_GRAVITY.POINTER_GRAVITY_Right); try { MarkupPointer deeperPoint = GetDeeperPoint(range.Start, range.End); MarkupPointer insertionPoint = _markupServices.CreateMarkupPointer(deeperPoint); insertionPoint.Cling = false; //if the insertion point parent block contains content, split the block. If the parent //block is now empty, then just delete the parent block. IHTMLElement parentBlock = insertionPoint.GetParentElement( ElementFilters.CreateCompoundElementFilter(ElementFilters.BLOCK_ELEMENTS, new IHTMLElementFilter(IsSplitStopElement))); //temporarily stage the range content at the end of the document so that the split //operation doesn't damage the original range content MarkupRange stagedBlockContent = _markupServices.CreateMarkupRange(); stagedBlockContent.Start.Gravity = _POINTER_GRAVITY.POINTER_GRAVITY_Left; stagedBlockContent.End.Gravity = _POINTER_GRAVITY.POINTER_GRAVITY_Right; stagedBlockContent.Start.MoveAdjacentToElement(GetBodyElement(), _ELEMENT_ADJACENCY.ELEM_ADJ_BeforeEnd); stagedBlockContent.End.MoveToPointer(stagedBlockContent.Start); MarkupPointer stagedBlockInsertionPoint = _markupServices.CreateMarkupPointer(GetBodyElement(), _ELEMENT_ADJACENCY.ELEM_ADJ_BeforeEnd); stagedBlockInsertionPoint.Gravity = _POINTER_GRAVITY.POINTER_GRAVITY_Right; // Pass over all opening elements between parent's adj_beforeend and the start of selection (range.start) // Any element (_enterscope) that is not closed before the close of selection is essentially // containing the selection completely, and needs to be copied into the staging area. // ex: <p><a href="http://msn.com">abc[selection]def</a></p> // Here, the <a> element encloses completely the selection and needs to be explicitly copied to the // staging area. for (MarkupPointer i = _markupServices.CreateMarkupPointer(parentBlock, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterBegin); i.IsLeftOf(range.Start); i.Right(true)) { MarkupContext iContext = i.Right(false); if (iContext.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope && iContext.Element is IHTMLAnchorElement) { MarkupPointer j = _markupServices.CreateMarkupPointer(iContext.Element, _ELEMENT_ADJACENCY.ELEM_ADJ_BeforeEnd); if (j.IsRightOfOrEqualTo(range.End)) { // Copy the tag at posn. i to location stagedBlockInsertionPoint // This is openning tag. Closing tag will be // automatically added by MSHTML. MarkupPointer i1 = i.Clone(); i1.Right(true); _markupServices.Copy(i, i1, stagedBlockInsertionPoint); // Skip over the closing tag, so stagedBlockInsertionPoint points between the openning and the closing stagedBlockInsertionPoint.Left(true); } j.Unposition(); } } //move the range content into the staged position _markupServices.Move(range.Start, range.End, stagedBlockInsertionPoint); stagedBlockInsertionPoint.Unposition(); bool splitBlock = !RemoveEmptyParentBlock(range.Clone(), parentBlock, maximumBounds); // If the range endpoint NOT chosen as the insertion point lies in a different block element, and // that parent block element is now empty, then just delete the parent block. MarkupPointer shallowerPoint = (deeperPoint.IsEqualTo(range.Start)) ? range.End : range.Start; MarkupPointer nonInsertionPoint = _markupServices.CreateMarkupPointer(shallowerPoint); IHTMLElement otherParentBlock = nonInsertionPoint.GetParentElement( ElementFilters.CreateCompoundElementFilter(ElementFilters.BLOCK_ELEMENTS, new IHTMLElementFilter(IsSplitStopElement))); if (otherParentBlock.sourceIndex != parentBlock.sourceIndex) RemoveEmptyParentBlock(range.Clone(), otherParentBlock, maximumBounds); if (splitBlock) { //split the block at the insertion point SplitBlockForApplyingBlockStyles(insertionPoint, maximumBounds); } //move the staged block content back to the insertion point and setup the range pointers //to wrap the re-inserted block content. range.Start.MoveToPointer(insertionPoint); range.End.MoveToPointer(insertionPoint); _markupServices.Move(stagedBlockContent.Start, stagedBlockContent.End, insertionPoint); //Note: the range is now re-positioned around the same content, but all of the parent //elements have been closed to prepare for getting new parent block elements around the //range //convert the range's content into block regions and remove block elements that were //parenting the regions. InnerBlockRegion[] blockRegions = GetNormalizedBlockContentRegions(range); //update all of the block regions with the desired new block style element foreach (InnerBlockRegion blockRegion in blockRegions) { IHTMLElement newParentElement = WrapRangeInBlockElement(blockRegion.ContentRange, styleTagId); // The old parent element may have had an alignment set on it. IHTMLElement oldParentElement = blockRegion.OldParentElement ?? parentBlock; if (oldParentElement != null) { string oldAlignment = oldParentElement.getAttribute("align", 2) as string; if (!String.IsNullOrEmpty(oldAlignment)) newParentElement.setAttribute("align", oldAlignment, 0); } } } finally { range.Start.PopCling(); range.Start.PopGravity(); range.End.PopCling(); range.End.PopGravity(); } }
private void ExpandDamageToPreviousWord(MarkupRange damageRange) { MarkupRange previousWordRange = damageRange.Clone(); previousWordRange.Start.MoveUnit(_MOVEUNIT_ACTION.MOVEUNIT_PREVWORDBEGIN); // If we were already at the first word in the document, moving to the previous word would have put the // MarkupPointer outside the body element. if (previousWordRange.Start.GetParentElement(ElementFilters.BODY_ELEMENT) != null) { previousWordRange.Collapse(true); previousWordRange.End.MoveUnit(_MOVEUNIT_ACTION.MOVEUNIT_NEXTWORDEND); if (!previousWordRange.IsEmpty() && !_editorControl.IgnoreRangeForSpellChecking(previousWordRange)) { damageRange.Start.MoveToPointer(previousWordRange.Start); } } }
private bool ValidateTableSelection(IHTMLTable table, MarkupRange selectionMarkupRange, out bool tableFullySelected) { // assume table is not fully selected tableFullySelected = false; // first check to see that this is a "Writer" editable table if (!TableHelper.TableElementContainsWriterEditingMark(table as IHTMLElement)) return false; // get elemental objects we need to analyze the table IHTMLElement tableElement = table as IHTMLElement; MarkupRange tableMarkupRange = selectionMarkupRange.Clone(); tableMarkupRange.MoveToElement(table as IHTMLElement, true); // analyze selection bool selectionAtTableStart = tableMarkupRange.Start.IsEqualTo(selectionMarkupRange.Start); bool selectionAtTableEnd = tableMarkupRange.End.IsEqualTo(selectionMarkupRange.End); // is the table fully selected? if (selectionAtTableStart && selectionAtTableEnd) { tableFullySelected = true; return true; } else { MarkupRange selectionMarkupRange2 = selectionMarkupRange.Clone(); // is the selection bounded by the table IHTMLElement beginParentTable = selectionMarkupRange2.Start.SeekElementLeft(ElementFilters.CreateEqualFilter(tableElement)); IHTMLElement endParentTable = selectionMarkupRange2.End.SeekElementRight(ElementFilters.CreateEqualFilter(tableElement)); return beginParentTable != null && endParentTable != null; } }
private static void NormalizeBounds(ref MarkupRange bounds) { bool cloned = false; if (bounds.Start.IsRightOf(bounds.End)) { if (!cloned) { cloned = true; bounds = bounds.Clone(); } bounds.Normalize(); } MarkupContext ctx = bounds.Start.Right(false); while (ctx.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope && ElementFilters.IsBlockElement(ctx.Element)) { if (!cloned) { cloned = true; bounds = bounds.Clone(); } bounds.Start.Right(true); bounds.Start.Right(false, ctx); } }
private void SplitBlockForApplyingBlockStyles(MarkupPointer splitPoint, MarkupRange maximumBounds) { //find the split stop parent IHTMLElement splitStop = splitPoint.GetParentElement(new IHTMLElementFilter(IsSplitStopElement)); if (splitStop != null) { MarkupPointer stopLocation = _markupServices.CreateMarkupPointer(splitStop, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterBegin); if (maximumBounds.InRange(stopLocation)) { stopLocation.MoveAdjacentToElement(splitStop, _ELEMENT_ADJACENCY.ELEM_ADJ_BeforeEnd); if (maximumBounds.InRange(stopLocation)) { maximumBounds = maximumBounds.Clone(); maximumBounds.MoveToElement(splitStop, false); } } } MarkupHelpers.SplitBlockForInsertionOrBreakout(_markupServices, maximumBounds, splitPoint); }
public SelectionPreserver(MarkupRange selectedMarkupRange) { _preservedMarkupRange = selectedMarkupRange.Clone(); _preservedMarkupRange.Start.Cling = true; _preservedMarkupRange.End.Cling = true; }
public void HighlightSpelling(MarkupRange range) { // check spelling MshtmlWordRange wordRange; if (range == null) //check the whole document. { wordRange = new MshtmlWordRange(_htmlDocument, false, _filter, _damageFunction); } else { //range is invalid for some reason--damage committed while switching views, getting it later on the timer if (!range.Positioned) return; else if (range.Text == null || String.IsNullOrEmpty(range.Text.Trim())) { //empty range--on a delete for instance, just clear _spellingHighlighter.ClearRange(range.Start, range.End); _ignoredOnce.ClearRange(range); return; } else { MarkupRange origRange = range.Clone(); //here are the words to check wordRange = new MshtmlWordRange(_htmlDocument, range, _filter, _damageFunction); //check for emptiness at start and end, clear those _spellingHighlighter.ClearRange(origRange.Start, range.Start); _spellingHighlighter.ClearRange(range.End, origRange.End); _ignoredOnce.ClearRange(range); } } _spellingHighlighter.CheckSpelling(wordRange); }
private MarkupRange FindBoundaries(MarkupRange range) { MarkupRange newRange = range.Clone(); // WinLive 194115: Find out if the MarkupPointer is in a table cell (without going outside the PostBodyElement). IHTMLElementFilter tableCellFilter = ElementFilters.CreateCompoundElementFilter(ElementFilters.TABLE_CELL_ELEMENT, ElementFilters.CreateElementEqualsFilter(PostBodyElement)); IHTMLElement startElement = range.Start.GetParentElement(tableCellFilter); if (startElement == null || HTMLElementHelper.ElementsAreEqual(startElement, PostBodyElement)) { startElement = range.Start.CurrentBlockScope(); } IHTMLElement endElement = range.End.GetParentElement(tableCellFilter); if (endElement == null || HTMLElementHelper.ElementsAreEqual(endElement, PostBodyElement)) { endElement = range.End.CurrentBlockScope(); } IHTMLElement postBodyElement = PostBodyElement; if (startElement != null) { if (startElement != postBodyElement) newRange.Start.MoveAdjacentToElement(startElement, _ELEMENT_ADJACENCY.ELEM_ADJ_BeforeBegin); else { IHTMLElement previousBlock = newRange.Start.SeekElementLeft(ElementFilters.IsBlockOrTableCellElement); if (previousBlock == postBodyElement) newRange.Start.MoveAdjacentToElement(startElement, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterBegin); else newRange.Start.MoveAdjacentToElement(previousBlock, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterEnd); } } if (endElement != null) { if (endElement != postBodyElement) newRange.End.MoveAdjacentToElement(endElement, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterEnd); else { IHTMLElement nextBlock = newRange.End.SeekElementRight(ElementFilters.IsBlockOrTableCellElement); if (nextBlock == postBodyElement) newRange.End.MoveAdjacentToElement(endElement, _ELEMENT_ADJACENCY.ELEM_ADJ_BeforeEnd); else newRange.End.MoveAdjacentToElement(nextBlock, _ELEMENT_ADJACENCY.ELEM_ADJ_BeforeBegin); } } return newRange; }
public static MarkupRange ApplyInlineTag(MshtmlMarkupServices markupServices, _ELEMENT_TAG_ID tagId, string attributes, MarkupRange selection, bool toggle) { HtmlStyleHelper htmlStyleHelper = new HtmlStyleHelper(markupServices); // Aligning the with the behavior of Word, we will make <SUP> and <SUB> mutually exclusive. // That is, if you are applying <SUB> to a selection that has <SUP> applied already, we will first remove the <SUP>, and vice versa. // Wait, if empty and we're on the very end of the already existing formatting, then we just want to jump out and apply... MarkupRange selectionToApply = selection.Clone(); if (toggle) { // If already entirely inside the tag // If empty and just inside of the closing tag, then jump outside the closing tag // Else remove the tag // If already entirely outside the tag // If empty, apply the tag and put selection inside // If non-empty, then apply tag and reselect // If partially inside the tag // Remove the tag _ELEMENT_TAG_ID mutuallyExclusiveTagId = _ELEMENT_TAG_ID.TAGID_NULL; switch (tagId) { case _ELEMENT_TAG_ID.TAGID_SUP: mutuallyExclusiveTagId = _ELEMENT_TAG_ID.TAGID_SUB; break; case _ELEMENT_TAG_ID.TAGID_SUB: mutuallyExclusiveTagId = _ELEMENT_TAG_ID.TAGID_SUP; break; default: break; } if (selection.IsEmpty()) { // If the selection is empty and we're just inside the tagId closing tag (meaning that there is no text before the closing tag), // then we just hop outside of the tagId closing tag. bool exitScopeMatchesTagIdToBeApplied; MarkupPointer pointerOutsideTagIdScope = htmlStyleHelper.NextExitScopeWithoutInterveningText(selection, tagId, mutuallyExclusiveTagId, out exitScopeMatchesTagIdToBeApplied); if (pointerOutsideTagIdScope != null) { selectionToApply = markupServices.CreateMarkupRange(pointerOutsideTagIdScope, pointerOutsideTagIdScope); if (exitScopeMatchesTagIdToBeApplied) { return selectionToApply; } // else we still need to apply tagId } } // If a mutually exclusive tag is applied, then remove it. if (selectionToApply.IsTagId(mutuallyExclusiveTagId, true)) { selectionToApply.RemoveElementsByTagId(mutuallyExclusiveTagId, false); } // If this tag is already applied, then remove it and return. if (selectionToApply.IsTagId(tagId, true)) { selectionToApply.RemoveElementsByTagId(tagId, false); return selectionToApply; } } return htmlStyleHelper.ApplyInlineTag(tagId, attributes, selectionToApply); }
internal void AddDamage(MarkupRange range, bool includeAdjacentWords) { MarkupRange wordRange = range.Clone(); _wordHelper.MoveToWordStart(wordRange.Start); _wordHelper.MoveToWordEnd(wordRange.End); if (includeAdjacentWords) { ExpandDamageToAdjacentWords(wordRange); } _AddDamage(wordRange); }
private void ApplyBlockFormatToEmptySelection(MarkupRange selection, _ELEMENT_TAG_ID styleTagId, MarkupRange maximumBounds) { bool deleteParentBlock = false; //expand the selection to include the parent content block. If the expansion can cover the block element //without exceeding the maximum bounds, then delete the parent element and wrap the selection in the //new block element. If the maximum bounds are exceeded, then just wrap the selection around the bounds. IHTMLElementFilter stopFilter = ElementFilters.CreateCompoundElementFilter(ElementFilters.BLOCK_ELEMENTS, new IHTMLElementFilter(IsSplitStopElement)); MovePointerLeftUntilRegionBreak(selection.Start, stopFilter, maximumBounds.Start); MovePointerRightUntilRegionBreak(selection.End, stopFilter, maximumBounds.End); MarkupRange tmpRange = selection.Clone(); tmpRange.End.MoveToPointer(selection.Start); IHTMLElement startStopParent = tmpRange.End.GetParentElement(stopFilter); if (startStopParent != null) { tmpRange.Start.MoveAdjacentToElement(startStopParent, _ELEMENT_ADJACENCY.ELEM_ADJ_BeforeBegin); if (tmpRange.IsEmptyOfContent()) //the range from the selection the the block start is empty { tmpRange.Start.MoveToPointer(selection.End); IHTMLElement endStopParent = tmpRange.Start.GetParentElement(stopFilter); if (endStopParent != null && startStopParent.sourceIndex == endStopParent.sourceIndex) { tmpRange.Start.MoveAdjacentToElement(endStopParent, _ELEMENT_ADJACENCY.ELEM_ADJ_BeforeEnd); if (tmpRange.IsEmptyOfContent()) //the range from the selection the the block end is empty { tmpRange.MoveToElement(endStopParent, true); if (maximumBounds.InRange(tmpRange) && !(endStopParent is IHTMLTableCell)) { deleteParentBlock = true; //the parent has no useful content outside the selection, so it's safe to delete } } } } } //delete the block parent (if appropriate) and wrap the selection in the new block element. if (deleteParentBlock) { (startStopParent as IHTMLDOMNode).removeNode(false); } IHTMLElement newBlock = WrapRangeInBlockElement(selection, styleTagId); selection.MoveToElement(newBlock, false); }