How to add a full list of Autocorrect words

It is already there.
AutoCorrectInContextMenu

1 Like

I am looking for libreoffice - basic (programming language) code to do this. I have written python code, but do not know much about basic.

For a list of many words, I would just use Calc to join everything together and paste into a text editor.
AutoCorrectAddToDocumentList-XML.ods (9.9 KB)
Drag the formula in C down as far as you need, it is only 20 rows in the attached spreadsheet. Language is set to None.

I am sceptical about a pure uno / basic solution. There is the ZipFile Access LibreOffice: ZipFileAccess Service Reference but it provides only read access

which API does it use internally when I add an auto-correct entry using right click?

@shantanuo you can get the DOM of XML from ZIP: How check ODF version - #5 by sokol92

Or here are some macros for ZIP files (I found ones before some years in some blog that not exists any more)

Sub unpackAndAddToZip
	dim sPackageURL$, sOutput$
	sPackageURL="file:///C:/landic/pokus.zip"	
	sOutput="file:///c:/landic/new.txt" 'the file from ZIP will be unpacked to this file
	
	GetZipContent(sPackageURL, "DocumentList.xml", sOutput) 'unpack DocumentList.xml to sOutput
	
	PutZipContent(sPackageURL, "NEW/DocuList.txt", sOutput) 'add sOutput to NEW in ZIP
End Sub

Function GetZipContent(sZipURL$, sContentName$, sOutURL$)
	dim oZipPkg as object,  oSFA as object
	dim oContentStream as object,  oInput as object, args(1), meta as new com.sun.star.beans.NamedValue
	oZipPkg=CreateUnoService("com.sun.star.packages.Package")
	args(0)=sZipURL
	with meta 'no META-INF&mimetype
		.Name="PackageFormat" 
		.Value=false
	end with
	args(1)=meta
	oZipPkg.initialize(args)
	'oZipPkg.initialize(array(sZipURL)) 'if you need META-INF&mimetype in ZIP then .initialize is simplier
	if oZipPkg.hasByHierarchicalName(sContentName) then
		oContentStream=oZipPkg.getByHierarchicalName(sContentName)
		oInput=oContentStream.getInputStream()
		oSFA=CreateUnoService("com.sun.star.ucb.SimpleFileAccess")
		oSFA.writeFile(sOutURL, oInput)
	end if
End Function

Function PutZipContent(sZipURL$, sContentName$, sInputURL$, optional bCompress as boolean)
	dim oZipPkg as object, oSFA as object, oContentStream as object, oZipFolder as object, args(1), meta as new com.sun.star.beans.NamedValue
	oZipPkg=CreateUnoService("com.sun.star.packages.Package")
	args(0)=sZipURL
	with meta 'no META-INF&mimetype
		.Name="PackageFormat" 
		.Value=false
	end with
	args(1)=meta
	oZipPkg.initialize(args)
	'oZipPkg.initialize(array(sZipURL)) 'if you need META-INF&mimetype in ZIP
	oZipFolder=oZipPkg.getByHierarchicalName("/")
	oContentStream=oZipPkg.createInstanceWithArguments(array(false))
	oSFA=CreateUnoService("com.sun.star.ucb.SimpleFileAccess")
	if oSFA.exists(sInputURL) then
		oContentStream.setInputStream(oSFA.openFileRead(sInputURL))
		if IsMissing(bCompress) then bCompress=True
		oContentStream.setPropertyValue("Compressed",  bCompress)
		if NOT oZipFolder.hasByName(sContentName) then
			oZipFolder.insertByName(sContentName, oContentStream)
		else
			oZipFolder.replaceByName(sContentName, oContentStream)
		end if
		oZipPkg.commitChanges()
	end if
End Function

