OpenNTF.org - NotesDatabase Export to DXL >
My Links (Not logged in)
Code Bin Search
 
Hosted by Prominic.NET
Rate This Code
5 - brilliant stuff
4 - very nice
3 - average
2 - needs work
1 - bad
   OpenNTF Code Bin
About This Code
Brief Description:
NotesDatabase Export to DXL > Modify DXL > Import (Handling Illegal Escape Characters) 
Rating:
Rating: 4 , Number of votes: 1 
Contributor:
Andrew Luke 
Category:
Lotusscript 
Type:
Example Code 
Notes Version:
R6.x 
Last Modified:
18 Aug 2006 
OpenNTF Disclaimer

All of the program code and information presented in the OpenNTF.org Code Bin are provided "as-is", and should be used at your own risk. OpenNTF.org make no express or implied warranty about anything in the Code Bin, and OpenNTF.org will not be responsible or liable for any damage caused by the use or misuse of anything from this site. OpenNTF.org makes no guarantees about anything. Please thoroughly test all of the knowledge and code you find here before you attempt to use them in your production environment.

Code / Description
>>> Scenario: <<<


So you want to export a notes database or design element but you keep finding that every time you try to import the changes, you get an error - usually "Expected Whitespace". This is handy (for me) if you want to make design changes to a database you otherwise wouldn't be able to make through script, like automatically setting read-only properties of a database like Template Name or Show in Open Dialog.

The suggested method to make changes to a Notes object is to use the NotesDOMParser, you can use this to convert your notes database/design element into a DOM and then using the NotesDOM objects (eg NotesDOMDocumentNode) walk the tree and alter whatever you want. Once you've done this you use the serialize() method of the NotesDOMParser to create your new DXL. With this you can send it to the NotesDXLImporter and apply your changes - magic.

>>> Problem: <<<

This works fine unless you have escape characters in your element attributes, this means one of the following characters: ' " & < >. This problem only seems to be a occur if the characters are in attributes, formula or text on forms is not a problem. If you have a quote in a column header or field help description for instance, then your import is going to fail.

The reason is, when you serialize the modified DOM the NotesDOMParser doesn't convert these characters into XML correctly (it would seem, or maybe it's supposed to work this way?). Instead of &apos; you get ' and instead of &quot; you get ". So your attributes look like this templatename='peoples'stemplate' - oops.

>>> Solution: <<<

You cannot use the NotesDOMParser simply put, I could not find a way to do it after two weeks of playing around. Admittedly this is my first delve into the world of DXL but I could not work around this issue. I even found a IBM TechNote detailing the problem.

The solution is to use the NotesSAXParser. The difference is the NotesSAXParser has a number of events, which can be used to capture problems in the DXL. The only issue is that the NotesSAXParser is not designed to work with large amounts of information so this means it's a rather slow process. For instance, my bench test of a 3 Mb file takes around 30 seconds for a 14 Mb database abotu 3 minutes.

Essentially we take the following steps;

1. Export the NotesDatabase using NotesDXLExporter
2. Using the NotesSAXParser re-write the entire DXL document from the export.
2.1 Whenever we encounter an attribute we convert any escape characters to their correct XML entity.
2.2 In addition we add some extra code to alter 'existing' attributes.
2.3 Unlike when we use the NotesDOMParser not ALL attributes are exported. Some that have not been set (such as Template Name) will not be included in the export - so if we want to modify them, we have to add them manually to the DXL being output.
3.0 Using the NotesDXLImporter import our newly modified and illegal escape character free DXL document to the new or even existing NotesDatabase object.

Here's the code... if anyone has a better way to do this - I WOULD LOVE TO HEAR!! : )

Thanks,


------------------------------CODE------------------------------
Declarations
Dim varAttributes As Variant
Dim varValues As Variant

Function fSetDBProperties(dbNew As NotesDatabase, varTmpAttributes As Variant, varTmpValues As Variant) As NotesDatabase

%REM
Name: fSetDBProperties
Description: Sets the database properties, calls the generic routine to set the properties using DXL
Returns: Boolean. True is the properties were modified correctly, False if it failed
Parameters: dbNew - The database to be altered
varTmpAttributes - List of attributes to modify in the database element
varTmpValues - List of corresponding values to replace or add in the attributes
%END REM

