探索黑客技术攻防,实战研究与安全创新

导航菜单

Internet Explorer 完全解析

 Markup 服务是一组可以允许你操作 HTML 文档内容的对象和接口。  本文将介绍这些对象和接口。

标签(tags)、元素  (elements)首先,在此引入一些概念帮助理解 Markup 服务。  第一个概念就是 html tag(标签)和它在浏览器里面对应的表现形式,也就是我们所知的 element(元素)。

查看 HTML 内容时,区分标签和元素是很重要的。HTML 内容包含各种标签,例如<B>。  这个标签会指定文档(document)的文本的一个表达形式(加粗)。当浏览器访问一个页面时

HTML 解析器会读取文件内容,并且从 tag 中解析生成 element。这些就是可以作为一个被编程修改的对象的元素。当然,这也是 Markup 服务可以操作的元素。

例如,一个 HTML 文件可能有如下内容:


<P>First<P>Second

当浏览器的解析器读取这个文本时,内部的元素配置会让文档的形式变为类似这样的:(当然,有时候也可以称作标准化,主要是我习惯这么称呼)

<HTML><HEAD><TITLE></TITLE></HEAD><BODY> 

<P>First</P><P>Second</P></BODY></HTML>

或者可以说,解析器将 HTML 内容转为了元素。在这个过程中,为了内容完整,有一些原始文档没有的内容加进去了,例如 html、head、title、body 会自动的被解析器构造出来。同时,解析器遇到第二个 p(段落)的时候,会自动的把第一个 p 给封闭起来。尽管你的文件没有封闭 p 标签,但是 IE 将会自动的给每个元素都加上封闭标签。还有必要但是你没有写入的标签,比如<html>、<body>,都会自动的被 IE 添加上,当然,他们的封闭标签也会被加上。

第二个需要注意的概念是 tree 和 stream(树、流)的区别,比如:

My <B>dog</B> has fleas


这里有“My dog has fleas”和一对 b 标签,在这个例子中,可以被转化为如下的树。text 被当为树叶,element 被作为内节点。

         ROOT 

            | 

      +-----+------+ 

      |     |      | 

     "My"   B  "has fleas." 

            | 

          "dog"

通过把文档转为 tree,所有的操作都会变为类似对树的操作,例如增删孩子节点。提供此类操作的 API 被称为 Tree Services。

当然,自 IE4.0 之后,元素的模型操作比简单的树更强悍,比如这个例子:


Where do <B>you <I>want to</B> go</I> today?

B、I 的范围互相交叉,这是一个部分互相交叉元素的例子,但是在 HTML 里面却很常见。因此,Markup Services 不提供类似树的操作,而是为内容的控制暴露了一个基于流操作的模型。因此,Markup Service 实际上是用来避免产生这种模型层间的疑惑的,因为这个时候,浏览器便不再使用 Tree Service,而是使用 Markup Service 来控制基于流操作的模型。 在基于树的模型中,网页内容被当作树的节点来处理,每个元素,或者一块 Text 都是一个节点。节点通过这种类似对树的操作方式来操作,例如从父节点中增删一个子节点。

在基于流的模型的内容操作方式中,比如现在说的这种通过 Markup Service 来操作的,文档的内容会通过使用类似迭代器的对象来操作。  比如使用 Markup Pointer,然后文档的内容则通过类似 Range 的操作来控制。  这个就像是在处理上面Where do <B>you <I>want to</B> go</I> today?的例子一样,这些带有部分重叠的元素通过两个 Markup Pointer来区分,每个 Markup Pointer 指定着 Tag 从哪儿开始,Tag 到哪儿结束。基于流的模型是基于树的模型的一个超集。

>

QQ截图20160906172718.png

有效和无效的文档

另一个让 Markup Service 更加容易理解的概念就是创建和操作无效文档的过程。

