Programming Guide: Reading and Writing Your Part

# Programming Guide

## Reading and Writing Your Part

In OpenDoc, reading an object (also called internalization) is the process of initializing (or re-initializing) the object by bringing its stored data into memory from persistent storage. Conversely, writing (also called externalization) is the process of copying an object's essential data to persistent external storage. In general, when a document is opened, its objects are read into memory. As the user edits the document, the objects are modified. When the user saves the document, the modified objects are written back to storage. (As noted earlier, the container suite controls how data is physically stored; reading and writing may not necessarily involve an immediate transfer of data between memory and an external physical storage medium.)

This section discusses how your part reads and writes its own content data, as well as related OpenDoc objects, such as frames, that it uses.

Your part editor must be able to read and write any parts whose data formats (part kinds) it recognizes. You are not required to read the entire contents of your part into memory at once, you need not write it all to storage at once. However, reading must put your part in a state in which it can accept events and handle requests, and writing it must ensure that changes made by the user are not lost.

Your part editor should never change the part kind of any part you handle except as a result of an explicit user request. Any translations required to make a part readable, whether performed by OpenDoc or by your part editor, are first selected by the user. See "Binding" for more information.

### Initializing and Reading a Part from Storage

A part must be able to read itself (reconstruct itself in memory by reading its stored data). To maximize performance, OpenDoc requires reading and writing only when absolutely necessary. A part is not instantiated and read into memory until its draft's CreatePart or AcquirePart method has been called. When either of these methods are called, the part editor is needed for tasks such as drawing, editing, frame negotiation, or script execution. When a part is read in, OpenDoc has already bound it to a part editor according to its part kind (and loaded the part editor if it is not already in memory). Whenever a document in which your part is visible is opened, or whenever your part is added to a document, your part's data must be read into memory. OpenDoc parts should always be ready to work from an empty storage unit as well. The two fundamental methods that your part must implement for initializing itself are InitPart and InitPartFromStorage.

#### The somInit Method

When your part object is first created (or recreated from storage), and before receiving a call to its InitPart or InitPartFromStorage method, your part receives a call to its System Object Model (SOM) object constructor somInit. The somInit method is inherited from the somObject class of SOM; when you subclass ODPart, you must override somInit.

Your somInit method should initialize (clear) your part object's instance variables. You must not perform any tasks in this method that might fail. You can set pointer variables to null and assign appropriate values to numeric variables. If you have any initialization code that can potentially fail, your part's InitPart or InitPartFromStorage method must handle it.

#### The InitPart Method

The InitPart method is called only once in your part's lifetime, by your draft's CreatePart method, when your part is first created and has no previously stored data to read. (If your part is created from stationery, this method is never called). This is its interface:

    void InitPart (in ODStorageUnit storageUnit,
                   in ODPart partWrapper);

In the simplest case, you could take these steps:

1. Call your inherited InitPart method, which in turn calls its inherited InitPersistentObject method to initialize the information your part needs as a persistent object. Your classes' initialization methods should always call their superclasses' initialization methods.

2. If your part stores its data in multiple part kinds, add a contents property (type kODPropContents) and a value (of your highest-fidelity part kind) to the storage unit passed to you, thus preparing your part for eventual writing of its data. Add values for other part kinds also, if appropriate. Initialize the values if necessary.

3. If your part stores more than one part kind, add a preferred-kind property (type kODPropPreferredKind) to the storage unit passed to you. Write into a value of that property an ISO string representing your editor's highest-fidelity part kind. Normally, that part kind should equal the type of the first value in your contents property.

4. Save, in a field (such as fSelf) of your part, the contents of the partWrapper parameter passed to this method. It represents an object reference through which OpenDoc interacts with your part. It represents an object reference (pointer) through which OpenDoc calls back to your part. See "Part-Wrapper Object" for more information.

5. Initialize any data pointers or size values that your part maintains. At this point you might mark your part as clean (unchanged). You could use a "dirty flag" for this purpose, which you initialize as cleared, and then set whenever you change your part's content.

Note:

> Another approach to InitPart is to defer all writing (such as that in steps 2 and 3) until your part's Externalize method is called.

#### The InitPartFromStorage Method

The InitPartFromStorage is similar to InitPart, except that InitPartFromStorage also reads in data. Your draft's AcquirePart method calls your InitPartFromStorage method and supplies a storage unit from which the part reads itself. Your part retrieves, from the kODPropContents property of the storage unit, the value (specified by part kind) that represents the data stream you wish to read. This is its interface:

    void InitPartFromStorage (in ODStorageUnit storageUnit,
                              in ODPart partWrapper);

