{"record":{"id":"ca88fc0e38e824a3","repo":"dianping/cat","slug":"attribute-value-no-end-c-match","errorCode":null,"errorMessage":"attribute value no end '{c}' match","messagePattern":"attribute value no end '(.+?)' match","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"cat-home/src/main/webapp/assets/js/editor/worker-xml.js","lineNumber":1530,"sourceCode":"\t\t\t\tattrName = source.slice(start,p);\n\t\t\t\ts = S_EQ;\n\t\t\t}else if(s === S_ATTR_S){\n\t\t\t\ts = S_EQ;\n\t\t\t}else{\n\t\t\t\tthrow new Error('attribute equal must after attrName');\n\t\t\t}\n\t\t\tbreak;\n\t\tcase '\\'':\n\t\tcase '\"':\n\t\t\tif(s === S_EQ){//equal\n\t\t\t\tstart = p+1;\n\t\t\t\tp = source.indexOf(c,start)\n\t\t\t\tif(p>0){\n\t\t\t\t\tvalue = source.slice(start,p).replace(/&#?\\w+;/g,entityReplacer);\n\t\t\t\t\tel.add(attrName,value,start-1);\n\t\t\t\t\ts = S_E;\n\t\t\t\t}else{\n\t\t\t\t\tthrow new Error('attribute value no end \\''+c+'\\' match');\n\t\t\t\t}\n\t\t\t}else if(s == S_V){\n\t\t\t\tvalue = source.slice(start,p).replace(/&#?\\w+;/g,entityReplacer);\n\t\t\t\tel.add(attrName,value,start);\n\t\t\t\terrorHandler.warning('attribute \"'+attrName+'\" missed start quot('+c+')!!');\n\t\t\t\tstart = p+1;\n\t\t\t\ts = S_E\n\t\t\t}else{\n\t\t\t\tthrow new Error('attribute value must after \"=\"');\n\t\t\t}\n\t\t\tbreak;\n\t\tcase '/':\n\t\t\tswitch(s){\n\t\t\tcase S_TAG:\n\t\t\t\tel.setTagName(source.slice(start,p));\n\t\t\tcase S_E:\n\t\t\tcase S_S:\n\t\t\tcase S_C:","sourceCodeStart":1512,"sourceCodeEnd":1548,"githubUrl":"https://github.com/dianping/cat/blob/e815e74d4c2dd74edac831241f1253fcc7d25381/cat-home/src/main/webapp/assets/js/editor/worker-xml.js#L1512-L1548","documentation":"Unrecoverable unterminated attribute value in the XML worker's attribute tokenizer (worker-xml.js:~1530). After seeing attrName '=', the scanner hits a quote and searches for the matching closing quote with source.indexOf; if none exists in the rest of the document, it cannot locate the value's end and throws instead of guessing.","triggerScenarios":"A start tag whose attribute value quote is never closed: '<a href=\"http://x>' with the closing double quote missing, or a single-quoted value containing a stray unescaped construct so indexOf lands past EOF. The 'if (p>0) ... else throw' at line 1526-1530 fires.","commonSituations":"Mid-edit state while typing in the editor (worker parses before you type the closing quote), quotes stripped by naive string concatenation or minifiers, attribute values containing the quote character unescaped in generated XML.","solutions":["Add the missing closing quote character shown in the message (the message names it, e.g. '\\' or \\\"\").","When generating XML, escape quotes inside values (&quot; / &apos;) or use the opposite quote type to wrap.","While typing, ignore the transient error — it clears once the value is closed."],"exampleFix":"<!-- before -->\n<img src=\"logo.png alt=\"logo\">\n\n<!-- after -->\n<img src=\"logo.png\" alt=\"logo\">","handlingStrategy":"validation","validationCode":"// Verify every attribute value in a tag string is closed before parsing:\nfunction quotesBalanced(tagText) {\n  for (const q of ['\\\"', \"'\"]) {\n    const parts = tagText.split(q);\n    // inside a tag, quotes must appear in pairs\n    if (parts.length % 2 === 0) return false;\n  }\n  return true;\n}","typeGuard":null,"tryCatchPattern":"try {\n  parseStartTag(source, start, tagNameReplacer);\n} catch (ex) {\n  if (/no end .+ match/.test(ex.message)) reportUnterminatedValue(ex); else throw ex;\n}","preventionTips":["Type both quotes first, then fill the value.","Escape embedded quotes as &quot;/&apos; in generated XML.","Prefer an XML builder (DOM serialization) over string concatenation."],"tags":["xml","sax","ace-editor","attributes","unterminated-string"],"backgroundTag":null,"analyzedSha":"e815e74d4c2dd74edac831241f1253fcc7d25381","analyzedAt":"2026-08-14T14:22:34.512Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}