Regex replacement within a partial text element in google apps script - regex

Below is Google's example for emboldening a partial text element. This is used for selected text that is not an entire element (e.g. just a sentence fragment is selected). I need to replace the action of emboldening with that of performing a regex replacement. The replaceText() function does not accept integers to tell it where to start and end (unlike the setBold() function).
This is a very similar (unanswered) question, but I believe Google Scripts has changed some of the commands, so I thought it worth posting again.
The Google example is:
// Bold all selected text.
var selection = DocumentApp.getActiveDocument().getSelection();
if (selection) {
var elements = selection.getRangeElements();
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
// Only modify elements that can be edited as text; skip images and other non-text elements.
if (element.getElement().editAsText) {
var text = element.getElement().editAsText();
// Bold the selected part of the element, or the full element if it's completely selected.
if (element.isPartial()) {
text.setBold(element.getStartOffset(), element.getEndOffsetInclusive(), true);
} else {
text.setBold(true);
}
}
}
}

The regex implementation of replaceText method does not support lookarounds or capture groups, which makes it impossible to perform such partial replacement with it.
A workaround is to use a JavaScript replacement to prepare new substring, and then put it in place with replaceText. This preserves the formatting of text, even if, say, italicized part overlaps the selection where replacement happens. A potential drawback is that if the element contains a piece of text identical to the selection, replacement will happen there as well.
var text = element.getElement().editAsText();
if (element.isPartial()) {
var start = element.getStartOffset();
var finish = element.getEndOffsetInclusive() + 1;
var oldText = text.getText().slice(start, finish);
var newText = oldText.replace(/a/g, "b");
text.replaceText(oldText, newText);
}

Related

format TextField as you type numbers and grouping

