public override void Invoke(MarkupContext context) { // Avoid parsing Html if required if (MightContainBlockquotes()) { Console.WriteLine($"Formatting blockquotes: {context.Html.Path}"); var htmlDoc = new HtmlDocument(); htmlDoc.LoadHtml(context.Html.Content); foreach (var quote in GetBlockquotes(htmlDoc)) { quote.AddClass("alert"); quote.AddClass("alert-primary"); quote.Attributes.Add("role", "alert"); } context.Html.Content = htmlDoc.DocumentNode.OuterHtml; } // Simple text check // Can return false positives but not false negatives bool MightContainBlockquotes() => context.Html.Content.Contains("<blockquote"); HtmlNodeCollection GetBlockquotes(HtmlDocument htmlDoc) { // Markdig converts quotes to: <blockquote><p>**Html-Content-Here**</p></blockquote> return(htmlDoc.DocumentNode.SelectNodes("//blockquote/p")); } }
/// <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 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 string GrowToAnchorParent(HTMLData htmlData) { if (htmlData.OnlyImageElement == null) { return(null); } string html; // Load up the html document from the clipboard to a document to examine the html about to be inserted MshtmlMarkupServices markupServices = new MshtmlMarkupServices(htmlData.HTMLDocument as IMarkupServicesRaw); MarkupRange range = markupServices.CreateMarkupRange(htmlData.OnlyImageElement, true); // look to see if this is a case where the inserted html is <a>|<img>|</a> MarkupContext markupContextStart = range.Start.Left(true); MarkupContext markupContextEnd = range.End.Right(true); // if that is the cause, change the html about to be inserted to |<a><img></a>| if (markupContextStart.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope && markupContextEnd.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope && markupContextStart.Element.tagName == "A" && markupContextEnd.Element.tagName == "A") { html = markupContextStart.Element.outerHTML; } else { html = htmlData.HTMLSelection; } return(html); }
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 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); } }
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; } } }
/*private void DumpBreakRegions(params ElementBreakRegion[] breakRegions) * { * foreach(ElementBreakRegion breakRegion in breakRegions) * { * String elementStartName = breakRegion.BreakStartElement != null ? breakRegion.BreakStartElement.tagName : ""; * String elementEndName = breakRegion.BreakEndElement != null ? breakRegion.BreakEndElement.tagName : ""; * String breakContent = breakRegion.ContentRange.Text; * if(breakContent != null) * breakContent = breakContent.Replace('\r', ' ').Replace('\n', ' '); * else * breakContent = ""; * Trace.WriteLine(String.Format("<{0}>{1}<{2}>", elementStartName, breakContent, elementEndName)); * } * }*/ /// <summary> /// Splits the specified range into regions based on a region break filter. /// </summary> /// <param name="range"></param> /// <returns></returns> private ElementBreakRegion[] SplitIntoElementRegions(MarkupRange range, IHTMLElementFilter regionBreakFilter) { ArrayList regions = new ArrayList(); MarkupRange blockRange = _markupServices.CreateMarkupRange(); blockRange.Start.MoveToPointer(range.Start); blockRange.End.MoveToPointer(range.Start); MarkupContext moveContext = new MarkupContext(); ElementBreakRegion currentRegion = new ElementBreakRegion(blockRange, null, null); while (currentRegion.ContentRange.End.IsLeftOf(range.End)) { if (moveContext.Element != null) { if (regionBreakFilter(moveContext.Element)) { //move the end of the region back before the break element to close this region currentRegion.ContentRange.End.Left(true); //save the closed region and start the next region currentRegion.BreakEndElement = moveContext.Element; regions.Add(currentRegion); currentRegion = new ElementBreakRegion(currentRegion.ContentRange.Clone(), moveContext.Element, null); currentRegion.ContentRange.Start.MoveToPointer(currentRegion.ContentRange.End); //move the region start over the break element currentRegion.ContentRange.Start.Right(true, moveContext); currentRegion.ContentRange.End.MoveToPointer(currentRegion.ContentRange.Start); } } currentRegion.ContentRange.End.Right(true, moveContext); } //save the last break region if (moveContext.Element != null && regionBreakFilter(moveContext.Element)) { //move the end of the region back before the break element to close this region currentRegion.ContentRange.End.Left(true); } if (currentRegion.ContentRange.End.IsRightOf(range.End)) { currentRegion.ContentRange.End.MoveToPointer(range.End); } regions.Add(currentRegion); return((ElementBreakRegion[])regions.ToArray(typeof(ElementBreakRegion))); }
/// <summary> /// Searches through the provided document for a start and end comment marker and then returns the fragment as /// a MarkupRange. /// </summary> /// <param name="document">The document to search.</param> /// <param name="startMarker">The comment text that marks the start of the fragment /// (e.g. <!--StartFragment--> ).</param> /// <param name="endMarker">The comment text that marks the end of the fragment /// (e.g. <!--EndFragment--> ).</param> /// <returns>The fragment as a MarkupRange or null if no valid fragment was found.</returns> private MarkupRange FindMarkedFragment(IHTMLDocument2 document, string startMarker, string endMarker) { MarkupPointer startFragment = null; MarkupPointer endFragment = null; MshtmlMarkupServices markupServices = new MshtmlMarkupServices((IMarkupServicesRaw)document); // Look for the markers in the document. foreach (IHTMLElement element in document.all) { if (element is IHTMLCommentElement && ((IHTMLCommentElement)element).text == startMarker) { startFragment = markupServices.CreateMarkupPointer(element, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterEnd); } else if (element is IHTMLCommentElement && ((IHTMLCommentElement)element).text == endMarker) { endFragment = markupServices.CreateMarkupPointer(element, _ELEMENT_ADJACENCY.ELEM_ADJ_BeforeBegin); } } if (startFragment == null || endFragment == null || !startFragment.Positioned || !endFragment.Positioned || startFragment.IsRightOf(endFragment)) { Trace.WriteLine("Unable to find fragment or invalid fragment!"); return(null); } // WinLive 251786: IE (and most other browsers) allow HTML like the following: // <p>This is a paragraph[cursor] // <p>This is a paragraph // However, when we use MarkupPointers to walk through this HTML, IE pretends there is a </p> at the end // of each of the above lines. This can cause issues when we copy part of this HTML somewhere else (e.g // everything after the [cursor]) and attempt to walk through both copies (e.g. during paste with keep // source formatting) at the same time. This holds true for some other elements, such as <li>s and <td>s. MarkupContext startContext = startFragment.Right(false); if (startFragment.IsLeftOf(endFragment) && startContext.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope && startContext.Element != null && ElementFilters.IsEndTagOptional(startContext.Element) && !Regex.IsMatch(startContext.Element.outerHTML, String.Format(CultureInfo.InvariantCulture, @"</{0}(\s[^>]*)?>\s*$", startContext.Element.tagName), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)) { startFragment.Right(true); } return(markupServices.CreateMarkupRange(startFragment, endFragment)); }
/// <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> /// Creates a MarkupRange that contains the entire provided document. /// </summary> /// <param name="document">The document to select.</param> /// <returns>A MarkupRange that contains the entire document.</returns> private MarkupRange SelectAll(IHTMLDocument2 document) { MshtmlMarkupServices markupServices = new MshtmlMarkupServices((IMarkupServicesRaw)document); MarkupRange entireDocument = markupServices.CreateMarkupRange(((IHTMLDocument3)document).documentElement, true); // Make sure the doctype and anything else outside the root element is selected too. MarkupContext context = entireDocument.Start.Left(true); while (context.Context != _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_None) { context = entireDocument.Start.Left(true); } context = entireDocument.End.Right(true); while (context.Context != _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_None) { context = entireDocument.End.Right(true); } return(entireDocument); }
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(); } }
public static void InsertContentIntoElement(string content, ISmartContent sContent, IContentSourceSidebarContext contentSourceContext, IHTMLElement element) { MshtmlMarkupServices MarkupServices = new MshtmlMarkupServices((IMarkupServicesRaw)element.document); //Note: undo/redo disabled for smart content since undo causes the HTML to get out of sync //with the inserter's settings state, so undo changes will be blown away the next time the //the inserter's HTML is regenerated. Also note that making this insertion without wrapping it //in an undo clears the undo/redo stack, which is what we want for beta. //string undoId = Guid.NewGuid().ToString(); MarkupRange htmlRange = MarkupServices.CreateMarkupRange(element, false); htmlRange.Start.PushCling(true); htmlRange.End.PushCling(true); MarkupServices.Remove(htmlRange.Start, htmlRange.End); htmlRange.Start.PopCling(); htmlRange.End.PopCling(); element.style.padding = ToPaddingString(sContent.Layout); if (sContent.Layout.Alignment == Alignment.None || sContent.Layout.Alignment == Alignment.Right || sContent.Layout.Alignment == Alignment.Left) { element.style.display = "inline"; element.style.marginLeft = "0px"; element.style.marginRight = "0px"; element.style.styleFloat = sContent.Layout.Alignment.ToString().ToLower(CultureInfo.InvariantCulture); } else if (sContent.Layout.Alignment == Alignment.Center) { element.style.styleFloat = Alignment.None.ToString().ToLower(CultureInfo.InvariantCulture); element.style.display = "block"; element.style.marginLeft = "auto"; element.style.marginRight = "auto"; } // Clear out any width on the overall smart content block, if the element is centered, we will add the width back in later // after we calcuate it from the childern, the current width value is stale. element.style.width = ""; //Note: we use MarkupServices to insert the content so that IE doesn't try to fix up URLs. //Element.insertAdjacentHTML() is a no-no because it rewrites relaive URLs to include //the fullpath from the local filesytem. //MarkupServices.ParseString() doesn't attempt to fix up URLs, so its safe to use. //We will now stage the new content into a MarkupContainer, and then move it into //the working document. MarkupPointer sc1 = MarkupServices.CreateMarkupPointer(); MarkupPointer sc2 = MarkupServices.CreateMarkupPointer(); //Create a temporary document from the html and set the start/end pointers to the //start and end of the document. MarkupServices.ParseString(content, sc1, sc2); IHTMLDocument2 doc = sc1.GetDocument(); MarkupRange stagingRange = MarkupServices.CreateMarkupRange(sc1, sc2); stagingRange.MoveToElement(doc.body, false); //IE7 hack: fixes bug 305512. Note that this will destroy the inner content of the element, //so make sure it is called before the refreshed content is inserted. BeforeInsertInvalidateHackForIE7(element); //move the content from the staging area into the actual insertion point. MarkupServices.Move(stagingRange.Start, stagingRange.End, htmlRange.End); if (sContent.Layout.Alignment == Alignment.Center) { MarkupContext mc = htmlRange.End.Right(false); MarkupRange range = MarkupServices.CreateMarkupRange(mc.Element, false); IHTMLElement[] childern = range.GetTopLevelElements(MarkupRange.FilterNone); int maxWidth = 0; foreach (IHTMLElement child in childern) { maxWidth = Math.Max(maxWidth, child.offsetWidth); } if (maxWidth != 0) { mc.Element.style.width = maxWidth; } } // Let the context provider know the smart content was edited. string contentSourceId, contentId; ContentSourceManager.ParseContainingElementId(element.id, out contentSourceId, out contentId); contentSourceContext.OnSmartContentEdited(contentId); }
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); }
/// <summary> /// Utility for printing the correct XHTML for a given MarkupContext. /// </summary> /// <param name="writer"></param> /// <param name="context"></param> /// <param name="text"></param> private static void printContext(HtmlWriter writer, MarkupContext context, string text, MarkupRange range) { switch (context.Context) { case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope: printElementStart(writer, context.Element); if (HtmlLinebreakStripper.IsPreserveWhitespaceTag(context.Element.tagName)) { // <pre> was losing whitespace using the normal markup pointer traversal method writer.WriteString(BalanceHtml(context.Element.innerHTML)); printElementEnd(writer, context.Element); range.End.MoveAdjacentToElement(context.Element, _ELEMENT_ADJACENCY.ELEM_ADJ_AfterEnd); break; } else { if (text != null) { writer.WriteString(trimHtmlText(text)); } break; } case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_ExitScope: if (text != null) { writer.WriteString(trimHtmlText(text)); } printElementEnd(writer, context.Element); break; case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_None: break; case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_NoScope: if (context.Element is IHTMLCommentElement || context.Element is IHTMLUnknownElement) { //bugfix: 1777 - comments should just be inserted raw. string html = context.Element.outerHTML; // bugfix: 534222 - embed tag markup generation issues if (html != null && html.ToUpper(CultureInfo.InvariantCulture) != "</EMBED>") { writer.WriteString(html); } } else { printElementStart(writer, context.Element); if (text == null && context.Element.innerHTML != null) { //Avoid MSHTML bug: in some cases (like title or script elements), MSHTML improperly //reports a tag as being NoScope, even through it clearly has a start and end tag with //text in between. To cover this case, we look for text in a noscope element, and add //it to the XML stream if it is detected. writer.WriteString(context.Element.innerHTML); } printElementEnd(writer, context.Element); } break; case _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_Text: if (text != null) { writer.WriteString(trimHtmlText(text)); } break; default: break; } }
public abstract void Invoke(MarkupContext context);
/// <summary> /// Navigates the editor's caret to the next editable region. /// </summary> /// <param name="direction"></param> private bool MoveCaretToNextRegion(MOVE_DIRECTION direction) { IHTMLElement nextRegion; _ELEMENT_ADJACENCY nextRegionAdjacency; bool preserveXLocation; if (direction == MOVE_DIRECTION.UP || direction == MOVE_DIRECTION.LEFT) { nextRegion = PreviousEditableRegion; nextRegionAdjacency = _ELEMENT_ADJACENCY.ELEM_ADJ_BeforeEnd; preserveXLocation = direction == MOVE_DIRECTION.UP; } else if (direction == MOVE_DIRECTION.DOWN || direction == MOVE_DIRECTION.RIGHT) { nextRegion = NextEditableRegion; nextRegionAdjacency = _ELEMENT_ADJACENCY.ELEM_ADJ_AfterBegin; preserveXLocation = direction == MOVE_DIRECTION.DOWN; if (nextRegion == null) { return(false); } MarkupPointer selectRegion = EditorContext.MarkupServices.CreateMarkupPointer(nextRegion, nextRegionAdjacency); MarkupContext mc = selectRegion.Right(false); if (mc.Context == _MARKUP_CONTEXT_TYPE.CONTEXT_TYPE_EnterScope && mc.Element is IHTMLElement3 && SmartContentSelection.SelectIfSmartContentElement(EditorContext, mc.Element) != null) { return(true); } } else { throw new ArgumentException("Unsupported move direction detected: " + direction); } IDisplayServicesRaw displayServices = (IDisplayServicesRaw)HTMLElement.document; IDisplayPointerRaw displayPointer; displayServices.CreateDisplayPointer(out displayPointer); IHTMLCaretRaw caret = GetCaret(); caret.MoveDisplayPointerToCaret(displayPointer); ILineInfo lineInfo; displayPointer.GetLineInfo(out lineInfo); if (nextRegion != null) { MarkupPointer mp = EditorContext.MarkupServices.CreateMarkupPointer(nextRegion, nextRegionAdjacency); DisplayServices.TraceMoveToMarkupPointer(displayPointer, mp); try { caret.MoveCaretToPointer(displayPointer, true, _CARET_DIRECTION.CARET_DIRECTION_SAME); if (preserveXLocation) { POINT caretLocation; caret.GetLocation(out caretLocation, true); caretLocation.x = lineInfo.x; uint hitTestResults; displayPointer.MoveToPoint(caretLocation, _COORD_SYSTEM.COORD_SYSTEM_GLOBAL, nextRegion, 0, out hitTestResults); caret.MoveCaretToPointer(displayPointer, true, _CARET_DIRECTION.CARET_DIRECTION_SAME); } //BEP: using this line causes scrolling (nextRegion as IHTMLElement2).focus(); (nextRegion as IHTMLElement3).setActive(); return(true); } catch (Exception e) { Debug.Fail("Unexpected exception in MoveCaretToNextRegion: " + e.ToString()); } caret.MoveCaretToPointer(displayPointer, true, _CARET_DIRECTION.CARET_DIRECTION_SAME); } return(false); }
/// <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); }