Ok folks I have bombed around for a few days trying to find a good solution for this one.
What I have is two possible address formats.
28 Main St Somecity, NY 12345-6789
or
Main St Somecity, Ny 12345-6789
What I need to do Is split both strings down into an array structured as such
address[0] = HousNumber
address[1] = Street
address[2] = City
address[3] = State
address[4] = ZipCode
My major problem is how to account for the lack of a house number. with out having the whole array shift the data up one.
address[0] = Street
address[1] = City
address[2] = State
address[3] = ZipCode
[Edit]
For those that are wondering this is what i am doing atm . (cleaner version)
place = response.Placemark[0];
point = new GLatLng(place.Point.coordinates[1],place.Point.coordinates[0]);
FCmap.setCenter(point,12);
var a = place.address.split(',');
var e = a[2].split(" ");
var x = a[0].split(" ");
var hn = x.filter(function(item,index){
return index == 0;
});
var st = x.filter(function(item,index){
return index != 0;
});
var street = '';
st.each(function(item,index){street += item + ' ';});
results[0] = new Hash({
FullAddie: place.address,
HouseNum: hn[0],
Dir: '',
Street: street,
City: a[1],
State: e[1],
ZipCode: e[2],
GPoint: new GMarker(point),
Lat: place.Point.coordinates[1],
Lng: place.Point.coordinates[0]
});
// End Address Splitting
Reverse the string, do the split and then reverse each item.
Update: From the snippet you posted, it seems to me that you get the address from a Google GClientGeocoder Placemark. If that is correct, why are you getting the unstructured address (Placemark.address) instead of the structured one (Placemark.AddressDetails)? This would make your life easier, as you would have to try and parse only the ThoroughfareName, which is the street level part of the address, instead of having to parse everything else as well.
function get_address (addr_str) {
var m = /^(\d*)\s*([-\s\w.]+\s(?:St|Rd|Ave)\.?)\s+([-\s\w\.]+),\s*(\w+)\s+([-\d]+)$/i.exec(s);
var retval = m.slice(1);
if (!retval[0]) retval = retval.slice(1);
return retval;
}
Assume all streets ends with St, Rd or Ave.
var address = /[0-9]/.match(string.charAt(0))
? string.split(" ") : [ " "
].concat(string.split(" "));
This is not particularly robust, but it accounts for the two enumerated cases and is concise at only one line.
I've got a similar problem I'm trying to solve. It seems that if you look for the first space to the right of the house number, you can separate the house number from the street name.
Here in Boston you can have a house number that includes a letter! In addition, I've seen house numbers that include "1/2". Luckily, the 1/2 is preceded by a hyphen, so there aren't any embedded spaces in the house number. I don't know if that's a standard or if I'm just getting lucky.
Related
I need some assistance figuring out how to sum a column of dynamic totals that could be a positive or negative dollar amount, or an indication of stock shares.
I have a tab-delimited text file of donor contributions for that I am matching up against a CSV file of other related customer data that I am using to create a statement letter which will show a "donation history" of a particular donor. Each donor has a different amount of donations, and to complicate things, the column of data for a particular donation record could show either "$1,000.00" or "($1,000.00)" or "2 Shares APPL". The number with the parentheticals is of course, representing a negative number.
At the end of this column, I need to show a string that will read either "Total: $1,000.00," or if any of the donation history contains a donation record that included shares of stock the returned string will simply read, "$1,000.00 & Stock."
I have been racking my brain trying to come up with the JS rule that can achieve this. I have the JS rule that is generating the donation history correctly, but summing the donation amount column is causing me to go crazy...
Here is the JS for generating my donation history list in the letter (this seems to be working fine):
var contributionList = new ExternalDataFileEx("/~wip/248839 Frontiers/Master Data/Double Data proof.txt", "\t");
var donor_id = Field("Supporter");
var lb = "<br>\n";
var matches = new Array();
for (var i = 0; i <= contributionList.recordCount; i++) {
var idVariable = contributionList.GetFieldValue(i, "Supporter");
var dateVariable = contributionList.GetFieldValue(i, "Donation Date");
var ministryVariable = contributionList.GetFieldValue(i, "Ministry Designation");
var giftVariable = contributionList.GetFieldValue(i, "Donation Amount");
var tsSettings = "<p tabstops=19550,Right,,;29600,Left,,;>";
var ts = "<t>";
if (donor_id == idVariable)
matches.push(tsSettings + dateVariable + ts + giftVariable + ts + ministryVariable);
}
//return matches;
return matches.join(lb);
Now here is the JS code that is not working just fine. I am trying to tally the donation amount column, it only returns "Total: $0.00 & Stock" every time (I have tried to explain my thought process via comments):
var contributionList = new ExternalDataFileEx("/~wip/248839 Frontiers/Master Data/Double Data proof.txt", "\t");
var donor_id = Field("Supporter");
for (var i = 0; i <= contributionList.recordCount; i++) {
var idVariable = contributionList.GetFieldValue(i, "Supporter");
var giftVariable = contributionList.GetFieldValue(i, "Donation Amount");
var sum = 0;
var shares = 0;
var tsSettings = "<p tabstops=19550,Right,,;29600,Left,,;>";
var ts = "<t>";
var totalStr = "Total ";
var stockStr = " & Stock";
var totalFormatted = FormatNumber("$#,###.00", Math.max(0, StringToNumber(sum)));
// Match data from linked file to current Supporter
if (donor_id == idVariable) {
// Look at current record and see if it contains the word "Share(s)"
// or not and act accordingly
if (giftVariable.match(/(^|\W)share($|\W)/i) || giftVariable.match(/(^|\W)shares($|\W)/i)) {
// Turn switch "on" if donation amount is a share or shares so
// we can have the " & Stock" appended to our string.
shares = 1;
// Because this donation is/are shares, we must "zero" this
// amount to make the math work when we sum everything up...
giftVariable = 0;
// This is where we are keeping our running total...
sum += giftVariable[i];
} else {
// This record was not a donation of share(s) so we now have to
// determine whether we are dealing with postive or negative numbers
// and then strip out all of the non-number characters, remove and
// replace the () whis just a "-," leaving us with a number we can
// work with...
// If number has parenthesis, then deal with it...
if (giftVariable.indexOf("(")) {
// Strip out all the ()$, characters...
giftVariable = giftVariable.replace(/[()$,]/g,"")
// Append the minus sign to the number...
giftVariable = "-" + giftVariable;
sum += giftVariable[i];
} else {
giftVariable = giftVariable.replace(/[$,]/g,"");
sum += giftVariable[i];
}
}
}
}
// Return Total...
if (shares == 1) {
return tsSettings + totalStr + ts + totalFormatted + stockStr;
} else {
return tsSettings + totalStr + ts + totalFormatted;
}
Any assistance would be greatly appreciated!
The problem (and code) needs to be broken into smaller, atomic steps. From your description it sounds like you should:
load a text file into memory
for each line in the file
extract: {
donor_id
charity
gift
and store the results in a contributions dictionary
for each item in the contributions dictionary
transform gift string into {
dollarAmount: float with a default of 0.0
stock: name with a default of ""
}
create an empty dictionary called totals
each item will have the shape {
id
dollarAmount as a float
stocks an an array
}
for each item in the contributions dictionary
lookup the id in the totals dictionary
if it exists
totals[id].dolarAmount += item.dollarAmount
totals[id].stocks.push(item.stock)
otherwise
totals[id].dollarAmount = item.dollarAmount
totals[id].stocks = [item.stock]
normalize your charities
for each item in totals dictionary
remove any empty strings from item.charities
create your report
for each item in totals dictionary
write`${item.id} donated `${item.dollarAmont}` ${item.stocks.length > 1 ? 'and stock' : ''
I believe you are trying to do too many things at once. Instead, the goal should be to normalize your data before you attempt to perform any calculations or aggrgrations, then normalize your aggregrations before writing your summaries or reports.
I would also stay away from using any direct string manipulation. You should have a dedicated function whose only purpose is to take a string like "($20.34) and 1 share of APPL" and return either 20.34, -20.34, or 0.0. And a different function whose only purpose is to take the same string and return either true or false is stock was present.
I am writing a Google Apps script to create a calendar event based on automated emails I receive for jobs. I am using regex expressions to extract information that I need to populate the event in Google Calendar. So far, I have everything functioning as expected except for one function, getEndTime(), which should find the end time of the job, but presently returns null any time it's called. All of my other functions using exec() work fine.
I have read many other questions regarding exec() returning null and have fixed common issues, such as removing the 'g' tag and resetting the lastIndex to 0 before calling exec(). I have also checked my regex expression using regex101.com with the Javascript option, which shows the match that I expect for my text.
My regex expression that works on regex101, but not in my code is:
/(Substitute\s+Report\s+Times:\s+[0-9_ ]*:[0-9_ ]*\s+[A-Z_ ]*\s+-\s+)([0-9_ ]*:[0-9_ ]*\s+(AM|PM))(\r|\n)/
My code is:
function findJobs() {
//Searches Gmail for substitute jobs and creates an event on the calendar
//Gets emails with 'NewJobs' label
var label = GmailApp.getUserLabelByName("NewJobs");
var threads = label.getThreads();
for (var i = 0; i < threads.length; i++){
var messages = threads[i].getMessages();
Logger.log("Thread " + i);
for (var j = 0; j < messages.length; j++) {
Logger.log("Message " + j);
//gets email body in plain text
var body = messages[j].getPlainBody();
Logger.log("Getting body..." + j);
//gets school name
var school = getSchool(body);
Logger.log(school);
//gets start time
var starttime = getStartTime(body);
Logger.log(starttime);
//gets end time
var endtime = getEndTime(body);
Logger.log(endtime);
//gets teacher name
var teacher = getTeacher(body);
Logger.log(teacher);
//gets school address
var address = getLocation(body);
Logger.log(address);
//gets date
var startdate = getDate(body);
Logger.log(startdate);
CalendarApp.getDefaultCalendar().createEvent("Subbing - " + school, new Date(startdate + " " + starttime), new Date(startdate + " " + endtime), {location: address, description: teacher});
//threads[j].removeLabel(label);
}
}
Logger.log("--Done--");
}
function getSchool(text){
//Gets the school name from an assignment email
//Regular expression for school name
var regex = /(School\s+:\s+)([a-zA-Z0-9_ ]*)(\r|\n)/;
regex.lastIndex = 0;
var match = regex.exec(text)[2];
return match;
}
function getDate(text){
//Gets the start date from an assignment email
//Regular expression for start date
var regex = /(Date:\s+)([0-9_ ]*\/[0-9_ ]*\/[0-9_ ]*)(\r|\n)/;
regex.lastIndex = 0;
var match = regex.exec(text)[2];
return match;
}
function getStartTime(text){
//Gets the start time from an assignment email
//Regular expression for start time
var regex = /(Substitute\s+Report\s+Times:\s+)([0-9_ ]*:[0-9_ ]*\s+(AM|PM))/;
regex.lastIndex = 0;
var match = regex.exec(text)[2];
return match;
}
function getEndTime(text){
//Gets the end time from an assignment email
//Regular expression for end time
var regex = /(Substitute\s+Report\s+Times:\s+[0-9_ ]*:[0-9_ ]*\s+[A-Z_ ]*\s+-\s+)([0-9_ ]*:[0-9_ ]*\s+(AM|PM))(\r|\n)/;
regex.lastIndex = 0;
Logger.log("End Time reset index...");
var match = regex.exec(text)[2];
Logger.log("End Time exec...");
return match;
}
function getTeacher(text){
//Gets the teacher name from an assignment email
//Regular expression for teacher name
var regex = /(Teacher\s+:\s+)([a-zA-Z0-9_ ]*,[a-zA-Z0-9_ ]*)(\r|\n)/;
regex.lastIndex = 0;
var match = regex.exec(text)[2];
return match;
}
function getLocation(text){
//Gets the location from an assignment email
//Regular expression for location
var regex = /(Address:\s+)(.*)(\r|\n)/;
regex.lastIndex = 0;
var match = regex.exec(text)[2];
return match;
}
Here is an typical email I receive:
You have been assigned as a substitute for a job starting on 9/21/2017.
The following are the details of the job:
*************
Job Summary
*************
Starting On : 9/21/2017
School : School Site
Title : Pre School Teacher
Teacher : Name, Teacher
Substitute : Name, Substitute
Confirmation # : 123456
**********
Job Days
**********
School
---------------------------------------
School Site
Date: 9/21/2017
Employee Times: 8:00 AM - 3:30 PM
Substitute Report Times: 8:00 AM - 3:30 PM
***********************************
School Contact Information
***********************************
School Site
-----------------------------------------------------------
Address: 123 Main Ave Anytown , USA 555555
Phone: 5555555555
-----------------------------------------------------------
**********************
Special Instructions
**********************
Please do not reply to this system generated message. If you need help or have additional questions, please send an email to abc#abc.com
Thank you for using the substitute assignment system. Powered by Aesop
The pattern you're using seems overly complicated. I can't say for sure what's causing it to fail, but my guess would be the (\r|\n) at the end (note that you can just type [\r\n] instead if you really want to do this).
Give this pattern a try:
Substitute Report Times:.+ - (\d{1,2}:\d{1,2} [AP]M)
This assumes that the end time is always preceded by a hyphen and a space, which looks to be the case from the sample text you provided.
I'd like to have on an ecommerce product page the simple "in stock" or "out of stock" message, but the ERP solution that I've to use only permit to retrive from its db the number of items currentrly in stock.
I can only use JS, so I'm thinking to a small function that will search for the string I have, extract only the number, do an if/else in order to replace a trigger id with the proper content.
here the content of the function:
var strAval = strAval.search (/\<div id\=\"\#avail\" class\=\"hidden\"\>/ + /\d+/ + /<\/div>/);
var strAval2 = strAval.substring (32,-6);
if (strAval2 > 0) {
var str = document.getElementById("#in-stock").innerHTML;
var res = str.replace("#in-stock","IN STOCK");
document.getElementById("#in-stock").innerHTML = res;
}
else {
var str = document.getElementById("#in-stock").innerHTML;
var res = str.replace("#in-stock","OUT OF STOCK");
document.getElementById("#in-stock").innerHTML = res;
}
the string that I've into the html is:
<div id="#avail" class="hidden">329</div>
where "329" is an example - this number is variable from 1 to 4 digits [ 0 - 12 - 329 - 2654 ]
There's something wrong, but I don't know what.
I'm learning JS, so I'm really new to it.
Thanks to all.
I've got a string with the following format:
City, State ZIP
I'd like to get City and State from this string.
How can I do that with JavaScript? edit: note that he doesn't mention he already has the zip code when he gets here, if that helps you in your solution ~~ drachenstern
var address = "San Francisco, CA 94129";
function parseAddress(address) {
// Make sure the address is a string.
if (typeof address !== "string") throw "Address is not a string.";
// Trim the address.
address = address.trim();
// Make an object to contain the data.
var returned = {};
// Find the comma.
var comma = address.indexOf(',');
// Pull out the city.
returned.city = address.slice(0, comma);
// Get everything after the city.
var after = address.substring(comma + 2); // The string after the comma, +2 so that we skip the comma and the space.
// Find the space.
var space = after.lastIndexOf(' ');
// Pull out the state.
returned.state = after.slice(0, space);
// Pull out the zip code.
returned.zip = after.substring(space + 1);
// Return the data.
return returned;
}
address = parseAddress(address);
This is probably better then using regular expressions and String.split(), as it takes into account that the state and city may have spaces.
EDIT: Bug fix: It only included the first word of multi-word state names.
And here's a minified version. :D
function parseAddress(a) {if(typeof a!=="string") throw "Address is not a string.";a=a.trim();var r={},c=a.indexOf(',');r.city=a.slice(0,c);var f=a.substring(c+2),s=f.lastIndexOf(' ');r.state=f.slice(0,s);r.zip=f.substring(s+1);return r;}
There are many ways to do this. Here's a very naive one:
var parts = "City, State ZIP".split(/\s+/); // split on whitespace
var city = parts[0].slice(0, parts[0].length - 1); // remove trailing comma
var state = parts[1];
var zip = parts[2];
Here's one that accounts for the presence of spaces in either the city or state or both:
var parts = "san fran bay, new mex state 666666".split(/\s+|,/),
partition = parts.indexOf(""),
city = parts.slice(0, partition).join(" "),
state = parts.slice(partition + 1, -1).join(" "),
zip = parts.pop();
This last one only works if you're lucky enough to be in an environment that supports destructuring assignment:
var city, statezip, state, zip, parts;
[city, statezip] = "Spaced City, New Mexico ZIP".split(/,\s*/);
parts = statezip.split(/\s+/);
zip = parts.pop();
state = parts.join(" ");
None of these perform any validation, of course.
Ok, since advising regex isn't good, here's my solution. It takes into account cities that have spaces in them, which the other responses here don't seem to do:
var str = "New York, NY 20101";
var cityAndRest = str.split(',');
var city = cityAndRest[0];
var stateAndZip = cityAndRest[1].trim().split(' ');
var state = stateAndZip[0];
var zip = stateAndZip[1];
First assumption: American addresses only.
First find out if the last 5 or the last 10 characters are numeric. A simpler test is to see if the last character is numeric. If so, it's probably got the zip code included. Then a simple test to see if the last 10 contains a space (city #####) or if the last ten include a dash (12345-6789) to figure out if it's a 5 or 5+4 zip. We'll test for a hyphen and no space. (city-du-lac 12345 captures -lac 12345)
Next, all addresses split the city and state by a comma, so we want the last comma. Find the index of the last comma, and split there. I don't know of a city that uses commas in it's name, and I'm sure not gonna let my parser burst on an unknown if I can help it. I do ignore the fact that Washington DC could also be Washington, DC. I figure edge cases are for libraries, not one off scripts.
Lastly, trim everything that remains to remove trailing or leading spaces.
function IsNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
var addr = 'New York City, New York 10101';
//var addr = 'San Bernadino, CA 11111';
function getCityStateZip(addr){
var city; var state;var zip;
city = ''; state = ''; zip = '';
var addrLen = addr.length;
if ( IsNumeric( addr.substring(addrLen - 1) ) ) {
//contains a zipcode - just a sanity check
//get last 10 characters for testing easily
var lastTen = addr.substring( addrLen - 10 );
if ( lastTen.indexOf('-') > 0 && ( lastTen.indexOf(' ') == -1 ) ) {
//found a hyphen and no space (matches our complex rule for zipcodes)
zip = lastTen;
} else {
zip = addr.substring( addrLen - 5 ); //assume a basic 5 zip code
}
}
var zipLen = zip.length;
addrLen = addrLen - zipLen - 1;
addr = addr.substring(0, addrLen ); //remove the chars we just moved into zip
var lastComma = addr.lastIndexOf(',');
if ( lastComma == -1 ) {
//you have a problem, how do you want to handle it?
}
city = addr.substring(0,lastComma); //skip the comma itself, yes?
state = addr.substring(lastComma + 2);
return { 'city':city,'state': state,'zip': zip};
}
getCityStateZip(addr)
IsNumeric js function can be found here Validate decimal numbers in JavaScript - IsNumeric()
Easy way but no validation:
var addrObj={};
parseAddress("Beverly Hills, CA 90210",addrObj);
function parseAddress(address, addrObj){
var arr=address.replace(","," ").split(" ");
addrObj.zip=arr.pop();
addrObj.state=arr.pop();
addrObj.city=arr.join(" ");
}
For this type of thing you might want to use JavaScripts RegEx functions.
Here's some info:
http://www.javascriptkit.com/javatutors/re.shtml
I want to try and detect the different parts of a person's name in Javascript, and cut them out so that I can pass them onto something else.
Names can appear in any format - for example:-
miss victoria m j laing
Miss Victoria C J Long
Bob Smith
Fred
Mr Davis
I want to try and write something simple, that'll do it's best to guess these and get them right 80% of the time or so (We have some extremely dodgy data)
I'm thinking of something along the lines of using a regex to check whether it has a prefix, then branch off to two places as to whether it has
/^(Dr|Mr|Mrs|Miss|Master|etc).? /
And then cutting the rest of it out using something like
/(\w+ )+(\w+)/
To match last name and other names. Though, I'm unsure on my greedy/ungreedy options here, and whether I can do soemthing to shortcut having all the different paths that might be available. Basically, hoping to find something simple, that does the job in a nice way.
It's also got to be written in Javascript, due to the limitations of the ETL tool I'm using.
Why not split() and just check the resulting parts:
// Split on each space character
var name = "Miss Victoria C J Long".split(" ");
// Check the first part for a title/prefix
if (/^(?:Dr|Mr|Mrs|Miss|Master|etc)\.?$/.test(name[0])) {
name.shift();
}
// Now you can access each part of the name as an array
console.log(name);
//-> Victoria,C,J,Long
Working Demo: http://jsfiddle.net/AndyE/p9ra4/
Of course, this won't work around those other issues people have mentioned in the comments, but you'd struggle on those issues even more with a single regex.
var title = '';
var first_name = '';
var last_name = '';
var has_title = false;
if (name != null)
{
var new_name = name.split(" ");
// Check the first part for a title/prefix
if (/^(?:Dr|Mr|Mrs|Miss|Master)\.?$/i.test(new_name[0]))
{
title = new_name.shift();
has_title = true;
}
if (new_name.length > 1)
{
last_name = new_name.pop();
first_name = new_name.join(" ");
}
else if(has_title)
{
last_name = new_name.pop();
}
else
{
first_name = new_name.pop();
}
}
Adapted from Accepted Answer :)