注意之前“My dog has fleas”的例子都可能不会被认为是一个有效的 HTML 文档。如果把它拷贝到文件中,然后在浏览器中打开的话,浏览器的解析器有可能会生成一些完全不一样的文档内容。例如,Internet Explorer 解析器可能将这个文档解析成这样:

<HTML><HEAD><TITLE></TITLE></HEAD> 

<BODY>My <B>dog</B> has fleas.</BODY></HTML>

 

解析器会试图读取一个指定的输入,然后通过它生成一个有效的 HTML 文档。最简单的有效HTML 文档至少要有 html、head、title 和 body 四个元素。当你提供的内容中没有这些元素时,解析器会自动为你建立这些,然后把它们放到合适的位置上。

在文档解析完成甚至是还没解析完成的时候,你都可以使用 Markup Service 来用任意方法删除或者重新排列文档内容。例如,你可以整块删除 html 和/或 body 元素。你可以将 head 放到 body 里面,但是这些样子的文档都会被认为是无效文档。

上面这些描绘出来了基本的 Marup Service 的概念,现在可以更进一步的看一下 Markup Service 的接口了。最好的入手点当然是 IMarkupService 接口。这个接口是所有的  Markup Service 的初始点,例如 IMarkupContainer 和 IMarkupPointer 也不例外。IMarkupService界面也包含了所有的可以修改文档中的元素的方法。

你可以通过 QueryInterface 来指定 IID_IMarkupService 来获取 IMarkupServices。

元素可以不通过 IMarkupContainer 的上下文来创建,但是如果需要将元素和文本互相关联起来的话,IMarkupContainer 还是必须要用的。

下面的例子将介绍如何使用 IMarkupServices::CreateMarkupContainer 从IMarkupServices 中创建一个 IMarkupContainer。


HRESULT CreateMarkupContainer( 

    IMarkupContainer **ppContainer 

);

最开始,新创建的 IMarkupContainer 不会包含有任何的 Markup。而且,也不会有 html、head、body 元素。所以,IMarkupContainer 的最初状态不是像是由解析器解析一个空文件的时候的样子(解析空文件的时候就会自动产生上述元素)。

正常情况下,IMarkupContainer 用来存储等待加入主 IMarkupContainer 的元素。主IMarkupContainer 是一个浏览器用来承载 HTML 解析之后内容的东西。你可以通过在一个HTML 文档上执行 QueryInterface IID_IMarkupContainer 操作来获取主IMarkupContainer。  例如你可以从 IID_IMarkupContainer 获取 IHTMLDocument2 接口。 MarkupPointer

IMarkupPointer 不是 IMarkupContainer(这个就是一个文档)的内容的某一部分。使用IMarkupPointer 的主要目的是指定文档中的某个特定位置。比如下面这个例子:

p1 指针表示 IMarkupPointer 的位置,尽管 p1 指在 d 和 o 之间,但是这个并不是说这里有任何其他的看不见的文字在文档里面,或者例子里面这个内容已经被修改了。文档里面可以存在任意多个指针,这些指针和文档是独立的,也就是说根本不需要也不会修改文档。

Markup 指针被放在了文档内容中间的某个地方,这些地方可以是:1、一个元素开始生效的区域(作用域开始);2、一个元素中止生效的区域;3、文本。因此,Markup 指针更像是编辑器里面的脱字符(|  ,或者通俗的叫光标,一闪一闪的这个东西)。因为 Markup 指针自己并不是文档内容,如果他们指向 HTML 内容中的同样的位置,这样他们也是不能互相区分开的。

也就是说,如果两个 Markup 指针都指到一个地方,要区分哪个是左,哪个是右是不可能的。

只能说,他们都指在了内容的同一个地点上。

你可以通过  IMarkupServices::CreateMarkupPointer  方法来创建一个 Markup 指针。

HRESULT CreateMarkupPointer( 

    IMarkupPointer **ppPointer 

);

定位 Markup 指针