Sub CreateEmptyZip
	dim oZipPkg as object, oSFA as object, oZipFolder as object, args(1), meta as new com.sun.star.beans.NamedValue
	const sZipURL="file:///c:/landic/new.zip"
	oZipPkg=CreateUnoService("com.sun.star.packages.Package")
	args(0)=sZipURL
	with meta 'no META-INF&mimetype
		.Name="PackageFormat" 
		.Value=false
	end with
	args(1)=meta
	oZipPkg.initialize(args) 'no META-INF&mimetype in ZIP
	'oZipPkg.initialize(array(sZipURL)) 'with META-INF&mimetype in ZIP
	oZipPkg.initialize(array(sZipURL))
	oZipFolder=oZipPkg.getByHierarchicalName("/")
	oZipPkg.commitChanges()
End Sub

Sub CreateEmptyZipFile
	dim sZipURL$, sTxt$, i&, nInt&, oPipe as object, oSFA as object
	sZipURL="file:///c:/landic/new.zip"
	const sEmptyZip="504B0506000000000000000000000000000000000000"
	sTxt=sEmptyZip
	dim nBytes(Int((Len(sTxt)/2) -1)) as integer
	for i=0 to UBound(nBytes)
		nInt=Int(CDec("&H" & Mid(sTxt, i*2 +1, 2)))
		if nInt > 127 then nInt=nInt - 256
		nBytes(i)=nInt
	next i
	oPipe=CreateUnoService("com.sun.star.io.Pipe")
	oPipe.writeBytes(nBytes)
	oPipe.flush()
	oPipe.closeOutput()
	oSFA=CreateUnoService("com.sun.star.ucb.SimpleFileAccess")
	oSFA.writeFile(sZipURL, oPipe)
	oPipe.closeInput()
End Sub

Sub zipFileAccess
	dim sPackageURL$, oZipAcc as object, sNames(), i&, oInput as object
	sPackageURL="file:///c:/landic/pokus.zip"
	oZipAcc=CreateUnoService("com.sun.star.packages.zip.ZipFileAccess")
	oZipAcc.initialize(array(sPackageURL))
	sNames=oZipAcc.getElementNames()
	for i=0 to UBound(sNames) step 1
		oInput=oZipAcc.getByName(sNames(i))
		'xray oInput
		oInput.closeInput()
	next
End Sub
1 Like

The Package is a service that provides access to a set of files and folders contained within a Package.

1 Like

And how do I append the file? This does not seem to work.

Open sOutput For Append As #1
Print #1, '<block-list:block block-list:abbreviated-name="teest" block-list:name="test">'

?? Neither ZIP, nor XML can usually just appended. You will need full access and have to find the point, where you can extend entries, then regenerate the “closing” tags for XML or checksums for ZIP (unless your library hides this from you).

1 Like

@shantanuo You cannot mix single and double quotation marks in LibreBasic :-), single quotation mark is only for comments. And double quotation mark in string is doubled "".

Print #1, "<block-list:block block-list:abbreviated-name=""teest"" block-list:name=""test"">"

Here is example how to append to UTF-8 file with the “autodetection” of Enter in file.

