Skip to content

Instantly share code, notes, and snippets.

@AMekss
Created October 26, 2011 17:03
Show Gist options
  • Save AMekss/1317006 to your computer and use it in GitHub Desktop.
Save AMekss/1317006 to your computer and use it in GitHub Desktop.
This class provides methods for XML structured data processing in Lotus Script.
%REM
Copyright 2009 TietoEnator Alise (developed by Arturs Mekss)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
%END REM
Type NodeQuery
nodeName As String
subNodeName As String
subNodeValue As String
attrName As String
attrValue As String
nth As Integer
isLast As Boolean
End Type
%REM
Version: 1.0.1
Author: AMe
Purpose: This class provides methods for XML structured data processing in LotusScript.
XML structure could be read from files or String variables and/or could be written to files or printed out as Text.
There are methods which can be used in order to modify existing XML structure or XML structure
could be built from scratch
Properties:
- useCache As Boolean (read/write) 'isn't finished yet
- isReady As Boolean (read only)
Methods:
- new(namespaceURI As String)
- createNew(rootNodeName As String, stylesheetHref As String) As Boolean
- parseString(sourceStr As String) As Boolean
- parseFile(sourceFilePath As String) As Boolean
- toStream() As NotesStream
- toText() As Boolean
- toFile(targetFilePath As String)
- setXSLT(sourceXSLT As XMLProcessor|String) As Boolean
- appendNode(parentNode As NotesDOMElementNode, nodeName As String, nodeValue As String, altNodeValue As String) As NotesDOMElementNode
- setNamespaceURI(namespaceURI As String)
- setNodeAttributes(elementNode As NotesDOMElementNode, attributeList List As String) As Boolean
- selectAllNodes(scope As NotesDOMElementNode, query As String) As Variant
- selectAllValues(scope As NotesDOMElementNode, query As String, altVal As String) As Variant
- selectNode(scope As NotesDOMElementNode, query As String) As NotesDOMElementNode
- selectValue(scope As NotesDOMElementNode, query As String, altVal As String) As String
- clearCache() As Boolean
Examples:
'1. Build XML from scratch and store to file
Dim xml As XMLProcessor
Dim personNode As NotesDOMElementNode
Set xml = New XMLProcessor("")
Call xml.createNew("persons", "")
Set personNode = xml.appendNode(Nothing, "person", "", "") 'if parent node is Nothing then root node will be used as parent node
Call xml.appendNode(personNode, "name", "Bart", "")
Call xml.appendNode(personNode, "sureName", "Simpson", "")
Set personNode = xml.appendNode(Nothing, "person", "", "")
Call xml.appendNode(personNode, "name", "Jonny", "")
Call xml.appendNode(personNode, "sureName", "Bravo", "")
Call xml.toFile("D:\WORK_TMP\xml\persons.xml")
'2. Read XML from file and print it as a plain text
Dim xml As XMLProcessor
Set xml = New XMLProcessor("")
Call xml.parseFile("D:\WORK_TMP\xml\persons.xml")
Call xml.toText()
'3. Read XML from file and get values via selector
Dim xml As XMLProcessor
Dim node As NotesDOMElementNode
Set xml = New XMLProcessor("")
Call xml.parseFile("D:\WORK_TMP\xml\persons.xml")
Messagebox xml.selectValue(Nothing, "person:2>name", "-")
Messagebox xml.selectValue(Nothing, "person(name=Bart)>sureName", "-")
'4. Tranform XMl with XSL
Dim xml As new XMLProcessor("")
Dim xsl As New XMLProcessor("")
Call xml.parseFile("D:\WORK_TMP\xml\page4.xml")
Call xsl.parseFile("D:\WORK_TMP\xml\content.xsl")
Call xml.setXSLT(xsl)
Call xml.toFile("D:\WORK_TMP\xml\page4.htm")
%END REM
Class XMLProcessor
'General variables
Private session As NotesSession
Private objIsReady As Boolean 'Object is properly initialized
Private namespaceURIStr As String
Private prefixStr As String
Private cacheList List As Variant
'XSLT variables
Private isXSLTDefined As Boolean
Private XSLT As NotesStream
Private transformerLog As String
'InputStream variables
Private InputStream As NotesStream
'OutputStream variables
Private outputStream As NotesStream
'DOM variables
Private domparser As NotesDOMParser
Private domdoc As NotesDOMDocumentNode
Private rootNode As NotesDOMElementNode
'PUBLIC Scope:
Public useCache As Boolean
Public Sub new(namespaceURI As String)
On Error Goto errh
Set Me.session = New NotesSession
Call Me.setNamespaceURI(namespaceURI)
'Me.useCache = True
Exit Sub
errh: Call Me.onError()
Exit Sub
End Sub
Public Sub Delete
On Error Goto errh
' -- Closing opened resources
' closing xslt stream
If Me.isXSLTDefined Then Call Me.XSLT.Close()
'closing output stream
If Not Me.outputStream Is Nothing Then Call Me.outputStream.Close
'closing input stream
If Not Me.inputStream Is Nothing Then Call Me.inputStream.Close
Exit Sub
errh: Call Me.onError()
Exit Sub
End Sub
Public Function createNew(rootNodeName As String, stylesheetHref As String) As Boolean
On Error Goto errh
Dim piNode As NotesDOMProcessingInstructionNode
If rootNodeName <> "" Then
Set domParser=session.CreateDOMParser
Set domdoc = domparser.Document
domParser.ExpandEntityReferences = True
Set piNode = domdoc.CreateProcessingInstructionNode(|xml|, |version="1.0" encoding="UTF-8"|)
Call domdoc.appendChild(piNode)
If stylesheetHref <> "" Then
Set piNode = domdoc.CreateProcessingInstructionNode(|xml-stylesheet|, |type="text/xsl" href="|+stylesheetHref+|"|)
Call domdoc.appendChild(piNode)
End If
If Me.namespaceURIStr = "" Then
Set rootNode = domdoc.CreateElementNode(rootNodeName)
Else
domParser.DoNamespaces = True
Set rootNode = domdoc.CreateElementNodeNS(Me.namespaceURIStr, rootNodeName)
Call rootNode.SetAttribute("xmlns", Me.namespaceURIStr)
End If
Call domdoc.appendChild(rootNode)
Me.createNew = True
Me.objIsReady = True
End If
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Public Function isReady() As Boolean
isReady = Me.objIsReady
End Function
Public Function parseString(sourceStr As String) As Boolean
On Error Goto errh
If Me.createDOMParserFromSource(sourceStr) Then
parseString = True
Me.objIsReady = True
End If
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Public Function parseFile(sourceFilePath As String) As Boolean
On Error Goto errh
Set Me.InputStream = session.CreateStream()
If Me.InputStream.Open(sourceFilePath, "UTF-8") Then
If Me.InputStream.Bytes = 0 Then Error 3000, "File does not exist or is empty: " + sourceFilePath
If Me.createDOMParserFromSource(Me.InputStream) Then
parseFile = True
Me.objIsReady = True
End If
Else
Error 3000, "Can't open " & sourceFilePath
End If
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Function toStream() As NotesStream
On Error Goto errh
If Me.processOutput("") Then
Set Me.toStream = Me.outputStream
End If
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Public Function toText() As Boolean
On Error Goto errh
If Me.processOutput("") Then
Do
Print Me.outputStream.ReadText(STMREAD_LINE, EOL_CRLF)
Loop Until Me.outputStream.IsEOS
Call Me.outputStream.Close
toText = True
End If
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Public Function toFile(targetFilePath As String) As Boolean
On Error Goto errh
If Me.processOutput(targetFilePath) Then
Call Me.outputStream.Close
toFile = True
End If
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Public Function setXSLT(sourceXSLT As Variant) As Boolean
On Error Goto errh
If Isobject(sourceXSLT) Then
If Not sourceXSLT Is Nothing Then
If sourceXSLT.isReady()Then
Set Me.XSLT = sourceXSLT.toStream()
Me.isXSLTDefined = True
End If
Else
Set Me.XSLT = Nothing
Me.isXSLTDefined = False
End If
Else
If Cstr(sourceXSLT) = "" Then
Set Me.XSLT = Nothing
Me.isXSLTDefined = False
Else
Set Me.XSLT = Me.session.CreateStream
Me.XSLT.WriteText(sourceXSLT)
Me.isXSLTDefined = True
End If
End If
setXSLT = True
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Public Function appendNode(parentNode As NotesDOMElementNode, nodeName As String, nodeValue As String, altNodeValue As String) As NotesDOMElementNode
On Error Goto errh
Dim pNode As NotesDOMElementNode
Dim childNode As NotesDOMElementNode
Dim value As String
If Not Me.objIsReady Then Error 3000, "Error: Object is not fully inicialized"
value = nodeValue
If value = "" Then value = altNodeValue
If parentNode Is Nothing Then
Set pNode = rootNode
Else
Set pNode = parentNode
End If
If Me.namespaceURIStr = "" Then
Set childNode = domdoc.CreateElementNode(nodeName)
Else
Set childNode = domdoc.CreateElementNodeNS(Me.namespaceURIStr, nodeName)
End If
If value <> "" Then
Call childNode.AppendChild(domdoc.CreateTextNode(value))
End If
Call pNode.appendChild(childNode)
Set appendNode = childNode
Exit Function
errh: Call Me.onError()
Exit Function
End Function
%REM
Function setNodeAttributes
Description: attributeList("<Attribute_Name>") = "<Attribute_Value>"
%END REM
Public Function setNodeAttributes(elementNode As NotesDOMElementNode, attributeList List As String) As Boolean
On Error Goto errh
If Not Me.objIsReady Then Error 3000, "Error: Object is not fully inicialized"
If elementNode Is Nothing Then Error 3000, "Error: Given node is nothing"
Forall attr In attributeList
If Me.namespaceURIStr = "" Then
Call elementNode.Setattribute(Listtag(attr), attr)
Else
Call elementNode.Setattributens(Me.namespaceURIStr, Listtag(attr), attr)
End If
End Forall
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Public Sub setNamespaceURI(namespaceURI As String)
Me.prefixStr = "srx"
Me.namespaceURIStr = namespaceURI
End Sub
%REM
'#AMe (17.09.2009) Get & return node from XML document by selector query
'Supported query syntax:
'E (Element)
'E>F (F is a child of E)
'E:n (nth E element in result set, default is the first element (for .selectNode) or all elements(for .select))
'E(F) (E which has child node F)
'E(F=abc) (E which has child node F with value is equal to 'abc')
'E[attr] (E which has attribute "attr")
'E[attr=abc] (E which has attribute "attr" which is equal to 'abc')
'For example:
'Product>Part:3 this query will return 3rd child Part tag of a Product
%END REM
Public Function selectAllNodes(scope As NotesDOMElementNode, query As String) As Variant
On Error Goto errh
Dim emptyValue(0) As NotesDOMElementNode
Dim rv As Variant
Dim nQueryList List As NodeQuery
Dim scopeNode As NotesDOMElementNode
Dim cacheKey As String
If Not Me.objIsReady Then Error 3000, "Error: Object is not fully inicialized"
' -- check in cache if this option is enabled
If Me.useCache Then
cacheKey = scope.Nodename + query
If Iselement(Me.cacheList(cacheKey)) Then
selectAllNodes = Me.cacheList(cacheKey)
Exit Function
End If
End If
' -- get nodes by query
rv = emptyValue
If Me.parseQuery(query, nQueryList) Then
If scope Is Nothing Then
Set scopeNode = rootNode
Else
Set scopeNode = scope
End If
Forall nq In nQueryList
rv = Me.getNodes(scopeNode, nq)
Set scopeNode = rv(0)
If scopeNode Is Nothing Then
Exit Forall
End If
End Forall
End If
' -- return result
If Me.useCache Then Me.cacheList(cacheKey) = rv
selectAllNodes = rv
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Public Function selectAllValues(scope As NotesDOMElementNode, query As String, altVal As String) As Variant
On Error Goto errh
Dim rv() As Variant
Dim nodes As Variant, i As Integer, max As Integer
Dim elementNode As NotesDOMElementNode
Set nodes = Me.selectAllNodes(scope, query)
max = Ubound(nodes)
Redim rv(max)
For i=0 To max
Set elementNode = nodes(i)
If Not elementNode Is Nothing Then
If Not elementNode.FirstChild.IsNull Then
rv(i) = elementNode.FirstChild.NodeValue
End If
End If
Next i
If rv(0) = "" Then rv(0) = altVal
selectAllValues = rv
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Public Function selectNode(scope As NotesDOMElementNode, query As String) As NotesDOMElementNode
On Error Goto errh
Set selectNode = Me.selectAllNodes(scope, query)(0)
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Public Function selectValue(scope As NotesDOMElementNode, query As String, altVal As String) As String
On Error Goto errh
Dim rv As String
Dim elementNode As NotesDOMElementNode
rv = altVal
Set elementNode = Me.selectNode(scope, query)
If Not elementNode Is Nothing Then
If Not elementNode.FirstChild.IsNull Then
rv = elementNode.FirstChild.NodeValue
End If
End If
selectValue = rv
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Function clearCache() As Boolean
On Error Goto errh
Erase Me.cacheList
Me.clearCache = True
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Public Function getTransformerLog() As String
getTransformerLog = transformerLog
End Function
'PRIVATE Scope
Private Sub onError()
Error 3000, " [XMLProcessor." & Getthreadinfo( 10 ) & ": " & Cstr( Erl ) & "] " & Error
End Sub
Private Function createDOMParserFromSource(source As Variant) As Boolean
On Error Goto errh
Set Me.domParser = Me.session.CreateDOMParser(source)
If Me.namespaceURIStr <> "" Then
domParser.DoNamespaces = True
End If
Call Me.domParser.Process
Set Me.rootNode = Me.domParser.Document.DocumentElement
createDOMParserFromSource = True
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Private Function processOutput(targetFilePath As String) As Boolean
On Error Goto errh
If Not Me.objIsReady Then Error 3000, "Error: Object is not fully inicialized"
Set Me.outputStream = session.CreateStream
If targetFilePath <> "" Then
If Me.outputStream.Open(targetFilePath, "utf-8") Then
Call Me.outputStream.Truncate
Else
Error 3000, "Can't open " & targetFilePath
End If
End If
'Me.domparser.AddXMLDeclNode = True
Call Me.domparser.SetOutput(Me.outputStream)
Call Me.domparser.serialize()
If Me.isXSLTDefined Then
Call Me.transform()
End If
Me.outputStream.Position = 0
processOutput = True
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Private Function transform() As Boolean
On Error Goto errh
Dim transformedOutputStream As NotesStream
Dim transformer As NotesXSLTransformer
'checking
If Not Me.objIsReady Then Error 3000, "Error: Object is not fully inicialized"
If Not Me.isXSLTDefined Then Error 3000, "Error: XSLT is not defined for transformation"
'transformation
Set transformedOutputStream = Me.session.Createstream()
Set transformer = Me.session.CreateXSLTransformer(Me.outputStream , Me.XSLT , transformedOutputStream)
Call transformer.Process()
'copy streams
Call Me.outputStream.Truncate()
Dim buffer As String
Do
buffer = transformedOutputStream.ReadText(STMREAD_LINE, EOL_CRLF)
Call outputStream.WriteText(buffer)
Loop Until transformedOutputStream.IsEOS
transform = True
Exit Function
errh:
If Not transformer Is Nothing Then
If transformer.log <> "" Then
transformerLog = transformer.log
Call Me.outputStream.Truncate()
Me.outputStream.Writetext(transformer.log)
End If
End If
Call Me.onError()
Exit Function
End Function
Private Function parseQuery(query As String, nQueryList List As NodeQuery) As Boolean
On Error Goto errh
Dim idx As Integer, max As Integer
Dim qLevels As Variant
Dim tmpVar As Variant
Dim nq As NodeQuery
qLevels = Split(query, ">")
max = Ubound(qLevels)
Forall level In qLevels
'if is not Last Node then default nth value is 1
nq.nth = 0
If max <> idx Then nq.nth = 1
nq.nodeName = ""
nq.subNodeName = ""
nq.subNodeValue = ""
nq.attrName = ""
nq.attrValue = ""
' get nth
tmpVar = Me.splitAttrValue(level, ":")
level = tmpVar(0)
If tmpVar(1) <> "" Then
If Isnumeric(tmpVar(1)) Then
nq.nth = Cint(tmpVar(1))
End If
End If
' check for ()
tmpVar = Me.excludeStr(level, "(", ")")
level = tmpVar(0)
tmpVar = Me.splitAttrValue(tmpVar(1), "=")
nq.subNodeName = tmpVar(0)
nq.subNodeValue = tmpVar(1)
' check for []
tmpVar = Me.excludeStr(level, "[", "]")
level = tmpVar(0)
tmpVar = Me.splitAttrValue(tmpVar(1), "=")
nq.attrName = tmpVar(0)
nq.attrValue = tmpVar(1)
nq.nodeName = level
nQueryList(Cstr(idx)) = nq
idx = idx + 1
End Forall
parseQuery = True
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Private Function excludeStr(Byval srcStr As String, startChar As String, endChar As String) As Variant
On Error Goto errh
Dim tmpStr As String
Dim rv(1) As String
rv(0) = srcStr
If Instr(srcStr, startChar) > 0 Then
If Instr(srcStr, endChar) > 0 Then
rv(0) = Strleft(srcStr, startChar) + Strright(srcStr, endChar)
tmpStr = Strright(srcStr, startChar)
tmpStr = Strleft(tmpStr, endChar)
rv(1) = tmpStr
End If
End If
excludeStr = rv
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Private Function splitAttrValue(Byval srcStr As String, sepStr As String) As Variant
On Error Goto errh
Dim rv(1) As String
rv(0) = srcStr
If Instr(srcStr, sepStr) > 0 Then
rv(0) = Strleftback(srcStr, sepStr)
rv(1) = Strrightback(srcStr, sepStr)
End If
splitAttrValue = rv
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Private Function getNodes(scopeNode As notesDOMElementNode, nq As NodeQuery) As Variant
On Error Goto errh
Dim nodeList As NotesDOMNodeList
Dim currNode As NotesDOMElementNode
Dim i As Integer, max As Integer, nth As Integer
'rv
Dim chk As Boolean
Dim rvList List As NotesDOMElementNode
If Me.namespaceURIStr = "" Then
Set nodeList = scopeNode.GetElementsByTagName(nq.nodeName)
Else
Set nodeList = scopeNode.Getelementsbytagnamens(Me.namespaceURIStr, nq.nodeName)
End If
max = nodeList.NumberOfEntries
If (max > 0) Then
'1. Simple query by tag name
If nq.subNodeName = "" Then
If nq.attrName = "" Then
If nq.nth > 0 Then
If nq.nth <= max Then
getNodes = Me.returnAsNodeArray(nodeList.GetItem(nq.nth))
Exit Function
End If
End If
End If
End If
'2. More complex query
For i=1 To max
chk = False
Set currNode = nodeList.GetItem(i)
chk = checkForAttribute(currNode, nq.attrName, nq.attrValue)
If chk Then chk = checkForContent(currNode, nq.subNodeName, nq.subNodeValue)
If chk Then
nth = nth + 1
If nq.nth = 0 Then
Set rvList(Cstr(nth)) = currNode
Else
If nth = nq.nth Then
getNodes = Me.returnAsNodeArray(currNode)
Exit Function
End If
End If
End If
Next i
End If
getNodes = Me.returnAsNodeArray(rvList)
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Private Function returnAsNodeArray(inputVal As Variant) As Variant
On Error Goto errh
Dim rv() As NotesDOMElementNode, idx As Integer
If Islist ( inputVal ) Then
Forall v In inputVal
idx = idx + 1
End Forall
Redim rv(idx-1)
idx = 0
Forall v In inputVal
Set rv(idx) = v
idx = idx + 1
End Forall
Else
Redim rv(0)
Set rv(0) = inputVal
End If
returnAsNodeArray = rv
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Private Function checkForAttribute(scopeNode As notesDOMElementNode, containsAttributeName As String, containsAttributeValue As String) As Boolean
On Error Goto errh
Dim rv As Boolean
Dim attrNode As NotesDOMAttributeNode
If containsAttributeName = "" Then
checkForAttribute = True
Exit Function
End If
If Me.namespaceURIStr = "" Then
Set attrNode = scopeNode.GetAttributeNode(containsAttributeName)
Else
Set attrNode = scopeNode.Getattributenodens(Me.namespaceURIStr, containsAttributeName)
End If
If Not attrNode.IsNull Then
If containsAttributeValue = "" Then
rv = True
Else
If attrNode.AttributeValue = containsAttributeValue Then rv = True
End If
End If
checkForAttribute = rv
Exit Function
errh: Call Me.onError()
Exit Function
End Function
Private Function checkForContent(scopeNode As notesDOMElementNode, containsTagName As String, containsTagValue As String) As Boolean
On Error Goto errh
Dim rv As Boolean
If containsTagName = "" Then
checkForContent = True
Exit Function
End If
If containsTagValue = "" Then
If Not Me.selectNode(scopeNode, containsTagName+":1") Is Nothing Then rv = True
Else
If Me.selectValue(scopeNode, containsTagName+":1", "") = containsTagValue Then rv = True
End If
checkForContent = rv
Exit Function
errh: Call Me.onError()
Exit Function
End Function
End Class
@ErinLyle
Copy link

Hello Arturs,

Thank you for posting this code. I've tried examples 2 and 3 and, unfortunately, I got the error:
[XMLProcessor:PARSEFILE:208]
[XMLProcessor:CREATEDOMPARSERFROMSOURCE:497] DOM parser information failed.

Have I set something up incorrectly?

Thank you in advance,
Erin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment