<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
<script:module xmlns:script="http://openoffice.org/2000/script" script:name="SF_Dictionary" script:language="StarBasic" script:moduleType="normal">REM =======================================================================================================================
REM ===			The ScriptForge library and its associated libraries are part of the LibreOffice project.				===
REM ===					Full documentation is available on https://help.libreoffice.org/								===
REM =======================================================================================================================

Option Compatible
Option ClassModule

Option Explicit

&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;
&apos;&apos;&apos;	SF_Dictionary
&apos;&apos;&apos;	=============
&apos;&apos;&apos;		Class for management of dictionaries
&apos;&apos;&apos;		A dictionary is a collection of key-item pairs
&apos;&apos;&apos;			The key is either a case-sensitive or a not case-sensitive string
&apos;&apos;&apos;			Items may be of any type
&apos;&apos;&apos;		Keys, items can be retrieved, counted, etc.
&apos;&apos;&apos;
&apos;&apos;&apos;		The implementation is based on 3 one-column arrays:
&apos;&apos;&apos;		1) The keys - sorted
&apos;&apos;&apos;		2) The positions in 3) - same sequence as 1)
&apos;&apos;&apos;		3) The item contents - stacked up when defined - erased items are set to Empty
&apos;&apos;&apos;
&apos;&apos;&apos;		Why a Dictionary class beside the builtin Collection class ?
&apos;&apos;&apos;			A standard Basic collection does not support the retrieval of the keys
&apos;&apos;&apos;			A standard Basic collection does not support the update/removal of entries
&apos;&apos;&apos;			No easy conversion to/from json or PropertyValues
&apos;&apos;&apos;
&apos;&apos;&apos;		Service instantiation example:
&apos;&apos;&apos;			Dim myDict As Variant
&apos;&apos;&apos;			myDict = CreateScriptService(&quot;Dictionary&quot;, True)		&apos;	Case-sensitive, default = False
&apos;&apos;&apos;
&apos;&apos;&apos;		Detailed user documentation:
&apos;&apos;&apos;			https://help.libreoffice.org/latest/en-US/text/sbasic/shared/03/sf_dictionary.html?DbPAR=BASIC
&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;&apos;

REM ================================================================== EXCEPTIONS

Const DUPLICATEKEYERROR			=	&quot;DUPLICATEKEYERROR&quot;		&apos;	Key exists already
Const UNKNOWNKEYERROR			=	&quot;UNKNOWNKEYERROR&quot;		&apos;	Key not found
Const INVALIDKEYERROR			=	&quot;INVALIDKEYERROR&quot;		&apos;	Key contains only spaces

REM ============================================================= PRIVATE MEMBERS

Private [Me]				As Object
Private [_Parent]			As Object
Private ObjectType			As String		&apos; Must be &quot;DICTIONARY&quot;
Private ServiceName			As String
Private CaseSensitive		As Boolean		&apos; Determined at dictionary creation, default = False
Private MapKeys				As Variant		&apos; Array of keys
Private MapPositions		As Variant		&apos; Array of indexes in MapItems, sorted as MapKeys
Private MapItems			As Variant		&apos; Array of ItemMaps
Private _MapSize			As Long			&apos; Total number of entries in the dictionary
Private _MapRemoved			As Long			&apos; Number of inactive entries in the dictionary

REM ===================================================== CONSTRUCTOR/DESTRUCTOR

REM -----------------------------------------------------------------------------
Private Sub Class_Initialize()
	Set [Me] = Nothing
	Set [_Parent] = Nothing
	ObjectType = &quot;DICTIONARY&quot;
	ServiceName = &quot;ScriptForge.Dictionary&quot;
	CaseSensitive = False
	MapKeys = Array()
	MapPositions = Array()
	MapItems = Array()
	_MapSize = 0
	_MapRemoved = 0
End Sub		&apos;	ScriptForge.SF_Dictionary Constructor

REM -----------------------------------------------------------------------------
Private Sub Class_Terminate()
	Call Class_Initialize()
End Sub		&apos;	ScriptForge.SF_Dictionary Destructor

REM -----------------------------------------------------------------------------
Public Function Dispose() As Variant
	RemoveAll()
	Set Dispose = Nothing
End Function	&apos;	ScriptForge.SF_Dictionary Explicit destructor

REM ================================================================== PROPERTIES

REM -----------------------------------------------------------------------------
Property Get Count() As Long
&apos;&apos;&apos;	Actual number of entries in the dictionary
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		myDict.Count

	Count = _PropertyGet(&quot;Count&quot;)

End Property	&apos;	ScriptForge.SF_Dictionary.Count

REM -----------------------------------------------------------------------------
Public Function Item(Optional ByVal Key As Variant) As Variant
&apos;&apos;&apos;	Return the value of the item related to Key
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		Key: the key value (string)
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		Empty if not found, otherwise the found value
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		myDict.Item(&quot;ThisKey&quot;)
&apos;&apos;&apos;	NB: defined as a function to not disrupt the Basic IDE debugger

	Item = _PropertyGet(&quot;Item&quot;, Key)