当一个 Markup 指针被创建的时候,它将处于一个特殊的状态——未指向状态,意思就是它事实上没指向任何内容。你可以使用这三个方法来把一个 Markup 指针放到一个 Markup 上。

MarkupPointer::MoveAdjacentToElement 

IMarkupPointer::MoveToContainer 

IMarkupPointer::MoveToPointer 

IMarkupPointer::MoveAdjacentToElement

 

方法接收 2 个参数,  一个 IHTMLElement 和一个枚举量,指定要放置指针的那个元素的相对偏移。这个枚举量有以下 4 个值。

HRESULT MoveAdjacentToElement( 

    IHTMLElement *elementTarget, 

    ELEMENT_ADJACENCY 

); 

    enum ELEMENT_ADJACENCY { 

         ELEMENT_ADJ_BeforeBegin 

         ELEMENT_ADJ_AfterBegin 

         ELEMENT_ADJ_BeforeEnd 

         ELEMENT_ADJ_AfterEnd 

    };

因此,把 p1 放到 b 结束前(ELEMENT_ADJ_BeforeEnd)的话,差不多就是这个结果:

My <B>dog[p1]</B> has fleas.

现在考虑如下例子:

a<B>[p1]<I>b</I></B>c

p1 现在可以说是放在 b 刚开始的地方,或者放在 i 开始之前。这两个描述方式都对,所以Markup 指针放置的位置指定方式是多种多样的。 另一个方式来放置一个 Markup 指针的方式是使用  IMarkupPointer::MoveToContainer  方式。这个方法会把一个 IMarkupContainer 接口和一个决定指针位置是在 IMarkupContainer开始还是结束地方的布尔值常量。

HRESULT MoveToContainer( 

    IMarkupContainer *containerTarget, 

    BOOL fAtStart 

);

因此,你可以把一个指针放在一个文档的最边缘处,例如[p1]<HTML><BODY>a<B><I>b</I></B>c</BODY></HTML>[p2]p1 在最左,而 p2 是最右。第三个方式是使用 IMarkupPointer::MoveToPointer 把一个指针移动到另一个已经定位过的 IMarkupPointer 的位置上。

HRESULT MoveToPointer( IMarkupPointer *pointerTarget );

通常,IMarkupPointer::MoveToPointer 在一个指针用来检查环绕元素时用来记录这个指针指向的位置。

比较指针位置

可以通过 IMarkupPointer 提供的一组函数来比较两个 Markup 指针的相对位置,函数列举如下:

HRESULT MoveToPointer( IMarkupPointer *pointerTarget );

 因此,当你像知道 p1 是否与 p2 不等,而且在 p2 的左边的时候,就可以这么用: BOOL fResult;


MarkupPointer * pointer 1, * pointer 2; 

.. 

[p1]->IsLeftOf( pointer2, & fResult ); 

if (fResult) 

{ 

    // [p1] is to the left of pointer2 

}

导向指针

当一个 IMarkupPointer 指针被放置在一个 IMarkupContainer 中时,你可以使用它来检查环绕内容,并且/或者将它移动到那个内容之外。  IMarkupPointer::Left、 IMarkupPointer::Right 两个方法可以做到这个。

HRESULT Left( 

    BOOL fMove, 

    MARKUP_CONTEXT_TYPE pContextType, 

    IHTMLElement **ppElement, 

    long *plCch, 

    OLE_CHAR *pch 

); 

HRESULT Right( 

    BOOL fMove, 

    MARKUP_CONTEXT_TYPE pContextType, 

    IHTMLElement **ppElement, 

    long *plCch, 

    OLE_CHAR *pch 

);

除了第一个参数之外都是可选的,fMove 参数控制着指针是否穿过环绕的内容。如果它的值是FALSE,指针不会移动,这里代表着环绕的内容。如果是 TRUE,这里不仅会描述环绕的内容,还会把指针从这个环绕内容上移动过去。

