I would like to query Excel for a range of cells and know whether there are merged cells within that range. I see that there is an API for merging cells and an API for unmerging cells. Does anyone know if it is possible to just check for merge cells?
Currently we don't have an API to query the state of merged cells.
Please log a feature request on UserVoice and we will take it into account for future API planning.
-Philip, Developer on the Office Extensibility team
Docs: https://learn.microsoft.com/en-us/javascript/api/excel/excel.range?view=excel-js-preview#getMergedAreasOrNullObject__
Note: This is still a WIP for me, would need a "isNull" check and I haven't adapted this for production code, but this should be a good starting point. I recommend examine the mergedAreas object if you need further details.
Here is how I did it:
async function Get_Merged_Areas_Arr_Of_Objs(context, ws) { //test for Null
var mergedAreas = ws.getUsedRange(true).getMergedAreasOrNullObject();
mergedAreas.load(["areas"]);
await context.sync()
var Merged_Areas_Arr_Of_Objs = []
var arrlen = mergedAreas.areas.items.length
for (var ai = 0; ai < arrlen; ai += 1) {
var obj = mergedAreas.areas.items[ai]
var rng_obj = {}
rng_obj['worksheet'] = obj['address'].split("!")[0]
rng_obj['rowIndex'] = obj['rowIndex']
rng_obj['columnIndex'] = obj['columnIndex']
rng_obj['rowCount'] = obj['rowCount']
rng_obj['columnCount'] = obj['columnCount']
Merged_Areas_Arr_Of_Objs.push(rng_obj)
}
return Merged_Areas_Arr_Of_Objs
}
var Merged_Areas_Arr_Of_Objs = await Get_Merged_Areas_Arr_Of_Objs(context,ws)
console.log('Merged_Areas_Arr_Of_Objs:')
console.log(Merged_Areas_Arr_Of_Objs)
[object Object],[object Object],[object Object],[object Object]
[
0: {
[functions]: ,
__proto__: { },
columnCount: 1,
columnIndex: 0,
rowCount: 3,
rowIndex: 1,
Symbol()_7.z1gk27bjwa1: undefined,
Symbol(nodejs.util.inspect.custom)_j.z1gk27bjwa1: undefined,
worksheet: "Sheet3"
},
1: { },
2: { },
3: { },
length: 4
]
Related
I have registered a webhook which constantly generates the data. It has several fields, primary field is id. I am updating this data in a postgres table with update query. The problem is, I have a google sheet and the same data is there. After updating postgres table, I want to update the row in google sheet. How can I use googleSheetsInstance.spreadsheets.values.update() function to update the row with a specific id.
The code I wrote is like this:
let listOfLists = []
jsonArray.forEach(obj => {
const values = Object.values(obj);
listOfLists.push(values);
});
await googleSheetsInstance.spreadsheets.values.update({
auth,
spreadsheetId,
range: "'Sheet1'",
valueInputOption: "USER_ENTERED",
resource: {
majorDimension: "ROWS"
values: listOfLists,
},
});
I believe your goal is as follows.
You want to update rows of the Spreadsheet by searching ticket_id.
The sample value of listOfLists is [[1022, 'ab', 'cd', 'de'], [1023, 'xyz', 'hbw', 'pyg']].
From this sample value, in your situation, the value of ticket_id is column "A".
You want to achieve this using googleapis for Node.js.
You have already been able to get and put values for Google Spreadsheet with Sheets API.
In this case, how about the following sample script?
Sample script:
const googleSheetsInstance = // Please use your client.
const spreadsheetId = "###"; // Please set your Spreadsheet ID.
const sheetName = "Sheet1"; // This is from your script.
const listOfLists = [[1022, "ab", "cd", "de"], [1023, "xyz", "hbw", "pyg"]]; // This sample value is from your comment.
const obj = listOfLists.reduce((o, r) => ((o[r[0]] = r), o), {});
const {data: { values }} = await googleSheetsInstance.spreadsheets.values.get({auth, spreadsheetId, range: sheetName});
await googleSheetsInstance.spreadsheets.values.update({
auth,
spreadsheetId,
range: sheetName,
resource: {values: values.map((r) => obj[r[0]] || r)}, // or values.map((r) => obj[r[0]] || Array(r.length).fill(null)),
valueInputOption: "USER_ENTERED",
});
When this script is run, first, the values are retrieved from the sheet. And, the values of ticket_id of listOfLists are searched from column "A" of the retrieved values. And, the updated values are put on the sheet.
In this sample, 2 API calls are used.
Note:
If the values of ticket_id are not column "A", please modify const obj = listOfLists.reduce((o, r) => ((o[r[0]] = r), o), {}); and resource: {values: values.map((r) => obj[r[0]] || r)}, for your actual column.
References:
Method: spreadsheets.values.get
Method: spreadsheets.values.update
I've been working with entire Col rng's and noticed some inconsistencies.
For instance, my Excel Spreadsheet has a Col Rng of A1:A1048576, but the following code fails.
var ws = context.workbook.worksheets.getActiveWorksheet();
var range = ws.getRange("A1:A1048576");
range.values = "A";
But, if I use the rng as "A1:A1048575" (minus one), it works. Am I doing something wrong? The RNG doesn't appear to be 0 indexed as A1 selects the correct cell. I suspect this might be a bug, but wanted to confirm.
Here is the error FYI:
InvalidOperation: This operation is not permitted for the current object.
{
[functions]: ,
__proto__: {
[functions]: ,
__proto__: {
[functions]: ,
__proto__: {
[functions]: ,
__proto__: null
},
message: "",
name: "Error",
Symbol()_7.rurcc9qe7b1: undefined,
Symbol(nodejs.util.inspect.custom)_j.rurcc9qe7hi: undefined
},
message: "",
name: "Error",
Symbol()_7.rurcc9qe7b1: undefined,
Symbol(nodejs.util.inspect.custom)_j.rurcc9qe7hi: undefined
},
code: "InvalidOperation",
data: undefined,
debugInfo: {
[functions]: ,
__proto__: { },
code: "InvalidOperation",
errorLocation: "Range.values",
fullStatements: [
0: "Please enable config.extendedErrorLogging to see full statements.",
length: 1
],
message: "This operation is not permitted for the current object.",
statement: "range.values = ...;",
surroundingStatements: [
0: "var workbook = context.workbook;",
1: "var worksheets = workbook.worksheets;",
2: "var activeWorksheet = worksheets.getActiveWorksheet();",
3: "var range = activeWorksheet.getRange(...);",
4: "// Instantiate {range}",
5: "// >>>>>",
6: "range.values = ...;",
7: "// <<<<<",
length: 8
],
Symbol()_7.rurcc9qe7b1: undefined,
Symbol(nodejs.util.inspect.custom)_j.rurcc9qe7hi: undefined
},
description: "This operation is not permitted for the current object.",
httpStatusCode: 400,
innerError: null,
message: "This operation is not permitted for the current object.",
name: "RichApi.Error",
stack: "InvalidOperation: This operation is not permitted for the current object.
at Anonymous function (https://appsforoffice.microsoft.com/lib/beta/hosted/excel-win32-16.01.js:26:305431)
at Anonymous function (http://localhost:3000/yo/dist/polyfill.js:1:76119)
at e (http://localhost:3000/yo/dist/polyfill.js:1:31843)",
Symbol()_7.rurcc9qe7b1: undefined,
Symbol(nodejs.util.inspect.custom)_j.rurcc9qe7hi: undefined,
traceMessages: [
length: 0
]
}
Let me give a detailed answer.
Normally, we use range.values should like this:
range.values=[["A"]] the value is a 2-d array.
As shown in the api doc: https://learn.microsoft.com/en-us/javascript/api/excel/excel.range?view=excel-js-preview#excel-excel-range-values-member
I tested your way and it did runs. So I would suppose we also support this kind of input.
And for your issue, yes I can repro it. And I have a workaround now.
var ws = context.workbook.worksheets.getActiveWorksheet();
var range = ws.getRange("A1:A1048575");
range.values = "A";
var range2 = ws.getRange("A1048576");
range2.values = [["A"]];
await context.sync();
First it will set the value for A1:A1048575, and set value for A1048576 separately.
Internal bug #7377474 created to track it (which is true).
And our public repo: https://github.com/OfficeDev/office-js. You can put your future issue here also. And my github profile: https://github.com/yuc014 for the prove.
Thanks for your report. :)
I am in the process of learning Node JS and how to web scrape. I thought it would be a good approach to extract columns from a wikipedia page https://en.wikipedia.org/wiki/List_of_S%26P_500_companies
I've been learning about web scraping with Cheerio but am unsure how to code this in NodeJS. I've become familiar with html selectors to identify elements on a page but am not sure how to extract into a program. I plan on extracting this information into a list. I am hoping to extract the Symbol and Security columns on the table on the wiki page.
Below is the code I have compiled and the result I am getting. I have created a const based off a selector on the webpage. I believe it is supposed to return all the values in the column based off the selector.
var AWS = require("aws-sdk");
var AWS = require("aws-sdk/global");
AWS.config.apiVersions = {
dynamodb: '2012-08-10'
};
var dynamodb = new AWS.DynamoDB();
const cheerio = require('cheerio');
const axios = require('axios');
const express = require('express');
async function getStocks() {
try {
const url = ' https://en.wikipedia.org/wiki/List_of_S%26P_500_companies'
const { data } = await axios({
method: "GET",
url: url,
})
const $ = cheerio.load(data)
const elemSelector = '#constituents > tbody > tr:nth-child(1) > td'
$(elemSelector).each((parentIdx, parentElem) => {
console.log(parentIdx)
})
console.log($)
} catch (err) {
console.error(err)
}
}
getStocks()
Result
[Function: initialize] {
html: [Function: html],
xml: [Function: xml],
text: [Function: text],
parseHTML: [Function: parseHTML],
root: [Function: root],
contains: [Function: contains],
merge: [Function: merge],
load: [Function: load],
_root: Node {
type: 'root',
name: 'root',
parent: null,
prev: null,
next: null,
children: [ [Node], [Node] ],
'x-mode': 'no-quirks'
},
_options: { xml: false, decodeEntities: true },
fn: Cheerio { constructor: [Function: LoadedCheerio] }
}
[Finished in 2.384s]
This should help. Follow the comments to understand what is happening. You didn't specify how you wanted the data output, so I have them as arrays but you can add code to map the table data with the table headers now that you have the raw data.
// parses HTML
var $ = cheerio.load(data);
// the number of columns you want to target on the table, starting from the left column
var number_of_columns = 2;
// targets the specific table with a selector
var html_table = $('table#constituents');
// gets table header titles; loops through all th data, and pulls an array of the values
var table_header = html_table.find('th').map(function() {return $(this).text().trim();}).toArray();
// removes columns after the number of columns specified
table_header = table_header.slice(0, number_of_columns);
// gets table cell values; loops through all tr rows
var table_data = html_table.find('tbody tr').map(function(tr_index) {
// gets the cells value for the row; loops through each cell and returns an array of values
var cells = $(this).find('td').map(function(td_index) {return $(this).text().trim();}).toArray();
// removes columns after the number of columns specified
cells = cells.slice(0, number_of_columns);
// returns an array of the cell data generated
return [cells];
// the filter removes empty array items
}).toArray().filter(function(item) {return item.length;});
// output the table headers
console.log('table_header', table_header);
// output the table data
console.log('table_data', table_data);
In nodejs you can user puppeteer for your web scraping. it's a very good library with a lot of browser settings and query selectors options!
You can find more about the library reading the documentation at https://pptr.dev/
If you want to integrate it with an API I can help you too.
I have the following Object Structure:
[
{
name: "someThing"
,securities: [ {"id": "2241926"} ]
}
]
I want to be able to return all objects in the outer array, that has at least one child secuirty with an id that starts with a value. I have tried a few things and keep running up short. On the mongoo console, this query works:
db.caseSummary.find({"securities.id": /^224.*/i})
We are using ES6, so please apologies for the generator syntax:
const q = require("q");
const startsWith = function(term){
return new RegExp('^' + term + '.*', 'i')
}
const find = function*(collection, criteria){
const command = q.nbind(collection.find, collection),
myCriteria = criteria || {},
results = yield command(myCriteria),
toArray = q.nbind(results.toArray, results) ;
return yield toArray()
}
const searchBySecurity = function*(mongo, term) {
const collection = mongo.collection("caseSummary"),
criteria = {"securities.id": startsWith(term) };
return yield find(collection, criteria);
}
so searchBySecurity(this.mongo, '224') It returns an empty array, it should return the same results as the mongo console. I guess I am missing a pretty basic concept in translating my search criteria or invoking the find writing this in node from raw mongo console query.
Edit 1: to be clear I want to return all parent objects, which have subarrays that contain a value that starts with the term passed in...
Edit 2:
I changed the criteria to be:
criteria = { "securities": {
"$elemMatch": {
"id": "901774109" //common.startsWith(term)
}
}
};
Still the same results.
Edit 3:
Using nodejs - mongodb "version": "1.4.38"
Edit 4:
this ended up not being an issue
I am writing a custom app to track iteration progress by day. Is there a builtin way in Rally to get the number of user stories that are in the "Accepted" state for a specific date, and the number of points (or do I have to get all user stories and parse their revision histories)?
There is IterationCumulativeFlowData object in WS API, which is populated at midnight of the Workspace Timezone when the Data Collection runs on workdays specified in the Workspace Setup screen.
Data is stored for each day of the Iteration and a corresponding state. There is CumulativeFlowData object for Day 1 of the Iteration for everything in a Defined state, Day 1 of Release for everything in an In-Progress state, etc.
The CumulativeFlowData object also stores CardEstimateTotal which is the sum of the estimates of cards in every state.
Here is a example of an app written with rally-node that returns iteration data for specific state (Accepted) as of the last day of the iteration.
In this examle the CreationDate of the last result is '2013-08-27T06:00:00.000Z, while the EndDate of the iteration in question was 2013-08-27 11:59:59 PM America/Denver (which is 2013-08-28T05:59:59.000Z), so I had to manipulate a date in order to make this query condition return the data for the last day of the iteration:
query = query.and('CreationDate', '>', endDateMinusOneDay);
Here is the full js file of the example:
var rally = require('rally'),
queryUtils = rally.util.query,
restApi = rally({
user: 'user#co.com',
pass: 'secret',
apiVersion: 'v2.0',
server: 'https://rally1.rallydev.com',
requestOptions: {
headers: {
'X-RallyIntegrationName': 'My cool node.js program',
'X-RallyIntegrationVendor': 'My company',
'X-RallyIntegrationVersion': '1.0'
},
}
});
function findIteration() {
return restApi.query({
type: 'Iteration',
start: 1,
pageSize: 2,
limit: 10,
fetch: ['ObjectID', 'EndDate'],
scope: {
project: '/project/12352608219',
up: false,
down: false
},
query: queryUtils.where('Name', '=', 'i777')
});
}
function queryIterationData(result) {
var endDate = result.Results[0].EndDate,
oid = result.Results[0].ObjectID;
console.log('endDate',endDate);
var date1 = new Date(endDate);
var ms = date1.getTime() - 86400000; //86400000 is the number of milliseconds in a day
var date2 = new Date(ms);
var endDateMinusOneDay = date2.toISOString();
console.log('date2 ISO', date2.toISOString());
var query = queryUtils.where('IterationObjectID', '=',oid );
query = query.and('CardState', '=', 'Accepted');
query = query.and('CreationDate', '>', endDateMinusOneDay);
return restApi.query({
type: 'IterationCumulativeFlowData',
fetch: ['CardCount', 'CardEstimateTotal', 'CardState', 'CardState', 'CreationDate'],
query: query,
});
}
function onSuccess(result) {
console.log('Success!', result);
}
function onError(errors) {
console.log('Failure!', errors);
}
findIteration()
.then(queryIterationData)
.then(onSuccess)
.fail(onError);
It returns: