It is already there.
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
The Package is a service that provides access to a set of files and folders contained within a Package.
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).
@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
@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)
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???
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)
- faster: change variable filePath in @sokol92 example to your location
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"
- 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
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!
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
- faster:
msgbox "34 entries added"
- better: with real count
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"