End Function	&apos;	ScriptForge.SF_Dictionary.Item

REM -----------------------------------------------------------------------------
Property Get Items() as Variant
&apos;&apos;&apos;	Return the list of Items as a 1D array
&apos;&apos;&apos;	The Items and Keys properties return their respective contents in the same order
&apos;&apos;&apos;		The order is however not necessarily identical to the creation sequence
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		The array is empty if the dictionary is empty
&apos;&apos;&apos;	Examples
&apos;&apos;&apos;		a = myDict.Items
&apos;&apos;&apos;		For Each b In a ...

	Items = _PropertyGet(&quot;Items&quot;)

End Property	&apos;	ScriptForge.SF_Dictionary.Items

REM -----------------------------------------------------------------------------
Property Get Keys() as Variant
&apos;&apos;&apos;	Return the list of keys as a 1D array
&apos;&apos;&apos;	The Keys and Items properties return their respective contents in the same order
&apos;&apos;&apos;		The order is however not necessarily identical to the creation sequence
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		The array is empty if the dictionary is empty
&apos;&apos;&apos;	Examples
&apos;&apos;&apos;		a = myDict.Keys
&apos;&apos;&apos;		For each b In a ...

	Keys = _PropertyGet(&quot;Keys&quot;)

End Property	&apos;	ScriptForge.SF_Dictionary.Keys

REM ===================================================================== METHODS

REM -----------------------------------------------------------------------------
Public Function Add(Optional ByVal Key As Variant _
						, Optional ByVal Item As Variant _
						) As Boolean
&apos;&apos;&apos;	Add a new key-item pair into the dictionary
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		Key: must not yet exist in the dictionary
&apos;&apos;&apos;		Item: any value, including an array, a Basic object, a UNO object, ...
&apos;&apos;&apos;	Returns: True if successful
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		DUPLICATEKEYERROR: such a key exists already
&apos;&apos;&apos;		INVALIDKEYERROR: zero-length string or only spaces
&apos;&apos;&apos;	Examples:
&apos;&apos;&apos;		myDict.Add(&quot;NewKey&quot;, NewValue)

Dim vItemMap As Variant			&apos;	Output of SF_Array._FindItem
Dim lIndex As Long				&apos;	Index in MapKeys and MapPositions
Const cstThisSub = &quot;Dictionary.Add&quot;
Const cstSubArgs = &quot;Key, Item&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	Add = False

Check:
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not SF_Utils._Validate(Key, &quot;Key&quot;, V_STRING) Then GoTo Catch
		If IsArray(Item) Then
			If Not SF_Utils._ValidateArray(Item, &quot;Item&quot;) Then GoTo Catch
		Else
			If Not SF_Utils._Validate(Item, &quot;Item&quot;) Then GoTo Catch
		End If
	End If
	If Key = Space(Len(Key)) Then GoTo CatchInvalid

Try:
	_MapSize = _MapSize + 1
	vItemMap = SF_Array._FindItem(MapKeys, Key, CaseSensitive, &quot;ASC&quot;)
	If vItemMap(0) Then GoTo CatchDuplicate		&apos;	Key exists already
	lIndex = vItemMap(1)
	MapKeys = SF_Array.Insert(MapKeys, lIndex, Key)
	MapPositions = SF_Array.Insert(MapPositions, lIndex, _MapSize)
	ReDim Preserve MapItems(1 To _MapSize)
	MapItems(_MapSize) = Item
	Add = True

Finally:
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
CatchDuplicate:
	SF_Exception.RaiseFatal(DUPLICATEKEYERROR, &quot;Key&quot;, Key)
	GoTo Finally
CatchInvalid:
	SF_Exception.RaiseFatal(INVALIDKEYERROR, &quot;Key&quot;)
	GoTo Finally
End Function	&apos;	ScriptForge.SF_Dictionary.Add

REM -----------------------------------------------------------------------------
Public Function ConvertToArray() As Variant
&apos;&apos;&apos;	Store the content of the dictionary in a 2-columns array:
&apos;&apos;&apos;	Key stored in 1st column, Item stored in 2nd
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		a zero-based 2D array(0:Count - 1, 0:1)
&apos;&apos;&apos;		an empty array if the dictionary is empty

Dim vArray As Variant		&apos;	Return value
Dim sKey As String			&apos;	Tempry key
Dim vKeys As Variant		&apos;	Array of keys
Dim lCount As Long			&apos;	Counter
Const cstThisSub = &quot;Dictionary.ConvertToArray&quot;
Const cstSubArgs = &quot;&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch

Check:
	SF_Utils._EnterFunction(cstThisSub, cstSubArgs)

Try:
	vArray = Array()
	If Count = 0 Then
	Else
		ReDim vArray(0 To Count - 1, 0 To 1)
		lCount = -1
		vKeys = Keys
		For Each sKey in vKeys
			lCount = lCount + 1
			vArray(lCount, 0) = sKey
			vArray(lCount, 1) = Item(sKey)
		Next sKey
	End If
		
