I'm using Go API https://developers.google.com/sheets/api/quickstart/go
I have a sheet looks like this and want to automate some cell filling logic
| B | C |
--+------+------+
1 | b1 | c1 |
--+------+ |
2 | b2 | |
--+------+------+
3 | b3 | |
--+------+------+
While using readRange := "B1:C" by
resp, _ := s.srv.Spreadsheets.Values.Get(spreadsheetId, readRange).Do()
for _, row := range resp.Values {
...
}
I can't tell the difference between C2 and C3. In this case, I would like to fill some values to C3 but not C2. How do I check this via API?
You want to retrieve the information of merge cells in a sheet using Sheets API.
From your replying, I could understand like above. If my understanding is correct, the method of spreadsheets.values.get cannot retrieve the information of merge cells. In this case, please use the method of spreadsheets.get. The modified script is as follows.
Modified script:
ranges := []string{"B1:C"}
resp, _ := s.srv.Spreadsheets.Get(spreadsheetId).Ranges(ranges...).Do()
for _, sheet := range resp.Sheets {
for _, merge := range sheet.Merges {
fmt.Printf("%#v\n", merge)
}
}
Note:
This modified script supposes that you have already used Sheets API.
Range of B1:C means B1:C of the first tab.
When you run above script, you can retrieve the coordinates of the merged cells as the GridRange like below.
{
"sheetId": 0,
"startRowIndex": 0,
"endRowIndex": 2,
"startColumnIndex": 2,
"endColumnIndex": 3
}
This GridRange is C1:C2 of sheet ID 0 as the a1Notation.
From above result, it is found that C1:C2 is the merged cell.
In this case, when the value is put in C1, the value is displayed. But when the value is put in C2, the value is not displayed. Please be careful this.
If you want to put the value using the GridRange, you can use the method of spreadsheets.batchUpdate.
References:
spreadsheets.get
GridRange
spreadsheets.batchUpdate
Related
I have a problem in Lazarus using FPSpreadsheet, where I am not able to define a format for a cell.
ex: inline formatting,
Value := 1500.60
MySheet.WriteNumber(0,1,Value, nfFixedTh,2);
I have this result in A2, 1,500.60, but when I define a formula I can't get this formatting.
ex: A2 = 1500.60
A3 = 260.00
MySheet.WriteFormula(0,4,'=SUM(A2:A3)');
The Result is 1760.6, how to format the result cell, so that it looks like this 1,760.60
In excel this is done like this:
MySheet.Range[A2,A3].NumberFormat := '###,##0.00';
But I am not able to do the same with FPSpreadsheet
tks
I tried like this:
A2 = 1500.6
A3 = 260.00
MySheet.WriteFormula(0,4,'=SUM(A2:A3)');
MyCell := MySheet.GetCell(0,4);
MyCel^.NumberFormat := nfFixedTh;
The Result is 1760.6, how to format the result cell, so that it looks like this 1,760.60.
I also reversed calling
MyCell := MySheet.GetCell(0,4);
MyCel^.NumberFormat := nfFixedTh;
before writing the formula
The TsWorksheet has a bunch of formatting methods. Read the wiki article which Tom mentioned.
In your case, in order to format a number cell, you should call one of the WriteNumberFormat methods, like in the following example. BTW, it does not matter whether you apply the format before or after the cell received its value.
program Project1;
uses
fpsTypes, fpspreadsheet, xlsxOOXML;
var
workbook: TsWorkbook;
sheet: TsWorksheet;
begin
workbook := TsWorkbook.Create;
try
sheet := workbook.AddWorksheet('Test');
sheet.WriteNumber(1, 0, 1500.6);
sheet.WriteNumber(2, 0, 260.0);
sheet.WriteNumberFormat(4, 0, nfFixedTh, 2);
sheet.WriteFormula(4, 0, 'Sum(A2:A3)');
workbook.WriteToFile('test.xlsx', true);
finally
workbook.Free;
end;
end.
What would be an efficient (performance and readability) of parsing lines in a log file and extracting points of interest?
For example:
*** Time: 2/1/2019 13:51:00
17.965 Pump 10 hose FF price level 1 limit 0.0000 authorise pending (Type 00)
17.965 Pump 10 State change LOCKED_PSTATE to CALLING_PSTATE [31]
38.791 Pump 10 delivery complete, Hose 1, price 72.9500, level 1, value 100.0000, volume 1.3700, v-total 8650924.3700, m-total 21885705.8800, T13:51:38
Things I need to extract are 10 (for pump 10), Price Level. Limit
The _PSTATE changes the values from the delivery completel line etc.
Currently I'm using a regular expression to capture each one and using capture groups. But it feels inefficient and there is quite a bit of duplication.
For example, I have a bunch of these:
reStateChange := regexp.MustCompile(`^(?P<offset>.*) Pump (?P<pump>\d{2}) State change (?P<oldstate>\w+_PSTATE) to (?P<newstate>\w+)_PSTATE`)
Then inside a while loop
if match := reStateChange.FindStringSubmatch(text); len(match) > 0 {
matched = true
for i, name := range match {
result[reStateChange.SubexpNames()[i]] = name
}
} else if match := otherReMatch.FindStringSubmatch(text); len(match) > 0 {
matched = true
for i, name := range match {
result[reStateChange.SubexpNames()[i]] = name
}
} else if strings.Contains(text, "*** Time:") {
}
It feels that there could be a much better way to do this. I would trade some performance for readability. The log files are only really 10MB max. Often smaller.
I'm after some suggestions on how to make this better in golang.
If all your log lines are similar to that sample you posted, they seem quite structured so regular expressions might be a bit overkill and hard to generalize.
Another option would be for you to transform each of those lines to a slice of strings ([]string) by using strings.Fields, or even strings.FieldFunc so that you can strip both white space and commas.
Then you can design an interface like:
type LogLineProcessor interface {
CanParse(line []string)
GetResultFrom(line []string) LogLineResult
}
Where LogLineResult is an struct containing the extracted information.
You can then define multiple structs with methods that implement LogLineProcessor (each implementation would look at specific positions on that []string to realize if it is a line it can process or not, like looking for the words "hose", "FF" and "price" in the positions it expects to find them).
The GetResultFrom implementations would also extract each data point from specific positions in the []string (it can rely on that information being there if it already determined it was one of the lines it can process).
You can create a var processors []LogLineProcessor, put all your processors in there and then just iterate that array:
line := strings.Fields(text)
for _, processor := range processors {
if processor.CanParse(line) {
result := processor.GetResultFrom(line)
// do whatever needed with the result
}
}
I am trying to update a Google Sheet using the Ruby API (that is just a wrapper around the SheetsV4 API)
I am running into the following error
Google::Apis::ClientError: badRequest: Range ('MySheet'!AA1) exceeds grid limits. Max rows: 1000, max columns: 26
I have found references of this problem on the google forum, however there did not seem to be a solution to the problem other that to use a different method to write to the spreadsheet.
The thing is, I need to copy an existing spreadsheet template, and enter my raw data in various sheets. So far I have been using this code (where service is a client of the Ruby SheetsV4 API)
def write_table(values, sheet: 'Sheet1', column: 1, row: 1, range: nil, value_input_option: 'RAW')
google_range = begin
if range
"#{sheet}!#{range}"
elsif column && row
"#{sheet}!#{integer_to_A1_notation(column)}#{row}"
end
end
value_range_object = ::Google::Apis::SheetsV4::ValueRange.new(
range: google_range, values: values
)
service.update_spreadsheet_value(spreadsheet_id,
google_range,
value_range_object,
value_input_option: value_input_option
)
end
It was working quite well so far, but after adding more data to my extracts, I went over the 26th column, (columns AA onwards) and now I am getting the error.
Is there some option to pass to update_spreadsheet_value so we can raise this limit ?
Otherwise, what is the other way to write to the spreadsheet using append ?
EDIT - A clear description of my scenario
I have a template Google spreadsheet with 8 sheets(tabs), 4 of which are titled RAW-XX and this is where I try to update my data.
At the beginning, those raw tabs only have headers on 30 columns (A1 --> AD1)
My code needs to be able to fill all the cells A2 --> AD42
(1) for the first time
(2) and my code needs to be able to re-run again to replace those values by fresh ones, without appending
So basically I was thinking of using update_spreadsheet_value rather than append_xx because of the requirement (2). But becuase of this bug/limitation (unclear) in the API, this does not work. ALso important to note : I am not actually updating all those 30 columns in one go, but actually in several calls to the update method (with up to 10 columns each time)
I've thought that
- Maybe I am missing an option to send to the Google API to allow more than 26 columns in one go ?
- Maybe this is actually an undocumented hard limitation of the update API
- Maybe I can resort to deleting existing data + using append
EDIT 2
Suppose I have a template at version 1 with multiple sheets (Note that I am using =xx to indicate a formula, and [empty] to indicate there is nothing in the cell, and 1 to indicate the raw value "1" was supplied
Sheet1 - RAW
RAW Number of foos | RAW Number of Bars |
[empty] | [empty] |
Sheet2 - FORMATTED
Number of foos | Number of Bars
='Sheet1 - RAW'!A2 | ='Sheet1 - RAW'B2
Now I call my app "for the first time", this copies the existing template to a new file "generated_spreadsheet" and injects data in the RAW sheet. It turns out at this moment, my app says there is 1 foo and 0 bar
Sheet1 - RAW
RAW Number of foos | RAW Number of Bars |
1 | 0 |
Sheet2 - FORMATTED
Number of foos | Number of Bars
='Sheet1 - RAW'!A2 | ='Sheet1 - RAW'!B2
Maybe if I call my app later, maybe the template AND the data have changed in between, so I want to REPLACE everything in my "generated_spreadsheet"
The new template has become in between
Sheet1 - RAW
RAW Number of foos | RAW Number of Bars |
[empty] | [empty] |
Sheet2 - FORMATTED
Number of foos | Number of Bars | All items
='Sheet1 - RAW'!A2 | ='Sheet1 - RAW'!B2 | =A2 + B2
Suppose now my app says there is still 1 foo and the number of bars went from 0 to 2, I want to update the "generated_spreadsheet" so it looks like
Sheet1 - RAW
RAW Number of foos | RAW Number of Bars |
1 | 3 |
Sheet2 - FORMATTED
Number of foos | Number of Bars | All items
='Sheet1 - RAW'!A2 | ='Sheet1 - RAW'!B2 | =A2 + B2
How about using values.append? In my environment, I also experienced the same situation with you. In order to avoid this issue, I used values.append.
Please modify as follows and try it again.
From:
service.update_spreadsheet_value(
To:
service.append_spreadsheet_value(
Reference:
Method: spreadsheets.values.append
If this was not the result you want, I'm sorry.
this because out of range.
AA1 means column is AA, also means 27, so this start point AA1 not exist, that's why you met this error.
you can try Z1, this should be ok.
worksheet.resize(2000)
will resize your sheet 2000 rows
It happened to me as well when I didn't have the empty columns (I removed all the empty columns from the spreadsheet). I simply added an empty one next to my last column and it works.
In Google Sheets, I have a spreadsheet list, which includes a cell (E1) that chooses a random number like so:
C | D | E
------+---+------------------
1 | | |=RANDBETWEEN(1,99)
I would like to make another cell show the text in column C, row of random number from E1. How can I accomplish this?
Try this to retrieve the value from column c,
=index(c:c, e1)
I want to auto sort values in Google Sheets as soon as I enter value in a cell. Below is an example:
| S. No. | Task | Value |
| 1 | Task 1 | $$ |
| 2 | Task 2 | $$$ |
| 3 | Task 3 | $$$$ |
| | | |
In the above table, as soon as I enter Value field for Task 3, I want it to go to top and the first one should come to the end. I don't want to achieve this manually by sorting every time.
Similar to Chris Hick's suggestion, you might enter your data in any order and have a copy sorted to suit. Since your example appears well ordered (ascending) I have assumed you would like it ordered descending (by S. No.) and that that is in A1:
=query(A:C,"Select * where A is not NULL order by A desc")
Add a couple of entries9 in A5, 8 in A6 and the resulting list will be ordered 9,8,3,2,1.
You can use a script to automatically sort your table. To do this, go to tools > script editor. This will open a new window.
Delete the code that you see and paste the below in:
function onEdit(event) {
var sh = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var cellColumn = sh.getActiveRange().getColumnIndex();
var currentSheet = sh.getName();
if ((cellColumn == 3) && currentSheet == "enter sheet name here") {
var range = sh.getRange("A2:C");
range.sort({column:2, ascending:false});
}
}
You will need to change the "enter sheet name here" to the name of the sheet that you want to be sorted. Make sure to leave the quote marks in.
If you want to change the sort so that it is ascending, change the last line from
ascending:false
to
ascending:true
If you want to change the range of the data that is sorted, then you can do that on the row above. At the moment it is set to sort the range A2:C.