也就是说,如果你想知道一个指针的左边是什么,尽管调用  IMarkupPointer::Left  就可以了。右边也是,换成 Right 即可。  pContextType 参数返回挨着 Pointer 后面的内容。 以下是可选的内容类型:

指针左边或者右边没有内容,这个仅当指针指向IMarkupContainer最左或者最右的时候会用到。给定方向上的内容是文本。

CONTEXT_TYPE_None 

CONTEXT_TYPE_Text


给定方向上的元素正在进入一个区域(scope)。也就是CONTEXT_TYPE_EnterScope 说,如果向左看是一个终止 tag(带/的  tag),向右看是一个起始 tag。

在给定方向上,一个元素即将离开一个区域。也即,向左CONTEXT_TYPE_ExitScope 看的时候是一个起始 tag,而朝右看是一个终止 tag。

给定的选区中有一个无区域元素,你不能用CONTEXT_TYPE_NoScope IMarkupPointer指向这类元素,例如  br。

如果 ppElement 参数是非 NULL 的话,那么上下文的类型就是 EnterScope、ExitScope、NoScope 中的一种,ppElement 参数会返回进入、退出、无 scope 的元素。

如果上下文是 Text,pCch 和 pch 参数就是有意义的。pCch 参数提供这三个主要作用:

它限制了 IMarkupPointer::Left 或者 Right 会查询的字数。

它限制了给出方向上应该有多少文本实际存在。

它描述了 pch 参数会指向多大的缓冲区(如果它指向的内容是非空的话)

pCch 参数可以是 NULL,或者-1  。这两个值表示  IMarkupPointer::Left  或者 IMarkupPointer::Right 应该查询任意数量的文字,直到找到下一个无 scope 的元素或者找到某个元素的 scope 位置。

IMarkupPointer::Left  和  IMarkupPointer::Right 两个方法提供了遍历文档的功能。要确定IMarkupPointer 挡墙指着哪儿,使用如下 IMarkupPointer::CurrentScope 方法:


HRESULT CurrentScope( IHTMLElement **ppElementCurrent ); 

[p1]Where [p2]<I>do </I>[p3]<B>you <BR>[p4]want</B> to go today[p5]?


比如上面的"Where do you want to go today?"例子,  p1 使用IMarkupPointer::CurrentScope 的话,获取的值是 NULL,因为它的左边没有任何未结束的起始 tag。  而 p4 则是<B> tag。注意 br 是一个无 scope 类型的 tag。

指针重力

一般地,当一个文档被修改之后,文档中之前的那些指针还是停在操作发生之前的位置,比如下面这个有 2 个指针插入的文档:

abc[p1]defg[p2]hij


现在文档内容发生了变化,XYZ 插入了 e 和 f 之间,现在文档的内容如下:

abc[p1]deXYZfg[p2]hij


注意 p1 和 p2 还是指向操作前的同样的文本。比如下面的例子:

x[p1]y


现在考虑一下,如果 Z 插在了 x、y 中间是什么情况。记住指针并不会成为内容的一部分,因此x、y 是互相挨着的。在插入之后,有可能有如下两个情况:

x[p1]Zy 

xZ[p1]y


现在就需要有重力这个设定了。比如,通常当内容准确地插入了指针所在的位置的时候,指针的终止区域判定就会编的有歧义。通过引入重力设定,可以消除这种歧义。左重力会让指针定位到新插入的内容的左边,右重力下则是右边。

重力的不仅仅会影响到文本,还会影响到元素的插入,例如:

a[p1,right][p2,left]b


这里,p1 有右重力,p2 有左重力,如果 b 的周围插入了一个<B>标签会怎样?结果是:

a[p1,right][p2,left]b


注意现在指针是如何从之前的相对位置上转换成现在的样子的。插入 B  时这两个指针的位置的移动方向都是有歧义的。