Finally:
	ConvertToArray = vArray()
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	ScriptForge.SF_Dictionary.ConvertToArray

REM -----------------------------------------------------------------------------
Public Function ConvertToJson(ByVal Optional Indent As Variant) As Variant
&apos;&apos;&apos;	Convert the content of the dictionary to a JSON string
&apos;&apos;&apos;	JSON = JavaScript Object Notation: https://en.wikipedia.org/wiki/JSON
&apos;&apos;&apos;	Limitations
&apos;&apos;&apos;		Allowed item types: String, Boolean, numbers, Null and Empty
&apos;&apos;&apos;		Arrays containing above types are allowed
&apos;&apos;&apos;		Dates are converted into strings (not within arrays)
&apos;&apos;&apos;		Other types are converted to their string representation (cfr. SF_String.Represent)
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		Indent:
&apos;&apos;&apos;			If indent is a non-negative integer or string, then JSON array elements and object members will be pretty-printed with that indent level.
&apos;&apos;&apos;			An indent level &lt;= 0 will only insert newlines.
&apos;&apos;&apos;			&quot;&quot;, (the default) selects the most compact representation.
&apos;&apos;&apos;			Using a positive integer indent indents that many spaces per level.
&apos;&apos;&apos;			If indent is a string (such as Chr(9)), that string is used to indent each level.
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		the JSON string
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		myDict.Add(&quot;p0&quot;, 12.5)
&apos;&apos;&apos;		myDict.Add(&quot;p1&quot;, &quot;a string àé&quot;&quot;ê&quot;)
&apos;&apos;&apos;		myDict.Add(&quot;p2&quot;, DateSerial(2020,9,28))
&apos;&apos;&apos;		myDict.Add(&quot;p3&quot;, True)
&apos;&apos;&apos;		myDict.Add(&quot;p4&quot;, Array(1,2,3))
&apos;&apos;&apos;		MsgBox a.ConvertToJson()	&apos;	{&quot;p0&quot;: 12.5, &quot;p1&quot;: &quot;a string \u00e0\u00e9\&quot;\u00ea&quot;, &quot;p2&quot;: &quot;2020-09-28&quot;, &quot;p3&quot;: true, &quot;p4&quot;: [1, 2, 3]}

Dim sJson As String				&apos;	Return value
Dim vArray As Variant			&apos;	Array of property values
Dim oPropertyValue As Object	&apos;	com.sun.star.beans.PropertyValue
Dim sKey As String				&apos;	Tempry key
Dim vKeys As Variant			&apos;	Array of keys
Dim vItem As Variant			&apos;	Tempry item
Dim iVarType As Integer			&apos;	Extended VarType
Dim lCount As Long				&apos;	Counter
Dim vIndent As Variant			&apos;	Python alias of Indent
Const cstPyHelper = &quot;$&quot; &amp; &quot;_SF_Dictionary__ConvertToJson&quot;

Const cstThisSub = &quot;Dictionary.ConvertToJson&quot;
Const cstSubArgs = &quot;[Indent=Null]&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch

Check:
	If IsMissing(Indent) Or IsEmpty(INDENT) Then Indent = &quot;&quot;
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not SF_Utils._Validate(Indent, &quot;Indent&quot;, Array(V_STRING, V_NUMERIC)) Then GoTo Finally
	End If
	sJson = &quot;&quot;

Try:
	vArray = Array()
	If Count = 0 Then
	Else
		ReDim vArray(0 To Count - 1)
		lCount = -1
		vKeys = Keys
		For Each sKey in vKeys
			&apos;	Check item type
			vItem = Item(sKey)
			iVarType = SF_Utils._VarTypeExt(vItem)
			Select Case iVarType
				Case V_STRING, V_BOOLEAN, V_NUMERIC, V_NULL, V_EMPTY
				Case V_DATE
					vItem = SF_Utils._CDateToIso(vItem)
				Case &gt;= V_ARRAY
				Case Else
					vItem = SF_Utils._Repr(vItem)
			End Select
			&apos;	Build in each array entry a (Name, Value) pair
			Set oPropertyValue = SF_Utils._MakePropertyValue(sKey, vItem)
			lCount = lCount + 1
			Set vArray(lCount) = oPropertyValue
		Next sKey
	End If

	&apos;Pass array to Python script for the JSON conversion
	With ScriptForge.SF_Session
		vIndent = Indent
		If VarType(Indent) = V_STRING Then
			If Len(Indent) = 0 Then vIndent = Null
		End If
		sJson = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper &amp; cstPyHelper, vArray, vIndent)
	End With
		
Finally:
	ConvertToJson = sJson
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	ScriptForge.SF_Dictionary.ConvertToJson

