Truncate values

That code works for me. Can we take a look at what’s happening on the webpage?

Unfortunally it is passw protected, I don’t know if this saved copy of the page can help… I’m using TamperMonkey or Autofill extension

891.7… seems working (truncated at second decimal place)
891.8… is truncated at first decimal place, instead of second

I don’t know what’s happening on the website, but the formatCode() function that you most recently posted is working really well.

See example code where it converts all values on a test page: https://jsfiddle.net/ws63d7kn/

Paul_Wilkins, if you add to JSfiddle’s HTML:

<p><input name='collocazione' value="891.888"></p>

the console returns 891, NOT 891.88, so not complying with this part of script:

groupRx: /891\.[7-8]/,
truncate: (code) => trunc(code, 2)

Just some thoughts on refactoring that long list. You could group the truncate methods based on the number of decimal places. Something like this.

Truncate code functions:

const trunc = (num, places = 0) => {
  if (places <= 0) {
    return String(Math.trunc(num.split(' ')[0]))
  }

  const regex = new RegExp(`\\d+\\.\\d{1,${places}}`)
  const match = num.match(regex)

  return (match !== null) ? match[0] : null
}

// For readability
const toInteger = trunc

const toOneDecimalPlace = (num) => trunc(num, 1)

const toTwoDecimalPlaces = (num) => trunc(num, 2)

const toThreeDecimalPlaces = (num) => trunc(num, 3)

Truncate group matches:

You can join the group regexes using the pipe | operator. Think of it like ‘or’.

const truncGroups = [
  // Truncate to Integer
  {
    groupRx: /\b80[1-7]\b|\b85[4-9]\b|8[7-8][0-8]\b/,
    truncate: toInteger
  },
  
  // Truncate to One decimal place
  {
    groupRx: /809\.9|\b81[0-9]\b|(8[2,4][0-9])[0-9. ]+$|\b83[0-8]\b|839\.7|85[0-3]\b/,
    truncate: toOneDecimalPlace
  },
  
  // Truncate to Two decimal places
  {
    groupRx: /839\.3[1,6]|889\.3[3-4]|891\.[7-8]/,
    truncate: toTwoDecimalPlaces
  },
  
  // Truncate to Three decimal places
  {
    groupRx: /839\.693/,
    truncate: toThreeDecimalPlaces
  },
  
  // Custom Operations
  
  // Match: Numbers starting with 820-849 ending in two letters
  // Replace with the first three digits and .9
  // e.g. 823.556 3 IE → 823.9
  {
   groupRx: /^(8[2,4][0-9]).*?[A-Z]{2}$/,
    truncate: (code) => code.substring(0, 3) + '.9'
  },

  // Match: Numbers starting with 860-869 + optional region letters
  // Truncate number to 1 decimal place, 
  // moving the letters to prefix the number
  {
    groupRx: /\b86[0-9]\b/, 
    truncate: (code) => {
      const [num, letters] = code.split(/ ([A-Z]{2})/);
      const truncNumber = toOneDecimalPlace(num)

      return (letters)
        ? `${letters} ${truncNumber}`
        : `${truncNumber}`
    }
  }
]

Format code function:

const formatCode = (code) => {
  for (const { groupRx, truncate } of truncGroups) {
    if (groupRx.test(code)) {
      return truncate(code)
    }
  }
  return code;
};

// Update value of <input name='collocazione'>
const collocaziones = document.querySelectorAll('[name="collocazione"]')

collocaziones.forEach(
  (collocazione) => collocazione.value = formatCode(collocazione.value)
)

The above does need some testing.

It is being picked up by the integer match /8[7-8][0-8]\b/, which is matching the 888 in 891.888

Let me figure it out :slight_smile:

BTW with the last code I posted it was easy to test

const toInteger = (num) => { console.log(num); return trunc(num) } // 891.888

I think this might have fixed it

/* truncate functions */

const trunc = (num, places = 0) => {
  if (places <= 0) {
    return String(Math.trunc(num.split(' ')[0]))
  }

  const regex = new RegExp(`\\d+\\.\\d{1,${places}}`)
  const match = num.match(regex)

  return (match !== null) ? match[0] : null
}

const toInteger = (num) => trunc(num)

const toOneDecimalPlace = (num) => trunc(num, 1)

const toTwoDecimalPlaces = (num) => trunc(num, 2)

const toThreeDecimalPlaces = (num) => trunc(num, 3)

/* END truncate functions */

