google sheets - replace the second letter in a cell - google-apps-script

Is there a way I can replace the 2nd character in a cell with an asterisk(*)?
Something like this:
var name = publicWinners.getRange(i, 1);
name.setValue( → 2nd character = "*" ← );
I want to create a list of winners in a contest that can be posted publicly with the winners' personal information partially hidden.
Any help would be greatly appreciated. Thank you!

Use String.replace :
/*<ignore>*/console.config({maximize:true,timeStamps:false,autoScroll:false});/*</ignore>*/
const str = "John Doe";
const output = str.replace(/(.{1})./,"$1*");
console.info(output);
<!-- https://meta.stackoverflow.com/a/375985/ --> <script src="https://gh-canon.github.io/stack-snippet-console/console.min.js"></script>
. - Any character
(.{1}) - Any character repeated 1 time. {1} may be removed, but used to quantify previous characters, if you want to replace the third or fourth character. Capture group$1
. - Final character to replace with *

You can use the split() and join() methods to manipulate the string.
/**
* Replace a character of a string with a new character at the specified position.
* #param {string} word - The word to modify.
* #param {number} index - The character index to modify. Starts from zero.
* #param {string} newChar - The new character that will overwrite the existing character.
* #returns {string}
*/
function replaceCharAt(word, index, newChar) {
return word.split('').map(function(char, charIndex) {
if (charIndex == index) {
return newChar;
}
return char;
}).join('');
}
A simple test you can run:
var originals = [
'Sangmyeong',
'Jaedong',
'Sangjin',
'Gueho'
];
originals.forEach(original => console.log(replaceCharAt(original, 1, '*'))); // [S*ngmyeong, J*edong, S*ngjin, G*eho]
function replaceCharAt(word, index, newChar) {
return word.split('').map(function(char, charIndex) {
if (charIndex == index) {
return newChar;
}
return char;
}).join('');
}

Related

Converting cell coordinates without using a column number to column letter method?

I'm trying to figure out what my options are here when I need to use a column number in a formula, and if I really need to write a column number to column letter method to accomplish what I'm trying to do.
See this method I have here:
createFormulas(lookupField, lookupColumns) {
// Iterate through the lookupColumn array
lookupColumns.forEach(value => {
let columnNumber = this.getColumn(this.headers, value);
let range = this.sheet.getRange(2, columnNumber, this.lastRow - 1, 1);
// range.setFormula('=$A2');
range.setFormula('=' + columnNumber + '2' ); // doesn't work obviously
})
}
I'm trying to add formulas in a column based on the column.
this.getColumn() returns the column number based on the column name being passed in.
let range sets the range I want to set the formula in
range.setFormula('=$A2') pastes this formula into range and updates the reference accordingly (i.e., $A3, $A4, etc.). This isn't the formula I ultimately want to use, just a simplified example.
I need to set the column in the reference dynamically, however.
What I have obviously won't work: range.setFormula('=' + columnNumber + '2' );. That would just result in something like 72 where 7 is the column number.
I know I can write a method that will convert the column number into a letter. I'm just surprised there isn't a built in method for doing that or some other native way of accomplishing this.
For example, in Excel VBA I think you can do something like "=" & Cells(2, columnNumber).Address or something like that (been a while, I could be wrong), which should equate to =A2, =A3, =A4, etc. in the range.
So before writing this column number to letter method, I just wanted to check: is that the only way to accomplish what I'm after or is there a native way of handling this that I'm just not seeing?
Actually, was able to do this using .getA1Notation().
Refactored to the following and it works as expected:
createFormulas(lookupField, lookupColumns) {
// Iterate through the lookupColumn array
lookupColumns.forEach(value => {
let columnNumber = this.getColumn(this.headers, value);
let formulaRange = this.sheet.getRange(2, columnNumber, this.lastRow - 1, 1);
let referenceRange = this.sheet.getRange(2, this.idColumn, this.lastRow - 1, 1);
formulaRange.setFormula("=" + referenceRange.getCell(1, 1).getA1Notation());
})
}
Column To Letters
I followed Yuri's path to the numbers to letter functions and I'm a bit baffled that we have forgotten that there are 26 letters in the alphabet and so after looking at the various functions at that reference none of them seem to have worked for me. So here's my replacement:
function colToletters(num) {
let a = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (num < 27) return a[num % a.length];
if (num > 26) {
num--;
let letters = '';
while (num >= 0) {
letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[num % 26] + letters;
num = Math.floor(num / 26) - 1;
}
return letters;
}
}
This will calculate the column letters for 1 to 1000 and I check all the way to 703 where the letters go to AAA and they look good all the way.
Just in case. Based on https://stackoverflow.com/a/64456745/14265469
function numberToLetters(num) {
// num--; // if you need 1 --> A, 2 --> B, 26 --> Z
let letters = '';
while (num >= 0) {
letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[num % 26] + letters;
num = (num - num % 26) / 26 - 1;
}
return letters;
}
console.log(numberToLetters(0)); // --> A
console.log(numberToLetters(25)); // --> Z
console.log(numberToLetters(26)); // --> AA