REM -----------------------------------------------------------------------------
Public Function ConvertToPropertyValues() As Variant
&apos;&apos;&apos;	Store the content of the dictionary in an array of PropertyValues
&apos;&apos;&apos;	Key stored in Name, Item stored in Value
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		a zero-based 1D array(0:Count - 1). Each entry is a com.sun.star.beans.PropertyValue
&apos;&apos;&apos;			Name: the key in the dictionary
&apos;&apos;&apos;			Value:
&apos;&apos;&apos;				Dates are converted to UNO dates
&apos;&apos;&apos;				Empty arrays are replaced by Null
&apos;&apos;&apos;		an empty array if the dictionary is empty

Dim vArray As Variant			&apos;	Return value
Dim oPropertyValue As Object	&apos;	com.sun.star.beans.PropertyValue
Dim sKey As String				&apos;	Tempry key
Dim vKeys As Variant		&apos;	Array of keys
Dim lCount As Long				&apos;	Counter
Const cstThisSub = &quot;Dictionary.ConvertToPropertyValues&quot;
Const cstSubArgs = &quot;&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch

Check:
	SF_Utils._EnterFunction(cstThisSub, cstSubArgs)

Try:
	vArray = Array()
	If Count = 0 Then
	Else
		ReDim vArray(0 To Count - 1)
		lCount = -1
		vKeys = Keys
		For Each sKey in vKeys
			&apos;	Build in each array entry a (Name, Value) pair
			Set oPropertyValue = SF_Utils._MakePropertyValue(sKey, Item(sKey))
			lCount = lCount + 1
			Set vArray(lCount) = oPropertyValue
		Next sKey
	End If
		
Finally:
	ConvertToPropertyValues = vArray()
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	ScriptForge.SF_Dictionary.ConvertToPropertyValues

REM -----------------------------------------------------------------------------
Public Function Exists(Optional ByVal Key As Variant) As Boolean
&apos;&apos;&apos;	Determine if a key exists in the dictionary
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		Key: the key value (string)
&apos;&apos;&apos;	Returns: True if key exists
&apos;&apos;&apos;	Examples:
&apos;&apos;&apos;		If myDict.Exists(&quot;SomeKey&quot;) Then &apos; don&apos;t add again

Dim vItem As Variant		&apos;	Item part in MapKeys
Const cstThisSub = &quot;Dictionary.Exists&quot;
Const cstSubArgs = &quot;Key&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	Exists = False

Check:
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not SF_Utils._Validate(Key, &quot;Key&quot;, V_STRING) Then GoTo Catch
	End If

Try:
	Exists = SF_Array.Contains(MapKeys, Key, CaseSensitive, SortOrder := &quot;ASC&quot;)

Finally:
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	ScriptForge.SF_Dictionary.Exists

REM -----------------------------------------------------------------------------
Public Function GetProperty(Optional ByVal PropertyName As Variant _
								, Optional ByVal Key As Variant _
								) As Variant
&apos;&apos;&apos;	Return the actual value of the given property
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		PropertyName: the name of the property as a string
&apos;&apos;&apos;		Key: mandatory if PropertyName = &quot;Item&quot;, ignored otherwise
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		The actual value of the property
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		ARGUMENTERROR		The property does not exist
&apos;&apos;&apos;	Examples:
&apos;&apos;&apos;		myDict.GetProperty(&quot;Count&quot;)

Const cstThisSub = &quot;Dictionary.GetProperty&quot;
Const cstSubArgs = &quot;PropertyName, [Key]&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	GetProperty = Null

Check:
	If IsMissing(Key) Or IsEmpty(Key) Then Key = &quot;&quot;
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
	End If

Try:
	GetProperty = _PropertyGet(PropertyName, Key)

Finally:
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	ScriptForge.SF_Dictionary.GetProperty

REM -----------------------------------------------------------------------------
Public Function ImportFromJson(Optional ByVal InputStr As Variant _
											, Optional ByVal Overwrite As Variant _
											) As Boolean