Dim session As New NotesSession
Dim streamOut As NotesStream
Dim streamIn As NotesStream
Dim dxlExporter As NotesDXLExporter
Dim dxlImporter As NotesDXLImporter
Dim varUnique As Variant

On Error Goto errorRoutine

varAttributes = varTmpAttributes
varValues = varTmpValues
Print "Setting database properties, please wait for large databases this may take a few minutes."

Set dxlExporter = session.CreateDXLExporter
'Create the stream that will store the DXL
Set streamOut = session.CreateStream
Set streamIn = session.CreateStream
Call streamOut.Truncate
Call streamIn.Truncate

'Convert the db to XML and remove illegal characters e.g. " & ' < >
Set streamIn = fSAXConvert(dbNew)

'Now import the changes
Set dxlImporter = session.CreateDXLImporter
dxlImporter.ReplaceDbProperties = True
Call dxlImporter.SetInput(streamIn.ReadText())
Call dxlImporter.SetOutput(dbNew)
Call dxlImporter.Process

Set fSetDBProperties = dbNew

Exit Function
errorRoutine:
Messagebox "Error! '" & Error & "' (" & Cstr(Err) & ") on line " & Cstr(Erl) &_
" occurred in fModifyDBDXL Function. Please contact your system administrator", 0+48+0+0, "Error"
If Err = 4522 Or Err = 4603 Then
Messagebox "You have a problem with the XML structure or possibly corrupt design element in your database." &_
Chr(13) & "Note: Check field help for single quotes (eg 'text'), this will cause import errors." &_
Chr(13) & "DXL Importer failed because:" & Chr(13) & _
"'" & dxlImporter.Log & "'"

'Record errornous XML file so we can run it through a validator
'varUnique = Evaluate(|@Unique|)
'Set streamErr = session.CreateStream
'Call streamErr.Open("C:\err_" & varUnique(0) & ".xml")
'Call streamErr.WriteText(streamIn.ReadText())
'Call streamErr.Close
'Print "Saved errornous XML to C:\err_" & varUnique(0) & ".xml"
End If
'If the database was created remove it
Call dbNew.Remove
Set fSetDBProperties = Nothing
Exit Function

End Function

Function fSAXConvert(dbNew As NotesDatabase) As NotesStream

Dim session As New NotesSession
Dim saxParser As NotesSAXParser
Dim dxlExporter As NotesDXLExporter
Dim streamDXL As NotesStream

On Error Goto errorRoutine

'We're exporting the database (exporting to DXL)
Set dxlExporter = session.CreateDXLExporter(dbNew)
dxlExporter.OutputDOCTYPE = True
Set streamDXL = session.CreateStream
Call streamDXL.Truncate

'Create the SAX Parser, the results of the parse will be pushed into our stream (streamDXL)
Set saxParser = session.CreateSAXParser(dxlExporter, streamDXL)
On Event SAX_Characters From saxParser Call SAXCharacters
On Event SAX_EndElement From saxParser Call SAXEndElement
On Event SAX_StartDocument From saxParser Call SAXStartDocument
On Event SAX_StartElement From saxParser Call SAXStartElement

'Initiate parsing, by doing this the SAX events are called
'It is in there that the DXL is rewritten and the illegal characters are converted
Call dxlExporter.Process()

'Return the clean XML to be altered
Set fSAXConvert = streamDXL

Exit Function
errorRoutine:
Messagebox "Error! '" & Error & "' (" & Cstr(Err) & ") on line " & Cstr(Erl) &_
" occurred in fSAXConvert Function. Please contact your system administrator", 0+48+0+0, "Error"
Exit Function

End Function

Sub SAXStartDocument (Source As Notessaxparser)

'Write DXL header
Source.Output("<?xml version='1.0'?>" & Chr(10))

End Sub

Sub SAXCharacters (Source As Notessaxparser, Byval Characters As String, _
Count As Long)

'Remove illegal characters from inbetween tags.. eg <formula>remove illegal character's from here</formula>
If Characters <> Chr(10) Then
Source.Output(fReturnCleanString(Characters))
End If

End Sub

Sub SAXEndElement (Source As NotesSAXParser, Byval ElementName As String)

