Click to return to the Reusing Browser Technology home page    
IMarkupServices Interface    
Web Workshop  |  Reusing Browser Technology

Introduction to Markup Services


Markup Services is a set of interfaces and objects that allow you to manipulate the contents of an HTML document. This article introduces these interfaces and objects.

Tags Versus Elements

A few concepts help to make Markup Services easier to understand. One such concept is the idea of an HTML tag versus its representation inside the browser, which is known as an element.

It is important to distinguish between tags and elements in viewing HTML content. HTML content includes tags, such as <B> and </B>, that specify the representation of text in a document. When a page is accessed by the browser, the HTML parser reads the contents of the file and creates elements from the tags. It is the elements, as objects, that you can program. Similarly, it is the elements that Markup Services can manipulate.

For example, an HTML file might contain the following text:

<P>First<P>Second

However, when the browser's parser reads this text, the internal configuration of elements making up the state of the document looks like the following:

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

In other words, the parser turns the contents of the HTML file into elements—in this case, a few more than existed in the original file. Note that the HTML, HEAD, TITLE, and BODY elements were automatically constructed by the parser for completeness. Furthermore, the parser ended the first paragraph element when the second was introduced. Although your HTML file might not include end tags, the Microsoft® Internet Explorer representation of your file automatically includes end tags for each element. In addition, any required elements not included in your file, such as <HTML> and <BODY>, will be automatically inserted into Internet Explorer's representation of the file; both begin tags and end tags will be included.

The second concept worth noting is the idea of a tree versus a stream. Consider the following document:

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

This document consists of the text "My dog has fleas." and a single bold (B) element. The bold element begins just before the letter d in dog and ends right after the letter g.

This example could be modeled as a tree, with text as the leaves and elements as interior nodes.

               ROOT
                |
          +-----+------+
          |     |      |
         "My"   B  "has fleas."
                |
              "dog"

By modeling a document as a tree, all that would be needed to arbitrarily manipulate it would be tree-like operations, such as adding/removing children. An application programming interface (API) for such a model most likely would be called Tree Services.

However, the type of content that Internet Explorer 4.0 and later is capable of modeling is more powerful than simple trees. Consider the following example:

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

This document has B and I (italic) elements. Everything between the <B> and </B> tags will display as bold, and everything between the <I> and </I> tags will display as italic. However, it is impossible to model this document as a tree, because the I element is not properly nested under the B element. The B element ends before the I element ends, and it begins before the I element begins. This is an example of partially overlapping elements, which are typical of some HTML content.

Because of this, Markup Services does not provide tree-like manipulation operations. Instead it exposes a stream-based model for content manipulation. Thus, instead of using Tree Services to refer to this kind of data structure, Markup Services is used to avoid confusion between the two models.

A tree-based model of content manipulation is one where the content is expressed as nodes in a tree, and each element or chunk of text is a node. The nodes are manipulated with tree-like operations, usually the insertion and removal of child nodes from parent nodes.

A stream-based model for content manipulation, such as that expressed by Markup Services, is one where the content of the document is navigated by the use of iterator-like objects, such as markup pointers, and the content is manipulated by range-like operations. This was demonstrated by the preceding "Where do you want to go today?" example, which included partially overlapping elements and the use of two markup pointers, each specifying where the begin and end tags are to be located. This stream-based model is a super-set of the tree-based model.

Valid and Invalid Documents

Another concept that makes Markup Services easier to understand is that of creating and manipulating invalid documents.

Notice that the previous "My dog has fleas." example might not be considered a valid HTML document by all browsers. If that example were to be copied to an HTML file (a text file ending with .html, for example) and loaded into an HTML browser, the browser's parser would produce a significantly different document. For example, the Internet Explorer 4.0 parser would read this document and then represent it internally as follows:

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

The parser reads a given input and tries to produce a valid HTML document. At a minimum, a valid HTML document must have HTML, HEAD, TITLE, and BODY elements. The parser creates these elements for you, and puts them in their proper locations.

You can use Markup Services to Remove or rearrange the content of the document in an arbitrary fashion, once the parser has completed parsing the document or even before the document is completely parsed. For example, you can remove the HTML and/or BODY elements entirely. You could place the HEAD element inside the BODY element. Such documents are termed invalid documents.

This illustrates a fundamental Markup Services feature that is capable of creating and manipulating invalid documents. This feature provides maximum flexibility to a programmer, and allows a document to be manipulated without the arcane and complex rules required by an HTML parser. Thus, you can temporarily alter a document through invalid states on the way to creating a valid document.

IMarkupServices

