1/* pneu-veiculo, motor de identificacao da medida do pneu pelo modelo do veiculo. 2 * 3 * Sem marca, sem tema, sem texto de campanha e sem nenhum canal de contato: 4 * o motor devolve dados e o projeto que o usa decide o que fazer com eles. 5 * 6 * Funciona no navegador (variavel global PneuVeiculo) e no Node (module.exports). 7 * Nao depende de nada: nem framework, nem biblioteca, nem requisicao a servidor. 8 * 9 * Tabelas normativas embutidas: 10 * indice de carga (ISO 4209-1 / ETRTO), 0 a 199, em quilos por pneu 11 * simbolo de velocidade (ISO 4209-1 / ETRTO), de A1 a Y, em km/h 12 * 13 * Dados: os arquivos de dados/ sao gerados por tools/montar.py a partir de 14 * tools/base.tsv, onde cada linha guarda a URL de onde a medida foi lida, e por 15 * tools/pirelli.py, que acrescenta a camada do catalogo de aplicacao da Pirelli 16 * (entradas marcadas com "c":1, cada versao com os proprios anos em "y"). 17 * As duas camadas nunca se misturam: a busca mostra a ficha tecnica quando ela existe 18 * e usa o catalogo quando a base nao tem ficha daquele modelo naquele ano. 19 * 20 * 1.3.0 (05/10/2026), base v2 (tools/montar2.py): cada linha traz os proprios anos-modelo em "y" e a 21 * confianca em "q" ('a' alta, 'm' media, 'b' baixa). A linha so aparece nos anos em que vale; ano sem 22 * linha nao cai mais para "todos os anos"; a versao escolhida casa pelo nome exato. 23 */ 24(function (raiz, fabrica) { 25 'use strict'; 26 var api = fabrica(); 27 if (typeof module === 'object' && module.exports) module.exports = api; 28 else raiz.PneuVeiculo = api; 29}(typeof self !== 'undefined' ? self : this, function () { 30 'use strict'; 31 32 var VERSAO = '1.3.0'; 33 34 // ================================================================ [1] tabelas 35 // Indice de carga -> quilos por pneu. ISO 4209-1, mesma tabela da ETRTO. 36 var INDICE_CARGA = (function () { 37 var baixos = [45, 46.2, 47.5, 48.7, 50, 51.5, 53, 54.5, 56, 58, 38 60, 61.5, 63, 65, 67, 69, 71, 73, 75, 77.5, 39 80, 82.5, 85, 87.5, 90, 92.5, 95, 97.5, 100, 103, 40 106, 109, 112, 115, 118, 121, 125, 128, 132, 136, 41 140, 145, 150, 155, 160, 165, 170, 175, 180, 185, 42 190, 195, 200, 206, 212, 218, 224, 230, 236, 243, 43 250, 257, 265, 272, 280, 290, 300, 307, 315, 325, 44 335, 345, 355]; 45 var altos = [365, 375, 387, 400, 412, 425, 437, 450, 462, 475, 487, 500, 515, 530, 545, 560, 580, 600, 615, 630, 46 650, 670, 690, 710, 730, 750, 775, 47 800, 825, 850, 875, 900, 925, 950, 975, 1000, 1030, 1060, 1090, 1120, 1150, 1180, 1215, 1250, 1285, 48 1320, 1360, 49 1400, 1450, 1500, 1550, 1600, 1650, 1700, 1750, 1800, 1850, 1900, 1950, 2000, 2060, 2120, 2180, 2240, 50 2300, 2360, 2430, 51 2500, 2575, 2650, 2725, 2800, 2900, 3000, 3075, 3150, 3250, 3350, 3450, 3550, 3650, 3750, 3875, 4000, 52 4125, 4250, 4375, 53 4500, 4625, 4750, 4875, 5000, 5150, 5300, 5450, 5600, 5800, 6000, 6150, 6300, 6500, 6700, 6900, 7100, 54 7300, 7500, 7750, 55 8000, 8250, 8500, 8750, 9000, 9250, 9500, 9750, 10000, 10300, 10600, 10900, 11200, 11500, 11800, 56 12150, 12500, 12800, 13200, 13600]; 57 var t = {}, i; 58 for (i = 0; i < baixos.length; i++) t[i] = baixos[i]; // 0 = 45 kg ... 72 = 355 kg 59 for (i = 0; i < altos.length; i++) t[73 + i] = altos[i]; // 73 = 365 kg ... 199 = 13600 kg 60 return t; 61 })(); 62 63 // Simbolo de velocidade -> km/h. ISO 4209-1 / ETRTO. 64 var SIMBOLO_VELOCIDADE = { 65 A1: 5, A2: 10, A3: 15, A4: 20, A5: 25, A6: 30, A7: 35, A8: 40, 66 B: 50, C: 60, D: 65, E: 70, F: 80, G: 90, J: 100, K: 110, L: 120, M: 130, 67 N: 140, P: 150, Q: 160, R: 170, S: 180, T: 190, U: 200, H: 210, 68 V: 240, W: 270, Y: 300, ZR: 240 69 }; 70 71 // Serie assumida quando a medida nao traz a serie (padrao historico). 72 var SERIE_PADRAO = 82; 73 74 // ================================================================ [2] utilidades 75 function texto(v) { return v == null ? '' : String(v); } 76 77 function limpa(v) { return texto(v).replace(/\s+/g, ' ').trim(); } 78 79 function semAcento(v) { 80 return limpa(v) 81 .replace(/[áà âãä]/g, 'a').replace(/[ÃÃÃÃÃ]/g, 'A') 82 .replace(/[éèêë]/g, 'e').replace(/[ÃÃÃÃ]/g, 'E') 83 .replace(/[Ãìîï]/g, 'i').replace(/[ÃÃÃÃ]/g, 'I') 84 .replace(/[óòôõö]/g, 'o').replace(/[ÃÃÃÃÃ]/g, 'O') 85 .replace(/[úùûü]/g, 'u').replace(/[ÃÃÃÃ]/g, 'U') 86 .replace(/[ç]/g, 'c').replace(/[Ã]/g, 'C') 87 .replace(/[ñ]/g, 'n').replace(/[Ã]/g, 'N'); 88 } 89 90 function chave(v) { return semAcento(v).toLowerCase(); } 91 92 function slug(v) { 93 return chave(v).replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'x'; 94 } 95 96 function num(v, padrao) { 97 var n; 98 if (typeof v === 'number') n = v; 99 else { 100 var s = limpa(v); 101 if (s.indexOf(',') >= 0) s = s.replace(/\./g, '').replace(',', '.'); 102 n = parseFloat(s); 103 } 104 return isFinite(n) ? n : (padrao === undefined ? 0 : padrao); 105 } 106 107 function arred(n, casas) { 108 var f = Math.pow(10, casas || 0); 109 return Math.round(n * f) / f; 110 } 111 112 function unico(lista) { 113 var vistos = {}, saida = []; 114 for (var i = 0; i < lista.length; i++) { 115 var k = chave(lista[i]); 116 if (!vistos[k]) { vistos[k] = 1; saida.push(lista[i]); } 117 } 118 return saida; 119 } 120 121 // ================================================================ [3] leitura de medida 122 // Formas aceitas, todas testadas em test/formatos.js: 123 // 175/70R14 175/70 R14 175/70r14 175/70-14 175/70/14 17570R14 124 // P205/55R16 LT265/70R17 225/75R16C 175/70ZR14 185R14 (serie assumid
124a) 125 // 5.60-15 31x10.50R15 175 70 14 126 var RE_METRICA = /(?:P|LT|ST|T)?\s*(\d{3})\s*[\/\s-]\s*(\d{2})\s*(ZR|R|B|D|-|\/)?\s*(\d{2}(?:\.\d)?)\s*(C|LT|CP)?/i; 127 var RE_SEM_SERIE = /(?:P|LT)?\s*(\d{3})\s*(ZR|R|-)\s*(\d{2})\s*(C|LT)?/i; 128 var RE_COLADA = /(\d{3})(\d{2})\s*(ZR|R|-)?\s*(\d{2})\s*(C|LT)?/i; 129 var RE_POLEGADA = /(\d{1,2}(?:[.,]\d{1,2})?)\s*(L)?\s*(ZR|R|-)\s*(\d{2}(?:\.\d)?)\s*(C|LT)?/i; 130 var RE_FLUTUACAO = /(\d{2}(?:[.,]\d{1,2})?)\s*[xX]\s*(\d{1,2}(?:[.,]\d{1,2})?)\s*(ZR|R|-)?\s*(\d{2}(?:\.\d)?)/; 131 // gente escreve "aro 15, 185/60" com o aro separado da medida 132 var RE_LARG_SERIE = /(\d{3})\s*\/\s*(\d{2})(?!\s*[\/\d])/; 133 var RE_ARO_SOLTO = /\b(?:ARO|RODA)\s*(\d{2})\b/i; 134 135 // um numero colado antes do trecho encontrado quase sempre quer dizer que pegamos 136 // o pedaco errado da frase, entao esse casamento nao vale 137 function coladoEmNumero(alvo, m) { 138 if (!m || m.index === 0) return false; 139 return /[\d.,]/.test(alvo.charAt(m.index - 1)); 140 } 141 142 // "-" e "D" marcam diagonal de verdade. Sem letra nenhuma (ou com "/" no lugar dela) 143 // quem digitou quase sempre quis dizer radial, entao assumimos radial e marcamos. 144 function construcaoDe(c) { 145 var s = texto(c).toUpperCase(); 146 if (s === 'R' || s === 'ZR') return 'radial'; 147 if (s === 'B') return 'cinta diagonal'; 148 if (s === '-' || s === 'D') return 'diagonal'; 149 return 'radial'; 150 } 151 152 function construcaoAssumida(c) { 153 var s = texto(c).toUpperCase(); 154 return s === '' || s === '/'; 155 } 156 157 function lerMedida(cru) { 158 var s = limpa(cru).toUpperCase().replace(/["â]/g, ''); 159 if (!s) return null; 160 var m, r = null, alvo = s, achado = null; 161 162 m = RE_FLUTUACAO.exec(s); 163 if (m && !coladoEmNumero(s, m)) { 164 var diamPol = num(m[1].replace(',', '.')); 165 var largPol = num(m[2].replace(',', '.')); 166 r = { 167 tipo: 'flutuacao', larguraPol: largPol, larguraMm: arred(largPol * 25.4, 1), 168 serie: null, construcao: construcaoDe(m[3] || 'R'), aro: num(m[4]), 169 diametroMm: arred(diamPol * 25.4, 1), alturaMm: arred((diamPol * 25.4 - num(m[4]) * 25.4) / 2, 1), 170 sufixo: '', texto: s 171 }; 172 achado = m; 173 } 174 if (!r) { 175 m = RE_METRICA.exec(s); 176 if (m && !coladoEmNumero(s, m)) { 177 r = montaMetrica(num(m[1]), num(m[2]), m[3], num(m[4]), m[5], s, false); 178 achado = m; 179 } 180 } 181 if (!r) { 182 m = RE_SEM_SERIE.exec(s); 183 if (m && !coladoEmNumero(s, m)) { 184 r = montaMetrica(num(m[1]), SERIE_PADRAO, m[2], num(m[3]), m[4], s, true); 185 achado = m; 186 } 187 } 188 if (!r) { 189 m = RE_POLEGADA.exec(s); 190 if (m && s.indexOf('/') < 0 && !coladoEmNumero(s, m)) { 191 var lp = num(m[1].replace(',', '.')); 192 var aroP = num(m[4]); 193 var serieP = m[2] ? 60 : 92; // as flutuacao "L" sao mais baixas que as de carga 194 var altP = lp * 25.4 * serieP / 100; 195 r = { 196 tipo: 'polegada', larguraPol: lp, larguraMm: arred(lp * 25.4, 1), 197 serie: null, serieAssumida: serieP, construcao: construcaoDe(m[3]), aro: aroP, 198 alturaMm: arred(altP, 1), diametroMm: arred(aroP * 25.4 + 2 * altP, 1), 199 sufixo: (m[5] || '').toUpperCase(), texto: s 200 }; 201 achado = m; 202 } 203 } 204 if (!r) { 205 alvo = s.replace(/\s+/g, ''); 206 m = RE_COLADA.exec(alvo); 207 if (m && !coladoEmNumero(alvo, m)) { 208 r = montaMetrica(num(m[1]), num(m[2]), m[3], num(m[4]), m[5], s, false); 209 achado = m; 210 } 211 } 212 if (!r) { 213 alvo = s; 214 var ls = RE_LARG_SERIE.exec(s), ar = RE_ARO_SOLTO.exec(s); 215 if (ls && ar) { 216 r = montaMetrica(num(ls[1]), num(ls[2]), '', num(ar[1]), '', s, false); 217 r.aroSolto = true; 218 achado = ls; 219 } 220 } 221 if (!r) return null; 222 if (r.aro < 8 || r.aro > 34) return null; 223 // o indice fica fora do trecho da medida, senao "225/75R16C" viraria indice 16 simbolo C 224 r.bruto = achado[0]; 225 var antes = alvo.slice(0, achado.index); 226 var depois = alvo.slice(achado.index + achado[0].length); 227 var idx = lerIndice(depois) || lerIndice(antes); 228 if (idx) r.indice = idx; 229 if (/\bXL\b|REINFOR|EXTRA ?LOAD/.test(s)) r.reforcado = true; 230 r.normal = normalizarMedida(r); 231 r.circunferenciaMm = arred(Math.PI * r.diametroMm, 1);
232 r.voltasPorKm = r.circunferenciaMm ? arred(1000000 / r.circunferenciaMm, 1) : 0; 233 return r; 234 } 235 236 function montaMetrica(largura, serie, constr, aro, sufixo, s, assumida) { 237 var altura = largura * serie / 100; 238 var o = { 239 tipo: 'metrica', larguraMm: largura, serie: serie, construcao: construcaoDe(constr), 240 aro: aro, alturaMm: arred(altura, 1), diametroMm: arred(aro * 25.4 + 2 * altura, 1), 241 sufixo: (sufixo || '').toUpperCase(), texto: s 242 }; 243 if (construcaoAssumida(constr)) o.construcaoAssumida = true; 244 if (assumida) { o.serieAssumida = serie; o.serie = null; } 245 if (String(constr || '').toUpperCase() === 'ZR') o.zr = true; 246 return o; 247 } 248 249 function normalizarMedida(r) { 250 if (!r) return ''; 251 if (r.tipo === 'flutuacao') { 252 return arred(r.diametroMm / 25.4, 2) + 'x' + arred(r.larguraPol, 2) + 253 (r.construcao === 'radial' ? 'R' : '-') + r.aro; 254 } 255 if (r.tipo === 'polegada') { 256 return arred(r.larguraPol, 2) + (r.construcao === 'radial' ? 'R' : '-') + r.aro + (r.sufixo || ''); 257 } 258 var serie = r.serie == null ? '' : ('/' + (r.serie < 10 ? '0' + r.serie : r.serie)); 259 return r.larguraMm + serie + (r.construcao === 'radial' ? 'R' : '-') + r.aro + (r.sufixo || ''); 260 } 261 262 function medidaValida(cru) { return !!lerMedida(cru); } 263 264 // ================================================================ [4] leitura de indice 265 var RE_INDICE = /(\d{2,3})\s*(?:\/\s*(\d{2,3}))?\s*\(?\s*(A[1-8]|ZR|[B-Z])\s*\)?(?!\d)/i; 266 267 function lerIndice(cru) { 268 var s = limpa(cru).toUpperCase(); 269 if (!s) return null; 270 var m = RE_INDICE.exec(s); 271 if (!m) return null; 272 var li = parseInt(m[1], 10); 273 var liPar = m[2] ? parseInt(m[2], 10) : null; 274 var sim = m[3].toUpperCase(); 275 if (!(li in INDICE_CARGA)) return null; 276 if (!(sim in SIMBOLO_VELOCIDADE)) return null; 277 var o = { 278 indice: li, cargaKg: INDICE_CARGA[li], simbolo: sim, velocidadeKmh: SIMBOLO_VELOCIDADE[sim], 279 texto: (m[2] ? li + '/' + liPar : String(li)) + sim 280 }; 281 if (liPar != null) { 282 o.indicePar = liPar; 283 o.cargaParKg = INDICE_CARGA[liPar] || null; 284 o.rodadoDuplo = true; 285 } 286 return o; 287 } 288 289 function cargaDoIndice(i) { return INDICE_CARGA[parseInt(i, 10)] || null; } 290 291 function velocidadeDoSimbolo(s) { 292 return SIMBOLO_VELOCIDADE[limpa(s).toUpperCase()] || null; 293 } 294 295 function indiceParaCarga(kg) { 296 var alvo = num(kg), achou = null; 297 for (var i in INDICE_CARGA) { 298 if (INDICE_CARGA[i] >= alvo && (achou === null || INDICE_CARGA[i] < INDICE_CARGA[achou])) achou = i; 299 } 300 return achou === null ? null : { indice: parseInt(achou, 10), cargaKg: INDICE_CARGA[achou] }; 301 } 302 303 // ================================================================ [5] comparacao de medidas 304 function comparar(a, b) { 305 var x = lerMedida(a), y = lerMedida(b); 306 if (!x || !y) return null; 307 var difMm = arred(y.diametroMm - x.diametroMm, 1); 308 var pct = x.diametroMm ? arred(100 * difMm / x.diametroMm, 2) : 0; 309 return { 310 de: x.normal, para: y.normal, 311 diametroDe: x.diametroMm, diametroPara: y.diametroMm, 312 diferencaMm: difMm, diferencaPct: pct, 313 diferencaAlturaMm: arred(y.alturaMm - x.alturaMm, 1), 314 diferencaLarguraMm: arred((y.larguraMm || 0) - (x.larguraMm || 0), 1), 315 mesmoAro: x.aro === y.aro, 316 // 3% e o limite usual de tolerancia de diametro em troca de medida 317 dentroDe3Pct: Math.abs(pct) <= 3, 318 erroVelocimetroPct: arred(-pct, 2) 319 }; 320 } 321 322 function equivalentes(medida, toleranciaPct, universo) { 323 var base = lerMedida(medida); 324 if (!base) return []; 325 var tol = toleranciaPct == null ? 3 : num(toleranciaPct, 3); 326 var lista = universo || medidasConhecidas(); 327 var saida = []; 328 for (var i = 0; i < lista.length; i++) { 329 var c = comparar(base.normal, lista[i]); 330 if (!c || c.para === base.normal) continue; 331 if (Math.abs(c.diferencaPct) <= tol) { 332 saida.push({ medida: c.para, diferencaMm: c.diferencaMm, diferencaPct: c.diferencaPct, 333 mesmoAro: c.mesmoAro, veiculos: (MEDIDAS[c.para] || 0) }); 334 } 335 } 336 saida.sort(function (p, q) { return Math.abs(p.diferencaPct) - Math.abs(q.diferencaPct); }); 337 return saida; 338 } 339 340 // ================================================================ [6] dados 341 var MARCAS = {}; // slug -> nome 342 var VEICULOS = {}; // slug -> [ {n, a1, a2, vs:[...]} ] 343 var FONTES = []; // [codigo, url] 344 var MEDIDAS = {}; // medida -> quantos veiculos usam 345 var CONFIG = { caminho: 'dados/', tempoLimite: 12000, catalogo: true, versao: '' }; 346 var NOME_FONTE = { 347 of: 'ficha ou catalogo oficial da montadora', 348 ca: 'ficha oficial da montadora hospedada por terceiro', 349 eo: 'fabricante de pneu declarando o equipamento original', 350 ae: 'imprensa especializada', 351 ga: 'imprensa especializada', 352 im: 'imprensa especializada ou base do setor de transporte', 353 fc: 'base de fichas tecnicas', 354 au: 'base de fichas tecnicas', 355 gp: 'guia de aplicacao de fabricante de pneu', 356 pc: 'catalogo de aplicacao da Pirelli', 357 ou: 'outra fonte' 358 }; 359 // camada separada: o que uma loja associa ao veiculo. NAO e medida de fabrica. 360 var COMPAT = {}; // slug da marca -> {nome, vs:[[veiculo da loja, medida, indice]]}
361 var INDICE_LOJA = {}; // medida -> indices que a loja vende 362 363 function configurar(o) { 364 o = o || {}; 365 if (o.caminho != null) CONFIG.caminho = texto(o.caminho); 366 if (o.tempoLimite != null) CONFIG.tempoLimite = num(o.tempoLimite, 12000); 367 // catalogo:false deixa o motor so com a base de ficha tecnica 368 if (o.catalogo != null) CONFIG.catalogo = !!o.catalogo; 369 // versao dos dados: vai como ?v= no endereco do arquivo da marca, para o navegador nao usar copia velha 370 if (o.versao != null) CONFIG.versao = texto(o.versao); 371 return CONFIG; 372 } 373 374 /** Entradas de uma marca. As do catalogo ("c":1) somem com configurar({catalogo:false}). */ 375 function entradas(s) { 376 var lista = VEICULOS[s] || []; 377 if (CONFIG.catalogo) return lista; 378 var saida = []; 379 for (var i = 0; i < lista.length; i++) if (!lista[i].c) saida.push(lista[i]); 380 return saida; 381 } 382 383 /** A linha vale para o ano? Linha do catalogo traz os proprios anos em "y". */ 384 function valeNoAno(v, g, a) { 385 if (!a) return true; 386 if (a < g.a1 || a > g.a2) return false; 387 return !v.y || (a >= v.y[0] && a <= v.y[1]); 388 } 389 390 /** Injeta dados na mao. Serve para Node, para teste e para quem embute tudo num arquivo. */ 391 function usarDados(d) { 392 d = d || {}; 393 if (d.marcas) MARCAS = d.marcas; 394 if (d.veiculos) for (var s in d.veiculos) VEICULOS[s] = d.veiculos[s]; 395 if (d.fontes) FONTES = d.fontes; 396 if (d.medidas) MEDIDAS = d.medidas; 397 else recontarMedidas(); 398 if (d.compat) COMPAT = d.compat; 399 if (d.indiceLoja) INDICE_LOJA = d.indiceLoja; 400 return { marcas: contaChaves(MARCAS), veiculos: contaChaves(VEICULOS), fontes: FONTES.length, 401 compat: contaChaves(COMPAT) }; 402 } 403 404 function contaChaves(o) { var n = 0; for (var k in o) if (o.hasOwnProperty(k)) n++; return n; } 405 406 function recontarMedidas() { 407 MEDIDAS = {}; 408 for (var s in VEICULOS) { 409 var lista = entradas(s); 410 for (var i = 0; i < lista.length; i++) { 411 var vs = lista[i].vs || []; 412 for (var j = 0; j < vs.length; j++) MEDIDAS[vs[j].m] = (MEDIDAS[vs[j].m] || 0) + 1; 413 } 414 } 415 } 416 417 function pegaGlobais() { 418 var g = (typeof window !== 'undefined') ? window : null; 419 if (!g) return; 420 if (g.PV_MARCAS) MARCAS = g.PV_MARCAS; 421 if (g.PV_FONTES) FONTES = g.PV_FONTES; 422 if (g.PV_MEDIDAS) MEDIDAS = g.PV_MEDIDAS; 423 if (g.PV_VEICULOS) for (var s in g.PV_VEICULOS) VEICULOS[s] = g.PV_VEICULOS[s]; 424 if (g.PV_COMPAT) COMPAT = g.PV_COMPAT; 425 if (g.PV_INDICE_LOJA) INDICE_LOJA = g.PV_INDICE_LOJA; 426 } 427 428 /** Carrega o arquivo de uma marca. No navegador injeta <script>; no Node le do disco. */ 429 function carregar(marca, pronto, erro) { 430 var s = slugDaMarca(marca); 431 pegaGlobais(); 432 if (!s) { if (erro) erro(new Error('marca desconhecida: ' + marca)); return; } 433 if (VEICULOS[s]) { if (pronto) pronto(VEICULOS[s]); return; } 434 if (typeof window === 'undefined' || !window.document) { 435 try { 436 var fs = require('fs'), caminho = require('path').join(CONFIG.caminho, 'veiculos', s + '.js'); 437 var codigo = fs.readFileSync(caminho, 'utf8'); 438 var alvo = { PV_VEICULOS: {} }; 439 (new Function('window', codigo))(alvo); 440 VEICULOS[s] = alvo.PV_VEICULOS[s]; 441 recontarMedidas(); 442 if (pronto) pronto(VEICULOS[s]); 443 } catch (e) { if (erro) erro(e); } 444 return; 445 } 446 var el = window.document.createElement('script'); 447 var caiu = false; 448 el.src = CONFIG.caminho + 'veiculos/' + s + '.js' + (CONFIG.versao ? '?v=' + encodeURIComponent(CONFIG.versao) : ''); 449 el.async = true; 450 el.onload = function () { 451 pegaGlobais(); recontarMedidas(); 452 if (VEICULOS[s]) { if (pronto) pronto(VEICULOS[s]); } 453 else if (erro) erro(new Error('arquivo carregado sem dados: ' + s)); 454 }; 455 el.onerror = function () { caiu = true; if (erro) erro(new Error('falhou ao carregar ' + el.src)); }; 456 window.setTimeout(function () { 457 if (!caiu && !VEICULOS[s] && erro) erro(new Error('tempo esgotado ao carregar ' + el.src)); 458 }, CONFIG.tempoLimite); 459 window.document.head.appendChild(el); 460 } 461 462 function carregarTudo(pronto, erro) { 463 pegaGlobais(); 464 var lista = marcas(), i = 0, faltam = lista.length; 465 if (!faltam) { if (pronto) pronto(); return; } 466 for (i = 0; i < lista.length; i++) { 467 carregar(lista[i].slug, function () { if (--faltam === 0 && pronto) pronto(); }, 468 function (e) { if (--faltam === 0 && pronto) pronto(); if (erro) erro(e); }); 469 } 470 } 471 472 // ================================================================ [7] consulta 473 function marcas() { 474 pegaGlobais(); 475 var saida = []; 476 for (var s in MARCAS) if (MARCAS.hasOwnProperty(s)) saida.push({ slug: s, nome: MARCAS[s] }); 477 saida.sort(function (a, b) { return chave(a.nome) < chave(b.nome) ? -1 : 1; }); 478 return saida; 479 } 480 481 function slugDaMarca(marca) { 482 pegaGlobais(); 483 var k = chave(marca); 484 if (!k) return ''; 485 if (MARCAS[k]) return k; 486 var alvo = slug(marca); 487 if (MARCAS[alvo]) return alvo; 488 for (var s in MARCAS) { 489 if (chave(MARCAS[s]) === k || s === alvo) return s; 490 } 491 for (var s2 in MARCAS) { 492 if (chave(MARCAS[s2]).indexOf(k) === 0) return s2; 493 } 494 return ''; 495 } 496 497 function listaDaMarca(marca) { 498 var s = slugDaMarca(marca); 499 if (!s) return []; 500 pegaGlobais(); 501 return entradas(s); 502 } 503 504 /** Modelos de uma marca, com as geracoes de cada um. */ 505 function modelos(marca) { 506 var lista = listaDaMarca(marca), por = {}, ordem = []; 507 for (var i = 0; i < lista.length; i++) { 508 var k = chave(lista[i].n); 509 if (!por[k]) { por[k] = { nome: lista[i].n, geracoes: [], a1: lista[i].a1, a2: lista[i].a2 }; ordem.push(k); } 510 por[k].geracoes.push({ a1: lista[i].a1, a2: lista[i].a2, versoes: (lista[i].vs || []).length, 511 catalogo: !!lista[i].c }); 512 por[k].a1 = Math.min(por[k].a1, lista[i].a1); 513 por[k].a2 = Math.max(por[k].a2, lista[i].a2); 514 } 515 var saida = []; 516 for (var j = 0; j < ordem.length; j++) saida.push(por[ordem[j]]); 517 saida.sort(function (a, b) { return chave(a.nome) < chave(b.nome) ? -1 : 1; }); 518 return saida; 519 } 520 521 function geracoes(marca, modelo) {
522 var lista = listaDaMarca(marca), k = chave(modelo), saida = []; 523 for (var i = 0; i < lista.length; i++) if (chave(lista[i].n) === k) saida.push(lista[i]); 524 saida.sort(function (a, b) { return b.a1 - a.a1; }); 525 return saida; 526 } 527 528 function anos(marca, modelo) { 529 var gs = geracoes(marca, modelo), vistos = {}, saida = []; 530 for (var i = 0; i < gs.length; i++) { 531 var vs = gs[i].vs || []; 532 for (var j = 0; j < vs.length; j++) { 533 var de = vs[j].y ? Math.max(vs[j].y[0], gs[i].a1) : gs[i].a1; 534 var ate = vs[j].y ? Math.min(vs[j].y[1], gs[i].a2) : gs[i].a2; 535 for (var a = ate; a >= de; a--) if (!vistos[a]) { vistos[a] = 1; saida.push(a); } 536 } 537 } 538 saida.sort(function (a, b) { return b - a; }); 539 return saida; 540 } 541 542 function fonteDe(v) { 543 var ids = v.s || [], saida = []; 544 for (var i = 0; i < ids.length; i++) { 545 var f = FONTES[ids[i]]; 546 if (f) saida.push({ codigo: f[0], tipo: NOME_FONTE[f[0]] || 'outra fonte', url: f[1] }); 547 } 548 return saida; 549 } 550 551 function enfeita(v, modelo, marcaNome, g) { 552 var med = lerMedida(v.m) || {}; 553 var idx = v.i ? lerIndice(v.i) : null; 554 return { 555 marca: marcaNome, modelo: modelo, a1: v.y ? v.y[0] : g.a1, a2: v.y ? v.y[1] : g.a2, 556 versao: v.v, medida: v.m, 557 // 'ficha': base de ficha tecnica; 'catalogo': catalogo de aplicacao da Pirelli 558 camada: g.c ? 'catalogo' : 'ficha', 559 indice: v.i || '', cargaKg: idx ? idx.cargaKg : null, 560 velocidadeKmh: idx ? idx.velocidadeKmh : null, 561 eixo: v.e === 'd' ? 'dianteiro' : (v.e === 't' ? 'traseiro' : 'todos'), 562 aro: med.aro || null, larguraMm: med.larguraMm || null, serie: med.serie, 563 diametroMm: med.diametroMm || null, 564 anoDaFonte: v.y ? v.y[0] : (v.ar || null), anoDaFonteAte: v.y ? v.y[1] : (v.ar2 || null), 565 qualidadeDaFonte: v.f || '', tipoDaFonte: NOME_FONTE[v.f] || '', 566 // de onde veio o indice: ficha da montadora, fabricante de pneu declarando o original, ou 567 // guia de aplicacao (que recomenda, mas nao declara o original) 568 fonteDoIndice: v.i ? (v.ic || v.f || '') : '', 569 indiceDoGuia: !!(v.i && v.ic === 'gp'), 570 fontes: fonteDe(v), observacao: v.o || '', 571 // 'alta', 'media' ou 'baixa'; linha sem "q" (base antiga) conta como media 572 confianca: v.q === 'a' ? 'alta' : (v.q === 'b' ? 'baixa' : 'media') 573 }; 574 } 575 576 /** Versoes de um modelo num ano, das duas camadas (a da ficha tecnica vem primeiro). 577 * Sem ano, devolve as versoes de todas as geracoes. */ 578 function versoes(marca, modelo, ano) { 579 var gs = geracoes(marca, modelo), a = ano ? num(ano) : 0, saida = []; 580 var nome = MARCAS[slugDaMarca(marca)] || limpa(marca); 581 for (var i = 0; i < gs.length; i++) { 582 if (a && (a < gs[i].a1 || a > gs[i].a2)) continue; 583 var vs = gs[i].vs || []; 584 for (var j = 0; j < vs.length; j++) { 585 if (valeNoAno(vs[j], gs[i], a)) saida.push(enfeita(vs[j], gs[i].n, nome, gs[i])); 586 } 587 } 588 saida.sort(function (p, q) { 589 if (p.camada !== q.camada) return p.camada === 'ficha' ? -1 : 1; 590 var kp = chave(p.versao), kq = chave(q.versao); 591 if (kp !== kq) return kp.localeCompare(kq, 'pt-BR', { numeric: true }); 592 if (p.eixo !== q.eixo) return p.eixo === 'dianteiro' ? -1 : (q.eixo === 'dianteiro' ? 1 : 0); 593 if (p.aro !== q.aro) return (p.aro || 0) - (q.aro || 0); 594 return p.medida < q.medida ? -1 : (p.medida > q.medida ? 1 : 0); 595 }); 596 return saida; 597 } 598 599 /** Separa a lista nas duas camadas. */ 600 function porCamada(lista) { 601 var r = { ficha: [], catalogo: [] }; 602 for (var i = 0; i < lista.length; i++) r[lista[i].camada === 'catalogo' ? 'catalogo' : 'ficha'].push(lista[i]); 603 return r; 604 } 605 606 function filtraVersao(lista, versao) { 607 var k = chave(versao); 608 // a opcao escolhida na lista e o nome exato da versao: so ela, sem pegar "LT 1.0 Turbo" junto de "LT 1.0" 609 var exatas = lista.filter(function (x) { return chave(x.versao) === k; }); 610 if (exatas.length) return exatas; 611 var palavras = k.split(/\s+/).filter(function (x) { return x.length > 0; }); 612 var filtrado = []; 613 for (var i = 0; i < lista.length; i++) { 614 var rotulo = chave(lista[i].versao); 615 // casa se o rotulo contem o texto, se o texto contem o rotulo, ou se toda palavra 616 // do texto aparece no rotulo (e assim "ltz turbo" acha "Turbo LT / LTZ / Premier RS") 617 var todas = palavras.length > 0 && palavras.every(function (w) { return rotulo.indexOf(w) >= 0; }); 618 if (rotulo.indexOf(k) >= 0 || k.indexOf(rotulo) >= 0 || todas) filtrado.push(lista[i]); 619 } 620 return filtrado; 621 } 622 623 function medidasDe(lista) { 624 var saida = []; 625 for (var i = 0; i < lista.length; i++) if (saida.indexOf(lista[i].medida) < 0) saida.push(lista[i].medida); 626 return saida; 627 } 628 629 /** Compara as medidas das duas camadas: 'batem', 'batem em parte' ou 'divergem'. */ 630 function conferir(a, b) { 631 function norm(lista) { 632 var s = {}; 633 for (var i = 0; i < lista.length; i++) { 634 var m = lerMedida(lista[i]); 635 s[m ? m.normal.replace(/C$/, '') : chave(lista[i])] = 1; 636 } 637 return s; 638 } 639 var x = norm(a), y = norm(b), iguais = 0, nx = contaChaves(x), ny = contaChaves(y); 640 for (var k in x) if (y[k]) iguais++; 641 if (iguais === nx && iguais === ny) return 'batem'; 642 return iguais ? 'batem em parte' : 'divergem'; 643 } 644 645 /** Busca principal. Devolve as medidas do veiculo e os avisos que o caso merece. */ 646 function buscar(p) { 647 p = p || {}; 648 var r = { ok: false, marca: '', modelo: '', ano: null, versoes: [], medidas: [], 649 principal: null, avisos: [], fontes: [] }; 650 var s = slugDaMarca(p.marca);
651 if (!s) { r.avisos.push('marca não encontrada na base'); return r; } 652 r.marca = MARCAS[s]; 653 if (!p.modelo) { r.avisos.push('informe o modelo'); return r; } 654 var achados = geracoes(s, p.modelo); 655 if (!achados.length) { 656 var alt = acharModelo(s, p.modelo); 657 if (alt) { achados = geracoes(s, alt); r.avisos.push('modelo lido como "' + alt + '"'); } 658 } 659 if (!achados.length) { r.avisos.push('modelo não encontrado nesta marca'); return r; } 660 r.modelo = achados[0].n; 661 r.ano = p.ano ? num(p.ano) : null; 662 var lista = versoes(s, r.modelo, r.ano); 663 if (!lista.length && r.ano) { 664 // antes caia para "todos os anos" e mandava medida de outro ano; agora nao responde 665 r.avisos.push('a base não tem esse ano para esse modelo'); 666 return r; 667 } 668 // A ficha tecnica manda. O catalogo Pirelli vem junto, em r.catalogo, para conferir; e so vira 669 // a resposta principal quando a base nao tem ficha desse modelo nesse ano. 670 var camadas = porCamada(lista); 671 var principal = camadas.ficha.length ? camadas.ficha : camadas.catalogo; 672 var doCatalogo = camadas.catalogo; 673 r.camada = camadas.ficha.length ? 'ficha' : (camadas.catalogo.length ? 'catalogo' : ''); 674 if (p.versao) { 675 var filtrado = filtraVersao(principal, p.versao); 676 if (!filtrado.length && r.camada === 'ficha' && camadas.catalogo.length) { 677 filtrado = filtraVersao(camadas.catalogo, p.versao); 678 if (filtrado.length) { 679 r.camada = 'catalogo'; 680 r.avisos.push('versão achada só no catálogo Pirelli'); 681 } 682 } 683 if (filtrado.length) principal = filtrado; 684 else r.avisos.push('versão não encontrada; mostrando todas as versões do ano'); 685 var catFiltrado = filtraVersao(camadas.catalogo, p.versao); 686 if (catFiltrado.length) doCatalogo = catFiltrado; 687 } 688 lista = principal; 689 r.versoes = lista; 690 r.catalogo = r.camada === 'ficha' ? doCatalogo : []; 691 r.medidasCatalogo = medidasDe(doCatalogo); 692 r.conferencia = ''; 693 if (camadas.ficha.length && doCatalogo.length) { 694 r.conferencia = conferir(medidasDe(r.camada === 'ficha' ? lista : camadas.ficha), medidasDe(doCatalogo)); 695 } 696 if (r.camada === 'catalogo') { 697 r.avisos.push('a base não tem ficha técnica desse modelo nesse ano; as medidas vêm do catálogo de ' + 698 'aplicação da Pirelli'); 699 } else if (r.conferencia === 'divergem') { 700 r.avisos.push('a ficha técnica e o catálogo Pirelli não têm nenhuma medida em comum nesse ano; ' + 701 'confirme pela etiqueta da porta'); 702 } 703 var medidas = [], fontes = []; 704 for (var j = 0; j < lista.length; j++) { 705 if (medidas.indexOf(lista[j].medida) < 0) medidas.push(lista[j].medida); 706 for (var f = 0; f < lista[j].fontes.length; f++) { 707 if (fontes.indexOf(lista[j].fontes[f].url) < 0) fontes.push(lista[j].fontes[f].url); 708 } 709 if (lista[j].observacao) r.avisos.push(lista[j].versao + ': ' + lista[j].observacao); 710 } 711 r.medidas = medidas; 712 r.fontes = fontes; 713 // medida que pode ir para o cliente: so das linhas de confianca alta ou media 714 r.medidasConfiaveis = []; 715 r.aConfirmar = false; 716 for (var c = 0; c < lista.length; c++) { 717 if (lista[c].confianca === 'baixa') { r.aConfirmar = true; continue; } 718 if (r.medidasConfiaveis.indexOf(lista[c].medida) < 0) r.medidasConfiaveis.push(lista[c].medida); 719 } 720 if (r.aConfirmar) r.avisos.push('há versão com medida não confirmada nesse ano; a equipe confirma'); 721 r.principal = lista.length ? lista[0] : null; 722 r.ok = lista.length > 0; 723 if (medidas.length > 1) { 724 r.avisos.push('esse modelo sai de fábrica com ' + medidas.length + 725 ' medidas diferentes; confirme pela etiqueta da porta ou pelo pneu que está no carro'); 726 } 727 var semIndice = 0, doGuia = 0; 728 for (var g = 0; g < lista.length; g++) { 729 if (!lista[g].indice) semIndice++; 730 if (lista[g].indiceDoGuia) doGuia++; 731 } 732 if (semIndice === lista.length && lista.length) { 733 r.avisos.push('a fonte publica só a medida, sem o Ãndice de carga e velocidade'); 734 } 735 if (doGuia) { 736 r.avisos.push('o Ãndice de ' + doGuia + ' linha(s) vem do guia de aplicação de um fabricante de pneu, ' + 737 'que recomenda o pneu mas não declara o original; confirme na etiqueta da porta'); 738 } 739 return r; 740 } 741 742 /** Distancia de edicao (Levenshtein). Serve para aceitar "corola" no lugar de "Corolla". */ 743 function distancia(a, b) { 744 if (a === b) return 0; 745 if (!a.length) return b.length; 746 if (!b.length) return a.length; 747 var linha = [], i, j; 748 for (j = 0; j <= b.length; j++) linha[j] = j; 749 for (i = 1; i <= a.length; i++) { 750 var anterior = linha[0]; 751 linha[0] = i; 752 for (j = 1; j <= b.length; j++) { 753 var guarda = linha[j]; 754 linha[j] = Math.min(linha[j] + 1, linha[j - 1] + 1, 755 anterior + (a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1)); 756 anterior = guarda; 757 } 758 } 759 return linha[b.length]; 760 } 761 762 function erroAceitavel(a, b) { 763 var maior = Math.max(a.length, b.length); 764 if (maior <= 3) return 0; 765 return maior >= 6 ? 2 : 1; 766 } 767 768 function acharModelo(marca, texto) {
769 var lista = listaDaMarca(marca), k = chave(texto), melhor = '', nota = 0; 770 var soLetras = k.replace(/[^a-z0-9]/g, ''); 771 for (var i = 0; i < lista.length; i++) { 772 var n = chave(lista[i].n), p = 0; 773 var nLetras = n.replace(/[^a-z0-9]/g, ''); 774 if (n === k) p = 100; 775 else if (nLetras === soLetras) p = 90; 776 else if (n.indexOf(k) === 0 || k.indexOf(n) === 0) p = 80 - Math.min(10, Math.abs(n.length - k.length)); 777 else if (n.indexOf(k) >= 0 || k.indexOf(n) >= 0) p = 65 - Math.min(10, Math.abs(n.length - k.length)); 778 else { 779 var d = distancia(nLetras, soLetras); 780 if (d <= erroAceitavel(nLetras, soLetras)) p = 60 - 10 * d; 781 } 782 if (p > nota) { nota = p; melhor = lista[i].n; } 783 } 784 return nota >= 40 ? melhor : ''; 785 } 786 787 /** Indice reverso: quem sai de fabrica com essa medida. Precisa das marcas carregadas. */ 788 function porMedida(medida) { 789 var alvo = lerMedida(medida); 790 if (!alvo) return []; 791 pegaGlobais(); 792 var saida = []; 793 for (var s in VEICULOS) { 794 var nome = MARCAS[s] || s, lista = entradas(s); 795 for (var i = 0; i < lista.length; i++) { 796 var m = lista[i], vs = m.vs || []; 797 for (var j = 0; j < vs.length; j++) { 798 var lida = lerMedida(vs[j].m); 799 if (lida && lida.normal === alvo.normal) saida.push(enfeita(vs[j], m.n, nome, m)); 800 } 801 } 802 } 803 saida.sort(function (p, q) { 804 if (chave(p.marca) !== chave(q.marca)) return chave(p.marca) < chave(q.marca) ? -1 : 1; 805 if (chave(p.modelo) !== chave(q.modelo)) return chave(p.modelo) < chave(q.modelo) ? -1 : 1; 806 if (p.camada !== q.camada) return p.camada === 'ficha' ? -1 : 1; 807 return q.a1 - p.a1; 808 }); 809 return saida; 810 } 811 812 function medidasConhecidas() { 813 pegaGlobais(); 814 if (!CONFIG.catalogo || !contaChaves(MEDIDAS)) recontarMedidas(); 815 var saida = []; 816 for (var m in MEDIDAS) if (MEDIDAS.hasOwnProperty(m)) saida.push(m); 817 saida.sort(); 818 return saida; 819 } 820 821 /** Busca livre por texto, do tipo "gol 2012" ou "hilux srv". */ 822 function sugerir(consulta, limite) { 823 pegaGlobais(); 824 var termos = chave(consulta).split(/\s+/).filter(function (t) { return t.length > 1; }); 825 if (!termos.length) return []; 826 var ano = null; 827 for (var t = 0; t < termos.length; t++) { 828 if (/^(19|20)\d{2}$/.test(termos[t])) { ano = parseInt(termos[t], 10); } 829 } 830 // uma sugestao por modelo: as geracoes e as duas camadas do mesmo modelo se somam 831 var por = {}, ordem = []; 832 for (var s in VEICULOS) { 833 var nome = MARCAS[s] || s, lista = entradas(s); 834 for (var i = 0; i < lista.length; i++) { 835 var m = lista[i]; 836 var kModelo = chave(m.n), kMarca = chave(nome); 837 var nota = 0, casou = 0; 838 for (var j = 0; j < termos.length; j++) { 839 if (/^(19|20)\d{2}$/.test(termos[j])) continue; 840 var termo = termos[j], p = 0; 841 // nome exato do modelo vale mais que pedaco de nome, senao "gol" cairia atras de "golf" 842 if (kModelo === termo) p = 10; 843 else if (kModelo.indexOf(termo) === 0) p = 6 - Math.min(3, (kModelo.length - termo.length) * 0.5); 844 else if (kModelo.indexOf(termo) > 0) p = 3; 845 if (kMarca === termo) p = Math.max(p, 7); 846 else if (kMarca.indexOf(termo) === 0) p = Math.max(p, 5); 847 else if (kMarca.indexOf(termo) > 0) p = Math.max(p, 2); 848 if (p > 0) { casou++; nota += p; } 849 } 850 var precisa = termos.length - (ano ? 1 : 0); 851 if (casou < precisa || precisa === 0) continue; 852 if (ano) { 853 if (ano < m.a1 || ano > m.a2) continue; 854 nota += 2; 855 } 856 var km = s + '|' + kModelo, item = por[km]; 857 if (!item) { 858 item = por[km] = { marca: nome, marcaSlug: s, modelo: m.n, a1: m.a1, a2: m.a2, versoes: 0, 859 nota: nota, ano: ano, fichaTecnica: false, catalogo: false }; 860 ordem.push(km); 861 } 862 item.a1 = Math.min(item.a1, m.a1); 863 item.a2 = Math.max(item.a2, m.a2); 864 item.versoes += (m.vs || []).length; 865 item.nota = Math.max(item.nota, nota); 866 if (m.c) item.catalogo = true; else item.fichaTecnica = true; 867 } 868 } 869 var saida = []; 870 for (var o = 0; o < ordem.length; o++) saida.push(por[ordem[o]]); 871 saida.sort(function (p, q) { return q.nota - p.nota || q.a2 - p.a2; }); 872 return saida.slice(0, limite || 12); 873 } 874 875 // ================================================================ [7b] compatibilidade da loja 876 // Nome da loja: "ONIX PLUS LT SEDAN", "L200 TRITON HPE PICKUP", "X1 SDRIVE 20I (E84) SUV". 877 // Casa com o modelo da base quando comeca pelas mesmas palavras. Se dois modelos da marca casam 878 // ("Onix" e "Onix Plus"), fica com o de nome mais comprido. 879 function palavras(t) { return chave(t).replace(/[^a-z0-9]+/g, ' ').trim().split(/\s+/).filter(
879Boolean); } 880 881 function casaInicio(nomeLoja, modelo) { 882 var a = palavras(nomeLoja), b = palavras(modelo); 883 if (!a.length || !b.length) return 0; 884 var i = 0; 885 for (; i < b.length; i++) if (a[i] !== b[i]) break; 886 if (i === b.length) return b.length; 887 // "HB20" contra "HB 20": junta as palavras da loja ate cobrir o nome do modelo sem espaco 888 var alvo = b.join(''), junto = ''; 889 for (var j = 0; j < a.length && junto.length < alvo.length; j++) { 890 junto += a[j]; 891 if (junto === alvo) return j + 1; 892 } 893 return 0; 894 } 895 896 /** O que a loja associa a um modelo, agrupado por medida. Nao tem ano e NAO e medida de fabrica. */ 897 function compativeis(marca, modelo) { 898 pegaGlobais(); 899 var s = slugDaMarca(marca) || slug(marca), bloco = COMPAT[s]; 900 if (!bloco) { 901 for (var k in COMPAT) if (chave(COMPAT[k].nome) === chave(marca)) { bloco = COMPAT[k]; break; } 902 } 903 var saida = { marca: bloco ? bloco.nome : limpa(marca), modelo: limpa(modelo), medidas: [], 904 versoesDaLoja: 0, 905 aviso: 'lista da loja: inclui medida alternativa e plus size, não diz ano e não é a medida de fábrica' }; 906 if (!bloco || !modelo) return saida; 907 var outros = modelos(s).map(function (m) { return m.nome; }); 908 var por = {}, ordem = [], vistas = {}; 909 for (var i = 0; i < bloco.vs.length; i++) { 910 var nomeLoja = bloco.vs[i][0], med = bloco.vs[i][1], idx = bloco.vs[i][2]; 911 var n = casaInicio(nomeLoja, modelo); 912 if (!n) continue; 913 var dono = true; 914 for (var o = 0; o < outros.length; o++) { 915 if (chave(outros[o]) !== chave(modelo) && casaInicio(nomeLoja, outros[o]) > n) { dono = false; break; } 916 } 917 if (!dono) continue; 918 if (!por[med]) { por[med] = { medida: med, indiceNaLoja: idx || INDICE_LOJA[med] || '', versoes: [] }; ordem.push(med); } 919 por[med].versoes.push(nomeLoja); 920 vistas[nomeLoja] = 1; 921 } 922 for (var q = 0; q < ordem.length; q++) saida.medidas.push(por[ordem[q]]); 923 saida.medidas.sort(function (a, b) { return b.versoes.length - a.versoes.length; }); 924 saida.versoesDaLoja = contaChaves(vistas); 925 return saida; 926 } 927 928 /** Os indices de carga e velocidade que a loja vende para uma medida. */ 929 function indiceNaLoja(medida) { 930 pegaGlobais(); 931 var m = lerMedida(medida); 932 if (!m) return ''; 933 return INDICE_LOJA[m.normal] || INDICE_LOJA[m.normal.replace(/C$/, '')] || ''; 934 } 935 936 function resumo() { 937 pegaGlobais(); 938 var ger = 0, ver = 0, comIndice = 0, mods = {}; 939 var cat = { marcas: {}, modelos: {}, geracoes: 0, versoes: 0 }, fic = { marcas: {}, modelos: {}, versoes: 0 }; 940 for (var s in VEICULOS) { 941 var lista = entradas(s); 942 for (var i = 0; i < lista.length; i++) { 943 var km = s + '|' + chave(lista[i].n), vs = lista[i].vs || []; 944 ger++; 945 mods[km] = 1; 946 ver += vs.length; 947 if (lista[i].c) { cat.marcas[s] = 1; cat.modelos[km] = 1; cat.geracoes++; cat.versoes += vs.length; } 948 else { fic.marcas[s] = 1; fic.modelos[km] = 1; fic.versoes += vs.length; } 949 for (var j = 0; j < vs.length; j++) if (vs[j].i) comIndice++; 950 } 951 } 952 return { versao: VERSAO, marcas: contaChaves(MARCAS), marcasCarregadas: contaChaves(VEICULOS), 953 modelos: contaChaves(mods), geracoes: ger, versoes: ver, 954 comIndice: comIndice, medidas: medidasConhecidas().length, fontes: FONTES.length, 955 compatMarcas: contaChaves(COMPAT), 956 fichaTecnica: { marcas: contaChaves(fic.marcas), modelos: contaChaves(fic.modelos), versoes: fic.versoes }, 957 catalogo: { ativo: CONFIG.catalogo, marcas: contaChaves(cat.marcas), modelos: contaChaves(cat.modelos), 958 geracoes: cat.geracoes, versoes: cat.versoes } }; 959 } 960 961 // ================================================================ [8] cascata no DOM 962 /** Liga quatro campos de selecao. Sem estilo e sem texto proprio: quem chama decide tudo. 963 * opcoes: {marca, modelo, ano, versao} podem ser elemento ou seletor CSS. 964 * rotulos: {marca:'...', modelo:'...', ano:'...', versao:'...'} para a opcao vazia. 965 * onResultado(resultado, contexto) roda a cada escolha completa. 966 * onEstado({carregando, etapa}) avisa quando esta carregando uma marca. 967 * exigirVersao: false mostra o resultado assim que o ano e escolhido. 968 * autoVersao: false nao escolhe sozinho a versao quando o ano so tem uma. 969 */ 970 function ligarCascata(opcoes) { 971 opcoes = opcoes || {}; 972 var doc = (typeof window !== 'undefined') ? window.document : null; 973 if (!doc) throw new Error('ligarCascata precisa de um documento'); 974 975 function el(x) { return typeof x === 'string' ? doc.querySelector(x) : x; } 976 var cMarca = el(opcoes.marca), cModelo = el(opcoes.modelo); 977 var cAno = el(opcoes.ano), cVersao = el(opcoes.versao); 978 if (!cMarca || !cModelo || !cAno) throw new Error('ligarCascata precisa de marca, modelo e ano'); 979 var rot = opcoes.rotulos || {}; 980 var exigirVersao = opcoes.exigirVersao !== false && !!cVersao; 981 var estado = { marca: '', modelo: '', ano: 0, versao: '' }; 982 983 function avisaEstado(carregando, etapa) { 984 if (opcoes.onEstado) opcoes.onEstado({ carregando: !!carregando, etapa: etapa || '' }); 985 } 986 987 function enche(campo, itens, rotulo, valorDe, textoDe) { 988 if (!campo) return; 989 campo.innerHTML = ''; 990 var vazio = doc.createElement('option'); 991 vazio.value = ''; 992 vazio.textContent = rotulo || ''; 993 campo.appendChild(vazio); 994 for (var i = 0; i < itens.length; i++) { 995 var o = doc.createElement('option'); 996 o.value = valorDe(itens[i]); 997 o.textContent = textoDe(itens[i]); 998 campo.appendChild(o); 999 } 1000 campo.disabled = itens.length === 0; 1001 } 1002 1003 function zera(campo, rotulo) { 1004 if (!campo) return; 1005 campo.innerHTML = ''; 1006 var o = doc.createElement('option'); 1007 o.value = ''; o.textContent = rotulo || ''; 1008 campo.appendChild(o); 1009 campo.disabled = true; 1010 } 1011 1012 function dispara() { 1013 if (!estado.marca || !estado.modelo || !estado.ano) return; 1014 if (exigirVersao && !estado.versao) return; 1015 var r = buscar({ marca: estado.marca, modelo: estado.modelo, ano: estado.ano, 1016 versao: exigirVersao ? estado.versao : '' }); 1017 if (opcoes.onResultado) opcoes.onResultado(r, { estado: estado, motor: api }); 1018 } 1019 1020 enche(cMarca, marcas(), rot.marca || '', function (m) { return m.slug; }, function (m) { return m.nome; }); 1021 zera(cModelo, rot.modelo || ''); 1022 zera(cAno, rot.ano || ''); 1023 zera(cVersao, rot.versao || ''); 1024 1025 cMarca.addEventListener('change', function () { 1026 estado.marca = cMarca.value; estado.modelo = ''; estado.ano = 0; estado.versao = ''; 1027 zera(cModelo, rot.modelo || ''); zera(cAno, rot.ano || ''); zera(cVersao, rot.versao || '');
1028 if (!estado.marca) { avisaEstado(false, 'marca'); return; } 1029 avisaEstado(true, 'marca'); 1030 carregar(estado.marca, function () { 1031 avisaEstado(false, 'marca'); 1032 enche(cModelo, modelos(estado.marca), rot.modelo || '', 1033 function (m) { return m.nome; }, 1034 function (m) { return m.nome + (opcoes.mostrarAnos ? ' (' + m.a1 + ' a ' + m.a2 + ')' : ''); }); 1035 }, function (e) { 1036 avisaEstado(false, 'marca'); 1037 if (opcoes.onErro) opcoes.onErro(e); 1038 }); 1039 }); 1040 1041 cModelo.addEventListener('change', function () { 1042 estado.modelo = cModelo.value; estado.ano = 0; estado.versao = ''; 1043 zera(cAno, rot.ano || ''); zera(cVersao, rot.versao || ''); 1044 if (!estado.modelo) return; 1045 enche(cAno, anos(estado.marca, estado.modelo), rot.ano || '', 1046 function (a) { return a; }, function (a) { return a; }); 1047 }); 1048 1049 cAno.addEventListener('change', function () { 1050 estado.ano = num(cAno.value); estado.versao = ''; 1051 zera(cVersao, rot.versao || ''); 1052 if (!estado.ano) return; 1053 // a ficha tecnica quando existe, senao o catalogo; uma opcao por versao, com todas as medidas dela 1054 var camadas = porCamada(versoes(estado.marca, estado.modelo, estado.ano)); 1055 var lista = [], porNome = {}; 1056 (camadas.ficha.length ? camadas.ficha : camadas.catalogo).forEach(function (v) { 1057 var item = porNome[v.versao]; 1058 if (!item) { item = porNome[v.versao] = { versao: v.versao, medidas: [] }; lista.push(item); } 1059 var txt = v.medida + (v.eixo !== 'todos' ? ' ' + v.eixo : ''); 1060 if (item.medidas.indexOf(txt) < 0) item.medidas.push(txt); 1061 }); 1062 enche(cVersao, lista, rot.versao || '', 1063 function (v) { return v.versao; }, 1064 function (v) { return v.versao + ': ' + v.medidas.join(' / '); }); 1065 // opcoes.autoVersao:false deixa a pessoa escolher mesmo quando so ha uma versao (o nome pode nao ser o dela) 1066 if (cVersao && lista.length === 1 && opcoes.autoVersao !== false) { 1067 cVersao.value = lista[0].versao; 1068 estado.versao = lista[0].versao; 1069 } 1070 dispara(); 1071 }); 1072 1073 if (cVersao) { 1074 cVersao.addEventListener('change', function () { 1075 estado.versao = cVersao.value; 1076 dispara(); 1077 }); 1078 } 1079 1080 return { 1081 estado: estado, 1082 escolher: function (p) { 1083 p = p || {};
1084 if (p.marca) { cMarca.value = slugDaMarca(p.marca); cMarca.dispatchEvent(new Event('change')); } 1085 var seguir = function () { 1086 if (p.modelo) { cModelo.value = p.modelo; cModelo.dispatchEvent(new Event('change')); } 1087 if (p.ano) { cAno.value = String(p.ano); cAno.dispatchEvent(new Event('change')); } 1088 if (p.versao && cVersao) { cVersao.value = p.versao; cVersao.dispatchEvent(new Event('change')); } 1089 }; 1090 if (p.marca) carregar(p.marca, seguir, seguir); else seguir(); 1091 }, 1092 zerar: function () { 1093 cMarca.value = ''; cMarca.dispatchEvent(new Event('change')); 1094 } 1095 }; 1096 } 1097 1098 // ================================================================ saida 1099 var api = { 1100 VERSAO: VERSAO, 1101 INDICE_CARGA: INDICE_CARGA, 1102 SIMBOLO_VELOCIDADE: SIMBOLO_VELOCIDADE, 1103 configurar: configurar, 1104 usarDados: usarDados, 1105 carregar: carregar, 1106 carregarTudo: carregarTudo, 1107 marcas: marcas, 1108 modelos: modelos, 1109 geracoes: geracoes, 1110 anos: anos, 1111 versoes: versoes, 1112 buscar: buscar, 1113 porMedida: porMedida, 1114 medidasConhecidas: medidasConhecidas, 1115 sugerir: sugerir, 1116 resumo: resumo, 1117 lerMedida: lerMedida, 1118 medidaValida: medidaValida, 1119 normalizarMedida: normalizarMedida, 1120 lerIndice: lerIndice, 1121 cargaDoIndice: cargaDoIndice, 1122 velocidadeDoSimbolo: velocidadeDoSimbolo, 1123 indiceParaCarga: indiceParaCarga, 1124 comparar: comparar, 1125 equivalentes: equivalentes, 1126 ligarCascata: ligarCascata, 1127 compativeis: compativeis, 1128 indiceNaLoja: indiceNaLoja, 1129 // ajudantes expostos porque quem integra costuma precisar 1130 utilidades: { slug: slug, chave: chave, semAcento: semAcento, num: num, arred: arred, unico: unico } 1131 }; 1132 return api; 1133}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.