In your InitPartFromStorage method, take steps like these:

1. Call your inherited InitPartFromStorage method. That method calls its inherited InitPersistentObject method, which initialize the persistent object information perviously stored for your part.

2. Determine the appropriate part kind of data to read in:

   - Your editor may be reading in a part that was created or previously edited by a different editor (see "Binding Process"). Focus on the kODPropPreferredKind of the storage unit passed to you, and retrieve the part's preferred kind (the part kind you should read, if possible). This part kind may or may not equal the type of the first value in the part's contents property.

   - If the kODPropPreferredKind property does not exist or if you cannot read data of that part kind, find the highest-fidelity (earliest in storage order) part that you can read.

     If there is no preferred kind, take the kind that you can read to be the preferred kind. Use that preferred-kind designation later, when you write to storage.

   If the data is non-OpenDoc file data to which your part editor has just been bound because of a drop operation (see "Accepting Non-OpenDoc Data"), you can take the following steps. Otherwise, go on to step 3.

   - Find, in the contents property, a value type representing the file type. If the item is a text file, for example, OpenDoc will have provided the value type (OpenDoc ISO prefix followed by) "OpenDoc:Type:Text".

   - Using this information (such as the file specification), you can then make file-system-specific calls to open the file and process its contents into a buffer in memory. If you must store the data immediately and the draft permissions allow you to, write the data into an appropriate value or values in your part's contents property. Otherwise, you can wait until your Externalize method is called before writing it to your storage unit.

     Skip to Step 7 (you have already read the data into your part).

3. Focus the storage unit on its contents property and on the value containing the part's preferred kind, if you can read that kind. If you cannot read the preferred kind, focus on the highest-fidelity part kind that you can read. (Even if you do not read in the preferred kind at this stage, do not yet change the value in the kODPropPreferredKind property).

4. Read the data into your part's buffer, using the GetValue method.

5. Read in additional objects as needed, as persistent references within your main storage unit's kODPropContents property lead you to information stored elsewhere. See, for example, "Creating Additional Storage Units", "Storing and Retrieving Embedded Frames", and "Reading Linked Content from Storage".

6. If your part's display frames have been stored, read them in as discussed in "Storing and Retrieving Display Frames".

7. Call your draft's GetPermissions method and save the results in a field of your part. Respect the permissions in later attempts to write to the draft. See "Drafts" for more information.

8. Save, in a field (such as fSelf) of your part, the contents of the partWrapper parameter passed to this method. The parameter represents a reference (pointer) through which OpenDoc calls back to your part. See "Part-Wrapper Object" for more information.

9. Initialize any data pointers or size values that your part maintains. If you maintain a dirty flag, you can mark your part as clean (unchanged).

### Writing a Part to Storage

Your part must be able to write its data to persistent storage when instructed. This instruction may come at any time, not just when the user saves or closes your document. To maximize performance, OpenDoc requires writing only when necessary.

- OpenDoc data is not persistently written to a file until the user performs a save operation on its document, causing the part's Externalize method to be called. If the user does not perform a save operation on a document, no changes to any of its parts are permanently recorded.

- When its document is saved or closed, no writing is required of a part if it has not been altered since it was read into memory.

Your part editor can follow these policies or deviate from them. For example, you can write out your part at any time, without waiting for the user to save the entire document. Note, however, that no matter how many times your part writes its data to storage, none of those changes will become persistent unless the user performs a save.

This section discusses the Externalize and ExternalizeKinds methods of your part editor. Another method that involves writing your part data to storage is the CloneInto method, described in "CloneInto Method of Your Part Editor".

#### The Externalize Method

A caller instructs your part to write its data to storage by calling your part's override of its inherited Externalize method. Your part then writes its data into its storage unit.

As a minimum, your part must write one property: kODPropContents. The kODPropContents property contains your part's intrinsic data. You can write your part's data in multiple part kinds, representing multiple data formats, in the kODPropContents property. Each format is a separate stream, defined as a separate value in the property, and each value must be a complete representation of your part's contents.

You can add other properties and values as annotations to your part's storage unit, and you can access them for your own purposes. However, remember that the OpenDoc binding process looks only at the value types of kODPropContents when assigning an editor to a part.

The fundamental method that your part must override for writing its data to storage is the Externalize method, inherited from the class ODPersistentObject. This is its interface:

    void Externalize ();