Sub appendStringToFile 'if file doesn't exist then it is created
	on local error goto bug
	dim sEncoding$, sFileName$, sUrl$, oSFA as object, oFileHandler as object, oInputStream as object, oOutputStream as object, s$, sEnter$, data(), sLine$
	
	sFileName="c:\landic\some.txt" 'your file
	sEncoding="UTF-8" 'encoding
	s="blablabla blebleble glogloglo" 'your string to append
	
	sUrl=ConvertToUrl(sFileName)
	oSFA=CreateUnoService("com.sun.star.ucb.SimpleFileAccess")
	if NOT oSFA.exists(sUrl) OR (oSFA.exists(sUrl) AND oSFA.getSize(sUrl)=0) then 'file not exists, or has zero size
		oFileHandler=oSFA.openFileWrite(sUrl) 'new file for write
		oOutputStream=CreateUnoService("com.sun.star.io.TextOutputStream")	
		oOutputStream.setEncoding(sEncoding)
		oOutputStream.setOutputStream(oFileHandler)
		oOutputStream.writeString(s) 'write the string
		goto closing
	end if
	
	oFileHandler=oSFA.openFileReadWrite(sUrl) 'appending need to open for read&write
	rem input stream, to set the position for writting to the end of file
	oInputStream=CreateUnoService("com.sun.star.io.TextInputStream")
	oInputStream.setEncoding(sEncoding)
	oInputStream.setInputStream(oFileHandler)	
	rem detect Enter in TXT file
	sLine=oInputStream.readLine()
	oInputStream.InputStream.seek(Len(sLine)) 'seek to the end of 1st line
	if oInputStream.InputStream.Position<oInputStream.InputStream.Length then 'there is more than 1 line in file
		oInputStream.readBytes(data, 1) 'read 1 byte after 1st line to detect Enter
		if data(0)=chr(10) then 'LF
			sEnter=chr(10) 'linux
		else 'suppose CR
			sEnter=chr(13) 'suppose mac
			if oInputStream.InputStream.Position<oInputStream.InputStream.Length then 'there isn't only 1 line ended by CR
				oInputStream.readBytes(data, 1) 'read byte after CR
				if data(0)=chr(10) then sEnter=chr(13) & chr(10) 'after CR is LF = windows
			end if
		end if
	end if
	if sEnter="" then 'bad detection of Enter, or only one line without Enter in file -> take Enter according to OS
		select case getGuiType
		case 3 'mac
			sEnter=chr(13)
		 case 4 'linux
			sEnter=chr(10)
		case 1 'win
			sEnter=chr(13) & chr(10)
		case else 'something is wrong in detection of Enter according to OS
			msgbox("Error in OS detection of Enter :-(, the line isn't append!")
			goto closing
		end select
	end if
	oInputStream.InputStream.seek(oInputStream.InputStream.Length) 'set position for writting to end of file -> for Append
	
	rem output stream
	oOutputStream=CreateUnoService("com.sun.star.io.TextOutputStream")	
	oOutputStream.setEncoding(sEncoding)
	oOutputStream.setOutputStream(oFileHandler)
	oOutputStream.writeString(sEnter & s) 'write the string
	
	rem closing
closing:
	if NOT isNull(oOutputStream) then oOutputStream.closeOutput()
	if NOT isNull(oInputStream) then oInputStream.closeInput()
	oFileHandler.closeOutput()
	exit sub
bug:
	msgbox("line: " & Erl & chr(13) & Err & ": " & Error, 16, "appendStringToFile")
End Sub

The path is not short.
Change the right side in the filePath assignment to match your locale. And, just in case, make a copy of the acor_xx_YY.dat file.

Sub Test() 
  Dim oPathSettings As Object, oPackage As Object, oDom As Object, oNode As Object
  Dim oPackageFolder As Object, oPackageStream As Object, oOutputStream As Object, oTempFile As Object
  Dim filePath As String, DocumentList As String, arr
  oPathSettings = GetDefaultContext.getValueByName("/singletons/com.sun.star.util.thePathSettings")
  
  filePath=Split(oPathSettings.AutoCorrect, ";")(1) & "/acor_ru-RU.dat"    ' Change ru-RU to your locale!!
  
  oPackage=createUnoService("com.sun.star.packages.Package")
  oPackage.initialize Array(filePath)
  
  DocumentList="DocumentList.xml"
  oPackageStream=oPackage.getByHierarchicalName(DocumentList)
  oDom=createUnoService("com.sun.star.xml.dom.DocumentBuilder").parse(oPackageStream.inputStream)
  
  oNode=oDom.documentElement.appendChild(oDom.createElement("block-list:block"))
  oNode.setAttribute("block-list:abbreviated-name", "Teest")
  oNode.setAttribute("block-list:name", "Test")
  
  oTempFile=createUnoService("com.sun.star.io.TempFile")
  oDom.setOutputStream oTempFile.OutputStream
  oDom.start
  oTempFile.OutputStream.closeOutput
   
  oPackageFolder=oPackage.getByHierarchicalName("")
  oPackageFolder.removeByName DocumentList  
  oPackageStream=oPackage.createInstanceWithArguments(Array(False))
  oPackageStream.SetInputStream(oTempFile.InputStream)
  oPackageFolder.insertByName(DocumentList, oPackageStream)
  
  oPackage.commitChanges 
