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.
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.
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 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:
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 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); |
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.
Skip to Step 7 (you have already read the data into your part).
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.
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".
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:
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".
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.
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.
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:
Your part can later retrieve the auxiliary storage unit from the main storage unit, in this way:
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.
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").
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"):
When your part is reinstated at a later time, your part can then retrieve the frame this way:
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".
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.