const truncGroups = [
  // Truncate to integer
  {
    groupRx: /^80[1-7]\b|^85[4-9]\b|^8[7-8][0-8]\b/,
    truncate: toInteger
  },
  
  // Truncate to one decimal place
  {
    groupRx: /^809\.9|^81[0-9]\b|8[2,4][0-9][0-9. ]+$|^83[0-8]\b|^839\.7|^85[0-3]\b/,
    truncate: toOneDecimalPlace
  },
  
  
  // Truncate to two decimal places
  {
    groupRx: /^839\.3[1,6]|^889\.3[3-4]|^891\.[7-8]/,
    truncate: toTwoDecimalPlaces
  },
  
  // Truncate to three decimal places
  {
    groupRx: /^839\.693/,
    truncate: toThreeDecimalPlaces
  },
  
  // Custom Operations
  
  // Match: Numbers starting with 820-849 ending in two letters
  // Replace with the first three digits and .9
  // e.g. 823.556 3 IE → 823.9
  {
   groupRx: /^(8[2,4][0-9]).*?[A-Z]{2}$/,
    truncate: (code) => code.substring(0, 3) + '.9'
  },

  // Match: Numbers starting with 860-869 + optional region letters
  // Truncate number to 1 decimal place, 
  // moving the letters to prefix the number
  {
    groupRx: /^86[0-9]\b/, 
    truncate: (code) => {
      const [num, letters] = code.split(/ ([A-Z]{2})/);
      const truncNumber = toOneDecimalPlace(num)

      return (letters)
        ? `${letters} ${truncNumber}`
        : `${truncNumber}`
    }
  }
];

const formatCode = (code) => {
  for (const { groupRx, truncate } of truncGroups) {
    if (groupRx.test(code)) {
      return truncate(code)
    }
  }
  return code;
};

// Update value of <input name='collocazione'>
const collocaziones = document.querySelectorAll('[name="collocazione"]')

collocaziones.forEach(
  (collocazione) => collocazione.value = formatCode(collocazione.value)
)

I needed to swap the first boundary \b for a starts with ^. I have made this ammend to the rest of the regexes as well. e.g.

/^8[7-8][0-8]\b/

891.888 // fail
888.345 // pass
7888.323 // fail
888.323 // pass
8887.623 // fail
1 Like

Thanks a lot, rpg_digital, last method is much more comfortable to add values!! However, it seems the problem ^891\.[7-8] has not been completely resolved yet, even with ^.

891.823 54, for example, returns 891.8, NOT 891.82

:frowning:

I will have a look.

I realise that those longer regexes are a bit difficult to read, so have come up with an alternative to combine multiple regexes — something I have done before.

// Combines multiple regexes using the pipe '|' operator
const combineRegexes = (regexes) => {
  return new RegExp(
    regexes
      .map((regex) => regex.source)
      .join('|')
  )
}

const truncGroups = [
  // Truncate to integer
  {
    groupRx: combineRegexes([
      /^80[1-7]\b/, 
      /^85[4-9]\b/, 
      /^8[7-8][0-8]\b/
    ]),
    truncate: toInteger
  },
  
  // Truncate to one decimal place
  {
    groupRx: combineRegexes([
      /^809\.9/, 
      /^81[0-9]\b/,
      /^8[2,4][0-9][0-9. ]+$/,
      /^83[0-8]\b/,
      /^839\.7/,
      /^85[0-3]\b/
    ]),
    truncate: toOneDecimalPlace
  },
  
  
  // Truncate to two decimal places
  {
    groupRx: combineRegexes([
      /^839\.3[1,6]/,
      /^889\.3[3-4]/,
      /^891\.[7-8]/,
    ]),
    truncate: toTwoDecimalPlaces
  },
  
  // Truncate to three decimal places
  {
    groupRx: /^839\.693/,
    truncate: toThreeDecimalPlaces
  },
  
  // Custom Operations
  
  // Match: Numbers starting with 820-849 ending in two letters
  // Replace with the first three digits and .9
  // e.g. 823.556 3 IE → 823.9
  {
    groupRx: /^(8[2,4][0-9]).*?[A-Z]{2}$/,
    truncate: (code) => code.substring(0, 3) + '.9'
  },

  // Match: Numbers starting with 860-869 + optional region letters
  // Truncate number to 1 decimal place, 
  // moving the letters to prefix the number
  {
    groupRx: /^86[0-9]\b/, 
    truncate: (code) => {
      const [num, letters] = code.split(/ ([A-Z]{2})/);
      const truncNumber = toOneDecimalPlace(num)

      return (letters)
        ? `${letters} ${truncNumber}`
        : `${truncNumber}`
    }
  }
]

Let me see if I can figure out that specific issue

Edit: I think it maybe fixed! There was a ^ missing from one of the regexes in the ‘to one decimal place’ group.

Edit2: This one /^8[2,4][0-9][0-9. ]+$/ is a bit iffy to me. It will match 820-849 followed by 0-9 or a dot one or many times e.g. 82045.6 .3 3…23. I would like to fix it. What is the match criteria for numbers starting with 820-849?