&apos;&apos;&apos;	Adds the content of a Json string into the current dictionary
&apos;&apos;&apos;	JSON = JavaScript Object Notation: https://en.wikipedia.org/wiki/JSON
&apos;&apos;&apos;	Limitations
&apos;&apos;&apos;		The JSON string may contain numbers, strings, booleans, null values and arrays containing those types
&apos;&apos;&apos;		It must not contain JSON objects, i.e. sub-dictionaries
&apos;&apos;&apos;	An attempt is made to convert strings to dates if they fit one of next patterns:
&apos;&apos;&apos;		YYYY-MM-DD, HH:MM:SS or YYYY-MM-DD HH:MM:SS
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		InputStr: the json string to import
&apos;&apos;&apos;		Overwrite: when True entries with same name may exist in the dictionary and their values are overwritten
&apos;&apos;&apos;			Default = False
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		True if successful
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		DUPLICATEKEYERROR: such a key exists already
&apos;&apos;&apos;		INVALIDKEYERROR: zero-length string or only spaces
&apos;&apos;&apos;	Example:
&apos;&apos;&apos;		Dim s As String
&apos;&apos;&apos;		s = &quot;{&apos;firstName&apos;: &apos;John&apos;,&apos;lastName&apos;: &apos;Smith&apos;,&apos;isAlive&apos;: true,&apos;age&apos;: 66, &apos;birth&apos;:  &apos;1954-09-28 20:15:00&apos;&quot; _
&apos;&apos;&apos;			&amp; &quot;,&apos;address&apos;: {&apos;streetAddress&apos;: &apos;21 2nd Street&apos;,&apos;city&apos;: &apos;New York&apos;,&apos;state&apos;: &apos;NY&apos;,&apos;postalCode&apos;: &apos;10021-3100&apos;}&quot; _
&apos;&apos;&apos;			&amp; &quot;,&apos;phoneNumbers&apos;: [{&apos;type&apos;: &apos;home&apos;,&apos;number&apos;: &apos;212 555-1234&apos;},{&apos;type&apos;: &apos;office&apos;,&apos;number&apos;: &apos;646 555-4567&apos;}]&quot; _
&apos;&apos;&apos;			&amp; &quot;,&apos;children&apos;: [&apos;Q&apos;,&apos;M&apos;,&apos;G&apos;,&apos;T&apos;],&apos;spouse&apos;: null}&quot;
&apos;&apos;&apos;		s = Replace(s, &quot;&apos;&quot;, &quot;&quot;&quot;&quot;)
&apos;&apos;&apos;		myDict.ImportFromJson(s, OverWrite := True)
&apos;&apos;&apos;			&apos;	The (sub)-dictionaries &quot;address&quot; and &quot;phoneNumbers(0) and (1) are reduced to Empty	

Dim bImport As Boolean			&apos;	Return value
Dim vArray As Variant			&apos;	JSON string converted to array
Dim vArrayEntry As Variant		&apos;	A single entry in vArray
Dim vKey As Variant				&apos;	Tempry key
Dim vItem As Variant			&apos;	Tempry item
Dim bExists As Boolean			&apos;	True when an entry exists
Dim dDate As Date				&apos;	String converted to Date
Const cstPyHelper = &quot;$&quot; &amp; &quot;_SF_Dictionary__ImportFromJson&quot;

Const cstThisSub = &quot;Dictionary.ImportFromJson&quot;
Const cstSubArgs = &quot;InputStr, [Overwrite=False]&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	bImport = False

Check:
	If IsMissing(Overwrite) Or IsEmpty(Overwrite) Then Overwrite = False
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not SF_Utils._Validate(InputStr, &quot;InputStr&quot;, V_STRING) Then GoTo Finally
		If Not SF_Utils._Validate(Overwrite, &quot;Overwrite&quot;, V_BOOLEAN) Then GoYo Finally
	End If

Try:
	With ScriptForge.SF_Session
		vArray = .ExecutePythonScript(.SCRIPTISSHARED, _SF_.PythonHelper &amp; cstPyHelper, InputStr)
	End With
	If Not IsArray(vArray) Then GoTo Finally	&apos;	Conversion error or nothing to do

	&apos;	vArray = Array of subarrays = 2D DataArray (cfr. Calc)
	For Each vArrayEntry In vArray
		vKey = vArrayEntry(0)
		If VarType(vKey) = V_STRING Then	&apos;	Else skip
			vItem = vArrayEntry(1)
			If Overwrite Then bExists = Exists(vKey) Else bExists = False
			&apos;	When the item matches a date pattern, convert it to a date
			If VarType(vItem) = V_STRING Then
				dDate = SF_Utils._CStrToDate(vItem)
				If dDate &gt; -1 Then vItem = dDate
			End If
			If bExists Then
				ReplaceItem(vKey, vItem)
			Else
				Add(vKey, vItem)	&apos;	Key controls are done in Add
			End If
		End If
	Next vArrayEntry

	bImport = True

Finally:
	ImportFromJson = bImport
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	ScriptForge.SF_Dictionary.ImportFromJson

REM -----------------------------------------------------------------------------
Public Function ImportFromPropertyValues(Optional ByVal PropertyValues As Variant _
											, Optional ByVal Overwrite As Variant _
											) As Boolean
&apos;&apos;&apos;	Adds the content of an array of PropertyValues into the current dictionary
&apos;&apos;&apos;	Names contain Keys, Values contain Items
&apos;&apos;&apos;	UNO dates are replaced by Basic dates
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		PropertyValues: a zero-based 1D array. Each entry is a com.sun.star.beans.PropertyValue
&apos;&apos;&apos;		Overwrite: when True entries with same name may exist in the dictionary and their values are overwritten
&apos;&apos;&apos;			Default = False
&apos;&apos;&apos;	Returns:
&apos;&apos;&apos;		True if successful
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		DUPLICATEKEYERROR: such a key exists already
&apos;&apos;&apos;		INVALIDKEYERROR: zero-length string or only spaces

Dim bImport As Boolean			&apos;	Return value
Dim oPropertyValue As Object	&apos;	com.sun.star.beans.PropertyValue
Dim vItem As Variant			&apos;	Tempry item
Dim sObjectType As String		&apos;	UNO object type of dates
Dim bExists As Boolean			&apos;	True when an entry exists
Const cstThisSub = &quot;Dictionary.ImportFromPropertyValues&quot;
Const cstSubArgs = &quot;PropertyValues, [Overwrite=False]&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	bImport = False