'Terminate the element
Source.Output("</" & elementname & ">" & Chr(10))

End Sub

Sub SAXStartElement (Source As NotesSAXParser,_
Byval strElementName As String, Attributes As NotesSaxAttributeList)

Dim i As Integer, x As Integer
Dim strAttrVal As String
Dim strAttrName As String
Dim varLoc As Variant
Dim varAttrExtra As Variant, varValExtra As Variant
Dim bFound As Boolean

Source.Output("<" & strElementName) 'Open Element
If Attributes.Length > 0 Then
For i = 1 To Attributes.Length
'Get the name of the attribute
strAttrName = Attributes.GetName(i)

'For my purposes, I only need to modify database attributes. I have two global arrays which are pre-populated, if we find ourselves
'modifying the database element, then we need to cycle through all the attributes and modify them.
If strElementName = "database" Then
'For some 'Lotus Notes' reason I could not use ArrayGetIndex here to search the array, so I
'had to use a for loop. When I used the ArrayGetIndex is constantly returned Null even
'though all the parameters seemed to be correct. Go figure.
For x = 0 To Ubound(strAttributes)
bFound = False
If varAttributes(x) = strAttrName Then
'Get the new value for the attribute from our array of values
strAttrVal = fReturnCleanString(varValues(x))
varAttributes(x) = ""
varValues(x) = ""
bFound = True
Exit For
End If
Next
'Didn't find the attribute in the array so put the original value in
If bFound = False Then
strAttrVal = fReturnCleanString(Attributes.GetValue(strAttrName))
End If
Else
'Not the database element, so use the existing attribute value
strAttrVal = fReturnCleanString(Attributes.GetValue(strAttrName))
End If

'Write the attribute
Source.Output(| | & strAttrName & |="| & strAttrVal & |"|) 'Record attribute details
Next

'After all the other element attributes have been processed, add the
'one which have still not been added manually
If strElementName = "database" Then
varAttrExtra = Fulltrim(varAttributes)
varValExtra = Fulltrim(varValues)
For i = 0 To Ubound(varAttrExtra)
Source.Output(| | & varAttrExtra(i) & |="| & varValExtra(i) & |"|)
Next
End If

End If

'Close the element tag here
Source.Output(">")

End Sub

Function fReturnCleanString(strDirtyString As String) As String

'This is where the magic happens removes the illegal characters
'replaces with the correct XML entities so that the DXL can be imported
'by the NotesDXLImporter
If Instr(strDirtyString, |<|)>0 Then
strDirtyString = Replace(strDirtyString,|<|,"&lt;")
End If
If Instr(strDirtyString, |>|)>0 Then
strDirtyString = Replace(strDirtyString,|>|,"&gt;")
End If
If Instr(strDirtyString, |&|)>0 Then
strDirtyString = Replace(strDirtyString,|&|,"&amp;")
End If
If Instr(strDirtyString, |'|)>0 Then
strDirtyString = Replace(strDirtyString,|'|,"&apos;")
End If
If Instr(strDirtyString, |"|)>0 Then
strDirtyString = Replace(strDirtyString,|"|,"&quot;")
End If

fReturnCleanString = strDirtyString

End Function

Usage / Example
Dim session As New NotesSession
Dim dbToModify As NotesDatabase
Dim varAttribs As Variant, varValues As Variant

Redim Preserve varAttribs(1), varValues(1)
varAttribs(0) = "templatename"
varAttribs(1) = "showinopendialog"
varValues(0) = "mytemplate"
varValues(1) = "false" 'Do not show in open database dialog

Set dbToModify = New NotesDatabase("", "c:\lotus\notes\data\mydatabase.nsf")
If dbToModify.IsOpen = False Then
'Database was not found/opened
Exit Sub
End If

Set dbToModify = fSetDBProperties(dbToModify, varAttribs, varValues)
 Comments
Posted by Martin Vereecken on 09/03/2007 01:24:11 AMThank you!
Thank you for pointing out that the serialize method is the big evil when you get this error. This gave me at least some direction to look in. This is the only place on the web where I could find some explanation for this problem.
To me it seems a bit complicated to replace all domcode by saxcode for now, but I remove the ' in all attributes and that seems to do the trick as well (but I know by doing that I change more than I should do).
 Add your comment!