默认的重力是左重力,你可以通过 IMarkupPointer 接口的如下方法来设置 IMarkupPointer的重力值。

enum POINTER_GRAVITY { 

    POINTER_GRAVITY_Left, 

    POINTER_GRAVITY_Right 

}; 

HRESULT Gravity(
 
POINTER_GRAVITY *pGravityOut 
); 
HRESULT SetGravity( 
    POINTER_GRAVITY newGravity 
); 
指针粘滞(cling)
有如下 Markup:
[p2]ab[p1]cdxy


现在考虑一下,当之前这个例子中,bc 两个字被移动到 x、y 中间的时候,p1 会发生什么?

可能答案有两种:

1、 [p2]a[p1]dxbcy

2、 [p2]adxb[p1]cy

这两个例子里面,可以确定的是 p2 没有受到影响,因为它并不在被操作的部分附近。上面两个结果中,(1)里面的 p1 并没有 IMarkupPointer::Cling,  而(2)则是有设置

IMarkupPointer::Cling。  IMarkupPointer::Cling 设置的结果导致了当一部分内容移动的时候,这个内容中间被 Cling 的部分也会跟着移动。不管内容移动到哪儿,有MarkupPointer::Cling 的指针都会在那块内容中。

但是,这个很有可能产生歧义。比如带有 IMarkupPointer::Cling 的 p1: a[p1]bcxy

如果 b 被移动到了 x、y 中间,p1 是否应该跟着 b 走呢?因此,这里就要用到之前说的重力。

如果 p1 有右重力,那么它会跟着 b 跑,如果是左重力,那么就会跟着它左边的内容,也就是a,而不会跟着 b 跑。

如果 p1 所在的内容被删除了,IMarkupPointer::Cling 依然会控制指针的目标。比如下面的例子: ab[p1]cd

如果 b、c 被删除了,而且 p1 没有 IMarkupPointer::Cling,  p1 会继续在文档中,夹在还剩下来的,环绕着它的内容里面: a[p1]d

如果 p1 有 IMarkupPointer::Cling,这个时候 p1 就会变成未指定位置的状态,就像已经被删除一样。  (p1 此时虽然被从文档里面移除removed   了,但是它本身并没有被删除destroy,所以以后也可以重用。  这个设计理念导致出漏洞的话,也一样会被"重用"。)

IMarkupPointer::Cling 可以通过 IMarkupPointer::SetCling 来设置,IMarkupPointer::Cling来查询。

HRESULT Cling( 

    BOOL *pClingOut 

); 

HRESULT SetCling( 

    BOOL NewCling 

);

新建元素

可以通过 IMarkupService::CreateElement 来创建新元素,

enum ELEMENT_TAG_ID { 

    TAGTADID_A, 

    TAGTADID_ACRONYM, 

        .. 

    TAGTADID_WBR, 

    TAGTADID_XMP 

}; 

HRESULT CreateElement( 

    TAG_ID tagID, 

    OLECHAR *pchAttrs, 

    IHTMLElement **ppNewElement 

);


例如,IMarkupServices::CreateElement ( TAGID_B, "id=anID", & pElement )将会创建一个 元素,而且 IHTMLElement::id 的属性会设置为 anID,当然,这里的属性项是可选的。在元素建立之后也是可以设置属性的,但是在创建元素时就指定属性的话,会让 Internet Explorer处理时有更高的效率。也有一些属性是只能在元素创建时指定的。 还可以通过克隆一个已经存在的元素,使用 IMarkupService::Clone 即可:


HRESULT CloneElement( 
    IHTMLElement *pElementCloneElementMe,
 
        IHTMLElement **ppNewElement 
);


插入一个元素

通过调用 IMarkupServices::InsertElement 可以插入一个元素。

HRESULT InsertElement( 

    IHTMLElement *pElementInsertThis, 

    IMarkupPointer *pPointerStart, 

    IMarkupPointer *pPointerFinish 

);


