When I add the formula FORECAST.ETS, it adds an # after the equal symbol, like this: = #FORECAST.ETS. Why is this happening?
The code snippet is:
ws.cell(column=1, row=2, value="=FORECAST.ETS(...)"
When I open it with Excel (latest Office 365 version), it shows as =#FORECAST.ETS(..)
I have hit the same issue, but not with Python and openpyxl, but with dotnet Core C# and EPPLUS. What follows is perhaps a workaround based on my findings... but not ideal. I suspect it will work with openpyxl too.
Re-creating the problem
I have written a simplified C# console app that firstly creates a new XLSX (foo.xlsx), writes out some data and my formula, and then outputs the cell with the formula and the value to the Console. It then saves and closes the XLSX, and reopens it and again outputs the formula cell and its value. The code is as follows:
using OfficeOpenXml;
using System;
using System.IO;
namespace TestFormula
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Test starts");
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
if (File.Exists($".\\foo.xlsx"))
{
File.Delete($".\\foo.xlsx");
}
using (var ep = new ExcelPackage(new FileInfo($".\\foo.xlsx")))
{
ExcelWorkbook wb = ep.Workbook;
ExcelWorksheet wsTest = null;
wsTest = wb.Worksheets.Add("Test");
// Add some look up data...
for (int row = 1; row <= 5; row++)
{
wsTest.Cells[row, 1].Value = row;
wsTest.Cells[row, 2].Value = $"Name {row}";
}
wsTest.Cells[1, 4].Formula = $"=XLOOKUP($A3,$A:$A,$B:$B))";
Console.WriteLine($"Add: formula=\"{wsTest.Cells[1, 4].Formula}\"");
Console.WriteLine($"Add: value=\"{wsTest.Cells[1, 4].Value}\"");
ep.Save();
ep.Dispose();
}
using (var ep = new ExcelPackage(new FileInfo($".\\foo.xlsx")))
{
ExcelWorkbook wb = ep.Workbook;
ExcelWorksheet wsTest = null;
wsTest = wb.Worksheets["Test"];
Console.WriteLine($"Open: formula=\"{wsTest.Cells[1, 4].Formula}\"");
Console.WriteLine($"Open: value=\"{wsTest.Cells[1, 4].Value}\"");
ep.Dispose();
}
Console.WriteLine("Test ends");
}
}
}
The output from the above looks like this...
Note that the formula after closing and re-opening the XLSX with EPPLUS reads just as it was written.
However, if I open the file with Excel I can see that an # has been inserted after the = sign.
If I then double click on the formula cell, I get an Excel error message...
I answered "no" to this question because I wanted to continue to experiment with what was happening behind the scenes.
After double clicking the formula cell to edit it, when I now hit ENTER with the # in the formula, it works. At this point I save the XLSX with the change made.
If I now delete some of my code and just run...
using OfficeOpenXml;
using System;
using System.IO;
namespace TestFormula
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Test starts");
ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
using (var ep = new ExcelPackage(new FileInfo($".\\foo.xlsx")))
{
ExcelWorkbook wb = ep.Workbook;
ExcelWorksheet wsTest = null;
wsTest = wb.Worksheets["Test"];
Console.WriteLine($"Open: formula=\"{wsTest.Cells[1, 4].Formula}\"");
Console.WriteLine($"Open: value=\"{wsTest.Cells[1, 4].Value}\"");
ep.Dispose();
}
Console.WriteLine("Test ends");
}
}
}
I get the following output...
What's particularly interesting about the output is that the formula has been modified by Excel and has been prefixed with _alfn.SINGLE.
Research
It is worth declaring here that I am running Microsoft 365 and I always have patches and updates automatically applied as soon as they become available. So my version of Excel is the latest version.
Google-ing for _alfn.SINGLE provides a number of hits (see References below) and from these I have concluded the following:
In Aug 2019 Microsoft released an update that introduced a new formula keyword called XLOOKUP... intended to replace VLOOKUP and HLOOKUP. As such, the XLSX file format was updated to allow for this new feature. The second reference below mentions dates of the introduction of other formulas around Sep 2018.
I'm guessing that the EPLUS library (and probably the openpyxl) have not updated their file format to compensate for the addition of these new/changed features.
When Excel opens an older file version and detects a more recent formula keyword (i.e. a keyword that was not available in the earlier file version), it does not automatically resolve the formula, but instead throws the error I mentioned above, and then resolves the problem by prefixing the new formula keyword with _alfn.SINGLE.
Solution
It's dirty and short term until the EPPLUS/openpyxl libraries catch up. In my case, in code simply replace...
wsTest.Cells[1, 4].Formula = $"=XLOOKUP($A3,$A:$A,$B:$B)";
... with ...
wsTest.Cells[1, 4].Formula = $"_xlfn.SINGLE(_xlfn.XLOOKUP($A3,$A:$A,$B:$B))";
References
Issue: An _xlfn. prefix is displayed in front of a formula by Microsoft
XLOOKUP XMATCH FILTER RANDARRAY SEQUENCE SORT SORTBY UNIQUE CONCAT IFS MAXIFS MINIFS SWITCH TEXTJOIN by Andreas Killer
I need to make sure there's copyrights notice at the beginning of each file.
How can I use grunt to fail my build in case the copyrights statement is missing?
First of all, I'm assuming you are referring to *.js or *.html or other similar work files, and not to graphic or binary files.
This can be done, with a grunt.registerTask which will:
1. loop through all relevant files
2. Read and compare first line to copyright line
3. If different - re-write file but a new first line which will be the copyright info
Pretty simple. Again - this will not work on binary files. I wrote this for you but it seems very useful, I might consider adding it as a plugin. Field tested:
run it by grunt verifyCopyright and also make sure that if your files are in a different directory your change it, and also if you want to process other files add them to the list as well. good luck...
grunt.registerTask('verifyCopyright', function () {
var fileRead, firstLine, counter = 0, fileExtension, commentWrapper;
copyrightInfo = 'Copyright by Gilad Peleg #2013';
//get file extension regex
var re = /(?:\.([^.]+))?$/;
grunt.log.writeln();
// read all subdirectories from your modules folder
grunt.file.expand(
{filter: 'isFile', cwd: 'public/'},
["**/*.js", ['**/*.html']])
.forEach(function (dir) {
fileRead = grunt.file.read('public/' + dir).split('\n');
firstLine = fileRead[0];
if (firstLine.indexOf(copyrightInfo > -1)) {
counter++;
grunt.log.write(dir);
grunt.log.writeln(" -->doesn't have copyright. Writing it.");
//need to be careful about:
//what kind of comment we can add to each type of file. i.e /* <text> */ to js
fileExtension = re.exec(dir)[1];
switch (fileExtension) {
case 'js':
commentWrapper = '/* ' + copyrightInfo + ' */';
break;
case 'html':
commentWrapper = '<!-- ' + copyrightInfo + ' //-->';
break;
default:
commentWrapper = null;
grunt.log.writeln('file extension not recognized');
break;
}
if (commentWrapper) {
fileRead.unshift(commentWrapper);
fileRead = fileRead.join('\n');
grunt.file.write( 'public/' + dir, fileRead);
}
}
});
grunt.log.ok('Found', counter, 'files without copyright');
})
Instead of checking to see if it's there and failing, why not just have a task that automatically injects it? See grunt-banner.
https://github.com/thekua/grunt-regex-check could be what you want. You define the regex to check for and if it finds it then the task fails.
i need a batch for downloading files from a http website (http://www.rarlab.com/download.htm).
From this website i only need the most recent version for the 32bit and 64bit english
program which is always listed at the top of this website.
Problem 1: There are more than this two files for download on the website
Problem 2: The name of the file changes with every new version
How can i download these 2 files (the most recent version) without knowing the exact file name
(and without first visiting the web page to find out the file name) ??
Maybe i can use wget, curl or aria2 for that task but i don't know the parameters/options.
Can anyone help me solving this problem ?
(Please only batch solutions - no vbs, java, jscript, powershell etc.)
thank you.
Sorry, i forgot to say that i use windows 7 32bit. And i prefer batch because the script should be able to run on all windows versions without having to download extra programs or resource kits for different windows version (as of powershell which must be downloaded for windows xp etc.) - and because i only understand batch scripting.
Here's a batch + JScript hybrid script. I know you said no vbs, java, jscript, etc, but you're going to have an awfully hard time scraping HTML with pure batch. But this does meet your other criteria -- running on all Windows versions without having to rely on optional software (like powershell or .Net).* And with JScript's XMLHTTP object you don't even need a 3rd party app to fetch web content.
As for not understanding JScript, aside from a few proprietary ActiveX objects it's just like JavaScript. In case you aren't familiar with JavaScript or regular expressions, I added copious amounts of comments to help you out. Hopefully whatever I didn't bother commenting is pretty obvious what it does.
Update
The script now detects the system locale, matches it with a language on the WinRAR download page, and downloads that language release.
Anyway, save this with a .bat extension and run it as you would any other batch script.
#if (#a==#b) #end /*
:: batch script portion
#echo off
setlocal
set "url=http://www.rarlab.com/download.htm"
set /p "savepath=Location to save? [%cd%] "
if "%savepath%"=="" set "savepath=%cd%"
cscript /nologo /e:jscript "%~f0" "%url%" "%savepath%"
goto :EOF
:: JScript portion */
// populate translation from locale identifier hex value to WinRAR language label
// http://msdn.microsoft.com/en-us/library/dd318693.aspx
var abbrev={}, a=function(arr,val){for(var i=0;i<arr.length;i++)abbrev[arr[i]]=val};
a(['1401','3c01','0c01','0801','2001','4001','2801','1c01','3801','2401'],'Arabic');
a(['042b'],'Armenian');
a(['082c','042c'],'Azerbaijani');
a(['0423'],'Belarusian');
a(['0402'],'Bulgarian');
a(['0403'],'Catalan');
a(['7c04'],'Chinese Traditional');
a(['0c04','1404','1004','0004'],'Chinese Simplified');
a(['101a'],'Croatian');
a(['0405'],'Czech');
a(['0406'],'Danish');
a(['0813','0413'],'Dutch');
a(['0425'],'Estonian');
a(['040b'],'Finnish');
a(['080c','0c0c','040c','140c','180c','100c'],'French');
a(['0437'],'Georgian');
a(['0c07','0407','1407','1007','0807'],'German');
a(['0408'],'Greek');
a(['040d'],'Hebrew');
a(['040e'],'Hungarian');
a(['0421'],'Indonesian');
a(['0410','0810'],'Italian');
a(['0411'],'Japanese');
a(['0412'],'Korean');
a(['0427'],'Lithuanian');
a(['042f'],'Macedonian');
a(['0414','0814'],'Norwegian');
a(['0429'],'Persian');
a(['0415'],'Polish');
a(['0816'],'Portuguese');
a(['0416'],'Portuguese Brazilian');
a(['0418'],'Romanian');
a(['0419'],'Russian');
a(['7c1a','1c1a','0c1a'],'Serbian Cyrillic');
a(['181a','081a'],'Serbian Latin');
a(['041b'],'Slovak');
a(['0424'],'Slovenian');
a(['2c0a','400a','340a','240a','140a','1c0a','300a','440a','100a','480a','080a','4c0a','180a','3c0a','280a','500a','0c0a','040a','540a','380a','200a'],'Spanish');
a(['081d','041d'],'Swedish');
a(['041e'],'Thai');
a(['041f'],'Turkish');
a(['0422'],'Ukranian');
a(['0843','0443'],'Uzbek');
a(['0803'],'Valencian');
a(['042a'],'Vietnamese');
function language() {
var os = GetObject('winmgmts:').ExecQuery('select Locale from Win32_OperatingSystem');
var locale = new Enumerator(os).item().Locale;
// default to English if locale is not in abbrev{}
return abbrev[locale.toLowerCase()] || 'English';
}
function fetch(url) {
var xObj = new ActiveXObject("Microsoft.XMLHTTP");
xObj.open("GET",url,true);
xObj.setRequestHeader('User-Agent','XMLHTTP/1.0');
xObj.send('');
while (xObj.readyState != 4) WSH.Sleep(50);
return(xObj);
}
function save(xObj, file) {
var stream = new ActiveXObject("ADODB.Stream");
with (stream) {
type = 1; // binary
open();
write(xObj.responseBody);
saveToFile(file, 2); // overwrite
close();
}
}
// fetch the initial web page
var x = fetch(WSH.Arguments(0));
// make HTML response all one line
var html = x.responseText.split(/\r?\n/).join('');
// create array of hrefs matching *.exe where the link text contains system language
var r = new RegExp('<a\\s*href="[^"]+\\.exe(?=[^\\/]+' + language() + ')', 'g');
var anchors = html.match(r)
// use only the first two
for (var i=0; i<2; i++) {
// use only the stuff after the quotation mark to the end
var dl = '' + /[^"]+$/.exec(anchors[i]);
// if the location is a relative path, prepend the domain
if (dl.substring(0,1) == '/') dl = /.+:\/\/[^\/]+/.exec(WSH.Arguments(0)) + dl;
// target is path\filename
var target=WSH.Arguments(1) + '\\' + /[^\/]+$/.exec(dl)
// echo without a new line
WSH.StdOut.Write('Saving ' + target + '... ');
// fetch file and save it
save(fetch(dl), target);
WSH.Echo('Done.');
}
Update 2
Here's the same script with a few minor tweaks to have it also detect the architecture (32/64-bitness) of Windows, and only download one installer instead of two:
#if (#a==#b) #end /*
:: batch script portion
#echo off
setlocal
set "url=http://www.rarlab.com/download.htm"
set /p "savepath=Location to save? [%cd%] "
if "%savepath%"=="" set "savepath=%cd%"
cscript /nologo /e:jscript "%~f0" "%url%" "%savepath%"
goto :EOF
:: JScript portion */
// populate translation from locale identifier hex value to WinRAR language label
// http://msdn.microsoft.com/en-us/library/dd318693.aspx
var abbrev={}, a=function(arr,val){for(var i=0;i<arr.length;i++)abbrev[arr[i]]=val};
a(['1401','3c01','0c01','0801','2001','4001','2801','1c01','3801','2401'],'Arabic');
a(['042b'],'Armenian');
a(['082c','042c'],'Azerbaijani');
a(['0423'],'Belarusian');
a(['0402'],'Bulgarian');
a(['0403'],'Catalan');
a(['7c04'],'Chinese Traditional');
a(['0c04','1404','1004','0004'],'Chinese Simplified');
a(['101a'],'Croatian');
a(['0405'],'Czech');
a(['0406'],'Danish');
a(['0813','0413'],'Dutch');
a(['0425'],'Estonian');
a(['040b'],'Finnish');
a(['080c','0c0c','040c','140c','180c','100c'],'French');
a(['0437'],'Georgian');
a(['0c07','0407','1407','1007','0807'],'German');
a(['0408'],'Greek');
a(['040d'],'Hebrew');
a(['040e'],'Hungarian');
a(['0421'],'Indonesian');
a(['0410','0810'],'Italian');
a(['0411'],'Japanese');
a(['0412'],'Korean');
a(['0427'],'Lithuanian');
a(['042f'],'Macedonian');
a(['0414','0814'],'Norwegian');
a(['0429'],'Persian');
a(['0415'],'Polish');
a(['0816'],'Portuguese');
a(['0416'],'Portuguese Brazilian');
a(['0418'],'Romanian');
a(['0419'],'Russian');
a(['7c1a','1c1a','0c1a'],'Serbian Cyrillic');
a(['181a','081a'],'Serbian Latin');
a(['041b'],'Slovak');
a(['0424'],'Slovenian');
a(['2c0a','400a','340a','240a','140a','1c0a','300a','440a','100a','480a','080a','4c0a','180a','3c0a','280a','500a','0c0a','040a','540a','380a','200a'],'Spanish');
a(['081d','041d'],'Swedish');
a(['041e'],'Thai');
a(['041f'],'Turkish');
a(['0422'],'Ukranian');
a(['0843','0443'],'Uzbek');
a(['0803'],'Valencian');
a(['042a'],'Vietnamese');
function language() {
var os = GetObject('winmgmts:').ExecQuery('select Locale from Win32_OperatingSystem');
var locale = new Enumerator(os).item().Locale;
// default to English if locale is not in abbrev{}
return abbrev[locale.toLowerCase()] || 'English';
}
function fetch(url) {
var xObj = new ActiveXObject("Microsoft.XMLHTTP");
xObj.open("GET",url,true);
xObj.setRequestHeader('User-Agent','XMLHTTP/1.0');
xObj.send('');
while (xObj.readyState != 4) WSH.Sleep(50);
return(xObj);
}
function save(xObj, file) {
var stream = new ActiveXObject("ADODB.Stream");
with (stream) {
type = 1; // binary
open();
write(xObj.responseBody);
saveToFile(file, 2); // overwrite
close();
}
}
// fetch the initial web page
var x = fetch(WSH.Arguments(0));
// make HTML response all one line
var html = x.responseText.split(/\r?\n/).join('');
// get OS architecture (This method is much faster than the Win32_Processor.AddressWidth method)
var os = GetObject('winmgmts:').ExecQuery('select OSArchitecture from Win32_OperatingSystem');
var arch = /\d+/.exec(new Enumerator(os).item().OSArchitecture) * 1;
// get link matching *.exe where the link text contains system language and architecture
var r = new RegExp('<a\\s*href="[^"]+\\.exe(?=[^\\/]+' + language() + '[^<]+' + arch + '\\Wbit)');
var link = r.exec(html)
// use only the stuff after the quotation mark to the end
var dl = '' + /[^"]+$/.exec(link);
// if the location is a relative path, prepend the domain
if (dl.substring(0,1) == '/') dl = /.+:\/\/[^\/]+/.exec(WSH.Arguments(0)) + dl;
// target is path\filename
var target=WSH.Arguments(1) + '\\' + /[^\/]+$/.exec(dl)
// echo without a new line
WSH.StdOut.Write('Saving ' + target + '... ');
// fetch file and save it
save(fetch(dl), target);
WSH.Echo('Done.');