Showing posts with label Excel. Show all posts
Showing posts with label Excel. Show all posts

Tuesday, October 13, 2015

How to Create and Deploy Custom Ribbon Entries in MS Office (including custom icons) and avoid some Pitfalls

Background

For a project in our company I wanted to create a Excel-macro-based user interface for some SQL Server-based database. The macro VBA-programming went quite well (due to some experience I already collected in this field), but I wanted to provide two buttons in the Excel 2010 ribbon to give the user the possibility to trigger macros to read/write from/to the database.
Many hints are available in the web, but I did not com across a receipe which provides the steps to create custom UIs in a concise form. Thus, and as I went into some traps, I want to share my experiences in this post.

Challenge

As described above, I wanted to provide two end-user buttons in the Excel ribbon as custom UI elements. To achieve this goal I had to problems to solve:
  • How to get the buttons into the ribbon?
  • How to deploy these custom buttons with my Excel in the most user-friendly way?

Custom Elements via Excel Options

Excel itself provides the means to create custom ribbon elements. There is a number of articles out there describing how to do this - for instance "How to Add Your Own Macros to Excel 2010" on changdoo.org. The process is quite simple and is easily achieved.
To deploy the customized ribbon to your customers, Excel offers the possibility to export the changed settings to an xml-file (called *.exportedUI) via the menu:
File --> Options --> "Customize Ribbon" menu entry --> "Import/Export" button:

As external reference see as well THIS microsoft article.

I do not have the administrator means to distribute these buttons centrally but the so created configuration file can be sent to those users who want to incorporate the new buttons into their work environment.
Nevertheless this process has some shortcomings:
  • The users have to follow a procedure to import the files with my buttons into their Excel environment 
  • One has to distribute a seperate file which has seperate versions
  • It is not possible to use customs icons for the buttons
And last but not least the export procedure creates xml-code with some traps, which become obvious if one investigates the *.exportedUI xml-code with some editor. The most critical to me is that the macro being called by "onAction" is referenced by the absolute path of the excel file where the export is made from. Here an example of a custom UI created by Excel Options:

Freshly exported to *.exportedUI, the code comes without formatting (no line breaks) and most important note the absolute path in the xml:

<mso:cmd app="Excel" dt="1" />
<mso:customUI xmlns:x2="http://schemas.microsoft.com/office/2009/07/customui/macro" xmlns:x1="TFCOfficeShim.Connect.14" xmlns:mso="http://schemas.microsoft.com/office/2009/07/customui">
<mso:ribbon>
<mso:qat>
<mso:sharedControls>
<mso:control idQ="mso:FileNewDefault" visible="false"/>
<mso:control idQ="mso:FileSave" visible="true" insertBeforeQ="mso:FileOpen"/>
<mso:control idQ="mso:FileSendAsAttachment" visible="false" insertBeforeQ="mso:FileOpen"/>
<mso:control idQ="mso:Spelling" visible="false" insertBeforeQ="mso:FileOpen"/>
<mso:control idQ="mso:Undo" visible="true" insertBeforeQ="mso:FileOpen"/>
<mso:control idQ="mso:Redo" visible="true" insertBeforeQ="mso:FileOpen"/>
<mso:control idQ="mso:SortAscendingExcel" visible="false" insertBeforeQ="mso:FileOpen"/>
<mso:control idQ="mso:SortDescendingExcel" visible="false" insertBeforeQ="mso:FileOpen"/>
<mso:control idQ="mso:FileOpenRecentFile" visible="false" insertBeforeQ="mso:FileOpen"/>
<mso:control idQ="mso:FileNew" visible="true" insertBeforeQ="mso:FileOpen"/>
<mso:control idQ="mso:FileOpen" visible="true"/>
<mso:control idQ="mso:PrintPreviewAndPrint" visible="true" insertBeforeQ="mso:FilePrintQuick"/>
<mso:control idQ="mso:FileSaveAsPdfOrXps" visible="true" insertBeforeQ="mso:FilePrintQuick"/>
<mso:control idQ="mso:FilePrintQuick" visible="true"/>
<mso:control idQ="mso:FileClose" visible="true"/>
</mso:sharedControls>
</mso:qat>
<mso:tabs>
<mso:tab id="mso_c4.1568D5E4" label="My New Tab" insertAfterQ="x1:IDC_TEAM_TAB">
<mso:group id="mso_c5.1568D5E4" label="My New Group" autoScale="true">
<mso:button idQ="myButtonID" label="Call My Macro" imageMso="ListMacros" onAction="Q:\FolderName\MyMacroExcel.xlsm!MyMacro" visible="true"/>
</mso:group>
</mso:tab>
</mso:tabs>

