Get different properties of two objects in LibreOffice Basic

The documentation of MRI mentions a useful tool called diff: if you can change a certain property through the GUI but the macro recorder doesn’t record this change then you can still find out the name of the changed property by using diff, which points you to the property that has been changed and that you need to use.

However, it requires python UNO bridge for LibreOffice, which may be a bit awkward to set up. For those who don’t want to bother themselves with python (like me), it may be more convenient to have such a diff macro in LibreOffice Basic. Therefore, I wrote such a macro, which I want to share here in the hope that it may be useful to other people, as well. The entire macro is based on Andrew Pitonyak’s book: OpenOffice.org Macros Explained, Third Edition.

How to use the macro

Create two instances (Obj1 and Obj2) of the same object of type Type and modify the properties in question in only one of them. Then call Diff_obj (Obj1, Obj2, Type, 0). After a sufficient run a message box will show a list of the different properties of the two objects. In the code below I compared to charts (with different data ranges).

The macro contains recursive function calls because some properties are objects themselves, having other properties, which again may be objects with properties and so on. However, some of these “chains” seem to be infinite, making it necessary to limit the steps made in the object hierarchy, which is done by introducing maxDepth.

I wrote comments in the code. For some more information on the function myGetPropertyValue please refer to Function to return properties of an object rather than their names.

In order to run the macro, you will need a library called “TestLib” and it also has to be loaded (double click on its name).

I am using Ubuntu 14.04 and LibreOffice 4.2.8.2.

Option Explicit

Dim oLib
Dim maxDepth As Integer

Sub myDiff
	Dim chart_1, chart_2 As Object
	
	oLib = GlobalScope.BasicLibraries.getByName("TestLib")
	maxDepth = 5

	chart_1 = ThisComponent.Sheets(0).Charts(0).EmbeddedObject
	chart_2 = ThisComponent.Sheets(0).Charts(1).EmbeddedObject
	
	MsgBox Diff_obj (chart_1, chart_2, "EmbeddedObject", 0), 0, "Different properties"
End Sub

Function IsWhiteSpace (iChar As Integer) As Boolean
	Select Case iChar
	Case 9, 10, 13, 32, 160 ' The character is tab, CR, LF, space or a non-breaking space.
		IsWhiteSpace = True
	Case Else
		IsWhiteSpace = False
	End Select
End Function

Function TrimWhite (s As String) As String ' Remove white spaces from both the front and the end of a string
	s = Trim(s)
	Do While Len(s) > 0
		If Not IsWhiteSpace(ASC(s)) Then Exit Do
		s = Right(s, Len(s) - 1)
	Loop
	Do While Len(s) > 0
		If Not IsWhiteSpace(ASC(Right(s, 1))) Then Exit Do
		s = Left(s, Len(s) - 1)
	Loop
	TrimWhite = s
End Function

Sub showMsg (dialMsg As String, Optional dialTitle As String)
	If IsMissing (dialTitle) Then
		If MsgBox (dialMsg, 1) = 2 Then Stop
	Else
		If MsgBox (dialMsg, 1, dialTitle) = 2 Then Stop
	EndIf
End Sub

Function myGetProperties (oObj As Object, sObjName As String) As Variant
	Dim iPos As Integer
	Dim sProps As String
	
	On Local Error GoTo NoDBGProperties
REM I have encountered an object that had no properties at all, so the DBG_Properties resulted in an error.
		sProps = oObj.DBG_Properties
REM Parse the output of DBG_Properties. It has some introductory information that we do not need.
REM We are only interested in what is after the colon.
		iPos = InStr(1, sProps, ":") + 4
		If iPos > 0 Then sProps = TrimWhite(Right(sProps, Len(sProps) - iPos))
REM split on the separator characters ("; ")
		myGetProperties = Split(sProps, "; ")
		Exit Function
	NoDBGProperties:
		showMsg (Error & Chr$(10) & sObjName, "NoDBGProperties")
