Part 4: Using DXL to create imagery in documents - continued
Tags: Lotus Notes Software DXL LotusScript
In this the fourth part of article about the Icon and Images database (started in this article), It continues where the third article left off.
In this the fourth part of article about the Icon and Images database (started in this article), It continues where the third article left off.
3. Analyze
the exported DXL file to understand the construction of DXL
When you have created a template document, then export it to a file and review the generated DXL file. To view the DXL file, you can use any editor such as Notepad or a specialized XML editor. My definitive XML editor is the firstObject XML Editor from firstObject.com. This is even free!
Don't be intimidated by the shear size and complex looks of the DXL file! I describe what you look for! I the screenshot below you see a sample DXL file in the firstObject XML Editor;
On the left you see the tree representation of the DXL file, and on the right you see the actual DXL file content. Note how firstObject XML Editor automatically navigates to the correct position within the huge DXL file when you double click on an element in the tree-view. The important stuff about the DXL file can be summarized like this;
The top-level database element contain all other elements, such as the databaseinfo element
This identifies which database we have dumped the documents from, and will be used when we import DXL again.
You may have one or more document elements beneath the database-element. Each document element represent a single document within the Notes database. So, if you selected just one document before you called the Export selected documents as DXL-agent, you will have only a single document-element.
Each document-element contains a list of item-elements, representing each and every Notes field in your document. Again, don't be too intimidated by all the items. We are going mainly for the rich text item Body when we are analyzing the content that Notes has generated for us.
So, basically the DXL contains a bunch of item-elements within a document-element.
If you dive into the rich text element from the Body-field, you will see that the structure of the DXL again has a hierarchical structure of elements. Below you see a sample;
At we have a pardef and par-element. These are paragraph-elements representing what the following paragraph should look like when it comes to fonts and sizes etc. A Notes rich text is stuffed with these elements, and you will see that there are lots and lots of these elements. Even empty rich text fields contains these elements.
At you see the first table element. When I created the sample here, I first created an empty two-row table. Only one cell in each row. This is purely because I want to control the look and feel of the table. Think old fashioned web design here, were you used to design the web page by tables within tables etc. At you see the tablecolumn-element and the tablerow-element for the first single cell row. The tablecoloum define the with of the column and the tablerow contains the content of the row itself. In my sample, this quickly leads to another table at , which again contain its own tablecolumns and tablerow elements. Down at the content of the actual cell starts and this is where the fun start!
When you get down to the tablerow- and tablecell elements, you expand it further and see the following elements within the par-element;
At you see a run-element. You can compare a run with a part of the rich text. For example may the simple sentence "The quick brown fox" consist of a single run. The sentence "The quick brown fox" (note the different decorations!) consist of 4 runs. The first run contain of the text "The", while the second run contain "quick" and so forth.,This makes sense if you try to remember that Notes actually needs to know how the different decorations should look like. Italics, bold and underline definitely are different than plain text. The exact same principle is also visible within HTML where the first sentence would look something like this; "<p>The quick brown fox", and the second sentence looks like this; "The <i>quick</i> <b>brown</b> <u>fox</u>".
Now that you know about runs for text, know that the same principle goes for any other content within the rich text field.
At you see a cool element, the actionhotspot sounds like something I've described above doesn't it! You can even see the plain text formula code in the formula-element!! Finally you see the picture-element at , containing some strange characters in the gif-element, The strange characters are indeed the gif image encoded as base64. Base64 is quickly explained a way of encoding a binary file as text, so it can be sent over plain text protocols. You find Base64 described on the internet, and you find several examples on how to implement Base64 also. I use the eminent Base64 LotusScript class described in this article, by Johan Kanngard.
A cool thing about the structure above, is that you can also see the containment, meaning how the actionspot-element surround the picture-element by its start- and end tags. This is how the Notes rich text create the action hotspot aorund the image!
Now you know something about DXL and the strict hierarchy amongst its elements, I hope you see that it makes sense to make Notes tell you how to do this, rather than trying to do this all by yourself.
The remaining concept form here on, is to extract only the necessary parts of the DXL in order to programmatically construct similar DXL by ourselves.
4. Recreate an DXL file similar to the one Notes exported in the second step.
Remember, now I am only organizing my LotusScript code to facilitate the generation of DXL files similar to what Notes exported itself in step 2.
The use of templates
Before we start, let me explain the small concept of templates. By this I mean strings of code containing the skeleton of whatever I want to recreate, and rather have some variables within that skeleton. Below I dive straight into some LotusScript code. First we start in the Declaration-section where I define a g_strDXLTemplatePictureTablecell-string. Now how I use the vertical bar (|) instead of the quotes (") in order to much easier copy and paste from the DXL file!!
' The following will be repeated as the first tablerow
Const g_strDXLTemplatePictureTablecell = |
<tablecell borderwidth='0px 0px 0px 0px'>
<pardef id='$(PARID_COUNTER)' keepwithnext='true' keeptogether='true'/>
<par def='$(PARID_COUNTER)'>
<run>
<font name='Arial' pitch='variable' truetype='true' familyid='20'/>
</run>
<actionhotspot hotspotstyle='none'>
<code event='click'>
<formula>@Environment("IconLibraryData"; txtDocumentUNID + "||$(COMBINATIONKEY)");
@Command([ToolsRunMacro];"(agnIconClickedActionHandler)")</formula>
</code>
<run>
<font name='Arial' pitch='variable' truetype='true' familyid='20'/>
</run>
<picture width='$(IMGWIDTH)px' height='$(IMGHEIGHT)px'>
<gif>
$(BASE64)
</gif>
</picture>
<run>
<font name='Arial' pitch='variable' truetype='true' familyid='20'/>
</run>
</actionhotspot>
<run>
<font name='Arial' pitch='variable' truetype='true' familyid='20'/>
</run>
</par>
</tablecell>
|
The code above is a template. Note how the template also contains variables such as $(PARID_COUNTER), $(IMGWIDTH) and $(BASE64). These variables will be replaced with actual content when I use the template. When I want to use the code above, I have something similar to this;
Dim strPictureTableCell as String
strPictureTableCell = g_strDXLTemplatePictureTablecell
Then I use the strPictureTableCell when I process the code and fill the variables with actual content
strPictureTableCell = Replace(strPictureTableCell, "$(PARID_COUNTER)", "5")
strPictureTableCell = Replace(strPictureTableCell, "$(IMGWIDTH)", "32")
strPictureTableCell = Replace(strPictureTableCell, "$(BASE64)", strTheBase64EncodedGIF)
The code above replaces the variables with the actual content.
The use of GIFs
Lotus Notes can import GIF and JPEG with full fidelty. While it also can import other image formats, Notes will quickly convert the other formats to its own variant of TIFF. So to ensure that we have best possible quality,!
The overview of creating imagery
My code open the resulting DXL files and output the generated content as its created. This may seem like an overkill, but remember, the DXL files can grow pretty large. So if you try to create the complete DXL file say in a LotusScript string only, you will pretty fast run into memory limitations. By opening the resulting DXL file in the start of the code, and write the data as its generated, I don't run into these problems. However, there are probably places in my code that can be even further optimized for speed.
The function creating the icon imagery is the BatchProcessPass2IconImagery within the "Import Processing" script library. Basically it follows this procedure;
Set up the sequence I want to process the images in. I want to present the largest images first, so the 256x256 comes before the 128x128 and 72x72 and so forth. I also ensure that I present the "normal" icons before the "Hot" and "Disabled" icons. I also define the tablecolum-elements for each image size. Remember this is the DXL tablecolumn-element!
Idenitfy the GIF files amongst the all the files for an icon. This will make it easier (and much faster later on) when I create icon imagery.
Start to create the DXL file. The code starts like this;
' Initialise the DXL file with standard headers stuff, identifying the current document
Print #fout, "<?xml version='1.0' encoding='utf-8'?>"
Print #fout, "<!-- DXLExporter version 1.00 (build 72), schema (DTD) version 1.01 -->"
Print #fout, "<!DOCTYPE document SYSTEM 'xmlschemas/domino_6_5.dtd'>"
Print #fout, "<document xmlns='http://www.lotus.com/dxl' version='6.5' replicaid='" & db.ReplicaID & "' form='formIcon'>"
Print #fout, "<noteinfo noteid='" & docIcon.NoteID & "' unid='" & docIcon.UniversalID & "'></noteinfo>"
Print #fout, "<item name='Pass'><number>2</number></item>" ' THIS UPDATES THE PASS NUMBER
Print #fout, "<item name='Body'><richtext>"
Note how the initial DXL is based upon the same basis as the DXL exported by Notes earlier. Also note how I blend in my own content such as db.ReplicaID and doc.NoteID. This will later identify the document I am about to create.
I will now build the tables manually, based upon the DXL templates I have stored in the Declaration-section. One thing to note here is that I pay extreme attention to building the table tablecell-by-tablecell. This means that I need to keep track on how many icons I have per line, and whether or not I have enough icons or not for a complete tablerow. If you am short of icons filling up a complete tablerow, I create empty tablecells to adhere to the DXL specification.
You will see a lot of code like the one below where I grab a template, and fill in the real content;
strDXLTableElement = g_strDXLTemplateTableAElementPrefix ' Start with fresh table-element template
strDXLTableElement = Replace(strDXLTableElement,"$(STATE)", vStateAndShadow(0))
strDXLTableElement = Replace(strDXLTableElement,"$(SHADOW)", vStateAndShadow(1))
After the processing has been completed, I simply write the variable to the DXL file with
Print #g_foutTable, strDXLTableElement
Use intermediate files. An important concept in my code, is that I use some intermediate files for table constructs. The intermediate files contains snippets that I will use later on in the resulting DXL file. This because it make the code easier to track and maintain.
The base64 encoding means that I grab the original file, use the Kanngard LotusScript class to convert it to a base64 file, and finally inject the base64 code into the resulting DXL file.
' Now, base64 encode the GIF file
strB64FileName = strTempPath & "Temp.B64"
Kill strB64FileName
rcB64 = b64.encodeFile(strFileName,strB64FileName)
Note that I also store the raw base64 data for specific GIF files. I will use the base64 data later when I perform so-called Gallery- and Favorites processing. By storing the raw base64 data in separate Notes fields, I can use this base64 data directly later, making the generation of Gallery and/or Favorites data very, very fast.
5. Use the NotesDXLImporter to import the handmade DXL file from the previous step.
When the code has completed the generation of the DXL file, we are ready to import it with the DXL Importer. The complete code to do this can be foundin the simple function below;
Function ImportImageDXLImport(db As NotesDatabase, strDXLFileName As String) As Variant
Dim session As New NotesSession
Dim stream As NotesStream
ImportImageDXLImport = True
g_strDXLImportError = ""
On Error Resume Next
If Not db.isopen Then
g_strDXLImportError = "ERROR: Database couldn't be opened."
ImportImageDXLImport = False
Exit Function
End If
Set stream = session.CreateStream
If Not stream.Open(strDXLFileName) Then
g_strDXLImportError = "ERROR: The file """ & strDXLFileName & """ couldn't be opened."
ImportImageDXLImport = False
Exit Function
End If
If stream.Bytes = 0 Then
g_strDXLImportError = "ERROR: File """ & strDXLFileName & """ is empty."
ImportImageDXLImport = False
Exit Function
End If
Dim importer As NotesDXLImporter
Set importer = session.CreateDXLImporter(stream, db)
importer.DocumentImportOption = DXLIMPORTOPTION_UPDATE_ELSE_CREATE
Call importer.Process
Call stream.Close
End Function
Just as when we exported selected documents as DXL, we create a connection between the database and the existing DXL file to import. The NotesDXLImporter is the class responsible for doing this. In the code above, we first open the DXL file with the CreateStream-method, and then a new NotesDXLImporter is created with the CreateDXLImporter-method, connecting the stream to the database. The line Call importer.Process kicks of the import as specified in the DXL file. And as stated previously, the import processing is much faster than the export processing!
How to detect if something went wrong?
Use a standard On Error Goto xxx, where you check the NotesDXLImporter member Log. The Log-member contains a XML file describing pretty accurately where the importer stumbled and what the reason was. Use this output to correct and refine your DXL file accordingly.
Conclusion
By using the tecniques described above, you can create pretty cool solutions with imagery. DXL is very very powerful, both as a learning tool to better understand how Notes stores its data, and as a source for dynamic content creation.
DXL rocks!!
The next article will dive into some extra features, such as the Gallery ....
The Gallery contains all icons and images for a given icon collection. You can click on any icon to go directly to the icon page!
The Favorites-feature let you "collect" different icons to your favorites for later use;
When you click on the icon in the Favoites page, you will see actions directly for the specific image you initially selected to add to your favories, like this;
I hope the favorite-feature makes it easier to collect images to your projects
When you have created a template document, then export it to a file and review the generated DXL file. To view the DXL file, you can use any editor such as Notepad or a specialized XML editor. My definitive XML editor is the firstObject XML Editor from firstObject.com. This is even free!
Don't be intimidated by the shear size and complex looks of the DXL file! I describe what you look for! I the screenshot below you see a sample DXL file in the firstObject XML Editor;
On the left you see the tree representation of the DXL file, and on the right you see the actual DXL file content. Note how firstObject XML Editor automatically navigates to the correct position within the huge DXL file when you double click on an element in the tree-view. The important stuff about the DXL file can be summarized like this;
The top-level database element contain all other elements, such as the databaseinfo element
This identifies which database we have dumped the documents from, and will be used when we import DXL again.
You may have one or more document elements beneath the database-element. Each document element represent a single document within the Notes database. So, if you selected just one document before you called the Export selected documents as DXL-agent, you will have only a single document-element.
Each document-element contains a list of item-elements, representing each and every Notes field in your document. Again, don't be too intimidated by all the items. We are going mainly for the rich text item Body when we are analyzing the content that Notes has generated for us.
So, basically the DXL contains a bunch of item-elements within a document-element.
If you dive into the rich text element from the Body-field, you will see that the structure of the DXL again has a hierarchical structure of elements. Below you see a sample;
At we have a pardef and par-element. These are paragraph-elements representing what the following paragraph should look like when it comes to fonts and sizes etc. A Notes rich text is stuffed with these elements, and you will see that there are lots and lots of these elements. Even empty rich text fields contains these elements.
At you see the first table element. When I created the sample here, I first created an empty two-row table. Only one cell in each row. This is purely because I want to control the look and feel of the table. Think old fashioned web design here, were you used to design the web page by tables within tables etc. At you see the tablecolumn-element and the tablerow-element for the first single cell row. The tablecoloum define the with of the column and the tablerow contains the content of the row itself. In my sample, this quickly leads to another table at , which again contain its own tablecolumns and tablerow elements. Down at the content of the actual cell starts and this is where the fun start!
When you get down to the tablerow- and tablecell elements, you expand it further and see the following elements within the par-element;
At you see a run-element. You can compare a run with a part of the rich text. For example may the simple sentence "The quick brown fox" consist of a single run. The sentence "The quick brown fox" (note the different decorations!) consist of 4 runs. The first run contain of the text "The", while the second run contain "quick" and so forth.,This makes sense if you try to remember that Notes actually needs to know how the different decorations should look like. Italics, bold and underline definitely are different than plain text. The exact same principle is also visible within HTML where the first sentence would look something like this; "<p>The quick brown fox", and the second sentence looks like this; "The <i>quick</i> <b>brown</b> <u>fox</u>".
Now that you know about runs for text, know that the same principle goes for any other content within the rich text field.
At you see a cool element, the actionhotspot sounds like something I've described above doesn't it! You can even see the plain text formula code in the formula-element!! Finally you see the picture-element at , containing some strange characters in the gif-element, The strange characters are indeed the gif image encoded as base64. Base64 is quickly explained a way of encoding a binary file as text, so it can be sent over plain text protocols. You find Base64 described on the internet, and you find several examples on how to implement Base64 also. I use the eminent Base64 LotusScript class described in this article, by Johan Kanngard.
A cool thing about the structure above, is that you can also see the containment, meaning how the actionspot-element surround the picture-element by its start- and end tags. This is how the Notes rich text create the action hotspot aorund the image!
Now you know something about DXL and the strict hierarchy amongst its elements, I hope you see that it makes sense to make Notes tell you how to do this, rather than trying to do this all by yourself.
The remaining concept form here on, is to extract only the necessary parts of the DXL in order to programmatically construct similar DXL by ourselves.
4. Recreate an DXL file similar to the one Notes exported in the second step.
Remember, now I am only organizing my LotusScript code to facilitate the generation of DXL files similar to what Notes exported itself in step 2.
The use of templates
Before we start, let me explain the small concept of templates. By this I mean strings of code containing the skeleton of whatever I want to recreate, and rather have some variables within that skeleton. Below I dive straight into some LotusScript code. First we start in the Declaration-section where I define a g_strDXLTemplatePictureTablecell-string. Now how I use the vertical bar (|) instead of the quotes (") in order to much easier copy and paste from the DXL file!!
' The following will be repeated as the first tablerow
Const g_strDXLTemplatePictureTablecell = |
<tablecell borderwidth='0px 0px 0px 0px'>
<pardef id='$(PARID_COUNTER)' keepwithnext='true' keeptogether='true'/>
<par def='$(PARID_COUNTER)'>
<run>
<font name='Arial' pitch='variable' truetype='true' familyid='20'/>
</run>
<actionhotspot hotspotstyle='none'>
<code event='click'>
<formula>@Environment("IconLibraryData"; txtDocumentUNID + "||$(COMBINATIONKEY)");
@Command([ToolsRunMacro];"(agnIconClickedActionHandler)")</formula>
</code>
<run>
<font name='Arial' pitch='variable' truetype='true' familyid='20'/>
</run>
<picture width='$(IMGWIDTH)px' height='$(IMGHEIGHT)px'>
<gif>
$(BASE64)
</gif>
</picture>
<run>
<font name='Arial' pitch='variable' truetype='true' familyid='20'/>
</run>
</actionhotspot>
<run>
<font name='Arial' pitch='variable' truetype='true' familyid='20'/>
</run>
</par>
</tablecell>
|
The code above is a template. Note how the template also contains variables such as $(PARID_COUNTER), $(IMGWIDTH) and $(BASE64). These variables will be replaced with actual content when I use the template. When I want to use the code above, I have something similar to this;
Dim strPictureTableCell as String
strPictureTableCell = g_strDXLTemplatePictureTablecell
Then I use the strPictureTableCell when I process the code and fill the variables with actual content
strPictureTableCell = Replace(strPictureTableCell, "$(PARID_COUNTER)", "5")
strPictureTableCell = Replace(strPictureTableCell, "$(IMGWIDTH)", "32")
strPictureTableCell = Replace(strPictureTableCell, "$(BASE64)", strTheBase64EncodedGIF)
The code above replaces the variables with the actual content.
The use of GIFs
Lotus Notes can import GIF and JPEG with full fidelty. While it also can import other image formats, Notes will quickly convert the other formats to its own variant of TIFF. So to ensure that we have best possible quality,!
The overview of creating imagery
My code open the resulting DXL files and output the generated content as its created. This may seem like an overkill, but remember, the DXL files can grow pretty large. So if you try to create the complete DXL file say in a LotusScript string only, you will pretty fast run into memory limitations. By opening the resulting DXL file in the start of the code, and write the data as its generated, I don't run into these problems. However, there are probably places in my code that can be even further optimized for speed.
The function creating the icon imagery is the BatchProcessPass2IconImagery within the "Import Processing" script library. Basically it follows this procedure;
Set up the sequence I want to process the images in. I want to present the largest images first, so the 256x256 comes before the 128x128 and 72x72 and so forth. I also ensure that I present the "normal" icons before the "Hot" and "Disabled" icons. I also define the tablecolum-elements for each image size. Remember this is the DXL tablecolumn-element!
Idenitfy the GIF files amongst the all the files for an icon. This will make it easier (and much faster later on) when I create icon imagery.
Start to create the DXL file. The code starts like this;
' Initialise the DXL file with standard headers stuff, identifying the current document
Print #fout, "<?xml version='1.0' encoding='utf-8'?>"
Print #fout, "<!-- DXLExporter version 1.00 (build 72), schema (DTD) version 1.01 -->"
Print #fout, "<!DOCTYPE document SYSTEM 'xmlschemas/domino_6_5.dtd'>"
Print #fout, "<document xmlns='http://www.lotus.com/dxl' version='6.5' replicaid='" & db.ReplicaID & "' form='formIcon'>"
Print #fout, "<noteinfo noteid='" & docIcon.NoteID & "' unid='" & docIcon.UniversalID & "'></noteinfo>"
Print #fout, "<item name='Pass'><number>2</number></item>" ' THIS UPDATES THE PASS NUMBER
Print #fout, "<item name='Body'><richtext>"
Note how the initial DXL is based upon the same basis as the DXL exported by Notes earlier. Also note how I blend in my own content such as db.ReplicaID and doc.NoteID. This will later identify the document I am about to create.
I will now build the tables manually, based upon the DXL templates I have stored in the Declaration-section. One thing to note here is that I pay extreme attention to building the table tablecell-by-tablecell. This means that I need to keep track on how many icons I have per line, and whether or not I have enough icons or not for a complete tablerow. If you am short of icons filling up a complete tablerow, I create empty tablecells to adhere to the DXL specification.
You will see a lot of code like the one below where I grab a template, and fill in the real content;
strDXLTableElement = g_strDXLTemplateTableAElementPrefix ' Start with fresh table-element template
strDXLTableElement = Replace(strDXLTableElement,"$(STATE)", vStateAndShadow(0))
strDXLTableElement = Replace(strDXLTableElement,"$(SHADOW)", vStateAndShadow(1))
After the processing has been completed, I simply write the variable to the DXL file with
Print #g_foutTable, strDXLTableElement
Use intermediate files. An important concept in my code, is that I use some intermediate files for table constructs. The intermediate files contains snippets that I will use later on in the resulting DXL file. This because it make the code easier to track and maintain.
The base64 encoding means that I grab the original file, use the Kanngard LotusScript class to convert it to a base64 file, and finally inject the base64 code into the resulting DXL file.
' Now, base64 encode the GIF file
strB64FileName = strTempPath & "Temp.B64"
Kill strB64FileName
rcB64 = b64.encodeFile(strFileName,strB64FileName)
Note that I also store the raw base64 data for specific GIF files. I will use the base64 data later when I perform so-called Gallery- and Favorites processing. By storing the raw base64 data in separate Notes fields, I can use this base64 data directly later, making the generation of Gallery and/or Favorites data very, very fast.
5. Use the NotesDXLImporter to import the handmade DXL file from the previous step.
When the code has completed the generation of the DXL file, we are ready to import it with the DXL Importer. The complete code to do this can be foundin the simple function below;
Function ImportImageDXLImport(db As NotesDatabase, strDXLFileName As String) As Variant
Dim session As New NotesSession
Dim stream As NotesStream
ImportImageDXLImport = True
g_strDXLImportError = ""
On Error Resume Next
If Not db.isopen Then
g_strDXLImportError = "ERROR: Database couldn't be opened."
ImportImageDXLImport = False
Exit Function
End If
Set stream = session.CreateStream
If Not stream.Open(strDXLFileName) Then
g_strDXLImportError = "ERROR: The file """ & strDXLFileName & """ couldn't be opened."
ImportImageDXLImport = False
Exit Function
End If
If stream.Bytes = 0 Then
g_strDXLImportError = "ERROR: File """ & strDXLFileName & """ is empty."
ImportImageDXLImport = False
Exit Function
End If
Dim importer As NotesDXLImporter
Set importer = session.CreateDXLImporter(stream, db)
importer.DocumentImportOption = DXLIMPORTOPTION_UPDATE_ELSE_CREATE
Call importer.Process
Call stream.Close
End Function
Just as when we exported selected documents as DXL, we create a connection between the database and the existing DXL file to import. The NotesDXLImporter is the class responsible for doing this. In the code above, we first open the DXL file with the CreateStream-method, and then a new NotesDXLImporter is created with the CreateDXLImporter-method, connecting the stream to the database. The line Call importer.Process kicks of the import as specified in the DXL file. And as stated previously, the import processing is much faster than the export processing!
How to detect if something went wrong?
Use a standard On Error Goto xxx, where you check the NotesDXLImporter member Log. The Log-member contains a XML file describing pretty accurately where the importer stumbled and what the reason was. Use this output to correct and refine your DXL file accordingly.
Conclusion
By using the tecniques described above, you can create pretty cool solutions with imagery. DXL is very very powerful, both as a learning tool to better understand how Notes stores its data, and as a source for dynamic content creation.
DXL rocks!!
The next article will dive into some extra features, such as the Gallery ....
The Gallery contains all icons and images for a given icon collection. You can click on any icon to go directly to the icon page!
The Favorites-feature let you "collect" different icons to your favorites for later use;
When you click on the icon in the Favoites page, you will see actions directly for the specific image you initially selected to add to your favories, like this;
I hope the favorite-feature makes it easier to collect images to your projects