Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix issue 261: parser.tag is containing tag in non-text nodes #263

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions lib/sax.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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: {} }
Expand Down Expand Up @@ -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 = ''
Expand Down Expand Up @@ -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 = ''
Expand Down
104 changes: 104 additions & 0 deletions test/issue-261.js
Original file line number Diff line number Diff line change
@@ -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 "<!--" + value + "-->" }
},
{
name: "cdsect", tag: false, events: ["cdata, opencdata, closecdata"],
sample: function (value) { return "<![CDATA[" + value + "]]>" }
},
{
name: "pi", tag: false, events: ["processinginstruction"],
sample: function (value) { return "<?" + value + "?>" }
},
{
name: "element", tag: true, events: ["opentag", "opentagstart", "closetag"],
sample: function (value) { return "<" + value + "></" + value + ">" }
},
];
/**
* Create a sample xml chunk within a containing tag.
* The result will look like this:
* `<inTag>before between after</inTag>`
* in which before, between and after are replaced with samples, e.g. (for chardata and comment):
* `<inTag>data_1<!--between-->data_2</inTag>`.
*/
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 + "</" + containingTag + ">";
}

/**
* 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);
});
});
});