Skip to content

Commit

Permalink
refactor: always defer attribute processing
Browse files Browse the repository at this point in the history
sax would follow different processing paths depending on whether xmlns
was on or off. We've reduced it to a single path to simplify the
logic.
  • Loading branch information
lddubeau committed Jul 4, 2018
1 parent 4327eec commit 0773c15
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 60 deletions.
31 changes: 9 additions & 22 deletions lib/saxes.js
Original file line number Diff line number Diff line change
Expand Up @@ -858,12 +858,6 @@ class SAXParser {
}

attrib() {
if (this.attribList.indexOf(this.attribName) !== -1 ||
this.tag.attributes[this.attribName]) {
this.attribName = this.attribValue = "";
return;
}

if (this.opt.xmlns) {
const { prefix, local } = qname(this.attribName, true);

Expand All @@ -886,21 +880,9 @@ class SAXParser {
tag.ns[local] = this.attribValue;
}
}

// defer onattribute events until all attributes have been seen
// so any new bindings can take effect. preserve attribute order
// so deferred events can be emitted in document order
this.attribList.push([this.attribName, this.attribValue]);
}
else {
// in non-xmlns mode, we can emit the event right away
this.tag.attributes[this.attribName] = this.attribValue;
this.emitNode("onattribute", {
name: this.attribName,
value: this.attribValue,
});
}

this.attribList.push([this.attribName, this.attribValue]);
this.attribName = this.attribValue = "";
}

Expand Down Expand Up @@ -957,7 +939,6 @@ ${XML_NAMESPACE}.`);
}
}

// handle deferred onattribute events
// Note: do not apply default ns to attributes:
// http://www.w3.org/TR/REC-xml-names/#defaulting
for (const [name, value] of this.attribList) {
Expand All @@ -981,8 +962,15 @@ ${XML_NAMESPACE}.`);
this.tag.attributes[name] = a;
this.emitNode("onattribute", a);
}
this.attribList.length = 0;
}
else {
for (const [name, value] of this.attribList) {
const a = { name, value };
this.tag.attributes[name] = value;
this.emitNode("onattribute", a);
}
}
this.attribList.length = 0;

this.tag.isSelfClosing = !!selfClosing;

Expand All @@ -996,7 +984,6 @@ ${XML_NAMESPACE}.`);
this.tagName = "";
}
this.attribName = this.attribValue = "";
this.attribList.length = 0;
}

closeTag() {
Expand Down
2 changes: 1 addition & 1 deletion test/attribute-no-space.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ require(".").test({
xml: "<root attr1=\"first\"attr2=\"second\"/>",
expect: [
["opentagstart", { name: "root", attributes: {} }],
["attribute", { name: "attr1", value: "first" }],
["error", "No whitespace between attributes\nLine: 0\nColumn: 20\nChar: a"],
["attribute", { name: "attr1", value: "first" }],
["attribute", { name: "attr2", value: "second" }],
["opentag", { name: "root", attributes: { attr1: "first", attr2: "second" }, isSelfClosing: true }],
["closetag", "root"],
Expand Down
3 changes: 2 additions & 1 deletion test/duplicate-attribute.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ require(".").test({
attributes: {},
}],
["attribute", { name: "id", value: "hello" }],
["attribute", { name: "id", value: "there" }],
["opentag", {
name: "span",
attributes: { id: "hello" },
attributes: { id: "there" },
isSelfClosing: false,
}],
["closetag", "span"],
Expand Down
67 changes: 31 additions & 36 deletions test/xml-internal-entities.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
"use strict";

const iExpect = [];
const myAttributes = {};
const ENTITIES = {};

// generates xml like test0="&control;"
const entitiesToTest = {
// 'ENTITY_NAME': IS_VALID || [invalidCharPos, invalidChar],
Expand All @@ -22,27 +18,20 @@ const entitiesToTest = {
};

let xmlStart = "<a test=\"&amp;\" ";
const xmlEnd = "/>";

iExpect.push([
"opentagstart",
{
name: "a",
attributes: {},
},
]);

iExpect.push([
const myAttributes = {};
const attributeEvents = [[
"attribute",
{
name: "test",
value: "&",
},
]);
]];
myAttributes.test = "&";

let entI = 0;

const attributeErrors = [];
const ENTITIES = {};
// eslint-disable-next-line guard-for-in
for (const entity in entitiesToTest) {
const attribName = `test${entI}`;
Expand All @@ -52,47 +41,53 @@ for (const entity in entitiesToTest) {
xmlStart += `${attribName}="&`;

if (typeof entitiesToTest[entity] === "object") {
iExpect.push([
attributeErrors.push([
"error",
`Invalid character in entity name\nLine: 0\nColumn: ${
xmlStart.length + entitiesToTest[entity][0] + 1
}\nChar: ${entitiesToTest[entity][1]}`,
]);
iExpect.push([
attributeEvents.push([
"attribute",
{ name: attribName, value: `&${entity};` },
]);
myAttributes[attribName] = `&${entity};`;
}
else {
ENTITIES[entity] = attribValue;
iExpect.push(["attribute", { name: attribName, value: attribValue }]);
attributeEvents.push(["attribute",
{ name: attribName, value: attribValue }]);
myAttributes[attribName] = attribValue;
}

xmlStart += `${entity};" `;
entI++;
}

iExpect.push([
"opentag",
{
name: "a",
attributes: myAttributes,
isSelfClosing: true,
},
]);
iExpect.push(["closetag", "a"]);

require(".").test({
name: "xml internal entities",
expect: iExpect,
expect: [
[
"opentagstart",
{
name: "a",
attributes: {},
},
],
...attributeErrors,
...attributeEvents,
[
"opentag",
{
name: "a",
attributes: myAttributes,
isSelfClosing: true,
},
],
["closetag", "a"],
],
fn(parser) {
// eslint-disable-next-line guard-for-in
for (const entity in entitiesToTest) {
parser.ENTITIES[entity] = ENTITIES[entity];
}

parser.write(xmlStart + xmlEnd).close();
Object.assign(parser.ENTITIES, ENTITIES);
parser.write(`${xmlStart}/>`).close();
},
});

0 comments on commit 0773c15

Please sign in to comment.