Skip to content

Commit

Permalink
feat: reinstating the attribute events
Browse files Browse the repository at this point in the history
These had been dropped in the fork, but upcoming features warrant their
reintroduction.
  • Loading branch information
lddubeau committed Jan 29, 2020
1 parent af531cf commit 7c80f7b
Show file tree
Hide file tree
Showing 20 changed files with 356 additions and 45 deletions.
52 changes: 37 additions & 15 deletions src/saxes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ export const EVENTS = [
"doctype",
"comment",
"opentagstart",
"attribute",
"opentag",
"closetag",
"cdata",
Expand All @@ -194,6 +195,7 @@ const EVENT_NAME_TO_HANDLER_NAME: Record<EventName, string> = {
doctype: "doctypeHandler",
comment: "commentHandler",
opentagstart: "openTagStartHandler",
attribute: "attributeHandler",
opentag: "openTagHandler",
closetag: "closeTagHandler",
cdata: "cdataHandler",
Expand Down Expand Up @@ -241,6 +243,17 @@ export type CommentHandler = (comment: string) => void;
*/
export type OpenTagStartHandler<O> = (tag: StartTagForOptions<O>) => void;

export type AttributeEventForOptions<O extends SaxesOptions> =
O extends { xmlns: true } ? SaxesAttributeNSIncomplete :
O extends { xmlns?: false | undefined } ? SaxesAttributePlain :
SaxesAttribute;

/**
* Event handler for attributes.
*/
export type AttributeHandler<O> =
(attribute: AttributeEventForOptions<O>) => void;

/**
* Event handler for an open tag. This is called when the open tag is
* complete. (We've encountered the ">" that ends the open tag.) The default
Expand Down Expand Up @@ -294,6 +307,7 @@ export type EventNameToHandler<O, N extends EventName> = {
"doctype": DoctypeHandler;
"comment": CommentHandler;
"opentagstart": OpenTagStartHandler<O>;
"attribute": AttributeHandler<O>;
"opentag": OpenTagHandler<O>;
"closetag": CloseTagHandler<O>;
"cdata": CDataHandler;
Expand Down Expand Up @@ -333,20 +347,17 @@ export interface SaxesAttributeNS {
}

/**
* This is an alias for SaxesAttributeNS which will eventually be removed in a
* future major version.
*
* @deprecated
* This is an attribute, as recorded by a parser which parses namespaces but
* prior to the URI being resolvable. This is what is passed to the attribute
* event handler.
*/
export type SaxesAttribute = SaxesAttributeNS;
export type SaxesAttributeNSIncomplete = Exclude<SaxesAttributeNS, "uri">;

/**
* This interface defines the structure of attributes when the parser is
* NOT processing namespaces (created with ``xmlns: false``).
*
* This is not exported because this structure is used only internally.
*/
interface SaxesAttributePlain {
export interface SaxesAttributePlain {
/**
* The attribute's name.
*/
Expand All @@ -356,6 +367,11 @@ interface SaxesAttributePlain {
value: string;
}

/**
* A saxes attribute, with or without namespace information.
*/
export type SaxesAttribute = SaxesAttributeNS | SaxesAttributePlain;

/**
* This are the fields that MAY be present on a complete tag.
*/
Expand Down Expand Up @@ -544,7 +560,7 @@ export type TagForOptions<O extends SaxesOptions> =

export type StartTagForOptions<O extends SaxesOptions> =
O extends { xmlns: true } ? SaxesStartTagNS :
O extends { xmlns: false | undefined } ? SaxesStartTagPlain :
O extends { xmlns?: false | undefined } ? SaxesStartTagPlain :
SaxesStartTag;

export class SaxesParser<O extends SaxesOptions = {}> {
Expand Down Expand Up @@ -582,7 +598,7 @@ export class SaxesParser<O extends SaxesOptions = {}> {
private prevI!: number;
private carriedFromPrevious?: string;
private forbiddenState!: number;
private attribList!: (SaxesAttributeNS | SaxesAttributePlain)[];
private attribList!: (SaxesAttributeNSIncomplete | SaxesAttributePlain)[];
private state!: number;
private reportedTextBeforeRoot!: boolean;
private reportedTextAfterRoot!: boolean;
Expand Down Expand Up @@ -611,6 +627,7 @@ export class SaxesParser<O extends SaxesOptions = {}> {
private errorHandler?: ErrorHandler;
private endHandler?: EndHandler;
private readyHandler?: ReadyHandler;
private attributeHandler?: AttributeHandler<O>;

/**
* Indicates whether or not the parser is closed. If ``true``, wait for
Expand Down Expand Up @@ -2087,8 +2104,7 @@ export class SaxesParser<O extends SaxesOptions = {}> {
let { i: start } = this;
// eslint-disable-next-line no-constant-condition
while (true) {
const code = this.getCode();
switch (code) {
switch (this.getCode()) {
case q:
this.pushAttrib(this.name,
this.text + chunk.slice(start, this.prevI));
Expand Down Expand Up @@ -2359,7 +2375,10 @@ export class SaxesParser<O extends SaxesOptions = {}> {

private pushAttribNS(name: string, value: string): void {
const { prefix, local } = this.qname(name);
this.attribList.push({ name, prefix, local, value, uri: undefined });
const attr = { name, prefix, local, value };
this.attribList.push(attr);
// eslint-disable-next-line no-unused-expressions
this.attributeHandler?.(attr as AttributeEventForOptions<O>);
if (prefix === "xmlns") {
const trimmed = value.trim();
if (this.currentXMLVersion === "1.0" && trimmed === "") {
Expand All @@ -2376,7 +2395,10 @@ export class SaxesParser<O extends SaxesOptions = {}> {
}

private pushAttribPlain(name: string, value: string): void {
this.attribList.push({ name, value });
const attr = { name, value };
this.attribList.push(attr);
// eslint-disable-next-line no-unused-expressions
this.attributeHandler?.(attr as AttributeEventForOptions<O>);
}

/**
Expand Down Expand Up @@ -2493,7 +2515,7 @@ export class SaxesParser<O extends SaxesOptions = {}> {
const seen = new Set();
// Note: do not apply default ns to attributes:
// http://www.w3.org/TR/REC-xml-names/#defaulting
for (const attr of attribList as SaxesAttributeNS[]) {
for (const attr of attribList as SaxesAttributeNSIncomplete[]) {
const { name, prefix, local } = attr;
let uri;
let eqname;
Expand Down
6 changes: 6 additions & 0 deletions test/attribute-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ test({
xml: "<root length='12345'></root>",
expect: [
["opentagstart", { name: "root", attributes: {}, ns: {} }],
["attribute", {
name: "length",
value: "12345",
prefix: "",
local: "length",
}],
[
"opentag",
{
Expand Down
32 changes: 32 additions & 0 deletions test/attribute-no-space.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ test({
xml: "<root attr1=\"first\"attr2=\"second\"/>",
expect: [
["opentagstart", { name: "root", attributes: {} }],
["attribute", {
name: "attr1",
value: "first",
}],
["error", "1:20: no whitespace between attributes."],
["attribute", {
name: "attr2",
value: "second",
}],
["opentag", {
name: "root",
attributes: {
Expand Down Expand Up @@ -35,6 +43,14 @@ test({
name: "root",
attributes: {},
}],
["attribute", {
name: "attr1",
value: "first",
}],
["attribute", {
name: "attr2",
value: "second",
}],
["opentag", {
name: "root",
attributes: {
Expand Down Expand Up @@ -63,6 +79,14 @@ test({
name: "root",
attributes: {},
}],
["attribute", {
name: "attr1",
value: "first",
}],
["attribute", {
name: "attr2",
value: "second",
}],
["opentag", {
name: "root",
attributes: {
Expand Down Expand Up @@ -91,6 +115,14 @@ test({
name: "root",
attributes: {},
}],
["attribute", {
name: "attr1",
value: "first",
}],
["attribute", {
name: "attr2",
value: "second",
}],
["opentag", {
name: "root",
attributes: {
Expand Down
8 changes: 8 additions & 0 deletions test/attribute-normalization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ test({
name: "root",
attributes: {},
}],
["attribute", {
name: "attr1",
value: "\r \n\t",
}],
["attribute", {
name: "attr2",
value: " a b",
}],
["opentag", {
name: "root",
attributes: { attr1: "\r \n\t", attr2: " a b" },
Expand Down
6 changes: 6 additions & 0 deletions test/attribute-unquoted.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ test({
expect: [
["opentagstart", { name: "root", attributes: {}, ns: {} }],
["error", "1:14: unquoted attribute value."],
["attribute", {
name: "length",
value: "12345",
prefix: "",
local: "length",
}],
["opentag", {
name: "root",
attributes: {
Expand Down
1 change: 1 addition & 0 deletions test/bom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ test({
xml: "\uFEFF<P BOM=\"\uFEFF\">\uFEFFStarts and ends with BOM\uFEFF</P>",
expect: [
["opentagstart", { name: "P", attributes: {} }],
["attribute", { name: "BOM", value: "\uFEFF" }],
["opentag", {
name: "P",
attributes: { BOM: "\uFEFF" },
Expand Down
1 change: 1 addition & 0 deletions test/cdata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ test({
name: "cdata end in attribute",
expect: [
["opentagstart", { name: "r", attributes: {} }],
["attribute", { name: "foo", value: "]]>" }],
["opentag", {
name: "r",
attributes: {
Expand Down
2 changes: 2 additions & 0 deletions test/duplicate-attribute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ test({
name: "span",
attributes: {},
}],
["attribute", { name: "id", value: "hello" }],
["attribute", { name: "id", value: "there" }],
["error", "1:28: duplicate attribute: id."],
["opentag", {
name: "span",
Expand Down
16 changes: 16 additions & 0 deletions test/eol-handling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ describe("eol handling", () => {
const expect = [
["text", "\n\n"],
["opentagstart", { name: "moo", attributes: {} }],
["attribute", {
name: "a",
value: "12 3",
}],
["opentag", {
name: "moo",
attributes: {
Expand Down Expand Up @@ -142,6 +146,14 @@ SYSTEM
["processinginstruction", { target: "fnord", body: "" }],
["text", "\n"],
["opentagstart", { name: "moo", attributes: {} }],
["attribute", {
name: "a",
value: "12 3",
}],
["attribute", {
name: "b",
value: " z ",
}],
["opentag", {
name: "moo",
attributes: {
Expand All @@ -160,6 +172,10 @@ abc
["processinginstruction", { target: "fnord", body: "" }],
["text", "\n"],
["opentagstart", { name: "abc", attributes: {} }],
["attribute", {
name: "a",
value: "bc",
}],
["opentag", {
name: "abc",
attributes: {
Expand Down
1 change: 1 addition & 0 deletions test/issue-47.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ test({
xml: "<a href=\"query.svc?x=1&amp;y=2&amp;z=3\"/>",
expect: [
["opentagstart", { name: "a", attributes: {} }],
["attribute", { name: "href", value: "query.svc?x=1&y=2&z=3" }],
["opentag", {
name: "a",
attributes: { href: "query.svc?x=1&y=2&z=3" },
Expand Down
16 changes: 16 additions & 0 deletions test/opentagstart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ describe("openstarttag", () => {
attributes: {},
},
],
[
"attribute",
{
name: "length",
value: "12345",
prefix: "",
local: "length",
},
],
[
"opentag",
{
Expand Down Expand Up @@ -70,6 +79,13 @@ describe("openstarttag", () => {
attributes: {},
},
],
[
"attribute",
{
name: "length",
value: "12345",
},
],
[
"opentag",
{
Expand Down
12 changes: 6 additions & 6 deletions test/testutil.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { expect } from "chai";
import { EVENTS, SaxesOptions, SaxesParser } from "../build/dist/saxes";
import { EventName, EVENTS, SaxesOptions,
SaxesParser } from "../build/dist/saxes";

export interface TestOptions {
xml?: string | string[];
Expand All @@ -8,14 +9,15 @@ export interface TestOptions {
expect: any[];
fn?: (parser: SaxesParser<{}>) => void;
opt?: SaxesOptions;
events?: EventName[];
}

export function test(options: TestOptions): void {
const { xml, name, expect: expected, fn } = options;
const { xml, name, expect: expected, fn, events } = options;
it(name, () => {
const parser = new SaxesParser(options.opt);
let expectedIx = 0;
for (const ev of EVENTS) {
for (const ev of events ?? EVENTS) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any, no-loop-func
parser.on(ev, (n: any) => {
if (process.env.DEBUG !== undefined) {
Expand Down Expand Up @@ -49,9 +51,7 @@ export function test(options: TestOptions): void {
}
}

if (fn !== undefined) {
fn(parser);
}
fn?.(parser);

expect(expectedIx).to.equal(expected.length);
});
Expand Down
1 change: 1 addition & 0 deletions test/trailing-attribute-no-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ test({
expect: [
["opentagstart", { name: "root", attributes: {} }],
["error", "1:13: attribute without value."],
["attribute", { name: "attrib", value: "attrib" }],
["opentag",
{ name: "root", attributes: { attrib: "attrib" }, isSelfClosing: false }],
["closetag",
Expand Down
Loading

0 comments on commit 7c80f7b

Please sign in to comment.