Having understood the fundamental concepts of Markup Services, you now are ready to take a closer look at Markup Services interfaces. The best place to start is the IMarkupServices interface. This interface is the starting point for all the MarkupServices objects, such as MarkupContainer and MarkupPointer. The IMarkupServices interface also contains all the methods for performing actual manipulations of elements of the document.

You can perform a QueryInterface operation on a document object for IID_IMarkupServices to obtain IMarkupServices.

MarkupContainer

While elements can be created without the context of a MarkupContainer, in order to relate elements and text to each other, a MarkupContainer must be used.

The following sample code demonstrates how to create a MarkupContainer from the IMarkupServices interface.

HRESULT CreateMarkupContainer(
    IMarkupContainer **ppContainer
);

Initially, a newly created MarkupContainer contains no markup. Specifically, there are no HTML, HEAD, or BODY elements. The initial state of a MarkupContainer is not what the parser produces when an empty file is parsed in. Rather, the parser automatically inserts these elements as a courtesy.

Normally, MarkupContainers are created to contain markup that is being staged for insertion into the primary MarkupContainer. The primary MarkupContainer is the one, for example, that the browser creates to hold the HTML being parsed in. You can perform a QueryInterface operation on an HTML document for IID_IMarkupContainer to obtain the primary MarkupContainer. For example, you could query the IHTMLDocument2 interface for IID_IMarkupContainer.

MarkupPointer

A MarkupPointer is not part of the content of a MarkupContainer (also known as a document). The primary purpose of the MarkupPointer is to specify a position in a document. Consider the following example:

My <B>d[p1]og</B> has fleas.

The position of the MarkupPointer is represented by the [p1] pointer. Although [p1] is positioned between the letters d and o in dog, this does not mean that there are any additional characters in the document, or that the content of our example has been altered. There can be any number of pointers in a document without altering the document.

Markup pointers are positioned between pieces of document content. These pieces can be one of three things: the start of the influence of an element, the end of the influence of an element, or text. Thus, markup pointers are like editor carets. Because markup pointers themselves are not content, their positions are mutually indistinguishable when located at the same point in the HTML content. That is, if two markup pointers are next to each other, it is not possible to tell which one is to the left or right of the other. All that can be said about them is that they are at the same location in the content.

You can create a markup pointer by calling the CreateMarkupPointer method on the IMarkupServices interface.

HRESULT CreateMarkupPointer(
    IMarkupPointer **ppPointer
);

Positioning Markup Pointers

When a markup pointer initially is created, it is in a special state called unpositioned, meaning it is not yet between any content in particular. The following are three primary ways you can position a markup pointer into a markup (all are available on the IMarkupPointer interface):

The MoveAdjacentToElement method takes two parameters: an IHTMLElement and an enumeration literal that indicates the relative position to that element to place the pointer. This enumeration has the following four possible values:

HRESULT MoveAdjacentToElement(
    IHTMLElement *elementTarget,
    ELEMENT_ADJACENCY
);

    enum ELEMENT_ADJACENCY {
         ELEMENT_ADJACENCY_BeforeBegin
         ELEMENT_ADJACENCY_AfterBegin
         ELEMENT_ADJACENCY_BeforeEnd
         ELEMENT_ADJACENCY_AfterEnd
    };

Thus, positioning [p1] before the end (ELEMENT_ADJACENCY_BeforeEnd) of the B element in the preceding example would result in the following:

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

Now consider the following example:

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

The [p1] could be considered to be positioned after the begin tag of the B element, or before the begin tag of the I element. This illustrates that certain positions in a document can be redundantly specified.

Another way to position a markup pointer is to use the MoveToContainer method on the IMarkupPointer interface. This method takes an IMarkupContainer interface and a Boolean that indicates whether to position the pointer at the beginning or end of the MarkupContainer.

HRESULT MoveToContainer(
    IMarkupContainer *containerTarget,
    BOOL fAtStart
);

Thus, you can get a pointer positioned at the extreme edge of a document, as is demonstrated in the following example:

[p1]<HTML><BODY>a<B><I>b</I></B>c</BODY></HTML>[p2]

Here, [p1] is at the far left of the document and [p2] is at the far right.

The third way to position a pointer is to use MoveToPointer to move the pointer to a position in the document already indicated by another MarkupPointer.

HRESULT MoveToPointer(
    IMarkupPointer *pointerTarget
);

Often, the MoveToPointer method is used to record a position in a document while another pointer is used to inspect the surroundings.

Comparing Pointer Positions

The relative position between two markup pointers can be established with a number of comparison methods of the IMarkupPointer interface, as the following five examples demonstrate.

HRESULT IsEqualTo(
    IMarkupPointer *compareTo,
    BOOL *fResult
);