Check:
	If IsMissing(Overwrite) Or IsEmpty(Overwrite) Then Overwrite = False
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If IsArray(PropertyValues) Then
			If Not SF_Utils._ValidateArray(PropertyValues, &quot;PropertyValues&quot;, 1, V_OBJECT, True) Then GoTo Finally
		Else
			If Not SF_Utils._Validate(PropertyValues, &quot;PropertyValues&quot;, V_OBJECT) Then GoTo Finally
		End If
		If Not SF_Utils._Validate(Overwrite, &quot;Overwrite&quot;, V_BOOLEAN) Then GoYo Finally
	End If

Try:
	If Not IsArray(PropertyValues) Then PropertyValues = Array(PropertyValues)
	For Each oPropertyValue In PropertyValues
		With oPropertyValue
			If Overwrite Then bExists = Exists(.Name) Else bExists = False
			If SF_Session.UnoObjectType(oPropertyValue) = &quot;com.sun.star.beans.PropertyValue&quot; Then
				If IsUnoStruct(.Value) Then
					sObjectType = SF_Session.UnoObjectType(.Value)
					Select Case sObjectType
						Case &quot;com.sun.star.util.DateTime&quot;	:	vItem = CDateFromUnoDateTime(.Value)
						Case &quot;com.sun.star.util.Date&quot;		:	vItem = CDateFromUnoDate(.Value)
						Case &quot;com.sun.star.util.Time&quot;		:	vItem = CDateFromUnoTime(.Value)
						Case Else							:	vItem = .Value
					End Select
				Else
					vItem = .Value
				End If
				If bExists Then
					ReplaceItem(.Name, vItem)
				Else
					Add(.Name, vItem)	&apos;	Key controls are done in Add
				End If
			End If
		End With
	Next oPropertyValue
	bImport = True

Finally:
	ImportFromPropertyValues = bImport
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	ScriptForge.SF_Dictionary.ImportFromPropertyValues

REM -----------------------------------------------------------------------------
Public Function Methods() As Variant
&apos;&apos;&apos;	Return the list or methods of the Dictionary class as an array

	Methods = Array( _
					&quot;Add&quot; _
					, &quot;ConvertToArray&quot; _
					, &quot;ConvertToJson&quot; _
					, &quot;ConvertToPropertyValues&quot; _
					, &quot;Exists&quot; _
					, &quot;ImportFromJson&quot; _
					, &quot;ImportFromPropertyValues&quot; _
					, &quot;Remove&quot; _
					, &quot;RemoveAll&quot; _
					, &quot;ReplaceItem&quot; _
					, &quot;ReplaceKey&quot; _
					)

End Function	&apos;	ScriptForge.SF_Dictionary.Methods

REM -----------------------------------------------------------------------------
Public Function Properties() As Variant
&apos;&apos;&apos;	Return the list or properties of the Dictionary class as an array

	Properties = Array( _
					&quot;Count&quot; _
					, &quot;Item&quot; _
					, &quot;Items&quot; _
					, &quot;Keys&quot; _
					)

End Function	&apos;	ScriptForge.SF_Dictionary.Properties

REM -----------------------------------------------------------------------------
Public Function Remove(Optional ByVal Key As Variant) As Boolean
&apos;&apos;&apos;	Remove an existing dictionary entry based on its key
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		Key: must exist in the dictionary
&apos;&apos;&apos;	Returns: True if successful
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		UNKNOWNKEYERROR: the key does not exist
&apos;&apos;&apos;	Examples:
&apos;&apos;&apos;		myDict.Remove(&quot;OldKey&quot;)

Dim vItemMap As Variant			&apos;	Output of SF_Array._FindItem
Dim lIndex As Long				&apos;	Index in MapKeys and MapPositions
Const cstThisSub = &quot;Dictionary.Remove&quot;
Const cstSubArgs = &quot;Key&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	Remove = False

Check:
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not SF_Utils._Validate(Key, &quot;Key&quot;, V_STRING) Then GoTo Catch
	End If
Try:
	vItemMap = SF_Array._FindItem(MapKeys, Key, CaseSensitive, &quot;ASC&quot;)
	If Not vItemMap(0) Then GoTo CatchUnknown
	lIndex = vItemMap(1)
	MapKeys(lIndex) = &quot;&quot;
	MapKeys = SF_Array.TrimArray(MapKeys)
	Erase MapItems(MapPositions(lIndex))
	MapPositions(lIndex) = Null
	MapPositions = SF_Array.TrimArray(MapPositions)
	_MapRemoved = _MapRemoved + 1
	Remove = True

Finally:
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
CatchUnknown:
	SF_Exception.RaiseFatal(UNKNOWNKEYERROR, &quot;Key&quot;, Key)
	GoTo Finally
End Function	&apos;	ScriptForge.SF_Dictionary.Remove