End Sub

2 Likes

@sokol92 small modification at the end: .replaceByName instead .removeByName + .insertByName

  oPackageFolder=oPackage.getByHierarchicalName("")
  oPackageStream=oPackage.createInstanceWithArguments(Array(False))
  oPackageStream.SetInputStream(oTempFile.InputStream)
  oPackageFolder.replaceByName(DocumentList, oPackageStream)
1 Like

This works only if my .dat file is placed in the interal path:

/usr/lib/libreoffice/share/autocorr

But my file location is this:

/home/ubuntu/.config/libreoffice/4/user/uno_packages/cache/uno_packages/lu282754f7as.tmp_/with_acor_N_9_0.oxt/autocorr/

(Because it’s installed using an extension)

You know the concrete path…and ask us in which way you would have to change the code???
:see_no_evil: :hear_no_evil: :speak_no_evil:

from pathlib import Path
acorpath = list(Path.home().glob(f".config/libreoffice/4/user/uno_packages/{'*/' * 5}acor*.dat"))[0]

I doubt if it can get past the compiler!

from lxml import etree
from pathlib import Path
from zipfile import ZipFile, ZIP_DEFLATED
from subprocess import check_call


#acorpath = list(Path.home().glob(f".config/libreoffice/4/user/uno_packages/{'*/'*5}acor*.dat"))[0]
acorpath = Path.home() /".config" / "libreoffice" / "4" / "user" / "autocorr" / "acor_de-DE.dat"

def main(xmlfile="DocumentList.xml"):

    with ZipFile(acorpath) as acor:
        acor.extract(xmlfile)

    base = 'http://openoffice.org/2001/block-list'
    tree = etree.parse(xmlfile)
    nodes = tree.findall('/')
    my_dict = {'asdf': 'jklö<',
               'ihk': 'Industrie "und" Handelskammer'}

    for key, value in my_dict.items(): 
        new = etree.Element(f'{{{base}}}block',
                            nsmap={'block-list': base}) 
                        
        new.attrib[f'{{{base}}}abbreviated-name'] = key
        new.attrib[f'{{{base}}}name'] = value    
        nodes.append(new)

    root = tree.getroot()
    root.extend(nodes)
    root = root.getroottree()

    check_call(["zip", "-d", acorpath, xmlfile ])#ZipFile cannot delete!

    root.write(xmlfile)
    with ZipFile(acorpath,"a") as acor:
        acor.write(xmlfile, compress_type=ZIP_DEFLATED)
1 Like
  1. faster: change variable filePath in @sokol92 example to your location :slight_smile:
filePath="/home/ubuntu/.config/libreoffice/4/user/uno_packages/cache/uno_packages/lu282754f7as.tmp_/with_acor_N_9_0.oxt/autocorr/acor_ru-RU.dat"
  1. better: get filePath from the information about extension (change NameOfExtension and acor_ru-RU.dat). It is functional also if you reinstalled the extension.
	dim oProvider as object
	oProvider=GetDefaultContext.getByName("/singletons/com.sun.star.deployment.PackageInformationProvider")
	filePath=oProvider.getPackageLocation("NameOfExtension") & "/autocorr/acor_ru-RU.dat"

For sure to see the names of extensions you can use

Sub showNamesOfExtensions
	dim oProvider as object, a, s$
	oProvider=GetDefaultContext.getByName("/singletons/com.sun.star.deployment.PackageInformationProvider")
	for each a in oProvider.ExtensionList
		s=s & a(0) & chr(13) 
	next
	msgbox s
End Sub
3 Likes

Awesome! It is working very well without any problem.

Is it possible to pick the incorrect > correct words from current open document? The format is something like this…

teest: test, tested
nermal: normal, abnormal

The incorrect word that is followed by colon should be replaced by the first one from comma separated list. In python:

mydic = dict()
for i in update.split('\n'):
    first = i.split(':')[0]
    mydic[first] = i.split(':')[1].split(',')[0].strip()

I have saved a lot of words in this format! :slight_smile:

There can be problem if you use Shift+Enter, Tab, multiple spaces etc. in your document, so there is initially Find&Replace for it (array aRepl)!
And set correctly constants cExtension and cAcor at start.

Sub addCurrentList
	REM !!! change this constants !!!
	const cExtension="NameOfExtension"
	const cAcor="acor_ru-RU.dat"
	
	Dim oPathSettings As Object, oPackage As Object, oDom As Object, oNode As Object
	Dim oPackageFolder As Object, oPackageStream As Object, oOutputStream As Object, oTempFile As Object
	Dim filePath As String, DocumentList As String
	dim oProvider as object, oDoc as object, oEnum as object, oPar as object, s$, s1$, s2$, i1&, i2&, oDesc as object, aRepl(), x

	oDoc=ThisComponent
	oProvider=GetDefaultContext.getByName("/singletons/com.sun.star.deployment.PackageInformationProvider")
	filePath=oProvider.getPackageLocation(cExtension) & "/autocorr/" & cAcor

	oPackage=createUnoService("com.sun.star.packages.Package")
	oPackage.initialize Array(filePath)

	DocumentList="DocumentList.xml"
	oPackageStream=oPackage.getByHierarchicalName(DocumentList)
	oDom=createUnoService("com.sun.star.xml.dom.DocumentBuilder").parse(oPackageStream.inputStream)

	rem replace some white characters
	aRepl=array( array("\n", "\n"), array("^$", ""), array("\t", ""), array("\s{2,}", "") )
	oDesc=oDoc.createReplaceDescriptor
	oDesc.SearchRegularExpression=true
	for each x in aRepl
		with oDesc
			.SearchString=x(0)
			.ReplaceString=x(1)
		end with
		oDoc.ReplaceAll(oDesc)
	next

	rem traverse paragraphs
	oEnum=oDoc.Text.createEnumeration()
	while oEnum.hasMoreElements()
		oPar=oEnum.nextElement()
		if oPar.supportsService("com.sun.star.text.Paragraph") then
			s=oPar.String
			if s<>"" then 'paragraph has text
				i1=InStr(s, ":")
				if i1>0 then ' : found
					i2=InStr(i1, s, ",")
					if i2>0 then ' , found
						s1=Trim(Left(s, i1-1))
						s2=Trim(Mid(s, i1+1, i2-i1-1))
						'msgbox s1 & chr(13) & s2
						oNode=oDom.documentElement.appendChild(oDom.createElement("block-list:block"))
						oNode.setAttribute("block-list:abbreviated-name", s1)
						oNode.setAttribute("block-list:name", s2)
					end if
				end if
			end if
		end if
	wend

	oTempFile=createUnoService("com.sun.star.io.TempFile")
	oDom.setOutputStream oTempFile.OutputStream
	oDom.start
	oTempFile.OutputStream.closeOutput

	oPackageFolder=oPackage.getByHierarchicalName("")
	oPackageStream=oPackage.createInstanceWithArguments(Array(False))
	oPackageStream.SetInputStream(oTempFile.InputStream)
	oPackageFolder.replaceByName(DocumentList, oPackageStream)

	oPackage.commitChanges
End Sub

The macro is working as expected. I really appreciate your help. Is it possible to count lines and show “34 entries added” message at the end?

Another issue is that when I tried the similar code to add entries to default English (US), I got an error “read only package”.

    Dim pathsubstitution
    pathsubstitution = createUnoService("com.sun.star.util.PathSubstitution")
    filePath = pathsubstitution.substituteVariables("$(inst)/share/autocorr", True ) & "/acor_en-US.dat"

Of course :slight_smile:

  1. faster: msgbox "34 entries added"
  2. better: with real count :slight_smile:
	dim iCount&
	'...
	if s<>"" then 'paragraph has text
		iCount=iCount+1
		'...
	end if

	msgbox(iCount & " entries added")

Use $(user) instead $(inst) in filePath. And copy acor_en-US.dat to your user profile if there isn’t.

filePath = pathsubstitution.substituteVariables("$(user)/share/autocorr", True ) & "/acor_en-US.dat"