HRESULT IsLeftOf(
    IMarkupPointer *compareTo,
    BOOL *fResult
);

HRESULT IsLeftOfOrEqualTo(
    IMarkupPointer *compareTo,
    BOOL *fResult
);

HRESULT IsRightOf(
    IMarkupPointer *compareTo,
    BOOL *fResult
);

HRESULT IsRightOfOrEqualTo(
    IMarkupPointer *compareTo,
    BOOL *fResult
);

Thus, if you want to know whether [p1] if left of and not equal to [p2], you can use the following example:

BOOL fResult;
IMarkupPointer * pointer 1, * pointer 2;

..

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

if (fResult)
{
    // [p1] is to the left of pointer2
}

Navigating the Pointer

Once a MarkupPointer is placed in a MarkupContainer, you can use it to inspect the surrounding content and/or Move it around that content. Two methods, Left and Right, on the IMarkupPointer interface are the sole mechanisms for doing this.

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
);

All but the first argument are optional. The fMove parameter controls whether the pointer is moved past the surrounding content. If FALSE, the pointer does not Move at all; rather, it describes the surrounding content. If TRUE, in addition to describing the surrounding content, the pointer is moved across that surrounding content.

To find out what is to the left of a pointer, you can call the Left method. To find out what is to the right of a pointer, you can call the Right method. The pContextType parameter of Left and Right returns whatever is next to the pointer (the context).

The following are some possible types of context.

CONTEXT_TYPE_None Nothing is to the left or right of the pointer. This occurs only when the pointer is positioned at the extreme left or right of the MarkupContainer
CONTEXT_TYPE_Text In the given direction, there is text.
CONTEXT_TYPE_EnterScope In the given direction, an element is coming into scope. Thus, if looking left, an end tag is present; if looking right, a begin tag is present.
CONTEXT_TYPE_ExitScope In the given direction, an element is going out of scope. Thus, if looking left, a begin tag is present; if looking right, an end tag is present.
CONTEXT_TYPE_NoScope In the given direction, a no-scope element exists. These are elements into which you cannot get a MarkupPointer positioned (BR, for example).

If the ppElement parameter is non-NULL, and the context type is one of EnterScope, ExitScope, or NoScope, the ppElement parameter will return the element that is coming into scope, exiting scope, or is no-scope, respectively.

If the context is Text, the pCch and pch parameters are relevant. The pCch parameter serves three purposes:

The pCch parameter can be NULL or -1 upon entry, indicating that Left or Right should look across an arbitrary amount of text, up to the next no-scope element or element scope transition.

The table following the next example describes what Left or Right will return in a variety of situations.

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

Ptr

Direction

Type

Element

Cch In

Cch Out

Text Notes

[p1]

Left

None

-

-

-

-

Moves to the left edge of the container.

[p1]

Right

Text

-

2

2

Wh

Returns a maximum of two characters.

[p1]

Right

Text

-

-1

6

-

Returns the total number of available characters to the right, up to the next markup.

[p1]

Right

Text

-

345

6

Where

Returns the number of characters available, up to the maximum specified. In this case, 6 of 345 characters are returned.

[p2]

Left

Text

-

NULL

-

-

Asks which character is to the left, not how many characters.

[p2]

Right

EnterScope

I

-

-

-

Moves begin tags to the right.

[p3]

Left

ExitScope

I

-

-

-

Moves end tags to the right.

[p4]

Left

NoScope

BR

-

-

-

Does not usually require end tags.

[p5]

Left

Text

-

100

12

NULL

Retrieves only the number of characters moved, not the characters themselves.

Note The Left and Right methods provide the mechanism for walking the document.

To determine where the immediate element of an IMarkupPointer interface is currently positioned, use the following CurrentScope method:

HRESULT CurrentScope(
    IHTMLElement **ppElementCurrent
);

Using the previous "Where do you want to go today?" example, the CurrentScope for [p1] is NULL, because there is no unended begin tag to its left. The CurrentScope of [p4] is the <B> tag. Note that the BR is a NoScope type tag.

Pointer Gravity

Normally, when a document is modified, pointers in that document pretty much stay where they were before the operation occurred. For example, consider the following document with two pointers in it.

abc[p1]defg[p2]hij

If this document were to be modified with the insertion of the text XYZ between the letters e and f, the document would look like the following example:

abc[p1]deXYZfg[p2]hij

Note that [p1] and [p2] are still located between the same pieces of text that they were before the operation. Consider the following:

x[p1]y

Now consider what would happen if the letter Z were to be inserted between the letters x and y. Remember that the pointer does not constitute content, and that the letters x and y are next to each other. There would be the following two possibilities as to where the pointer could be positioned after the insert.