REM -----------------------------------------------------------------------------
Public Function RemoveAll() As Boolean
&apos;&apos;&apos;	Remove all the entries from the dictionary
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;	Returns: True if successful
&apos;&apos;&apos;	Examples:
&apos;&apos;&apos;		myDict.RemoveAll()

Dim vKeys As Variant			&apos;	Array of keys
Dim sColl As String				&apos;	A collection key in MapKeys
Const cstThisSub = &quot;Dictionary.RemoveAll&quot;
Const cstSubArgs = &quot;&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	RemoveAll = False

Check:
	SF_Utils._EnterFunction(cstThisSub, cstSubArgs)

Try:
	vKeys = Keys
	For Each sColl In vKeys
		Remove(sColl)
	Next sColl
	Erase MapKeys
	Erase MapItems
	&apos;	Make dictionary ready to receive new entries
	Call Class_Initialize()
	RemoveAll = True

Finally:
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	ScriptForge.SF_Dictionary.RemoveAll

REM -----------------------------------------------------------------------------
Public Function ReplaceItem(Optional ByVal Key As Variant _
							, Optional ByVal Value As Variant _
							) As Boolean
&apos;&apos;&apos;	Replace the item value
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		Key: must exist in the dictionary
&apos;&apos;&apos;	Returns: True if successful
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		UNKNOWNKEYERROR: the  old key does not exist
&apos;&apos;&apos;	Examples:
&apos;&apos;&apos;		myDict.ReplaceItem(&quot;Key&quot;, NewValue)

Dim vItemMap As Variant			&apos;	Output of SF_Array._FindItem
Dim lIndex As Long				&apos;	Entry in the MapItems array
Const cstThisSub = &quot;Dictionary.ReplaceItem&quot;
Const cstSubArgs = &quot;Key, Value&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	ReplaceItem = False

Check:
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not SF_Utils._Validate(Key, &quot;Key&quot;, V_STRING) Then GoTo Catch
		If IsArray(Value) Then
			If Not SF_Utils._ValidateArray(Value, &quot;Value&quot;) Then GoTo Catch
		Else
			If Not SF_Utils._Validate(Value, &quot;Value&quot;) Then GoTo Catch
		End If
	End If

Try:
	&apos;	Find entry in MapItems and update it with the new value
	vItemMap = SF_Array._FindItem(MapKeys, Key, CaseSensitive, &quot;ASC&quot;)
	If Not vItemMap(0) Then GoTo CatchUnknown
	lIndex = vItemMap(1)
	MapItems(MapPositions(lIndex)) = Value
	ReplaceItem = True

Finally:
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
CatchUnknown:
	SF_Exception.RaiseFatal(UNKNOWNKEYERROR, &quot;Key&quot;, Key)
	GoTo Finally
End Function	&apos;	ScriptForge.SF_Dictionary.ReplaceItem

REM -----------------------------------------------------------------------------
Public Function ReplaceKey(Optional ByVal Key As Variant _
							, Optional ByVal Value As Variant _
							) As Boolean
&apos;&apos;&apos;	Replace existing key
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		Key: must exist in the dictionary
&apos;&apos;&apos;		Value: must not exist in the dictionary
&apos;&apos;&apos;	Returns: True if successful
&apos;&apos;&apos;	Exceptions:
&apos;&apos;&apos;		UNKNOWNKEYERROR: the  old key does not exist
&apos;&apos;&apos;		DUPLICATEKEYERROR: the new key exists
&apos;&apos;&apos;	Examples:
&apos;&apos;&apos;		myDict.ReplaceKey(&quot;OldKey&quot;, &quot;NewKey&quot;)

Const cstThisSub = &quot;Dictionary.ReplaceKey&quot;
Const cstSubArgs = &quot;Key, Value&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	ReplaceKey = False

Check:
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not SF_Utils._Validate(Key, &quot;Key&quot;, V_STRING) Then GoTo Catch
		If Not SF_Utils._Validate(Value, &quot;Value&quot;, V_STRING) Then GoTo Catch
	End If
	If Not Exists(Key) Then GoTo CatchUnknown
	If Value = Space(Len(Value)) Then GoTo CatchInvalid
	If Exists(Value) Then GoTo CatchDuplicate

Try:
	&apos;	Remove the Key entry and create a new one
	Add(Value, Item(Key))
	Remove(Key)
	ReplaceKey = True

Finally:
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
CatchUnknown:
	SF_Exception.RaiseFatal(UNKNOWNKEYERROR, &quot;Key&quot;, Key)
	GoTo Finally
CatchDuplicate:
	SF_Exception.RaiseFatal(DUPLICATEKEYERROR, &quot;Value&quot;, Value)
	GoTo Finally
CatchInvalid:
	SF_Exception.RaiseFatal(INVALIDKEYERROR, &quot;Key&quot;)
	GoTo Finally
End Function	&apos;	ScriptForge.SF_Dictionary.ReplaceKey