</mso:ribbon>
</mso:customUI>
PITFALL: If this is deployed to other users, the referenced macro will not be found if the Excel file is located in another location. To get around this trap you have to modify the code in *.exportedUI manually and remove the absolute path to the macro.

Custom Elements via Direct Ribbon Customization

With the above said, I was not very content with the "Excel Options"-option. After some searching I came across the "Custom UI Editor" option, which utilizes an external tool avaiable for example HERE.
With this little program one can modify the UI resources of an MS Office file directly without the need to export the changes to some external file. The UI is quite simple and offers the possibility to upload custom icons as images (any standard image format) and enter custom ribbon code directly in the Excel file:

 The resulting ribbon customization is stored inside the Excel file and is hence deployed directly with the file. The so created new ribbon entry is not visible in the "Options" dialog above.

The following code worked in MS Word 2010 and Excel 2010:

<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui">
<ribbon>
<tabs>
<tab id="myTab" label="MYLABEL">
<group id="testCaseTool" label="My Text">
<button id="ID1" label="Text 1" size="large" image="iconBitmap1" onAction="myMacro1" visible="true" />
<button id="ID2" label="Text 2" size="large" image="iconBitmap2" onAction="myMacro2" visible="true" />
</group>
</tab>
</tabs>
</ribbon>
</customUI>
It will create a new entry in to ribbon with two buttons calling two different macros. Add your icons and you're done!

PITFALL: Very critical is the fact that the macros referenced in the must have a special input parameter as described in the forum post "VBA error 'Wrong number of arguments or invalid property assignments' when running macro via custom button".
If one references routines created as VBA "Sub subname()", you will run into an error "Wrong number of arguments...". Hence you will have to modify your VBA code accordingly and create a new VBA sub with the right input parameter "Sub newsubname(control As IRibbonControl)".

This is how I was able to produce a satisfactory custom ribbon entry which I could deploy to my colleagues without any further hassle.

I hope you can get something out of this post for your own project - keep on geekin'

WolfiG

Friday, June 13, 2014

Barcode and QR-Code Generator Using Excel and Latex

Background

For a document scanning project I needed to find a (cheap) way to create barcode and QR-code labels to stick on the documents to scan. This blog post bescribes a Latex based Excel-driven label generator which is able to create sticky labels on A4-pages commonly available on the market.

System Requirements

I was working in the following system environment:
  • Windows 7
  • Office / Excel 2010
  •  Miktex 2.9

Latex Requirements

To generate bar- or QR-codes with the following code examples you need to have the following Latex packages installed:
  • pstricks
  • pst-barcodes
  • makecell
  • newdimensions
  • lmodern
Note: the Latex-code generated by the code generator will not compile with pdflatex because of the usage of pstricks. Use the command "xelatex" which is coming with the Miktex installation.

Output

Barcodes 70 x 37 mm

The barcode generator creates the following pdf-file which is formatted to fit on an standard A4 3 x 7 labels page

QR-Codes 38 x 21,2 mm

the QR-codes can be produced either as 3 x 7 labels per sheet (label size: 70 x 37 mm) or as 5 x 13 labels per sheet (label size: 38 x 21,2 mm). Here an example of the latter case:

Rough Architecture

In an Excel workbook you can define document names / IDs. These IDs are being read by a latex code generator (VBA macro) which creates either an output file for barcodes or an output file for QR-codes based on the IDs of the barcodes. This is due to the fact that we are using barcodes to label document folders and QR-codes to label the documents in these folders...
As soon as the output latex-file is written to the hard disk, the VBA macro calls a Windows batch file calling the xelatex-compiler with the newly created output file. Xelatex compiles the *.tex-file and generates a PDF in the end which can be printed.

Latex Files

Barcodes.tex

The file Barcodes.tex is the main file for the generation of barcodes. It only contains the definition of formats, etc. to generate the desired output (pages with 3 x 7 labes of size 70 x 37 mm). It calls a latex file "labelfile.tex" whic contains the actual label definitions.

