How to add a full list of Autocorrect words

Hello i need to add a custom autocorrect lists from a text file. only option is to add one by one so how to add multiple words to autocorrect.

See <LO user profile>/user/autocorr; for instance, c:\Users\<username>\AppData\Roaming\LibreOffice\4\user\autocorr on Windows.

The directory would contain some .dat files (after you’ve added at least one autocorrect entry). Those files are ZIP archives; you need the DocumentList.xml file in the archive.

1 Like

So i need to add words list to that xml file. also checked that file it has some other lines with autocorrect words.

It contains the pre-configured list of substitutions that you see when using clean profile; and also whatever you add there. Just format your list to have the required XML format:

<block-list:block block-list:abbreviated-name="word-to-replace" block-list:name="replacement"/>

keeping in mind that some characters (like <, >, &) need escaping in XML to become &lt;, &gt;, &amp;, etc., and append the result to the existing entries.

1 Like

Is it possible to write a macro? I need to automate this step.
If I type the incorrect > correct word in writer, running a macro should add it to that file.

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