|
| 1 | +___TERMS_OF_SERVICE___ |
| 2 | + |
| 3 | +By creating or modifying this file you agree to Google Tag Manager's Community |
| 4 | +Template Gallery Developer Terms of Service available at |
| 5 | +https://developers.google.com/tag-manager/gallery-tos (or such other URL as |
| 6 | +Google may provide), as modified from time to time. |
| 7 | + |
| 8 | + |
| 9 | +___INFO___ |
| 10 | + |
| 11 | +{ |
| 12 | + "type": "MACRO", |
| 13 | + "id": "cvt_temp_public_id", |
| 14 | + "version": 1, |
| 15 | + "securityGroups": [], |
| 16 | + "displayName": "Multiple Inputs RegEx Table", |
| 17 | + "description": "This template is similar to a \u003ca href\u003d\"https://j.st/4VwZ\"\u003eRegEx Table\u003c/a\u003e, with the addition of being able to use multiple variables as input.", |
| 18 | + "containerContexts": [ |
| 19 | + "WEB" |
| 20 | + ], |
| 21 | + "categories": [ |
| 22 | + "UTILITY" |
| 23 | + ] |
| 24 | +} |
| 25 | + |
| 26 | + |
| 27 | +___TEMPLATE_PARAMETERS___ |
| 28 | + |
| 29 | +[ |
| 30 | + { |
| 31 | + "type": "LABEL", |
| 32 | + "name": "info", |
| 33 | + "displayName": "This template is similar to a \u003ca href\u003d\"https://j.st/4VwZ\"\u003eRegEx Table\u003c/a\u003e, with the addition of being able to use multiple variables as input." |
| 34 | + }, |
| 35 | + { |
| 36 | + "type": "SIMPLE_TABLE", |
| 37 | + "name": "inputsTable", |
| 38 | + "displayName": "Inputs Table", |
| 39 | + "simpleTableColumns": [ |
| 40 | + { |
| 41 | + "defaultValue": "", |
| 42 | + "displayName": "Input Value", |
| 43 | + "name": "inputSource", |
| 44 | + "type": "TEXT" |
| 45 | + } |
| 46 | + ], |
| 47 | + "newRowButtonText": "Add Input", |
| 48 | + "help": "Individual \u003cem\u003eInput Values\u003c/em\u003e in the table below will be concatenated, and a regular expression match will be performed on each pattern in the \u003cem\u003eRegEx Table\u003c/em\u003e below. When a regular expression match is found, the corresponding \u003cem\u003eOutput value\u003c/em\u003e will be returned. \n\u003cbr/\u003e\u003cbr/\u003e\nPlease note the following:\n\u003cbr/\u003e- Values in the \u003cem\u003eInput Values\u003c/em\u003e table will be concatenated with a \"_\" by default; however, you can change this setting in the \u003cem\u003eAdvanced Settings\u003c/em\u003e.\n\u003cbr/\u003e- Regular expression pattern matching is evaluated from top to bottom; reorder these as needed.\n\u003cbr/\u003e- Input values may use \u003ca href\u003d\"https://support.google.com/tagmanager/topic/7683268?visit_id\u003d637556720797425891-3442928510\u0026amp;rd\u003d1#web\"\u003evariables\u003c/a\u003e such as \u003cstrong\u003e{{ Title }}\u003c/strong\u003e." |
| 49 | + }, |
| 50 | + { |
| 51 | + "type": "SIMPLE_TABLE", |
| 52 | + "name": "regexTable", |
| 53 | + "displayName": "RegEx Table", |
| 54 | + "simpleTableColumns": [ |
| 55 | + { |
| 56 | + "defaultValue": "", |
| 57 | + "displayName": "Pattern", |
| 58 | + "name": "pattern", |
| 59 | + "type": "TEXT" |
| 60 | + }, |
| 61 | + { |
| 62 | + "defaultValue": "", |
| 63 | + "displayName": "Output", |
| 64 | + "name": "output", |
| 65 | + "type": "TEXT" |
| 66 | + } |
| 67 | + ], |
| 68 | + "newRowButtonText": "Add Pattern", |
| 69 | + "help": "The concatenation of the \u003cem\u003evalues\u003c/em\u003e in the \u003cem\u003eInputs Table\u003c/em\u003e above will be matched against each Pattern in the RegEx Table below, from top to bottom. When a match is found, the Output value from that row will be returned. \n\u003cbr/\u003e\u003cbr/\u003e\nEnter Patterns using \u003ca href\u003d\"https://support.google.com/tagmanager/answer/7679109?visit_id\u003d637556570796808874-755449532\u0026rd\u003d1\"\u003eRegular Expressions\u003c/a\u003e. By default, patterns must match the full input string and are case insensitive. This behavior can be adjusted in \u003cem\u003eAdvanced Settings\u003c/em\u003e." |
| 70 | + }, |
| 71 | + { |
| 72 | + "type": "CHECKBOX", |
| 73 | + "name": "isDefaultValueChecked", |
| 74 | + "checkboxText": "Set Default Value", |
| 75 | + "simpleValueType": true, |
| 76 | + "help": "Explicitly set the value of this variable when no Patterns match.", |
| 77 | + "defaultValue": false |
| 78 | + }, |
| 79 | + { |
| 80 | + "type": "TEXT", |
| 81 | + "name": "defaultValue", |
| 82 | + "displayName": "Default Value", |
| 83 | + "simpleValueType": true, |
| 84 | + "enablingConditions": [ |
| 85 | + { |
| 86 | + "paramName": "isDefaultValueChecked", |
| 87 | + "paramValue": true, |
| 88 | + "type": "EQUALS" |
| 89 | + } |
| 90 | + ], |
| 91 | + "canBeEmptyString": true, |
| 92 | + "help": "To set the value to be an empty string, leave the field blank." |
| 93 | + }, |
| 94 | + { |
| 95 | + "type": "GROUP", |
| 96 | + "name": "advanced", |
| 97 | + "displayName": "Advanced Settings", |
| 98 | + "groupStyle": "ZIPPY_CLOSED", |
| 99 | + "subParams": [ |
| 100 | + { |
| 101 | + "type": "TEXT", |
| 102 | + "name": "concatenatingCharacter", |
| 103 | + "displayName": "Concatenating Character", |
| 104 | + "simpleValueType": true, |
| 105 | + "help": "The character that will be used to concatenate the input values, defaults to \"\u003ccode\u003e_\u003c/code\u003e\". For example, if there are two input values inside the \u003cem\u003eInputs Table\u003c/em\u003e, the first one being \u003cem\u003eFoo\u003c/em\u003e and the second one being \u003cem\u003eBar\u003c/em\u003e, the final input will be \u003cem\u003eFoo_Bar\u003c/em\u003e." |
| 106 | + }, |
| 107 | + { |
| 108 | + "type": "CHECKBOX", |
| 109 | + "name": "ignoreCase", |
| 110 | + "checkboxText": "Ignore Case", |
| 111 | + "simpleValueType": true, |
| 112 | + "defaultValue": true |
| 113 | + }, |
| 114 | + { |
| 115 | + "type": "CHECKBOX", |
| 116 | + "name": "fullMatchesOnly", |
| 117 | + "checkboxText": "Full Matches Only", |
| 118 | + "simpleValueType": true, |
| 119 | + "defaultValue": true, |
| 120 | + "help": "If enabled, patterns must match entire input. This is equivalent to having start (^) and end ($) anchors implicitly around your pattern. If disabled, patterns will match when they are found anywhere in the input. Disabling this option may cause unexpected behavior with the \u003cem\u003e\"Capture Groups and Replace Functionality\"\u003c/em\u003e." |
| 121 | + }, |
| 122 | + { |
| 123 | + "type": "CHECKBOX", |
| 124 | + "name": "captureGroups", |
| 125 | + "checkboxText": "Enable Capture Groups and Replace Functionality", |
| 126 | + "simpleValueType": true, |
| 127 | + "defaultValue": true, |
| 128 | + "help": "If enabled, you can use \u003ca href\u003d\"https://262.ecma-international.org/5.1/#sec-15.5.4.11\"\u003edollar-sign replacement syntax\u003c/a\u003e to include portions of the input (e.g. from capturing groups in the matched pattern) within the output. Given the \u003ca href\u003d\"https://support.google.com/tagmanager/thread/49104943/google-tag-manager-custom-templates-and-regex-support?hl\u003den\"\u003elimited support for regular expressions in Google Tag Manager templates\u003c/a\u003e, only \u003cstrong\u003e$n\u003c/strong\u003e and \u003cstrong\u003e$\u0026\u003c/strong\u003e are supported. Additionally, only the first 5 capture groups are available. \n\u003cbr/\u003e\u003cbr/\u003e\nPlease note the following:\n\u003cbr/\u003e- Using this option with \u003cem\u003e\"Full Matches Only\"\u003c/em\u003e disabled may result in unexpected behavior (i.e. returning the entire input value with the matched portion replaced). \n\u003cbr/\u003e- Using this option with \u003cem\u003e\"Ignore Case\"\u003c/em\u003e enabled, the returned value from the group capture will be in lowercase." |
| 129 | + } |
| 130 | + ] |
| 131 | + } |
| 132 | +] |
| 133 | + |
| 134 | + |
| 135 | +___SANDBOXED_JS_FOR_WEB_TEMPLATE___ |
| 136 | + |
| 137 | +// APIs |
| 138 | +//const log = require('logToConsole'); |
| 139 | +const getType = require('getType'); |
| 140 | + |
| 141 | + |
| 142 | +// Inputs |
| 143 | +const config = { |
| 144 | + // inputs, patterns |
| 145 | + inputsTable: data.inputsTable || [], |
| 146 | + concatenatingCharacter: data.concatenatingCharacter || '_', |
| 147 | + regexTable: data.regexTable || [], |
| 148 | + // default values |
| 149 | + isDefaultValueChecked: data.isDefaultValueChecked || false, |
| 150 | + defaultValue: data.defaultValue || '', |
| 151 | + // advanced |
| 152 | + ignoreCase: data.ignoreCase || false, |
| 153 | + fullMatch: data.fullMatch || false, |
| 154 | + captureGroups: data.captureGroups || false |
| 155 | +}; |
| 156 | + |
| 157 | + |
| 158 | +// Logic |
| 159 | +const input = config.inputsTable.map(item => { |
| 160 | + return getType(item.inputSource) == 'undefined' ? null : item.inputSource; |
| 161 | +}).filter(el => el) |
| 162 | + .map(el => config.ignoreCase ? el.toLowerCase() : el) |
| 163 | + .join(config.concatenatingCharacter); |
| 164 | + |
| 165 | +const results = config.regexTable.filter(row => { |
| 166 | + const regex = config.ignoreCase ? row.pattern.toLowerCase() : row.pattern; |
| 167 | + |
| 168 | + if (config.fullMatch) { |
| 169 | + return input.match(regex) && input.match(regex)[0] === input; |
| 170 | + } |
| 171 | + |
| 172 | + return input.match(regex); |
| 173 | +}); |
| 174 | + |
| 175 | + |
| 176 | +// Return value |
| 177 | +if (results.length === 0) { |
| 178 | + return getDefaultValue(config.isDefaultValueChecked, config.defaultValue); |
| 179 | +} else if (config.captureGroups) { |
| 180 | + return doRegexReplacements(results[0]); |
| 181 | +} |
| 182 | + |
| 183 | +return results[0].output; |
| 184 | + |
| 185 | + |
| 186 | +// Functions |
| 187 | +function getDefaultValue(isDefaultValueChecked, defaultValue) { |
| 188 | + if (isDefaultValueChecked) { |
| 189 | + return defaultValue; |
| 190 | + } |
| 191 | + |
| 192 | + return undefined; |
| 193 | +} |
| 194 | + |
| 195 | +function doRegexReplacements(result) { |
| 196 | + let output = result.output; |
| 197 | + const pattern = config.ignoreCase ? result.pattern.toLowerCase() : result.pattern; |
| 198 | + const inputMatch = input.match(pattern); |
| 199 | +
|
| 200 | + // matchAll/replaceALl are not supported!, so we need to use while :( |
| 201 | + // also, limited support: https://j.st/4VwL |
| 202 | + [1, 2, 3, 4, 5].map((index) => { |
| 203 | + // check if we need to replace any $n and if capture group exists in input |
| 204 | + while (output.match("\\$" + index) && typeof inputMatch[index] !== "undefined") { |
| 205 | + output = output.replace("$" + index, inputMatch[index]); |
| 206 | + } |
| 207 | + }); |
| 208 | + |
| 209 | + while (output.match("\\$\\&")) { |
| 210 | + output = output.replace("$&", inputMatch[0]); |
| 211 | + } |
| 212 | + |
| 213 | + return output; |
| 214 | +} |
| 215 | + |
| 216 | + |
| 217 | +___TESTS___ |
| 218 | + |
| 219 | +scenarios: |
| 220 | +- name: Test Capture Groups |
| 221 | + code: |- |
| 222 | + const regexTable = [ |
| 223 | + { |
| 224 | + pattern: 'My_(.*?)_My_(.*)', |
| 225 | + output: 'Testing! $2 $1 $2 $3 $& $&', |
| 226 | + }, |
| 227 | + { |
| 228 | + pattern: 'My_(.*?)_My_(.*)', |
| 229 | + output: 'Second Testing, to make sure we return the first one', |
| 230 | + } |
| 231 | + ]; |
| 232 | + |
| 233 | + let mockData = { |
| 234 | + inputsTable: inputsTable, |
| 235 | + regexTable: regexTable, |
| 236 | + ignoreCase: false, |
| 237 | + captureGroups: true |
| 238 | + }; |
| 239 | + |
| 240 | + // Call runCode to run the template's code. |
| 241 | + let variableResult = runCode(mockData); |
| 242 | + |
| 243 | + assertThat(variableResult).isEqualTo('Testing! Bar Foo Bar $3 My_Foo_My_Bar My_Foo_My_Bar'); |
| 244 | +- name: Test Capture Groups, ignore Case |
| 245 | + code: |- |
| 246 | + const regexTable = [ |
| 247 | + { |
| 248 | + pattern: 'my_(.*?)_My_(.*)', |
| 249 | + output: 'Testing! $2 $1 $2 $3 $& $&', |
| 250 | + }, |
| 251 | + { |
| 252 | + pattern: 'my_(.*?)_My_(.*)', |
| 253 | + output: 'Second Testing, to make sure we return the first one', |
| 254 | + } |
| 255 | + ]; |
| 256 | + |
| 257 | + let mockData = { |
| 258 | + inputsTable: inputsTable, |
| 259 | + regexTable: regexTable, |
| 260 | + ignoreCase: true, |
| 261 | + captureGroups: true |
| 262 | + }; |
| 263 | + |
| 264 | + // Call runCode to run the template's code. |
| 265 | + let variableResult = runCode(mockData); |
| 266 | + |
| 267 | + assertThat(variableResult).isEqualTo('Testing! bar foo bar $3 my_foo_my_bar my_foo_my_bar'); |
| 268 | +- name: Test without Capture Groups |
| 269 | + code: "const regexTable = [\n {\n pattern: 'my_(.*?)_My_(.*)',\n output:\ |
| 270 | + \ 'Testing! $2 $1 $2 $3 $& $&',\n },\n {\n pattern: 'my_(.*?)_My_(.*)',\n\ |
| 271 | + \ output: 'Second Testing, to make sure we return the first one',\n }\n];\n\ |
| 272 | + \nlet mockData = {\n inputsTable: inputsTable,\n regexTable: regexTable,\n \ |
| 273 | + \ ignoreCase: true, \n captureGroups: false\n};\n\n// Call runCode to run the\ |
| 274 | + \ template's code.\nlet variableResult = runCode(mockData);\n\nassertThat(variableResult).isEqualTo('Testing!\ |
| 275 | + \ $2 $1 $2 $3 $& $&');" |
| 276 | +- name: Test with Diff Concatenating Character |
| 277 | + code: |- |
| 278 | + const regexTable = [ |
| 279 | + { |
| 280 | + pattern: 'My_(.*?)-My_(.*)', |
| 281 | + output: 'Testing! $2 $1 $&', |
| 282 | + } |
| 283 | + ]; |
| 284 | + |
| 285 | + let mockData = { |
| 286 | + inputsTable: inputsTable, |
| 287 | + regexTable: regexTable, |
| 288 | + ignoreCase: false, |
| 289 | + captureGroups: true, |
| 290 | + concatenatingCharacter: '-' |
| 291 | + }; |
| 292 | + |
| 293 | + // Call runCode to run the template's code. |
| 294 | + let variableResult = runCode(mockData); |
| 295 | + |
| 296 | + assertThat(variableResult).isEqualTo('Testing! Bar Foo My_Foo-My_Bar'); |
| 297 | +- name: Test Full Match set to False |
| 298 | + code: |- |
| 299 | + const regexTable = [ |
| 300 | + { |
| 301 | + pattern: 'Foo', |
| 302 | + output: 'Testing $&', |
| 303 | + } |
| 304 | + ]; |
| 305 | + |
| 306 | + let mockData = { |
| 307 | + inputsTable: inputsTable, |
| 308 | + regexTable: regexTable, |
| 309 | + ignoreCase: false, |
| 310 | + captureGroups: true, |
| 311 | + fullMatch: false |
| 312 | + }; |
| 313 | + |
| 314 | + // Call runCode to run the template's code. |
| 315 | + let variableResult = runCode(mockData); |
| 316 | + |
| 317 | + assertThat(variableResult).isEqualTo('Testing Foo'); |
| 318 | +- name: Test Full Match set to True |
| 319 | + code: |- |
| 320 | + const regexTable = [ |
| 321 | + { |
| 322 | + pattern: 'Foo', |
| 323 | + output: 'Testing $&', |
| 324 | + } |
| 325 | + ]; |
| 326 | + |
| 327 | + let mockData = { |
| 328 | + inputsTable: inputsTable, |
| 329 | + regexTable: regexTable, |
| 330 | + ignoreCase: false, |
| 331 | + captureGroups: true, |
| 332 | + fullMatch: true |
| 333 | + }; |
| 334 | + |
| 335 | + // Call runCode to run the template's code. |
| 336 | + let variableResult = runCode(mockData); |
| 337 | + |
| 338 | + assertThat(variableResult).isEqualTo(undefined); |
| 339 | +- name: Test Default Value |
| 340 | + code: "const regexTable = [\n {\n pattern: 'Foo',\n output: 'Testing $&',\n\ |
| 341 | + \ }\n];\n\nlet mockData = {\n inputsTable: inputsTable,\n regexTable: regexTable,\n\ |
| 342 | + \ ignoreCase: false,\n captureGroups: true,\n fullMatch: true,\n isDefaultValueChecked:\ |
| 343 | + \ true,\n defaultValue: 'Def Value' \n};\n\n// Call runCode to run the template's\ |
| 344 | + \ code.\nlet variableResult = runCode(mockData);\n\nassertThat(variableResult).isEqualTo('Def\ |
| 345 | + \ Value');" |
| 346 | +setup: |- |
| 347 | + const inputsTable = [ |
| 348 | + { |
| 349 | + inputSource: "My_Foo" |
| 350 | + }, |
| 351 | + { |
| 352 | + inputSource: "My_Bar" |
| 353 | + }, |
| 354 | + { |
| 355 | + inputSource: undefined |
| 356 | + } |
| 357 | + ]; |
| 358 | + |
| 359 | + |
| 360 | +___NOTES___ |
| 361 | + |
| 362 | +Created on 5/8/2021, 12:35:44 PM |
| 363 | + |
| 364 | + |
0 commit comments