Issue trying to convert HTML to Ajax

I got this in the video script that I use for my website (Videos Page Layout):
<div class="video-views pull-left">
{$videos[i].viewnumber|kilo} {if $videos[i].viewnumber == '1'}{t c='global.view'}{else}{t c='global.views'}{/if}
</div>
Related videos section uses Ajax for videos page layout generated with the "show more" button.
My problem is: I don't know how to convert the "kilo" function in Ajax {$videos[i].viewnumber|kilo}. I attempted a few things but with no result.
$code[] = '<div class="video-views pull-left">';
$views = ($video['viewnumber'] == '1') ? $lang['global.view'] : $lang['global.views'];
$code[] = $video['viewnumber']. ' '.$views;
$code[] = '</div>';
Kilo is not a modifier that is included in the Smarty distribution, but if you have access to the code on your site, you can extract the code from the plugin file, it is most likely in smarty/libs/plugins/modifier.kilo.php.
It looks like you're working in PHP to construct a response to an AJAX request, so you can just pull that code out and re-use it.
If you do not have access to the modifier file, you can just recreate the formatting on your own. Judging by the context, it's something simple like:
<?php
/**
* Format integers into human readable strings indicating number of thousands
* Example: 1200 -> 1.2K
* #param int $value
* #return string
*/
function kilo(int $value): string
{
// If the value is less than 1000, just return it
if($value < 1000)
{
return $value;
}
/*
* If the value is evenly divisible by 1000, we want to show a whole number,
* otherwise format it as a single precision float. Add "K" string literal
* to indicate thousands
*/
$formatString = ($value % 1000) ? '%.1fK':'%dK';
// Divide value by 1000
$value /= 1000;
// Return the formatted string
return sprintf($formatString, $value);
}
// Define some test data and echo the formatted values
$testViewCounts = [1, 123, 1230, 12300, 123000];
foreach($testViewCounts as $views)
{
// If only one view, do not pluralize
$label = ($views == 1) ? 'view':'views';
echo kilo($views).' '.$label.PHP_EOL;
}

How to create an array (or one long string) of hyperlinks in google docs using google script