pPointerStart 描述了元素从哪里开始进入一个 Scope,pPointerFinish 描述了元素从哪里开始离开 Scope。当前正准备插入的元素必须是一个不在当前文档中的元素,而且两个指针都必须在同一个 IMarkupContainer 中定位。比如,假如调用 IMarkupServices::InsertElement入一个B元素,指针如下:My [pstart]dog[pend] has fleas. tyle="text-align: left; text-indent: 2em;"/>

插入的结果将在文档中体现如下:


My [pstart]<B>dog[pend]</B> has fleas.


至于什么新元素可以插入到哪儿,这个倒没有什么严格限制。因此,你甚至可以插入 n 个BODY 到文档里面,或者插入 n 个B  到文档的 head 部分。但是,如果你的文档最终是要用来显示出来的话,这个状态是未定义的,而且会导致 Markup Service 发生变化。

删除一个元素

删除一个元素并不需要使用 Markup  指针。调用 IMarkupService::RemoveElement,然后传入要删除的元素就可以了。


HRESULT RemoveElement( IHTMLElement *pElementRemoveThis );


要操作的元素必须要在文档里面,操作完成之后,元素就不在文档里了,因此是可以再次被插入的。

注意 要删除一个元素,然后把它插入到同一个位置上,你必须在删除之前把 Markup 指针插入到紧挨着这个元素区域的开始和结束位置。这个情况下,Markup 指针将记录该元素在该Markup 里影响到的范围。接下来 Markup 指针就可以被用来重新插入这个元素。当然,需要确保的是这个指针没有 IMarkupPointer::Cling 属性,因为它们可能在元素被移除时变成未定位的状态。

要向 Markup 中插入文本,可以使用 IMarkupServices::InsertText 函数。

HRESULT InsertText( 

    OLECHAR *pch, 

    long cch, 

    IMarkupPointer *pPointerTarget 

);


这个函数只接收单单一个 IMarkupPointer,然后把 text 插入到 markup 里面。Markup 指针在插入之后的位置(包括新插入的文本的位置也是)取决于 IMarkupPointer 的重力属性。

cch 参数可以设置为-1,  这个表示这个函数应该认为插入的文本是以 NULL 终止的。

另外一提,Internet Explorer 中的 cch 大多数是指  count of char 的意思。

移除内容

你可以使用 IMarkupContainer::Remove 来移除 IMarkupContainer 中一片连续区域。

HRESULT Remove( 

    IMarkupPointer *pPointerSourceStart, 

    IMarkupPointer *pPointerSourceFinish 

);


这里提供了两个 Markup 指针,一个指定从哪儿开始删除,另一个指定删除区域的末尾。所有这两个指针中间的文本内容都会被删除,而且,所有完全落入这个区域的 Markup 都会被删除,任何起始早于 Start、终止晚于 End 的 Markup 不会被删除,例如:
    

 <------------------- b -------------------> 

 <--------- i -----------> <---------- u -----------> 

a<I>b<B>c[pstart]d<S>e</I>f<U>g</S>h[pend]hi</B>j</U>kl 

                  <----- s ------->          

当调用 IMarkupServices::Remove 之后,结果变成了: 
     <------------- b -------------> 
 <------- i --------><------- u --------> 
a<I>b<B>c[pstart]</I><U>[pend]hi</B>j</U>kl

注意,现在 s 元素彻底小时了,i、u 还在文档里,尽管它们的 tags 的一部分在移除区域的中间。元素 b 包含整个删除区域,因此它也是不受影响的。

替换内容

前两个例子可以用来删除和插入内容,整合这两个操作可以用来替换内容,例如:

int MarkupSvc::RemoveNReplace( 

    MSHTML::IHTMLDocument2Ptr pDoc2, 

    _bstr_t bstrinputfrom, _bstr_t bstrinputto) 