In a simple Externalize method, you might take these basic steps:

1. Call the inherited Externalize method to make sure that the appropriate persistent-object information for your part is written to storage.

2. Examine your part to see whether it is dirty (that is, whether the content has been changed since the last write). If it is clean (unchanged), take no further action.

3. Get a reference to your main storage unit by calling your part's inherited GetStorageUnit method.

4. Your editor may be writing a part that was created or previously edited by a different editor (see "Binding Process"). If so, you need to prepare the contents property of the part to match the part kinds and fidelity order that your part editor uses. This step is required only the first time you write a part after having been first bound to it.

   - Remove any values in the storage unit that represent kinds that your part editor does not support or does not intend to save.

   - Add values if necessary, so that values for all part kinds that your part editor intends to include are present. Make sure the values are in fidelity order, and make sure that the preferred part kind, determined when you read the part in, is one of the values that you write.

5. Focus the storage unit on the contents property and on each of the part kinds of the data you are writing in turn. Write your data to the storage unit, using its SetValue method. Write one value for each part kind of data you store.

   If your part is a container part, you write persistent references to your embedded frames as part of writing your content. See "Storing and Retrieving Embedded Frames".

6. Store references to your display frames, as described in "Storing and Retrieving Display Frames".

7. Mark your part as clean (unchanged).

Typically, in writing your content you might write out just two values: one in the preferred part kind, and another in a standard, widely recognized part kind useful for data interchange. If your part editor supports many kinds, you might allow the user to select a single default kind, to avoid the creation of very large files full of redundant data.

Note:

> Do not change the preferred kind from what it was when you read the part, even if you can easily convert the data to a higher-fidelity part kind. You can store a higher-fidelity part kind and a preferred kind.

Your part can write its contents to its storage unit at any time, not just in response to a call to its Externalize method. However, changes to any parts in a document are actually made persistent only when the user performs an explicit save, and thus only when Externalize is called. If your part updates its storage unit but the user never saves the document, your changes are lost.

#### The ExternalizeKinds Method

Your part's ExternalizeKinds method can be called whenever your part is expected to save its data with a specific set of formats. For example, OpenDoc calls ExternalizeKinds when the user saves a copy of your part's document in multiple formats. A document-interchange utility or service might call the ExternalizeKinds method of all parts in a document to create a version of the document in which all parts are written in one or more common standard part kinds.

When your part's ExternalizeKinds method is called, it is passed a list of part kinds. This is the method's interface:

    void ExternalizeKinds (in ODTypeList kindSet);

The method should write as many of the specified part kinds as it supports, as well as your part's preferred kind. The method should ignore part kinds on the list that it does not support, and it should remove values (other than the preferred kind) from its storage unit that are not on the list. Just like Externalize, the ExternalizeKinds method should make sure to write the part kinds in proper fidelity order, and not change the preferred kind.

### Creating Additional Storage Units

Your part can use persistent references to create an auxiliary storage unit for keeping additional data. The auxiliary unit must be referenced from your part's main storage unit, using a strong persistent reference. In brief, you can follow these steps:

1. Call the CreateStorageUnit method of your part's draft to create the storage unit to hold the data.

2. Focus your part's main storage unit on the value in your contents property that is to contain the persistent reference. (It is not necessary to create a separate value to hold the reference).

3. Create the persistent reference, by calling the GetStrongStorageUnitRef method of your main storage unit.

4. Store the reference anywhere in the value; the only requirement is that you be able to recognize and retrieve the reference later. Write the value to persistent storage, by calling the SetValue method of your main storage unit.

Your part can later retrieve the auxiliary storage unit from the main storage unit, in this way:

1. Focus the main storage unit on the value that contains the persistent reference.

2. Read the value, using the GetValue method of your main storage unit. Retrieve the persistent reference from the value's data.

3. Get the storage unit ID of your auxiliary storage unit from the persistent reference, by calling the GetIDFromStorageUnitRef method of your main storage unit.

4. Get the storage unit itself by passing its ID to the AcquireStorageUnit method of your draft.

How you store the data in your auxiliary storage unit is completely up to you. You can define your own properties, or you can keep the kinds of properties used in your main storage unit. If you define your own property names, avoid using constants for them that start with kOD; only OpenDoc-defined constants should use those characters.

### Storing and Retrieving Display Frames