x[p1]Zy or xZ[p1]y

This is when gravity comes into play. For example, usually when content is inserted exactly where the pointer is, it is ambiguous as to where that pointer should end up. Gravity eliminates that ambiguity. Left gravity causes the pointer to be positioned to the left of the newly inserted content, and right gravity causes the pointer to be positioned to the right of the newly inserted content.

This applies not only to the insertion of text, but also to the insertion of an element. Consider the following:

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

Here, [p1] has right gravity and [p2] has left gravity. If a B element were to be inserted around the letter b, the following would result:

a[p2,left]<B>[p1,right]b</B>

Notice how the pointers now are inverted with respect to their original relative positions. Both were positioned such that it was ambiguous as to where they should go, given this insertion of the B element.

The default gravity on a pointer is left gravity. You can retrieve and set the gravity of a MarkupPointer with the following methods on the IMarkupPointer interface.

enum POINTER_GRAVITY {
    POINTER_GRAVITY_Left,
    POINTER_GRAVITY_Right
};

HRESULT Gravity(
    POINTER_GRAVITY *pGravityOut
);

HRESULT SetGravity(
    POINTER_GRAVITY newGravity
);

Pointer Cling

Consider the following markup:

[p2]ab[p1]cdxy

Now consider what would happen to [p1] if the preceding example were to be modified such that the text bc were to be moved between the letters x and y. Again, there are two alternatives.

[p2]a[p1]dxbcy or [p2]adxb[p1]cy

In both examples, [p2] is unaffected, because it is not located near where the primitive operation is being performed. However, notice that in the example on the left [p1] does not have Cling, whereas the example on the right does. Effectively, Cling causes a pointer to be considered part of the content with respect to the movement of that content. Where that content goes, so do pointers with Cling that are within that content.

Again, there can be ambiguities. Consider the following where [p1] has Cling:

a[p1]bcxy

If the letter b were to be moved between the letters x and y, consider whether the [p1] would be associated with the letter b. Again, gravity is used to disambiguate this. If [p1] has right gravity, it follows the letter b. If it has left gravity, it is associated with the content to its left (which, in this case, is the letter a) and will not follow the letter b.

If content is deleted instead of being moved, Cling also controls the destiny of pointers. Consider the following example:

ab[p1]cd

If the letters b and c are deleted and [p1] does not have Cling, [p1] will remain positioned in the document between the remaining content that surrounds it.

a[p1]d

However, if [p1] has Cling, [p1] will become unpositioned just like the deleted content ([p1] is removed from the document, but not destroyed in that it can be reinserted again and used).

ad

Cling can be queried and set with the following methods of the IMarkupPointer interface.

HRESULT Cling(
    BOOL *pClingOut
);

HRESULT SetCling(
    BOOL NewCling
);

Creating a New Element

A new element can be created using the CreateElement method of the IMarkupServices interface.

enum ELEMENT_TAG_ID {
    TAGTADID_A,
    TAGTADID_ACRONYM,
        ..
    TAGTADID_WBR,
    TAGTADID_XMP
};

HRESULT CreateElement(
    TAG_ID tagID,
    OLECHAR *pchAttrs,
    IHTMLElement **ppNewElement
);

For example, CreateElement ( TAGID_B, "id=anID", & pElement ) will create a new B element with the attribute ID set to "anID". The attributes parameter is optional. Attributes can be set on the element after its creation, but you might get better performance if you specify the attributes at the point the element is created. There also might be some attributes that can be specified only at element creation.

You can create a new element by cloning an existing element. To do this, use the CloneElement method of the IMarkupServices interface.

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

Inserting an Element

An element can be inserted into a document by calling the InsertElement method of the IMarkupServices interface.

HRESULT InsertElement(
    IHTMLElement *pElementInsertThis,
    IMarkupPointer *pPointerStart,
    IMarkupPointer *pPointerFinish
);

The start pointer describes where the element is to come into scope, and the finish pointer specifies where it goes out of scope. The element to be inserted currently must not be in a document, and both pointers must be positioned in the same MarkupContainer. For example, consider calling InsertElement on a B element with the following pointers:

My [pstart]dog[pend] has fleas.

They would produce a document with the following content:

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

Effectively, the start pointer is where the begin tag is placed, and the finish pointer is where the end tag is placed. Note that the pointers have left gravity, and position themselves to the left of the newly inserted content. If [pstart] were to have right gravity, the following would have resulted instead:

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