{ 

    HRESULT              hr = S_OK; 

    //IHTMLDocument2 *   pDoc2; 

    IMarkupServices  *   pMS; 

    IMarkupContainer *   pMarkup; 

    IMarkupPointer   *   pPtr1, * pPtr2; 

    TCHAR            *   pstrFrom = _T( bstrinputfrom ); 

    TCHAR            *   pstrTo = _T( bstrinputto ); 

    pDoc2->QueryInterface( IID_IMarkupContainer, (void **) & 

pMarkup ); 

    pDoc2->QueryInterface( IID_IMarkupServices, (void **) & pMS ); 

    // need two pointers for marking 

    pMS->CreateMarkupPointer( & pPtr1 ); 

    // beginning and ending position of text. 

    pMS->CreateMarkupPointer( & pPtr2 );  

    // 

    // Set gravity of this pointer so that when the replacement text 

    // is inserted it will float to be after it. 

    // 

    pPtr1->SetGravity( POINTER_GRAVITY_Right ); // Right gravity set 

    // 

    // Start the search at the beginning of the primary container 

    // 

    pPtr1->MoveToContainer( pMarkup, TRUE ); 

    for ( ; ; ) 

    {
 
       hr = pPtr1->FindText( (unsigned short *) pstrFrom, 0, pPtr2, 
NULL ); 
        if (hr == S_FALSE) // did not find the text 
            break; 
        // found it, removing.. http://nul.pw 
        pMS->Remove( pPtr1, pPtr2 ); 
        //inserting new text 
        pMS->InsertText( (unsigned short *) pstrTo, -1, pPtr1 ); 
    } 
    if (hr == S_FALSE) return FALSE; 
    else return(TRUE); 
}


移动内容

你可以通过 IMarkupServices::Move 方法来把一组区域内的内容移动到另一个地方。

HRESULT Move( 

    IMarkupPointer *pPointerSourceStart, 

    IMarkupPointer *pPointerSourceFinish, 

    IMarkupPointer *pPointerTarget 

);


IMarkupServices::Move 接受 3 个 Markup 指针,2 个用来指明要移动的原始位置,第三个指定目标地点。范围的影响可以参考 IMarkupServices::Remove 操作的。在 Source 区域内的内容将被移动到 Target 指定的位置。

所有被 Source 范围包括起来的内容都会原样移动到 Target 去。也就是说,这些元素的信息都会被保留。在区域外的元素不会受到影响,也不会被弄到目标地址上。但是,和区域部分重叠的内容会被克隆,它们的IMarkupService::CloneElement 会被移动到 Target 上。因此,之前 Move 操作的例子中,如果这个区域改为移动的话:X[pdest]Y

结果会是:

X[pdest]<I'>d<S>e</I'>f<U'>g</S>h</U'>Y


注意,pdest 在新插入的移动的内容的左边,这是因为它有左重力。而且还有 I'和 U'元素,他们是原来的 I、U 元素的克隆。因为元素只可以存在于一个 Markup 中,而且必须在一Markup 中影响到一个连续的范围。但是 s 这个元素却不会被MarkupService::CloneElement 影响到,这是因为 s 元素在移动时已经被 start 和 end 两个指针完全环绕了。

注意  经常在一次移动(或者一次拷贝)之后,你会需要两个指针指向新插入的内容的左边和右边。要实现这个的话,在 Move 之前创建 2 个 Markup 指针,一个设置为左重力,一个设置为右重力,右重力的那个指针会指向移动/复制的内容的右边,左重力的当然是指向左边。

移动操作的目标可以在 Source 开始和 End 区域中间。

复制内容

使用 IMarkupServices::Copy 可以复制一个内容区域。


HRESULT Copy( IMarkupPointer *SourceStart, IMarkupPointer *SourceEnd,IMarkupPointer *Target);


对目标 Markup 来说,Copy 的影响和 Move 一样,不会影响到源。

相关推荐