From fa825643a7ee583e1a54953b4ed74847e2d336ed Mon Sep 17 00:00:00 2001 From: Jens von Pilgrim Date: Wed, 15 Nov 2023 17:43:38 +0100 Subject: [PATCH] fix issue 261: parser.tag is containing tag in non-text nodes --- lib/sax.js | 10 ++++- test/issue-261.js | 104 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 test/issue-261.js diff --git a/lib/sax.js b/lib/sax.js index ffd441a..6dbf587 100644 --- a/lib/sax.js +++ b/lib/sax.js @@ -622,7 +622,7 @@ S = sax.STATE function emit (parser, event, data) { - parser[event] && parser[event](data) + parser[event] && parser[event](data) } function emitNode (parser, nodeType, data) { @@ -680,6 +680,8 @@ } function newTag (parser) { + // emit text event BEFORE open tag (start) event + if (parser.textNode) closeText(parser); if (!parser.strict) parser.tagName = parser.tagName[parser.looseCase]() var parent = parser.tags[parser.tags.length - 1] || parser var tag = parser.tag = { name: parser.tagName, attributes: {} } @@ -831,7 +833,9 @@ } else { parser.state = S.TEXT } - parser.tag = null + // do not delete tag here, since we need this in + // non-tag nodes as the containing tag + // parser.tag = null parser.tagName = '' } parser.attribName = parser.attribValue = '' @@ -902,6 +906,8 @@ emitNode(parser, 'onclosenamespace', { prefix: p, uri: n }) }) } + // reset the current tag to the containing tag + parser.tag = parser.tags[parser.tags.length - 1]; } if (t === 0) parser.closedRoot = true parser.tagName = parser.attribValue = parser.attribName = '' diff --git a/test/issue-261.js b/test/issue-261.js new file mode 100644 index 0000000..c3f2764 --- /dev/null +++ b/test/issue-261.js @@ -0,0 +1,104 @@ +/** + * Test that `parser.tag` actually is the containing element of the current node. + */ +var sax = require('../lib/sax') +var tap = require('tap') + +var containingTag = "container"; +var modes = ["loose", "strict"]; + +/** + * Types of nodes for which events are tested. + */ +var nodeTypes = [ + { + name: "chardata", tag: false, events: ["text"], + sample: function (value) { return value } + }, + { + name: "comment", tag: false, events: ["comment"], + sample: function (value) { return "" } + }, + { + name: "cdsect", tag: false, events: ["cdata, opencdata, closecdata"], + sample: function (value) { return "" } + }, + { + name: "pi", tag: false, events: ["processinginstruction"], + sample: function (value) { return "" } + }, + { + name: "element", tag: true, events: ["opentag", "opentagstart", "closetag"], + sample: function (value) { return "<" + value + ">" } + }, +]; +/** + * Create a sample xml chunk within a containing tag. + * The result will look like this: + * `before between after` + * in which before, between and after are replaced with samples, e.g. (for chardata and comment): + * `data_1data_2`. + */ +function createSimpleSample(typeBeforeAfter, typeInBetween) { + var before = typeBeforeAfter.sample("data_1"); + var between = typeInBetween.sample("between"); + var after = typeBeforeAfter.sample("data_2"); + return "<" + containingTag + ">" + before + between + after + ""; +} + +/** + * Executes actual test for a combination of mode, nodeBeforeAfter and nodeBetween using + * `createSimpleSample` to create the xml chunk. + */ +function testContainingTagAvailableInNonTag(mode, nodeBeforeAfter, nodeBetween) { + + var xmlChunk = createSimpleSample(nodeBeforeAfter, nodeBetween); + var expectedTag = mode === "loose" ? containingTag.toUpperCase() : containingTag; + var expectedValues = ["data_1"]; + if (nodeBetween === nodeBeforeAfter) { + expectedValues.push("between"); + } + expectedValues.push("data_2"); + + var parser = sax.parser(mode === "strict"); // loose mode or strict mode + nodeBeforeAfter.events.forEach(function (event, index) { + var iExpectedValueIndex = 0; + parser["on" + event] = function (data) { + // value correct + if (index === 0) { + var value = typeof data == 'object' ? data.name : data; + var expectedValue = expectedValues[iExpectedValueIndex++]; + tap.equal(value, expectedValue, "on" + event + ": expected value (" + (iExpectedValueIndex - 1) + ") of " + nodeBeforeAfter.name + " to be '" + expectedValue + "', got '" + value + "' in " + mode + " mode" + ", chunk: " + xmlChunk); + } + // containing tag correct + var tagName = parser.tag ? parser.tag.name : undefined; + tap.equal(tagName, expectedTag, "on" + event + ": expected element '" + expectedTag + "', got '" + tagName + "' in " + mode + " mode " + (iExpectedValueIndex == 0 ? "before" : "after") + " " + nodeBetween.name + ", chunk: " + xmlChunk); + } + }); + parser.write(xmlChunk); +} + +/** + * Creates and runs test combinations for + * - different modes (2) + * - different types of nodes before and after (5) + * - another type of node (5) + * + * That makes 2*5*5 = 50 combinations, each one is tested for + * - correct value + * - correct containing tag + * on all kind of events which may be emitted by the parser for the beofre/after node. + * + * This results 120 tests (some cases are omitted because they do not make sense). + */ +modes.forEach(function (mode) { + nodeTypes + .filter(function (nodeType) { return !nodeType.tag }) + .forEach(function (nodeBeforeAfter) { + nodeTypes + .filter(function (nt) { return nodeBeforeAfter.name !== "chardata" || nt.name != "chardata" }) + .forEach(function (nodeBetween) { + testContainingTagAvailableInNonTag(mode, nodeBeforeAfter, nodeBetween); + }); + }); +}); \ No newline at end of file