Or how hyperlink of google docs looks like in raw format.
I tried to do the next thing:
var links;
var nameArr = ["1", "2", "3", "4", "5", "6"];
var tempArr= ["11", "12", "13", "14", "15", "16"];
for (i = 0; i < nameArr.length; i++) {
nameArr[i].setUrlLink("https://en.wikipedia.org/wiki/" + tempArr[i] + "/detection"
links = links + ", "+ nameArr[i];
}
I get an error, as i can't use setLinkUrl on string, only on text object - didn't find a way to cast string into text.
Although, if i paste it "as it", the "http..." shows as a regular string - not a link.
I Want to get something like this:
1, 2, 3 ...... and paste it into google docs document.
Links are "rich" features of the associated element (usually Text). So to add a link to generic text, first you must get the associated Text element, and then invoke setLinkUrl on it.
As with other rich format methods, appended elements inherit the formatting specification of the preceding sibling element. Thus, if you format the last element of a parent, the next element appended to the parent will likely also be formatted in that manner. I explicitly specify a nullstring URL for the separator text to avoid the link extending beyond the actual display text. (This means that if you programmatically append to the Paragraph after calling this function, that appended text will have the same URL as the last display text from your array.)
This simple function takes a Paragraph as input, along with the array of display text and the URIs, and adds them to the end.
/**
* Create links at the end of the given paragraph with the given text and the given urls.
* #param {GoogleAppsScript.Document.Paragraph} pg The paragraph to hold the link array
* #param {string[]} values The display text associated with the given links
* #param {string[]} links The URI for the given link text
* #param {string} [separator] text that should separate the given links. Default is comma + space, `", "`
* #returns {GoogleAppsScript.Document.Paragraph} the input paragraph, for chaining
*/
function appendLinkArray(pg, values, links, separator) {
if (!pg || !values || !links)
return;
if (!values.length || !links.length || values.length > links.length)
throw new Error("Bad input arguments");
if (separator === undefined)
separator = ", ";
// Add a space before the link array if there isn't one at the end of any existing text.
if (pg.getText() && (!pg.getText().match(/ $/) || !pg.getText().match(/ $/).length))
pg.appendText(" ").setLinkUrl("");
// Add each link display text as a new `Text` object, and set its link url.
links.forEach(function (url, i) {
var text = values[i] || url;
pg.appendText(text)
.setLinkUrl(0, text.length - 1, url);
if (separator && i < links.length - 1)
pg.appendText(separator).setLinkUrl("");
});
return pg;
}

How to extract chart tool tip values from a web page using Google Script?

Looking at this chart:
I would like to extract the volume information at a given point in time (the volume is shown in the chart title, when the cursor is moved across the volume chart) – e.g:
[Fri. Jul 26, 2013 13:25:00] Volume 118
which is stored on the chart as:
< area shape="rect" coords="541,463,542,471" onmousemove="showStudyTooltip(event, 'B', '[Fri. Jul 26, 2013 13:25:00]', 'Volume', '118.0000000')" >
How can this been done?
Note that it is not only 'Volume' that I want to extract, but also different indicator values that can be added beneath the volume chart.
PS Among others I have search for inspiration the following places (but been unable to come up with a solution):
Does Google Apps Script have something like getElementById?
http://www.distilled.net/blog/distilled/guide-to-google-docs-importxml/
The data you're looking for isn't really well-formed for automated extraction, but we can work with it. The getElementByVal() function from this answer can be refactored to create getElementsByVal(), which will return an array of all matching document elements, giving us something to do further searching in.
/**
* Traverse the given XmlElement, and return an array of matches.
* Note: 'class' is stripped during parsing and cannot be used for
* searching, I don't know why.
* <pre>
* Example: getElementsByVal( body, 'input', 'value', 'Go' ); will find
*
* <input type="submit" name="btn" value="Go" id="btn" class="submit buttonGradient" />
* </pre>
*
* #param {XmlElement} element XML document element to start search at.
* #param {String} id HTML <div> id to find.
*
* #return {[XmlElements]} All matching elements (in doc order).
*/
function getElementsByVal( element, elementType, attr, val ) {
var results = [];
// If the current element matches, remember it.
if (element[attr]
&& element[attr] == val
&& element.getName().getLocalName() == elementType) {
results.push( element );
}
// Check element's children
var elList = element.getElements();
var i = elList.length;
while (i--) {
// (Recursive) Check each child, in document order.
results = results.concat(getElementsByVal( elList[i], elementType, attr, val ));
}
// Return summary of matches
return results;
}
To make use of this new helper function, what if we create a function getIndicators() that accepts a String parameter containing the point in time we're interested in - Mon. Jul 29, 2013 07:40:00 for example? The matching text will be found in an area element that's got shape="rect", and we'll find it inside an attribute called onmousemove. Here's our function:
function getIndicators(timeString) {
var txt = UrlFetchApp.fetch("http://www.barchart.com/chart.php?sym=DXU13&t=BAR&size=M&v=2&g=1&p=I:5&d=L&qb=1&style=technical&template=").getContentText();
var doc = Xml.parse(txt,true);
var body = doc.html.body;
var indicators = "not found";
// Look for elements matching: < area shape="rect" ... >
var chartPoints = getElementsByVal(body, 'area', 'shape', 'rect');
// Search for the chartPoint with tooltip containing the time we care about
for (var i=0; i<chartPoints.length; i++) {
if (chartPoints[i].onmousemove.indexOf(timeString) > -1) {
// found our match
indicators = chartPoints[i].onmousemove;
}
}
return indicators
}
As it stands, it will return the entire text value assigned to onmousemove; the exercise of parsing that intelligently is left to you.
Here's a test function to help:
function test_getIndicators() {
Logger.log( getIndicators("Mon. Jul 29, 2013 07:40:00" ) );
}
When run, here's the log (today, anyway...):
[13-07-29 16:47:47:266 EDT] showOHLCTooltip(event, 'B', '[Mon. Jul 29, 2013 07:40:00]', 'DXU13', '81.8050000', '81.8300000', '81.8000000', '81.8200000')

ActionScript3 - add thousands separator to negative values

This question relates to an animated map template which we have developed at the UKs Office for National Statistics. It has been applied to many datasets and geographies many uses without problem. For example,
http://www.ons.gov.uk/ons/interactive/vp3-census-map/index.html
http://www.statistica.md/pageview.php?l=ro&idc=390&id=3807
The .fla calls on a supporting .as file (see below) to introduce a thousand separator (in the UK a comma, in Germany a full stop (period) defined elsewhwere.
However, the dataset I am currently mapping has large negative values, and it tutrns out that the ORIGINAL HELPER FUNCTION below does not like negative values with 3, 6, 9 or 12 (etc) digits.
-100 to -999 for instance are rendered NaN,100 to NaN,999.
This is because such values are recognised as being 4 digits long. They are being split, the comma introduced, and the -ve sign is misunderstood.
I reckon the approach must be to use absolute values, add in the comma and then (for the negative values) add the -ve sign back in afterwards. But so far, trials of the ADAPTED HELPER FUNCTION have produced only error. :-(
Can anyone tell me how to put the -ve sign back in , please?
Many thanks.
Bruce Mitchell
==================================================================================
//ORIGINAL HELPER FUNCTION: ACCEPTS A NUMBER AND RETURNS A STRING WITH THOUSANDS SEPARATOR ATTACHED IF NECESSARY
function addThouSep(num) {
/*
a. Acquire the number - 'myTrendValue' or 'myDataValue' - from function calcValues
b. Record it (still as a number) to data precision.
1. Turn dataORtrend into a string
2. See if there is a decimal in it.
3. If there isn't, just run the normal addThouSep.
4. If there is, run addThouSep just on the first bit of the string - then add the decimal back on again at the end.
*/
var myNum:Number = correctFPE(num); // Create number variable myNum and populate it with 'num'
// (myTrendvalue or myData Value from calcValues function) passed thru 'correctPFE'
var strNum:String = myNum+""; // Create string version of the dataORtrend number - so instead of 63, you get '63'
var myArray = strNum.split("."); // Create array representing elements of strNum, split by decimal point.
//trace(myArray.length); // How long is the array?
if (myArray.length==1) { // Integer, no decimal.
if (strNum.length < 4)//999 doesn't need a comma.
return strNum;
return addThouSep(strNum.slice(0, -3))+xmlData.thouSep+strNum.slice(-3);
}
else { // Float, with decimal
if (myArray[0].length < 4)//999 doesn't need a comma
return strNum;
return (addThouSep(myArray[0].slice(0, -3))+xmlData.thouSep+myArray[0].slice(-3)+"."+myArray[1]);
}
}
==================================================================================
//ADAPTED HELPER FUNCTION: ACCEPTS A NUMBER AND RETURNS A STRING WITH THOUSANDS SEPARATOR ATTACHED IF NECESSARY
function addThouSep(num) {
/*
a. Acquire the number - 'myTrendValue' or 'myDataValue' - from function calcValues
b. Record it (still as a number) to data precision.
1. Turn dataORtrend into a string
2. See if there is a decimal in it.
3. If there isn't, just run the normal addThouSep.
4. If there is, run addThouSep just on the first bit of the string - then add the decimal back on again at the end.
*/
var myNum:Number = correctFPE(num); // Create number variable myNum and populate it with 'num'
// (myTrendvalue or myData Value from calcValues function) passed thru 'correctPFE'
var myAbsNum:Number = Math.abs(myNum); // ABSOLUTE value of myNum
var strNum:String = myAbsNum+""; // Create string version of the dataORtrend number - so instead of 63, you get '63'
var myArray = strNum.split("."); // Create array representing elements of strNum, split by decimal point.
//trace(myArray.length); // How long is the array?
if (myNum <0){ // negatives
if (myArray.length==1) { // Integer, no decimal.
if (strNum.length < 4)//999 doesn't need a comma.
return strNum;
return addThouSep(strNum.slice(0, -3))+xmlData.thouSep+strNum.slice(-3);
}
else { // Float, with decimal
if (myArray[0].length < 4)//999 doesn't need a comma
return strNum;
return (addThouSep(myArray[0].slice(0, -3))+xmlData.thouSep+myArray[0].slice(-3)+"."+myArray[1]);
}
}
else // positive
if (myArray.length==1) { // Integer, no decimal.
if (strNum.length < 4)//999 doesn't need a comma.
return strNum;
return addThouSep(strNum.slice(0, -3))+xmlData.thouSep+strNum.slice(-3);
}
else { // Float, with decimal
if (myArray[0].length < 4)//999 doesn't need a comma
return strNum;
return (addThouSep(myArray[0].slice(0, -3))+xmlData.thouSep+myArray[0].slice(-3)+"."+myArray[1]);
}
}
==================================================================================
If you're adding commas often (or need to support numbers with decimals) then you may want a highly optimized utility function and go with straightforward string manipulation:
public static function commaify( input:Number ):String
{
var split:Array = input.toString().split( '.' ),
front:String = split[0],
back:String = ( split.length > 1 ) ? "." + split[1] : null,
pos:int = input < 0 ? 2 : 1,
commas:int = Math.floor( (front.length - pos) / 3 ),
i:int = 1;
for ( ; i <= commas; i++ )
{
pos = front.length - (3 * i + i - 1);
front = front.slice( 0, pos ) + "," + front.slice( pos );
}
if ( back )
return front + back;
else
return front;
}
While less elegant it's stable and performant — you can find a comparison suite at my answer of a similar question https://stackoverflow.com/a/13410560/934195
Why not use something simple like this function I've made?
function numberFormat(input:Number):String
{
var base:String = input.toString();
base = base.split("").reverse().join("");
base = base.replace(/\d{3}(?=\d)/g, "$&,");
return base.split("").reverse().join("");
}
Tests:
trace( numberFormat(-100) ); // -100
trace( numberFormat(5000) ); // 5,000
trace( numberFormat(-85600) ); // -85,600
Explanation:
Convert the input number to a string.
Reverse it.
Use .replace() to find all occurrences of three numbers followed by another number. We use $&, as the replacement, which basically means take all of those occurences and replace it with the value we found, plus a comma.
Reverse the string again and return it.
Did you try using the built in Number formatting options that support localized number values:
Localized Formatting with NumberFormatter