\documentclass[a4paper,11pt]{article}
\usepackage{pst-barcode}
\usepackage[newdimens]{labels}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\renewcommand*\familydefault{\sfdefault}
\LabelCols=3
\LabelRows=7
\LeftPageMargin=5mm% These four parameters give the
\RightPageMargin=10mm% page gutter sizes. The outer edges of
\TopPageMargin=5mm% the outer labels are the specified
\BottomPageMargin=5mm% distances from the edge of the paper.
\InterLabelColumn=25mm% Gap between columns of labels
\InterLabelRow=5mm% Gap between rows of labels
\LeftLabelBorder=5mm% These four parameters give the extra
\RightLabelBorder=5mm% space used around the text on each
\TopLabelBorder=5mm% actual label.
\BottomLabelBorder=5mm%

\begin{document}
\include{labelfile}
\end{document}


labelfile.tex

File "labelfile.tex is generated by the VBA macro "MakeBarcodesForFolders" (code see below) and has the follwoing structure:

\begin{labels}

GSI-GF/K-
\begin{pspicture}(2,0.5in)\psbarcode[scalex=0.7,scaley=0.4,transy=3mm]{ARC-00001}{}{code39}\end{pspicture}
\vspace{-9mm}
\bf{ARC-00001}

GSI-GF/K-
\begin{pspicture}(2,0.5in)\psbarcode[scalex=0.7,scaley=0.4,transy=3mm]{ARC-00002}{}{code39}\end{pspicture}
\vspace{-9mm}
\bf{ARC-00002}

GSI-GF/K-
\begin{pspicture}(2,0.5in)\psbarcode[scalex=0.7,scaley=0.4,transy=3mm]{ARC-00003}{}{code39}\end{pspicture}
\vspace{-9mm}
\bf{ARC-00003}

GSI-GF/K-
\begin{pspicture}(2,0.5in)\psbarcode[scalex=0.7,scaley=0.4,transy=3mm]{ARC-00004}{}{code39}\end{pspicture}
\vspace{-9mm}
\bf{ARC-00004}

[...]

\end{labels} 


QR-CodesMain.tex 

Similar to "Barcodes.tex", "QR-CodesMain.tex" contains information concerning the formatting options for the desired output. QR-CodesMain.tex is generated by VBA macro "createMainFile" (code see below) to avoid the neccessity to adjust this file manually if you want to switch from label size 70 x 37 mm to 38 x 21,2 mm. QR-codesMain.tex looks as follows for labels of size 38 x 21,2 mm (5 x 13 labels per sheet):

\documentclass[a4paper,11pt]{article}
\usepackage{pst-barcode}
\usepackage[newdimens]{labels}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage{makecell}
\renewcommand*\familydefault{\sfdefault}
\renewcommand\cellalign{lt}
\LabelCols=5
\LabelRows=13
\LeftPageMargin=3mm% These four parameters give the
\RightPageMargin=5mm% page gutter sizes. The outer edges of
\TopPageMargin=10mm% the outer labels are the specified
\BottomPageMargin=5mm% distances from the edge of the paper.
\InterLabelColumn=-1mm% Gap between columns of labels
\InterLabelRow=1mm% Gap between rows of labels
\LeftLabelBorder=0mm% These four parameters give the extra
\RightLabelBorder=0mm% space used around the text on each
\TopLabelBorder=0mm% actual label.
\BottomLabelBorder=0mm%
\begin{document}
\include{QR-Codes}
\end{document}\begin{document}
\include{QR-Codes}
\end{document}

QR-Codes.tex

The Latex file"QR-codes.tex" is generated by VBA macro "Button_Weiter_Click()" and has the following structure:

\begin{labels}

\begin{tabular}{ll}
\makecell[{{p{14mm}}}]{\begin{pspicture}(0,0in)\psbarcode[]{GSI-GFK-ARC-00003.001}{}{qrcode}\end{pspicture} \\ ~ \\ ~}
& \makecell[b]{GSI-GFK- \\ ARC-00003 \\ .001 \\ ~}
\end{tabular}

\begin{tabular}{ll}
\makecell[{{p{14mm}}}]{\begin{pspicture}(0,0in)\psbarcode[]{GSI-GFK-ARC-00003.002}{}{qrcode}\end{pspicture} \\ ~ \\ ~}
& \makecell[b]{GSI-GFK- \\ ARC-00003 \\ .002 \\ ~}
\end{tabular}