End Function

Function myGetPropertyValue (v As Variant, oObj As Object, sPropName As String, sModName As String) As String
	Dim oPropInfo
	Dim sCode As String
	
	On Local Error GoTo NoProperty
		oPropInfo = oObj.getPropertySetInfo()
		If oPropInfo.hasPropertyByName (sPropName) Then
			v = oObj.getPropertyValue (sPropName)
			myGetPropertyValue = sModName
			Exit Function
		EndIf
	NoProperty:
		If sModName = "" Then ' the module "MyTest" does not exist
			sModName = "MyTest"
			sCode = "Option Explicit" & Chr$(10) & "Function getProp (oObj As Object)" & Chr$(10) & "On Local Error GoTo propErr" & Chr$(10) & _ 
				"getProp = oObj." & sPropName & Chr$(10) & "Exit Function" & Chr$(10) & _ 
				"propErr:" & Chr$(10) & "getProp = Error" & Chr$(10) & "End Function"
			oLib.insertByName(sModName, sCode)
		Endif
		v = getProp (oObj)
		myGetPropertyValue = sModName
End Function

Function Diff_var (Obj1 As Variant, Obj2 As Variant, ObjName As String, depth As Integer) As String
	Dim i, k As Integer
	Dim s As String
	
	s = ""
	If IsEmpty(Obj1) Then
		If Not IsEmpty(Obj2) Then
			s = ObjName & Chr$(10)
		EndIf
	ElseIf VarType(Obj1) >= 8192 Then ' array
		k = UBound(Obj1)
		If UBound(Obj2) < UBound(Obj1) Then
			k = UBound(Obj2)
			s = s & "UBound (" & ObjName & ")" & Chr$(10)
		Endif
		For i = LBound(Obj1) To k
			If VarType(Obj1(i)) = 9 Then ' object
				s = s & Diff_obj (Obj1(i), Obj2(i), ObjName & "(" & i & ")", depth)
			Else
				s = s & Diff_var (Obj1(i), Obj2(i), ObjName & "(" & i & ")", depth)
			Endif
		Next i
	ElseIf Not (Obj1 = Obj2) Then
		s = ObjName & Chr$(10)
	End If
	Diff_var = s
End Function

Function Diff_obj (Obj1 As Object, Obj2 As Object, ObjName As String, depth As Integer) As String
	Dim i, iPos, m As Integer
	Dim s, sPropName, sModName As String
	Dim oItems(), v_1, v_2
	
	m = depth + 1
	If m = maxDepth Then
		Diff_obj = ""
		Exit Function
	Endif
	s = ""
	If IsNull(Obj1) Then
		If Not IsNull(Obj2) Then
			s = ObjName & Chr$(10)
		EndIf
	Else
		oItems = myGetProperties (Obj1, ObjName)
		If Not IsNull (oItems) Then
			For i = 0 To UBound(oItems)
				iPos = InStr(1, oItems(i), " ") + 1
REM The first part of oItems(i) is the type of the property. We are only interested in its name, which stands after the space.
				sPropName = Mid(oItems(i), iPos)
	
				sModName = myGetPropertyValue (v_1, Obj1, sPropName, "")
				sModName = myGetPropertyValue (v_2, Obj2, sPropName, sModName)
				If sModName <> "" Then
					oLib.removeByName(sModName)
				EndIf
				
				If VarType(v_1) = 9 And VarType(v_2) = 9 Then ' object
					s = s & Diff_obj (v_1, v_2, ObjName & "." & sPropName, m)
				ElseIf VarType(v_1) = VarType(v_2) Then
					s = s & Diff_var (v_1, v_2, ObjName & "." & sPropName, m)
				Else
					s = s & ObjName & "." & sPropName & Chr$(10)
				EndIf
			Next i
		EndIf
	EndIf
	Diff_obj = s
End Function