Skip to content

Commit

Permalink
more attdef, notation
Browse files Browse the repository at this point in the history
  • Loading branch information
bathos committed Feb 11, 2017
1 parent 5d14e24 commit a0b973f
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 28 deletions.
4 changes: 4 additions & 0 deletions src/ast/nodes/element.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ class Element extends ASTNode {
if (attDef.type === 'IDREF') {
return this.document.findDeepByID(value);
}

if (attDef.type === 'NOTATION') {
return this.doctype.getNotation(value);
}
}

getReferences(key) {
Expand Down
36 changes: 33 additions & 3 deletions src/processor/productions/attlist-decl.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ import {

const typeCPs = [ C_UPPER, E_UPPER, I_UPPER, N_UPPER, PARENTHESIS_LEFT ];

const hasOneOfSameType = (nodes, name, type) => nodes.doctype
.getAll()
.filter(node => node.elementName === name)
.reduce((acc, node) => [ ...acc, ...node ], [])
.some(node => node.type === type);

export default function * (nodes) {
yield * series(ATTLIST_CPS, 1);
yield * plus(isWhitespaceChar);
Expand Down Expand Up @@ -88,7 +94,15 @@ export default function * (nodes) {
yield cp;
}
} else {
if (hasOneOfSameType(nodes, attlist.elementName, 'ID')) {
yield (
`element ${ attlist.elementName } not to have more than ` +
`one attribute of type ID`
);
}

attdef.type = 'ID';

yield cp;
}

Expand All @@ -110,7 +124,15 @@ export default function * (nodes) {
break;

case O_UPPER:
yield * series(NOTATION_CPS);
yield * series(NOTATION_CPS, 2);

if (hasOneOfSameType(nodes, attlist.elementName, 'NOTATION')) {
yield (
`element ${ attlist.elementName } not to have more than ` +
`one attribute of type NOTATION`
);
}

yield * plus(isWhitespaceChar);
yield * one(PARENTHESIS_LEFT);

Expand Down Expand Up @@ -171,10 +193,18 @@ export default function * (nodes) {

yield * plus(isWhitespaceChar);

cp = yield * oneOf(HASH_SIGN, QUOTE_DBL, QUOTE_SNG);
const hashContinuationCPs = [ I_UPPER, R_UPPER ];

if (attdef.type === 'ID') {
yield * one(HASH_SIGN);
cp = HASH_SIGN;
} else {
cp = yield * oneOf(HASH_SIGN, QUOTE_DBL, QUOTE_SNG);
hashContinuationCPs.push(F_UPPER);
}

if (cp === HASH_SIGN) {
switch (yield * oneOf(F_UPPER, I_UPPER, R_UPPER)) {
switch (yield * oneOf(...hashContinuationCPs)) {
case F_UPPER:
yield * series(FIXED_CPS, 1);
yield * plus(isWhitespaceChar);
Expand Down
13 changes: 13 additions & 0 deletions test/processor/test-element-and-element-decl.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,19 @@ tap.test('declared MIXED element (with children, violated)', test => {
});
});

tap.test('declared MIXED element (with dupes)', test => {
parse(`
<!DOCTYPE foo [
<!ELEMENT foo (#PCDATA|foo|foo)*>
]>
<foo><foo/></foo>
`).catch(err => {
test.match(err.message, 'appear twice');
test.end();
});
});

tap.test('declared element (with series child, singular)', test => {
parse(`
<!DOCTYPE foo [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,48 +133,42 @@ tap.test('supplying of attribute default', test => {
.then(test.end);
});

tap.test('default must match grammatical constraints even if unused', test => {
tap.test('ID type attribute may not have default', test => {
parse(`
<!DOCTYPE foo [
<!ELEMENT foo EMPTY>
<!ATTLIST foo
bar NMTOKEN '#baz'
>
<!ATTLIST foo bar ID 'baz'>
]>
<foo/>
<foo bar="qux"/>
`).catch(err => {
test.match(err.message, 'NMTOKEN');
test.match(err.message, 'ID');
test.end();
});
});

tap.test('default need not match other VCs if never used', test => {
tap.test('multiple ID attributes per element are invalid', test => {
parse(`
<!DOCTYPE foo [
<!ELEMENT foo EMPTY>
<!ATTLIST foo
bar ENTITY 'baz'
>
<!NOTATION corge PUBLIC "grault">
<!ENTITY qux SYSTEM "garply" NDATA corge>
<!ATTLIST foo bar ID #REQUIRED>
<!ATTLIST foo baz ID #REQUIRED>
]>
<foo bar="qux"/>
`).then(([ , elem ]) => {
test.equal(elem.bar, 'qux');
})
.catch(test.error)
.then(test.end);
<foo bar="qux" baz="qux"/>
`).catch(err => {
test.match(err.message, 'ID');
test.end();
});
});

tap.test('ID, IDREF, IDREFS attributes behavior and normalization', test => {
tap.test('ID, IDREF, IDREFS attributes behavior', test => {
parse(`
<!DOCTYPE foo [
<!ELEMENT foo (foo?)>
<!ATTLIST foo
bar ID #REQUIRED
baz IDREF #IMPLIED
bar ID #REQUIRED
baz IDREF #IMPLIED
qux IDREFS #IMPLIED
>
]>
Expand Down Expand Up @@ -204,16 +198,115 @@ tap.test('ID, IDREF, IDREFS attributes behavior and normalization', test => {
.then(test.end);
});

tap.test('ID type attribute may not have default', test => {
tap.test('NOTATION attribute behavior', test => {
parse(`
<!DOCTYPE foo [
<!NOTATION qux PUBLIC "qux">
<!NOTATION quux SYSTEM "quux">
<!NOTATION corge PUBLIC "corgePublic" "corgeSystem">
<!ELEMENT foo EMPTY>
<!ATTLIST foo bar ID 'baz'>
<!ATTLIST foo bar NOTATION (qux|quux|corge) #REQUIRED>
]>
<foo bar="qux"/>
<foo bar="corge"/>
`).then(([ , elem ]) => {
test.equal(elem.bar, 'corge');
test.equal(elem.notation.name, 'corge');
test.equal(elem.notation.publicID, 'corgePublic');
test.equal(elem.notation.systemID, 'corgeSystem');
test.equal(elem.getReference('bar'), elem.notation);
})
.catch(test.error)
.then(test.end);
});

tap.test('NOTATION redeclaration', test => {
parse(`
<!DOCTYPE foo [
<!NOTATION qux PUBLIC "qux">
<!NOTATION qux PUBLIC "qux">
<!ELEMENT foo EMPTY>
]>
<foo/>
`).catch(err => {
test.match(err.message, 'ID');
test.match(err.message, 'qux');
test.end();
});
});

tap.test('multiple NOTATION attributes per element are invalid', test => {
parse(`
<!DOCTYPE foo [
<!NOTATION qux PUBLIC "qux">
<!ELEMENT foo EMPTY>
<!ATTLIST foo
bar NOTATION (qux) #REQUIRED
baz NOTATION (qux) #REQUIRED
>
]>
<foo bar="qux" baz="qux"/>
`).catch(err => {
test.match(err.message, 'NOTATION');
test.end();
});
});

tap.test('NMTOKEN & NMTOKENS attribute behavior & normalization', test => {
parse(`
<!DOCTYPE foo [
<!ELEMENT foo EMPTY>
<!ENTITY d "&#xD;">
<!ENTITY a "&#xA;">
<!ENTITY da "&#xD;&#xA;">
<!ATTLIST foo
bar NMTOKEN #IMPLIED
baz NMTOKENS #IMPLIED
>
]>
<foo
bar="\n\nxyz"
baz="&d;&d;A&a;&#x20;&a;B&da;"
/>
`).then(([ , elem ]) => {
test.equal(elem.bar, 'xyz');
test.equal(elem.baz, 'A B');
})
.catch(test.error)
.then(test.end);
});

tap.test('default must match grammatical constraints even if unused', test => {
parse(`
<!DOCTYPE foo [
<!ELEMENT foo EMPTY>
<!ATTLIST foo bar NMTOKEN '#baz'>
]>
<foo/>
`).catch(err => {
test.match(err.message, 'NMTOKEN');
test.end();
});
});

tap.test('default need not match other VCs if never used', test => {
parse(`
<!DOCTYPE foo [
<!ELEMENT foo EMPTY>
<!ATTLIST foo
bar ENTITY 'baz'
>
<!NOTATION corge PUBLIC "grault">
<!ENTITY qux SYSTEM "garply" NDATA corge>
]>
<foo bar="qux"/>
`).then(([ , elem ]) => {
test.equal(elem.bar, 'qux');
})
.catch(test.error)
.then(test.end);
});

0 comments on commit a0b973f

Please sign in to comment.