\begin{tabular}{ll}
\makecell[{{p{14mm}}}]{\begin{pspicture}(0,0in)\psbarcode[]{GSI-GFK-ARC-00003.003}{}{qrcode}\end{pspicture} \\ ~ \\ ~}
& \makecell[b]{GSI-GFK- \\ ARC-00003 \\ .003 \\ ~}
\end{tabular}

[...]

\end{labels}

 Excel-UI


UI-Form of QR-Code Generator

Here the user form of the QR-code generator with the corresponding technical names of the UI-elements:

Batch Files

You need to create two batch files in the directory where the Excel workbook is stored.

ExcelLatex.bat

Code:
xelatex barcodes.tex

QR-CodesCompile.bat

Code:
xelatex QR-CodesMain.tex

VBA Macro Code

Barcode Generator: MakeBarcodesForFolders()

The code runs over cells A3 to A500 and generates a latex output file "labelfile.tex" which is used as input by a latex driver / format file "Barcodes.tex"
Sub MakeBarcodesForFolders()
Dim theOutput As String
theOutput = "\begin{labels}" & vbCrLf & vbCrLf
Worksheets("Ordnerlabel").Activate
For Each theCell In Range("A3:A500")
    theOutput = theOutput _
    & "GSI-GF/K-" & vbCrLf _
    & "\begin{pspicture}(2,0.5in)" _
    & "\psbarcode[scalex=0.7,scaley=0.4,transy=3mm]{" _
    & theCell.Text _
    & "}{}{code39}" _
    & "\end{pspicture}" & vbCrLf _
    & "\vspace{-9mm}" & vbCrLf _
    & "\bf{" & theCell.Text & "}" _
    & vbCrLf _
    & vbCrLf
Next theCell
theOutput = theOutput & "\end{labels}"
'Write Output
    Dim theFile As String, lFile As Long
   
    theFile = Application.ActiveWorkbook.Path & "/labelfile.tex"
    Set fs = CreateObject("Scripting.FileSystemObject")
    Set a = fs.CreateTextFile(theFile, True)
    a.WriteLine (theOutput)
    a.Close
End Sub



QR-Code Generator: Button_Weiter_Click()


The Sub "Button_weiter_Click()"(please excuse this very ugly name...) is the core latex code generator for the QR-code Latex-files. Depending on the user's choice of the label size (controls "Radio_21PerPage" or "Radio_65PerPage") it writes a string file with the required Latex markup:

Private Sub Button_Weiter_Click()

Dim theOutput As String
Dim theID As String

theOutput = "\begin{labels}" & vbCrLf & vbCrLf

If Form_ZwischenblattConfig.TextBox_BarcodeCount.Text <> "" Then

    If Form_ZwischenblattConfig.ListBox_Folder.ListIndex <> -1 Then

        For theCounter = 1 To Form_ZwischenblattConfig.TextBox_BarcodeCount.Value
       
            theID = "GSI-GFK-" & Form_ZwischenblattConfig.ListBox_Folder.Value & "." & Format(CStr(theCounter), "000")
           
            If Radio_21PerPage = True Then '21 labels per A4 page, 70 x 37 mm
                theOutput = theOutput _
                & "\begin{pspicture}(0,0in)" _
                & "\psbarcode[]{" & theID _
                & "}{}{qrcode}" _
                & "\end{pspicture}" & vbCrLf _
                & theID _
                & vbCrLf _
                & vbCrLf
            ElseIf Radio_65PerPage = True Then '65 label per page, 37 x 21,2 mm
                theOutput = theOutput _
                & "\begin{tabular}{ll}" & vbCrLf _
                & "\makecell[{{p{14mm}}}]{" _
                & "\begin{pspicture}(0,0in)" _
                & "\psbarcode[]{" & theID _
                & "}{}{qrcode}" _
                & "\end{pspicture}" _
                & " \\ ~ \\ ~}" & vbCrLf _
                & "& \makecell[b]{" _
                & "GSI-GFK- \\ " _
                & Form_ZwischenblattConfig.ListBox_Folder.Value & " \\ " _
                & "." & Format(CStr(theCounter), "000") & " \\ ~}" & vbCrLf _
                & "\end{tabular}" & vbCrLf _
                & vbCrLf
            End If

        Next theCounter
       
    End If
   
    theOutput = theOutput & "\end{labels}"
   
    Call writeFile("QR-Codes.tex", theOutput)
   
    Call createMainFile
   
    Call compileOutput
   