As noted in "Working with Your Display Frames and Facets", your part should keep a private list of its display frames. It is further recommended that your part persistently store those display frames when it writes itself to storage, and retrieve them when it reads itself from storage. Your part may need information on currently nonvisible, uninstantiated display frames in order to perform frame synchronization (see "Synchronizing Display Frames") or frame negotiation (see "Frame Negotiation").

If your part object includes a field that is a list of display-frame objects, your InitPart method can first create the field, and your DisplayFrameAdded, DisplayFrameRemoved, DisplayFrameConnected, and DisplayFrameClosed methods can add or delete elements of the list, as appropriate.

You can keep additional information about the frames in this field, such as what portion of your content (like a page number) each displays. That way, you can instantiate only those frames that you currently need to manipulate, using the techniques described in "Lazy Instantiation".

You should store your list of frames in an annotation property in your part's storage unit. Your Externalize method should create a property with the name kODPropDisplayFrames in your storage unit and should write the frame list as weak storage unit references into a value of that property.

When reading your part from storage, your InitPartFromStorage method can focus on the kODPropDisplayFrames property and value, and read the list of stored display frames back into your display-frame field. (Be sure your ReleaseAll method releases any remaining display frames in that field; see "The ReleaseAll Method").

### Storing and Retrieving Embedded Frames

Your part does not explicitly store the data of embedded parts; their own part editors take care of that. You do not even explicitly store the frame objects that you use; OpenDoc takes care of that. You do, however, store persistent references to the frames that are embedded in your part.

The process of storing an embedded frame for your part is simple. All you need to do is store a strong persistent reference to the embedded frame object; OpenDoc takes care of storing the frame itself. You follow the same steps that you take when creating and storing a reference to any storage unit (described in "Creating Additional Storage Units"):

1. Focus your storage unit on your contents property and the value that is to contain the persistent reference to the embedded frame.

2. Create the persistent reference and place it in your contents data.

3. Write the data back into the value.

When your part is reinstated at a later time, your part can then retrieve the frame this way:

1. Focus the storage unit on the value containing the persistent reference.

2. Retrieve the reference.

3. Get the storage-unit ID of the frame.

4. Recreate the frame itself, by calling your draft's AcquireFrame method.

5. If you have previously stored information regarding a change to the frame's link status (see "The LinkStatusChanged Method of your Part Editor"), set the frame's link status at this time. Link status is described in "Frame Link Status".

For efficient memory use, you can instantiate only those embedded frames that you currently need to manipulate or display, using the techniques described in "Lazy Instantiation".

### Reading and Writing Part Info Data

As noted in "Part Info Data Field", you can use the part info data of a frame or facet to store any display-related information you want to associate with that particular frame or facet. Because frames are persistent objects and facets are not, a frame's part info can be stored persistently, whereas a facet's part info cannot.

Just as you store multiple representations of your part's data, you should store multiple representations of your frames' part info, so that other editors can potentially read your part info as well as your part content. In that case, you should give your part info formats names equivalent to the part kinds with which they are associated with. For example, if you write part data whose part kind is "SurfCorp:SurfWriter:StyledText", your associated part info data should be written in a value whose type is "SurfCorp::SurfWriter:StyledText:PartInfo".

If you also write your part data in a standard format such as PICT or RTF or JPEG, you would not write associated part info for those formats, because they have no associated part info format.

As with other changes to the contents of your part's draft, any time you place or modify information in your display frame's part info field, you should follow the procedures listed in "Making Content Changes Known". (If your frame is a nonpersistent frame, however, you do not need to make changes to it known).

Whenever your document is saved, your part's WritePartInfo method is called for each of your part's display frames. Here is its interface:

    void WritePartInfo(in ODPointer partInfo,
                       in ODStorageUnitView storageUnitView);

On receiving this call, you could first examine your part's own part-info dirty flag (if you have defined one) to see if this frame's part info has changed since it was read in from storage. If it has, focus on as many values in the provided storage unit view as is appropriate and write your current part info into them. (And then clear the dirty flag).

Conversely, whenever a display frame of your part is read into memory, your part's ReadPartInfo method is called. Here is its interface:

    ODPtr ReadPartInfo(in ODFrame frame,
                       in ODStorageUnitView storageUnitView);

On receiving this call, you should allocate a structure to hold the part info, focus on the appropriate value in the provided storage-unit view, and read the data into your part-info structure. (And initialize your part-info dirty flag to clean).

Your part also writes part info data when its display frames are cloned; see "ClonePartInfo Method of Your Part Editor" for more information.

[ Top | Previous | Next | Contents | Index | Documentation Homepage ]