I'm having trouble doing something that should be straightforward simple on a good and clean framework, but with SwiftUI is all a big problem.
I simply need a user to be able to type in a TextField, showing only the numPad, and have the numbers automatically grouped as in 3,000,000 as the user types. Also, if the user taps anywhere in the field to correct a mistaken number, the focus should go to the correct tapped place and the user should be able to erase, keep the focus on the same position, and type the new corrected number. Something as simple as that.
Well, there are multiple problems I'm experiencing:
If using a formatter, the formatted number will only display after the field loses focus (i.e. it will show as 3000000 and then after losing focus, 3,000,000). That shouldn't be a great deal of a problem, but if I focus the field again to add more numbers, it ends up typing something like 3,000,000123 and after losing focus it will not recognize anything after the last 0, so it erases 123. Also, the numPad doesn't have a comma to type it manually.
That's the following case:
#State var number: Int = 0
let numberFormatter = {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.generatesDecimalNumbers = true
numberFormatter.maximumFractionDigits = 0
numberFormatter.groupingSize = 3
return numberFormatter
}()
TextField("1000", value: $number, formatter: numberFormatter)
.keyboardType(.numberPad)
If using Combine, the moment a user tries to correct a digit in the middle of the number, the focus goes to the end of the field, screwing the user experience and making people angry.
For this, I'm using this code:
#State var text: String = ""
TextField("1000", text: $text)
.keyboardType: .numberPad)
.onReceive(Just(text)) { _ in formatNumber() }
func formatNumber() {
if (text.count == 0) {
return
}
let filtered = text.filter { "0123456789".contains($0) }
let groups: Int = filtered.count / 3
let remaining: Int = filtered.count % 3
var res: String = ""
if (remaining > 0) {
res += filtered.substring(with: 0 ..< remaining)
res += ","
}
for i in 0 ..< groups {
res += filtered.substring(with: (remaining + i*3) ..< (remaining + (i+1)*3))
res += ","
}
res.removeLast(1)
text = res
}
Is there any clean solution to something as basic as this?
I use normally the .onChange Modifier directly attached to the TextField, like
TextField("Value:", text: $depotActionWertString)
.onChange(of: depotActionWertString) { _ in
let filtered = depotActionWertString.filter {"-0123456789.".contains($0)}
depotActionWertString = filtered
} // .onChange
It is more compact for the checks of the user input and you may combine it with the .onRecive statement for formatting.
For the first case in simple Versions for displaying text I use often the combination Text with String(format:...., like
Text("\(String(format: "%.2f", self._model.wrappedValue.progress * 100)) %")
I think its more a style question as to be right ore wrong....
I don't know if this is the most optimal solution, but one possible solution using Combine without losing the cursor position is to check the length of the published value.
Since the cursor moves to the end of the text because you updated your textfield with the subscriber, you can solve this problem by preventing your subscriber from updating the published value when you edit your text in the middle. Since when you edit, you always start by deleting, you can then just check in your subscriber whether the length of new value is shorter than the length of your old value. If it is, then just return.
I tried the following implementation and it works:
.sink { value in
let filtered = value.filter { "0123456789".contains($0) }
//check is just an Int saving the length of the old value
if filtered.count > 3 && filtered.count >= self.check {
//withCommas is just a formatter
self.text = String(Int(filtered)!.withCommas())
}
self.check = filtered.count
}
Note there are also some problems with this implementation:
since it will not update when you delete, you may end up with something like this: 123,456,789 and the user deleted "456" to become 123,,789. It won't autocorrect itself. I haven't figured out a way to make the commas autocorrect while keeping the cursor in the same place either.
the above implementation force unwrapped optional, which might cause a crash.

Word count with highlighting (excluding the occurrence of words in other words) in google doc

I am writing a script for a Google document that counts words and highlights them. The script works, but not quite as it should. Parts of words should not be counted and highlighted. For example, I am looking for the word cop, if there is a word robocop - skip it.
I tried regular expression with the word "me", but seems it doesn't fit, as I need to go through the text, highlighting words along the way. But maybe I just don’t understand how to do it right.
function findWords2(keys) {
var body = doc.getBody();
var keysMap = {}; // object for keys with quantity
// For every word in keys:
for (var w = 0; w < keys.length; ++w) {
// Get the current word:
//var rx = /(.){1}me(.){1}/;
//var foundElement = rx.exec(doc.getBody().getText());
//var foundElement = body.findText(rx);
var foundElement = body.findText(keys[w]);
var count = 0;
while (foundElement != null) {
// Get the text object from the element
var foundText = foundElement.getElement().asText();
count++;
// Where in the Element is the found text?
var start = foundElement.getStartOffset();
var end = foundElement.getEndOffsetInclusive();
// Change the background color to yellow
foundText.setBackgroundColor(start, end, "#FCFC00");
// Find the next match
foundElement = body.findText(keys[w], foundElement);
}
keysMap[keys[w]] = count; // add current searched keyword to keysMap with quantity
}
return JSON.stringify(keysMap, null, 1);
}
So, if we call findWords('cop') in text "Robocop cop cop", we found and highlighted cop 3 times, instead of two. In theory, I just need to check the previous and subsequent characters of the found word, but how to do it?
You should use word boundary\b:
\bcop\b
Note that body.findText() receives regex as string. So, You should escape \:
body.findText("\\bcop\\b")
If you're searching plain string, (using regexp.exec),
/\bcop\b/g
References:
Body#findText
re2

In POSTMAN how do i get substring of response header item?

I am using postman to get response header value like below:
var data = postman.getResponseHeader("Location") . //value is "http://aaa/bbb" for example
I can print the value via console.log(data) easily.
However, what I really want is "bbb". So I need some substring() type of function. And apparently 'data' is not a javascript string type, because data.substring(10) for example always return null.
Does anyone what i need to do in this case?
If any postman API doc existing that explains this?
You can set an environment variable in postman. try something like
var data = JSON.parse(postman.getResponseHeader("Location"));
postman.setEnvironmentVariable("dataObj", data.href.substring(10));
You have the full flexibility of JavaScript at your fingertips here, so just split the String and use the part after the last /:
var data = pm.response.headers.get("Location").split("/").pop());
See W3 school's documentation of split and pop if you need more in depth examples of JavaScript internals.
Some initial thought - I needed a specific part of the "Location" header like the OP, but I had to also get a specific value from that specific part.
My header would look something like this
https://example.com?code_challenge_method=S256&redirect_uri=https://localhost:8080&response_type=code&state=vi8qPxcvv7I&nonce=uq95j99qBCGgJvrHjGoFtJiBoo
And I need the "state" value to pass on to the next request as a variable
var location_header = pm.response.headers.get("Location");
var attributes = location_header.split('&');
console.log(attributes);
var len = attributes.length;
var state_attribute_value = ""
var j = 0;
for (var i = 0; i < len; i++) {
attribute_key = attributes[i].split('=')[0];
if (attribute_key == "state") {
state_attribute_value = attributes[i].split('=')[1];
}
j = j + 1;
}
console.log(state_attribute_value);
pm.environment.set("state", state_attribute_value);
Might you get the point here, "split" is the choice to give you some array of values.
If the text you are splitting is always giving the same array length it should be easy to catch the correct number

How to Simplify C++ Boolean Comparisons