REM -----------------------------------------------------------------------------
Public Function SetProperty(Optional ByVal PropertyName As Variant _
								, Optional ByRef Value As Variant _
								) As Boolean
&apos;&apos;&apos;	Set a new value to the given property
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		PropertyName: the name of the property as a string
&apos;&apos;&apos;		Value: its new value
&apos;&apos;&apos;	Exceptions
&apos;&apos;&apos;		ARGUMENTERROR		The property does not exist

Const cstThisSub = &quot;Dictionary.SetProperty&quot;
Const cstSubArgs = &quot;PropertyName, Value&quot;

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch
	SetProperty = False

Check:
	If SF_Utils._EnterFunction(cstThisSub, cstSubArgs) Then
		If Not SF_Utils._Validate(PropertyName, &quot;PropertyName&quot;, V_STRING, Properties()) Then GoTo Catch
	End If

Try:
	Select Case UCase(PropertyName)
		Case Else
	End Select

Finally:
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	ScriptForge.SF_Dictionary.SetProperty

REM =========================================================== PRIVATE FUNCTIONS

REM -----------------------------------------------------------------------------
Private Function _PropertyGet(Optional ByVal psProperty As String _
								, Optional pvKey As Variant _
								)
&apos;&apos;&apos;	Return the named property
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;		psProperty: the name of the property
&apos;&apos;&apos;		pvKey: the key to retrieve, numeric or string

Dim vItemMap As Variant			&apos;	Output of SF_Array._FindItem
Dim lIndex As Long				&apos;	Entry in the MapItems array
Dim vArray As Variant			&apos;	To get Keys or Items
Dim i As Long
Dim cstThisSub As String
Dim cstSubArgs As String

	If SF_Utils._ErrorHandling() Then On Local Error GoTo Catch

	cstThisSub = &quot;SF_Dictionary.get&quot; &amp; psProperty
	If IsMissing(pvKey) Then cstSubArgs = &quot;&quot; Else cstSubArgs = &quot;[Key]&quot;

	SF_Utils._EnterFunction(cstThisSub, cstSubArgs)

	Select Case UCase(psProperty)
		Case UCase(&quot;Count&quot;)
			_PropertyGet = _MapSize - _MapRemoved
		Case UCase(&quot;Item&quot;)
			If Not SF_Utils._Validate(pvKey, &quot;Key&quot;, V_STRING) Then GoTo Catch
			vItemMap = SF_Array._FindItem(MapKeys, pvKey, CaseSensitive, &quot;ASC&quot;)
			lIndex = vItemMap(1)
			If vItemMap(0) Then _PropertyGet = MapItems(MapPositions(lIndex)) Else _PropertyGet = Empty
		Case UCase(&quot;Keys&quot;), UCase(&quot;Items&quot;)
			vArray = Array()
			If UBound(MapKeys) &gt;= 0 Then
				ReDim vArray(0 To UBound(MapKeys))
				For i = 0 To UBound(MapKeys)
					Select Case UCase(psProperty)
						Case &quot;KEYS&quot;		:	vArray(i) = MapKeys(i)
						Case &quot;ITEMS&quot;	:	vArray(i) = MapItems(MapPositions(i))
					End Select
				Next i
			End If
			_PropertyGet = vArray
	End Select

Finally:
	SF_Utils._ExitFunction(cstThisSub)
	Exit Function
Catch:
	GoTo Finally
End Function	&apos;	ScriptForge.SF_Dictionary._PropertyGet

REM -----------------------------------------------------------------------------
Private Function _Repr() As String
&apos;&apos;&apos;	Convert the Dictionary instance to a readable string, typically for debugging purposes (DebugPrint ...)
&apos;&apos;&apos;	Args:
&apos;&apos;&apos;	Return:
&apos;&apos;&apos;		&quot;[Dictionary] (key1:value1, key2:value2, ...)

Dim sDict As String			&apos;	Return value
Dim vKeys As Variant		&apos;	Array of keys
Dim sKey As String			&apos;	Tempry key
Dim vItem As Variant		&apos;	Tempry item
Const cstDictEmpty = &quot;[Dictionary] ()&quot;
Const cstDict = &quot;[Dictionary]&quot;
Const cstMaxLength = 50	&apos;	Maximum length for items
Const cstSeparator = &quot;, &quot;

	_Repr = &quot;&quot;

	If Count = 0 Then
		sDict = cstDictEmpty
	Else
		sDict = cstDict &amp; &quot; (&quot;
		vKeys = Keys
		For Each sKey in vKeys
			vItem = Item(sKey)
			sDict = sDict &amp; sKey &amp; &quot;:&quot; &amp; SF_Utils._Repr(vItem, cstMaxLength) &amp; cstSeparator
		Next sKey
		sDict = Left(sDict, Len(sDict) - Len(cstSeparator)) &amp; &quot;)&quot;	&apos;	Suppress last comma
	End If

	_Repr = sDict

End Function	&apos;	ScriptForge.SF_Dictionary._Repr

REM ============================================ END OF SCRIPTFORGE.SF_DICTIONARY
</script:module>