rpg_digital, 891 works like a charm now, great!!

/^8[2,4][0-9][0-9. ]+$/

follows instructions of Post #70 and has to truncate values without letters (820-849 followed by 0-9) to first decimal place. Yes, it seems dot lacking, maybe better this string?

/^8[2,4][0-9]\.[0-9. ]+$/

Thank you!

1 Like

Updated code:

/* truncate functions */

const trunc = (num, places = 0) => {
  if (places <= 0) {
    return String(Math.trunc(num.split(' ')[0]))
  }

  const regex = new RegExp(`\\d+\\.\\d{1,${places}}`)
  const match = num.match(regex)

  return (match !== null) ? match[0] : null
}

const toInteger = (num) => trunc(num)

const toOneDecimalPlace = (num) => trunc(num, 1)

const toTwoDecimalPlaces = (num) => trunc(num, 2)

const toThreeDecimalPlaces = (num) => trunc(num, 3)

/* END truncate functions */

// Combines multiple regexes using the pipe '|' operator
const combineRegexes = (regexes) => {
  return new RegExp(
    regexes
      .map((regex) => regex.source)
      .join('|')
  )
}

const truncParsers = [
  // Truncate to integer
  {
    matches: combineRegexes([
      /^80[1-7]\b/, 
      /^85[4-9]\b/, 
      /^8[7-8][0-8]\b/
    ]),
    parse: toInteger
  },
  
  // Truncate to one decimal place
  {
    matches: combineRegexes([
      /^809\.9/, 
      /^81[0-9]\b/,
      /^8[2,4][0-9]\.[0-9]/,
      /^83[0-8]\b/,
      /^839\.7/,
      /^85[0-3]\b/
    ]),
    parse: toOneDecimalPlace
  },
  
  
  // Truncate to two decimal places
  {
    matches: combineRegexes([
      /^839\.3[1,6]/,
      /^889\.3[3-4]/,
      /^891\.[7-8]/,
    ]),
    parse: toTwoDecimalPlaces
  },
  
  // Truncate to three decimal places
  {
    matches: /^839\.693/,
    parse: toThreeDecimalPlaces
  },
  
  // Custom Operations
  
  // Match: Numbers starting with 820-849 ending in two letters
  // Replace with the first three digits and .9
  // e.g. 823.556 3 IE → 823.9
  {
    matches: /^(8[2,4][0-9]).*?[A-Z]{2}$/,
    parse: (code) => code.substring(0, 3) + '.9'
  },

  // Match: Numbers starting with 860-869 + optional region letters
  // Truncate number to 1 decimal place, 
  // moving the letters to prefix the number
  {
    matches: /^86[0-9]\b/, 
    parse: (code) => {
      const [num, letters] = code.split(/ ([A-Z]{2})/);
      const truncNumber = toOneDecimalPlace(num)

      return (letters)
        ? `${letters} ${truncNumber}`
        : `${truncNumber}`
    }
  }
]

const parseValue = (value, parsers = truncParsers) => {
  for (const { matches, parse } of parsers) {
    if (matches.test(value)) {
      return parse(value)
    }
  }
  return value;
};

// Update value of <input name='collocazione'>
const collocaziones = document.querySelectorAll('[name="collocazione"]')

collocaziones.forEach(
  (collocazione) => collocazione.value = parseValue(collocazione.value)
)

codepen updated

I opted for /^8[2,4][0-9]\.[0-9]/. Hopefully all is good :slight_smile:

1 Like

Okay, thank you again, rpg_digital, you are a problem solver, every value works well so far! :slight_smile:

1 Like

Good evening! Is it possible to edit last code adding a line to truncate numbers to four decimal places?

For example:

709.040 6525

(space after third decimal place) would be:

709.040 6

I tried:

.......
const toFourDecimalPlaces = (num) => trunc(num, 4)
.......
// Truncate to four decimal places
  {
    matches: combineRegexes([
       /^709\.040 [1-9]/,
          ]),
    parse: toFourDecimalPlaces
  }

but the space after the third decimal place prevents truncation.
Thank you!

I have simplified the trunc function

const trunc = (num, places = 0) => {
  const [integer, fractional] = num.split('.')

  // check how many places required and 
  // whether there is a fractional part to the number
  return (places > 0 && fractional)
    ? `${integer}.${fractional.substring(0, places)}`
    : integer
}

Can you please check this.

1 Like

Thanks very much, rpg_digital, the whole code seems working very well!!! :crossed_fingers:

1 Like

Sorry, rpg_digital. I would like to cut to two decimal places numbers starting with:
/^779\.994 5/,

