Skip to content

Commit

Permalink
refactor: simplify namespace processing
Browse files Browse the repository at this point in the history
BREAKING CHANGE:

* The ``ns`` field is no longer using the prototype trick that sax used. The
  ``ns`` field of a tag contains only those namespaces that the tag declares.

* We no longer have ``opennamespace`` and ``closenamespace`` events. The
  information they provide can be obtained by examining the tags passed to tag
  events.
  • Loading branch information
lddubeau committed Jul 23, 2018
1 parent 0dab2fa commit 2d4ce0f
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 188 deletions.
70 changes: 23 additions & 47 deletions lib/saxes.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,6 @@ exports.EVENTS = [
"error",
"end",
"ready",
"opennamespace",
"closenamespace",
];

const buffers = [
Expand Down Expand Up @@ -336,7 +334,7 @@ class SaxesParser {
// it always points at the current tag,
// which protos to its parent tag.
if (this.opt.xmlns) {
this.ns = Object.create(rootNS);
this.ns = Object.assign({}, rootNS);
}

this.startTagPosition = undefined;
Expand Down Expand Up @@ -435,26 +433,6 @@ class SaxesParser {
*/
onready() {}

/**
* Event handler indicating that a new namespace binding is effective. This
* is called only when namespaces are tracked. The default implementation is
* a no-op.
*
* @param {{prefix: string, uri: string}} binding The prefix being bound and
* the URI bound to it.
*/
onopennamespace() {}

/**
* Event handler indicating that a new namespace binding is no longer
* effective. This is called only when namespaces are tracked. The default
* implementation is a no-op.
*
* @param {{prefix: string, uri: string}} binding The prefix being
* unbound and the URI bound to it.
*/
onclosenamespace() {}

/**
* Event handler indicating an error. The default implementation throws the
* error. Override with a no-op handler if you don't want this.
Expand Down Expand Up @@ -1320,16 +1298,15 @@ class SaxesParser {
return;
}

const parent = this.tags[this.tags.length - 1] || this;
const tag = this.tag = {
name: this.tagName,
attributes: Object.create(null),
};

// will be overridden if tag contails an xmlns="foo" or xmlns:foo="bar"
if (this.opt.xmlns) {
tag.ns = parent.ns;
tag.ns = Object.create(null);
}

this.attribList = [];
this.emitNode("onopentagstart", tag);

Expand Down Expand Up @@ -1652,6 +1629,21 @@ class SaxesParser {
this[nodeType](data);
}

_resolve(prefix, index) {
if (index < 0) {
return this.ns[prefix];
}

const uri = this.tags[index].ns[prefix];
return uri !== undefined ? uri : this._resolve(prefix, index - 1);
}

resolve(prefix) {
const uri = this.tag.ns[prefix];
return uri !== undefined ? uri :
this._resolve(prefix, this.tags.length - 1);
}

/**
* Parse a qname into its prefix and local name parts.
*
Expand Down Expand Up @@ -1693,7 +1685,7 @@ class SaxesParser {
/**
* Handle a complete open tag. This parser code calls this once it has seen
* the whole tag. This method checks for well-formeness and then emits
* ``onopentag``. It also emits any necessary ``onopennamespace``.
* ``onopentag``.
*
* @param {boolean} [selfClosing=false] Whether the tag is self-closing.
*
Expand All @@ -1703,10 +1695,7 @@ class SaxesParser {
const { tag } = this;
if (this.opt.xmlns) {
// emit namespace binding events
const parent = this.tags[this.tags.length - 1] || this;

let { ns } = tag;
let disconnectedNS = false;
const { ns } = tag;
// eslint-disable-next-line prefer-const
for (let [name, uri] of this.attribList) {
const { prefix, local } = this.qname(name, true);
Expand Down Expand Up @@ -1744,12 +1733,7 @@ ${XML_NAMESPACE}.`);
default:
}

if (!disconnectedNS) {
ns = tag.ns = Object.create(parent.ns);
disconnectedNS = true;
}
ns[local] = uri;
this.emitNode("onopennamespace", { prefix: local, uri });
}
}
}
Expand All @@ -1758,7 +1742,7 @@ ${XML_NAMESPACE}.`);
const qn = this.qname(this.tagName);
tag.prefix = qn.prefix;
tag.local = qn.local;
tag.uri = ns[qn.prefix] || "";
tag.uri = this.resolve(qn.prefix) || "";

if (tag.prefix) {
if (tag.prefix === "xmlns") {
Expand All @@ -1777,7 +1761,7 @@ ${JSON.stringify(this.tagName)}.`);
// http://www.w3.org/TR/REC-xml-names/#defaulting
for (const [name, value] of this.attribList) {
const { prefix, local } = this.qname(name, true);
const uri = prefix === "" ? "" : (ns[prefix] || "");
const uri = prefix === "" ? "" : (this.resolve(prefix) || "");
const a = {
name,
value,
Expand Down Expand Up @@ -1835,7 +1819,7 @@ ${JSON.stringify(this.tagName)}.`);
/**
* Handle a complete close tag. This parser code calls this once it has seen
* the whole tag. This method checks for well-formeness and then emits
* ``onclosetag``. It also emits any necessary ``onclosenamespace``.
* ``onclosetag``.
*
* @private
*/
Expand Down Expand Up @@ -1873,14 +1857,6 @@ ${JSON.stringify(this.tagName)}.`);
while (s-- > t) {
const tag = this.tag = tags.pop();
this.emitNode("onclosetag", tag.name);

const parent = tags[tags.length - 1] || this;
if (this.opt.xmlns && tag.ns !== parent.ns) {
// remove namespace bindings introduced by tag
for (const p of Object.keys(tag.ns)) {
this.emitNode("onclosenamespace", { prefix: p, uri: tag.ns[p] });
}
}
}
if (t === 0) {
this.inRoot = false;
Expand Down
14 changes: 0 additions & 14 deletions test/xmlns-issue-41.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,6 @@ const t = require(".");

const expect = [
["opentagstart", { name: "parent", attributes: {}, ns: {} }],
[
"opennamespace",
{
prefix: "a",
uri: "http://ATTRIBUTE",
},
],
[
"opentag",
{
Expand Down Expand Up @@ -44,13 +37,6 @@ const expect = [
"closetag",
"parent",
],
[
"closenamespace",
{
prefix: "a",
uri: "http://ATTRIBUTE",
},
],
];

// should be the same both ways.
Expand Down
65 changes: 5 additions & 60 deletions test/xmlns-rebinding.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,6 @@ require(".").test({
ns: {},
},
],
[
"opennamespace",
{
prefix: "x",
uri: "x1",
},
],
[
"opennamespace",
{
prefix: "y",
uri: "y1",
},
],
[
"opentag",
{
Expand Down Expand Up @@ -80,17 +66,7 @@ require(".").test({
{
name: "rebind",
attributes: {},
ns: {
x: "x1",
y: "y1",
},
},
],
[
"opennamespace",
{
prefix: "x",
uri: "x2",
ns: {},
},
],
[
Expand Down Expand Up @@ -120,9 +96,7 @@ require(".").test({
{
name: "check",
attributes: {},
ns: {
x: "x2",
},
ns: {},
},
],
[
Expand All @@ -148,9 +122,7 @@ require(".").test({
local: "a",
},
},
ns: {
x: "x2",
},
ns: {},
isSelfClosing: true,
},
],
Expand All @@ -162,22 +134,12 @@ require(".").test({
"closetag",
"rebind",
],
[
"closenamespace",
{
prefix: "x",
uri: "x2",
},
],
[
"opentagstart",
{
name: "check",
attributes: {},
ns: {
x: "x1",
y: "y1",
},
ns: {},
},
],
[
Expand All @@ -203,10 +165,7 @@ require(".").test({
local: "a",
},
},
ns: {
x: "x1",
y: "y1",
},
ns: {},
isSelfClosing: true,
},
],
Expand All @@ -218,20 +177,6 @@ require(".").test({
"closetag",
"root",
],
[
"closenamespace",
{
prefix: "x",
uri: "x1",
},
],
[
"closenamespace",
{
prefix: "y",
uri: "y1",
},
],
],
opt: {
xmlns: true,
Expand Down
Loading

0 comments on commit 2d4ce0f

Please sign in to comment.