End If

Form_ZwischenblattConfig.Hide

End Sub





Private Sub createMainFile()

Dim theOutput As String

theOutput = _
"\documentclass[a4paper,11pt]{article}" & vbCrLf _
& "\usepackage{pst-barcode}" & vbCrLf _
& "\usepackage[newdimens]{labels}" & vbCrLf _
& "\usepackage[T1]{fontenc}" & vbCrLf _
& "\usepackage{lmodern}" & vbCrLf _
& "\usepackage{makecell}" & vbCrLf _
& "\renewcommand*\familydefault{\sfdefault}" & vbCrLf _
& "\renewcommand\cellalign{lt}" & vbCrLf

If Radio_65PerPage = True Then
    theOutput = theOutput _
    & "\LabelCols=5" & vbCrLf _
    & "\LabelRows=13" & vbCrLf _
    & "\LeftPageMargin=3mm% These four parameters give the" & vbCrLf _
    & "\RightPageMargin=5mm% page gutter sizes. The outer edges of" & vbCrLf _
    & "\TopPageMargin=10mm% the outer labels are the specified" & vbCrLf _
    & "\BottomPageMargin=5mm% distances from the edge of the paper." & vbCrLf _
    & "\InterLabelColumn=-1mm% Gap between columns of labels" & vbCrLf _
    & "\InterLabelRow=1mm% Gap between rows of labels" & vbCrLf _
    & "\LeftLabelBorder=0mm% These four parameters give the extra" & vbCrLf _
    & "\RightLabelBorder=0mm% space used around the text on each" & vbCrLf _
    & "\TopLabelBorder=0mm% actual label." & vbCrLf _
    & "\BottomLabelBorder=0mm%" & vbCrLf _
    & "\begin{document}" & vbCrLf _
    & "\include{QR-Codes}" & vbCrLf _
    & "\end{document}"
ElseIf Radio_21PerPage = True Then
    theOutput = theOutput _
    & "\LabelCols=3" & vbCrLf _
    & "\LabelRows=7" & vbCrLf _
    & "\LeftPageMargin=20mm% These four parameters give the" & vbCrLf _
    & "\RightPageMargin=5mm% page gutter sizes. The outer edges of" & vbCrLf _
    & "\TopPageMargin=20mm% the outer labels are the specified" & vbCrLf _
    & "\BottomPageMargin=5mm% distances from the edge of the paper." & vbCrLf _
    & "\InterLabelColumn=10mm% Gap between columns of labels" & vbCrLf _
    & "\InterLabelRow=10mm% Gap between rows of labels" & vbCrLf _
    & "\LeftLabelBorder=5mm% These four parameters give the extra" & vbCrLf _
    & "\RightLabelBorder=5mm% space used around the text on each" & vbCrLf _
    & "\TopLabelBorder=5mm% actual label." & vbCrLf _
    & "\BottomLabelBorder=5mm%" & vbCrLf
End If

theOutput = theOutput _
& "\begin{document}" & vbCrLf _
& "\include{QR-Codes}" & vbCrLf _
& "\end{document}"

Call writeFile("QR-CodesMain.tex", theOutput)

End Sub


Private Sub writeFile(fileName As String, textToWrite As String)

    'Write Output
    Dim theFile As String, lFile As Long

    theFile = Application.ActiveWorkbook.Path & "\" & fileName

    Set fs = CreateObject("Scripting.FileSystemObject")
    Set a = fs.CreateTextFile(theFile, True)
    a.WriteLine (textToWrite)
    a.Close

End Sub

Private Sub compileOutput()
    theCommand = Application.ActiveWorkbook.Path & "\QR-CodesCompile.bat"
    ChDir Application.ActiveWorkbook.Path
    Call Shell(theCommand)
End Sub

Monday, April 14, 2014

Fetching Geo-Coordinates from Mapquest-API using Excel-VBA

Background

For some little visualization project I had the idea of putting markers of each of our institute's partners on a world map. All I had was a list with names of about 1500 research institutes, addresses were not in the list.
So my plan was to find a suitable web service which I could query from MS Excel via Visual Basic for Applications (VBA) and programmatically fetch data from this service.