I'm trying to find a way to simplify the comparison cases of booleans. Currently, there are only three (as shown below), but I'm about to add a 4th option and this is getting very tedious.
bracketFirstIndex = message.indexOf('[');
mentionFirstIndex = message.indexOf('#');
urlFirstIndex = message.indexOf(urlStarter);
bool startsWithBracket = (bracketFirstIndex != -1);
bool startsWithAtSymbol = (mentionFirstIndex != -1);
bool startsWithUrl = (urlFirstIndex != -1);
if (!startsWithBracket)
{
if (!startsWithAtSymbol)
{
if (!startsWithUrl)
{
// No brackets, mentions, or urls. Send message as normal
cursor.insertText(message);
break;
}
else
{
// There's a URL, lets begin!
index = urlFirstIndex;
}
}
else
{
if (!startsWithUrl)
{
// There's an # symbol, lets begin!
index = mentionFirstIndex;
}
else
{
// There's both an # symbol and URL, pick the first one... lets begin!
index = std::min(urlFirstIndex, mentionFirstIndex);
}
}
}
else
{
if (!startsWithAtSymbol)
{
// There's a [, look down!
index = bracketFirstIndex;
}
else
{
// There's both a [ and #, pick the first one... look down!
index = std::min(bracketFirstIndex, mentionFirstIndex);
}
if (startsWithUrl)
{
// If there's a URL, pick the first one... then lets begin!
// Otherwise, just "lets begin!"
index = std::min(index, urlFirstIndex);
}
}
Is there a better/more simpler way to compare several boolean values, or am I stuck in this format and I should just attempt to squeeze in the 4th option in the appropriate locations?
Some type of text processing are fairly common, and for those, you should strongly consider using an existing library. For example, if the text you are processing is using the markdown syntax, consider using an existing library to parse the markdown into a structured format for you to interpret.
If this is completely custom parsing, then there are a few options:
For very simple text processing (like a single string expected to
be in one of a few formats or containing a piece of subtext in an expected format), use regular expressions. In C++, the RE2 library provides very powerful support for matching and extracting usign regexes.
For more complicated text processing, such as data spanning many lines or having a wide variety of content / syntax, consider using an existing lexer and parser generator. Flex and Bison are common tools (used together) to auto-generate logic for parsing text according to a grammar.
You can, by hand, as you are doing now, write your own parsing logic.
If you go with the latter approach, there are a few ways to simplify things:
Separate the "lexing" (breaking up the input into tokens) and "parsing" (interpreting the series of tokens) into separate phases.
Define a "Token" class and a corresponding hierarchy representing the types of symbols that can appear within your grammar (like RawText, Keyword, AtMention, etc.)
Create one or more enums representing the states that your parsing logic can be in.
Implement your lexing and parsing logic as a state machine that transforms the state given the current state and the next token or letter. Building up a map from (state, token type) to next_state or from (state, token type) to handler_function can help you to simplify the structure.
Since you are switching only on the starting letter, use cases:
enum State { Start1, Start2, Start3, Start4};
State state;
if (startswithbracket) {
state = Start1;
} else {
.
.
.
}
switch (state) {
case Start1:
dosomething;
break;
case Start2:
.
.
.
}
More information about switch syntax and use cases can be found here.

Fetch items from array using regex

I'm using action script and I have an array with more than 400.000 strings and now i'm using a loop and apply a regex to each item of the array to check if it's valid or not. In case it's valid, i put such item in a result array.
this process take too long, so it's a nuisance because all the process must executed many times.
I've been thinking about if there is any other way (faster) i could use for applying the regex to all items without using a loop.
Anyone could give me an idea?
EDIT
Here I attach the code used:
var list:Array;
var list_total:Array = new Array;
var pattern:String = '^['+some_letters+']{'+n+'}$';
var cleanRegExp:RegExp = new RegExp(pattern, 'gi');
for (var i:int=0; i<_words.length; i++) {
list = _words[i].match(cleanRegExp);
if (list != null)
for (var j:int=0; j < list.length; j++)
list_total.push(list[j]);
}
Thanks.
This is not a complete answer, but may help you optimize your code.
Try to do operations in your loop that are as efficient as possible. Time them using the global getTimer() function so you can compare which methods are the most efficient. When measuring/comparing, you may want to trigger your code many times, so that these differences are noticeable.
// before test
var startTime:Number = getTimer();
// do the expensive operation
var endTime:Number = getTimer();
trace("operation took ", endTime - startTime, " milliseconds.");
For example, one improvement is inside a for loop, is to not query the array for it's length each time:
for (var i:int = 0; i < myArray.length; i++)
Instead, store the length in a local variable outside of the array and use that:
var length:int = myArray.length;
for (var i:int = 0; i < length; i++)
The difference is subtle, but accessing the length from the local variable will be faster than getting it from the Array.
Another thing you can test is the regular expression itself. Try to come up with alternate expressions, or use alternate functions. I don't recall the specifics, but in one project we determined (in our case) that using the RegEx.test() method was the fastest way to do a comparison like this. It's likely that this may be as quick as String.match() -- but you won't know unless you measure these things.
Grant Skinner has some awesome resources available on his site. They are worth reading. This slide show/presentation on performance is worth looking at. Use the arrow keys to change slides.
Edit
Without hearing Grant's presentation, the initial slides may not seem that interesting. However, it does get very interesting (with concrete code examples) around slide #43: http://gskinner.com/talks/quick/#43
I do not think there is any good way to avoid using a loop.
The loop could be optimized further though.
Like someone already suggested read the array length to a var so the loop doesn't have to check length each iteration.
Instead of the nested loop use concat to join the list array to the lists_total. I'm not sure if this is actually faster. I guess it depends on how many matches the regexp gets.
Here is the modified code.
var list:Array;
var list_total:Array = new Array;
var pattern:String = '^['+some_letters+']{'+n+'}$';
var cleanRegExp:RegExp = new RegExp(pattern, 'gi');
var wordsLength:int = _words.length;
for (var i:int=0; i<wordsLength; i++) {
list = _words[i].match(cleanRegExp);
if (list != null)
lists_total = lists_total.concat(list);
}