Unfortunally I noticed the script works only when there’s not the space after the third decimal place:
/^779\.9945/ ------> 779.99
or (strangely) when there’s a space after 4th (but not third) decimal place, like:
/^779\.9945 4567/ ------> 779.99
/^779\.99454 567/ ------> 779.99
/^779\.994545 67/ ------> 779.99
/^779\.9945456 7/ ------> 779.99

Add it to the truncate to 2 matches, no?

// Truncate to two decimal places
{
    matches: [
      /^839\.3[1,6]/,
      /^889\.3[3-4]/,
      /^891\.[7-8]/,
      /^779\.994 5/
    ],
    truncate: truncateTo(2)
},

I have amended the codepen.

1 Like

Perfect, thank you, rpg_digital!

Good morning, rpg_digital, may I ask a question? Is it possible to add a little change to your code?

Given this number:

791.436 55

from the same old page I would like not only to truncate it to third decimal (so 791.436) in “Collocazione” field (this is possible with your code), but also add these three letters in “specificazione” field:

SOC

Thanks ever so much!

This is very much a sticking plaster fix and could probably do with some more thought.

/* truncate functions */

const trunc = (num, places = 0) => {
  const [integer, fractional] = num.split('.')

  return (places > 0 && fractional)
    ? `${integer}.${fractional.substring(0, places)}`
    : integer
}

const truncateTo = (places) => (num) => trunc(num, places)

/* END truncate functions */

const truncParsers = [
  // Truncate to integer
  {
    matches: [
      /^80[1-7]\b/, 
      /^85[4-9]\b/, 
      /^8[7-8][0-8]\b/
    ],
    truncate: truncateTo(0)
  },
  
  // Truncate to one decimal place
  {
    matches: [
      /^809\.9/, 
      /^81[0-9]\b/,
      /^8[2,4][0-9](?:\.[0-9 ]*)?$/, 
      /^83[0-8]\b/,
      /^839\.7/,
      /^85[0-3]\b/
    ],
    truncate: truncateTo(1)
  },
  
  
  // Truncate to two decimal places
  {
    matches: [
      /^839\.3[1,6]/,
      /^889\.3[3-4]/,
      /^891\.[7-8]/,
      /^779\.994 5/
    ],
    truncate: truncateTo(2)
  },
  
  // Truncate to three decimal places
  {
    matches: /^839\.693/,
    truncate: truncateTo(3)
  },
  
  // Custom Operations

  // Match: Numbers starting with 709.040
  // truncate to 5 places
  {
    matches: /^709\.040/,
    truncate: truncateTo(5)
  },
  
  // Match: Numbers starting with 820-849 ending in two letters
  // Replace with the first three digits and .9
  // e.g. 823.556 3 IE → 823.9
  {
    matches: /^(8[2,4][0-9]).*?[A-Z]{2}$/,
    truncate: (code) => trunc(code) + '.9'
  },

  // Match: Numbers starting with 860-869 + optional region letters
  // Truncate number to 1 decimal place, 
  // moving the letters to prefix the number
  {
    matches: /^86[0-9]\b/, 
    truncate: (code) => {
      const [num, letters] = code.split(/ ([A-Z]{2})/);
      const truncNumber = trunc(num, 1)

      return (letters)
        ? `${letters} ${truncNumber}`
        : `${truncNumber}`
    }
  },

  // Match: Numbers starting with 791.436
  // Truncate to 3 and set sibling specificazione to 'SOC'
  {
    matches: /^791.436\b/, 
    truncate: truncateTo(3),
    // provide a callback function
    callback: (elem) => {
      // starting at the input, traverse ancestor elements looking for a tableRow
      const tableRow = elem.closest('tr')

      if (tableRow) {
        const specificazione = tableRow.querySelector('[name="specificazione"]')
        specificazione.value = 'SOC'
      }
    }
  }
]

const parseCode = (code, elem) => {
  // now include an optional callback function
  for (const { matches, truncate, callback } of truncParsers) {
    // if matches is a single regex wrap it in an array.
    // loop through the regexes and test for a match
    for (const match of Array.isArray(matches) ? matches : [ matches ]) {
      if (match.test(code)) {
        // if a callback is provided, call it passing in the element and code.
        if (typeof callback === 'function') {
          callback(elem, code)
        }
        return truncate(code)
      }
    } 
  }
  return code
}

// Update value of <input name='collocazione'>
const collocaziones = document.querySelectorAll('[name="collocazione"]')

collocaziones.forEach(
  (collocazione) => {
    const value = collocazione.value.trim()

    collocazione.value = parseCode(value, collocazione)
  }
)

parseCode should really just do that one job of parsing the code, so adding a callback to handle your specificazione input is a bit of a code smell to me.

It seems to work, but I think needs some re-thinking.

1 Like