Implementation

After some web-research I finally stumbled over a video by DontFretBrett, where he explains how to fetch geo-data using VBA in Excel via the google maps API. For me the breakthrough was the usage of the XML-maps feature of Excel, where a web service can be called an can be databound to an Excel worksheet.
Furthermore I decided not to use the google maps API, but the openmaps "nominatim" web-service by openstreetmap.org which is free of charge and which can be used to search for non-well-formed geo-information, such as names of research institutes. I found out that openstreetmap restricts the number of calls per day so I finally switched to the free service of mapquest, which is based on nominatim.

Excel XML-Mapping

To do databinding of XML-data to an Excel worksheet, go to "developer tools" (mind that this tab needs to be activated in Excel-options) and there to the "XML-Source" button (please forgive my German Excel..):

When clicking on this button you'll get a new window to the right of your worksheet:

Therein click on the button "XML-Map" on the lower right of this window.

in the corresponding dialog enter the URL to the query you want to execute and give it some hany name (in my case "searchresults"). When you execute the search query, you will get something like this:

After achieving this, drag & drop the fields from the search results to a new worksheet. In my case this were the fields "lat", "lon" and "display_name" (address). Now you can update data in the XML-map window by executing the web-service and copy it to you driving worksheet with the information you have.

VBA Code

I implemented three routines:
1) One driver routine looping over the master worksheet
2) One routine calling the mapquest web service
3) One helper function which does a bit of data cleansing to get rid of special characters

Driver Routine

Sub GetAddressForInstitute()

Dim theSearchString As String
Dim selectedRow As Integer
Dim selectedColument As Integer

Worksheets("Collaborations").Activate

For Each theCell In Worksheets("Collaborations").Range("A5:A2000")
    theCell.Select
    selectedRow = Selection.Row
    selectedColumn = Selection.Column
    theSearchString = Replace(theCell.Text, " ", "+") + "+" + Cells(selectedRow, selectedColumn + 1).Text
    If (theSearchString <> "") Then
        theSearchString = cleanStringFromSpecialCharacters(theSearchString)
        If (theSearchString <> "") Then
            GetDataUpdateSheet (theSearchString)
        End If
    
        Cells(selectedRow, 10).Select
        Selection.Value = Worksheets("lat_lon").Cells(2, 1).Value 'Latitude
        Cells(selectedRow, 11).Select
        Selection.Value = Worksheets("lat_lon").Cells(2, 2).Value 'Longitude
        Cells(selectedRow, 12).Select
        Selection.Value = Worksheets("lat_lon").Cells(2, 3).Value 'Address
    End If
Next theCell
End Sub

Service Caller

Sub GetDataUpdateSheet(searchString As String)
    Dim theMap As XmlMap
   
    Set theMap = ActiveWorkbook.XmlMaps("searchresults")
    On Error Resume Next 'Special characters cause program to dump --> simply go over errors
    theMap.DataBinding.LoadSettings ("http://open.mapquestapi.com/nominatim/v1/search.php?q=" + searchString + "&format=xml")
    theMap.DataBinding.Refresh
End Sub

Special Character Cleaner

Function cleanStringFromSpecialCharacters(inString As String) As String
    Dim outString As String
    outString = Replace(inString, "ä", "ae")
    outString = Replace(outString, "ã", "a")
    outString = Replace(outString, "à", "a")
    outString = Replace(outString, "á", "a")
    outString = Replace(outString, "â", "a")
    outString = Replace(outString, "Ä", "Ae")
    outString = Replace(outString, "ç", "c")
    outString = Replace(outString, "í", "i")
    outString = Replace(outString, "ö", "oe")
    outString = Replace(outString, "ü", "ue")
    outString = Replace(outString, "Ö", "Oe")
    outString = Replace(outString, "Ü", "ue")
    outString = Replace(outString, "ß", "ss")
    outString = Replace(outString, "ó", "o")
    outString = Replace(outString, "é", "e")
    outString = Replace(outString, "è", "e")
    outString = Replace(outString, "É", "E")
    outString = Replace(outString, " ", "+")
    outString = Replace(outString, ",", "")
    outString = Replace(outString, "+-", "")
        
    cleanStringFromSpecialCharacters = outString
End Function

I hope, dear reader, this article was helpful for your own project. All the best
WolfiG