There is no restriction as to where a new element can be inserted. Thus, you can insert as many BODY elements as you want, or you can insert B elements into the head of the document. However, if the document were to be displayed or interacted with in such a state, the result would be undefined and should not be counted on to stay the same in future versions of Markup Services.

Removing an Element

Removing an element does not require markup pointers. You simply call the RemoveElement method of the IMarkupServices interface, passing the element to be removed.

HRESULT RemoveElement(
    IHTMLElement *pElementRemoveThis
);

The element must be in a document at the time of the operation. After the operation, the element no longer is in any document. Thus, it is available for insertion.

Note To remove an element and reinsert it in the exact same location, you must, before removing the element, insert markup pointers next to the start and end of the element. This way, the markup pointers record the range the element influenced when it was in the markup. The markup pointers can then be used to reinsert the element. Of course, make sure the pointers do not have Cling, because they might be unpositioned when the element is removed.

Inserting Text

To insert text into a markup, use the IMarkupPointer interface.

HRESULT InsertText(
    OLECHAR *pch,
    long cch,
    IMarkupPointer *pointerInsertHere
);

This method takes a single MarkupPointer and inserts the text at that point in the markup. The position of the markup pointer after the insertion (either before of after the newly inserted text) depends on the gravity property of the MarkupPointer. The cch parameter can be set to -1 to indicate that InsertText should assume that the text to insert is NULL terminated.

Removing Content

You can remove a contiguous section of content in a MarkupContainer with the Remove method of the IMarkupServices interface.

HRESULT Remove(
    IMarkupPointer *pointerFromHere,
    IMarkupPointer *pointerToHere
);

Here two markup pointers are supplied: one denoting where to start the removal, and another indicating the end. All textual content between the pointers is removed. Also, any markup that falls completely between the pointers is removed. Any markup that begins before the start pointers or ends after the end pointer is not removed. Consider the following example:

         <------------------- 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 ------->                                                    

When the Remove method is called on these pointers, the following results:

         <------------- b ------------->
     <------- i --------><------- u -------->
    a<I>b<B>c[pstart]</I><U>[pend]hi</B>j</U>kl

Notice that the S element is completely gone. Also notice that the I and U elements are still there, even though one of their tags was in the middle of the removal range. There also still is content between the pointers. This content can be only tags that were the start or end of elements that were partially selected before the removal operation. Note that the B element is unaffected, because it is entirely surrounded by the range removed.

Replacing Content

The two previous examples can be used to remove and insert content. Together these operations are similar to a replacing content operation, such as the one in the following example:

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..
        pMS->Remove( pPtr1, pPtr2 );
        
        //inserting new text
        pMS->InsertText( (unsigned short *) pstrTo, -1, pPtr1 );
    }
if (hr == S_FALSE) return FALSE;
else return(TRUE);
}

Moving Content

You can move a range of content from one place to another with the Move method of the IMarkupServices interface.

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

The Move operation takes three markup pointers: two for the source range to move, and a third for the destination. The effect on the content in the source markup is identical to the Remove operation. The content that was in the source will be moved into the markup specified by the target pointer.

All elements completely encompassed by the source range are moved intact to the target. That is, the identities of these elements are preserved. Elements that are completely outside the range of the source are unaffected, and are not transferred to the destination. However, elements that partially overlap the source range are cloned, and their CloneElements are moved to the target. Thus, given the preceding example in the Move operation, if that range were to be moved to the position shown in the following example:

X[pdest]Y

It would produce the following:

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

Notice that [pdest] is to the left of the newly inserted moved content. This is because it had left gravity. Notice that there are I' and U' elements. These are clones of the original I and U elements that were left back at the source. Elements can live only in one markup, and must influence one contiguous range in that markup. Note that the S element was not influenced by CloneElement. This is because the S element was entirely surrounded by the start and end pointers in the source before the move.

Tip Quite often, after a move (or a copy for that matter), you will want to have two pointers to the left and right, respectively, of the newly inserted content. To do this, create two markup pointers before the move: one with left gravity and another with right gravity. Place these two pointers at the destination pointer, then perform the move. After the move, the pointer with left gravity will be located to the left, and the pointer with right gravity will be located to the right of the newly moved/copied content.

Note The destination of the move can be between the source start and the source end of the range to move.

Copying Content

You can duplicate a range of content with the Copy method of the IMarkupServices interface.

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

Copying has the same effect on the destination markup that the Move method does, without disturbing the source.

Related Topics

The following articles provide more information about Markup Services.

The following articles provide more information about Microsoft® Visual Studio development.

The following articles provide more information about COM.



Back to topBack to top

Did you find this topic useful? Suggestions for other topics? Write us!

© 1999 Microsoft Corporation. All rights reserved. Terms of use.