Skip to content

Commit 5b82746

Browse files
authored
Merge pull request #89 from lyfeyaj/master
feat: add `recognizeNoValueAttribute` option for issue #75
2 parents 8dffdba + a2a2aa3 commit 5b82746

File tree

3 files changed

+73
-18
lines changed

3 files changed

+73
-18
lines changed

Diff for: readme.md

+23-18
Original file line numberDiff line numberDiff line change
@@ -81,45 +81,50 @@ Tag objects can contain three keys. The `tag` key takes the name of the tag as t
8181
## Options
8282

8383
### `directives`
84-
Type: `Array`
85-
Default: `[{name: '!doctype', start: '<', end: '>'}]`
86-
Description: *Adds processing of custom directives. Note: The property ```name``` in custom directives can be ```String``` or ```RegExp``` type*
84+
Type: `Array`
85+
Default: `[{name: '!doctype', start: '<', end: '>'}]`
86+
Description: *Adds processing of custom directives. Note: The property ```name``` in custom directives can be ```String``` or ```RegExp``` type*
8787

8888
### `xmlMode`
89-
Type: `Boolean`
90-
Default: `false`
89+
Type: `Boolean`
90+
Default: `false`
9191
Description: *Indicates whether special tags (`<script>` and `<style>`) should get special treatment and if "empty" tags (eg. `<br>`) can have children. If false, the content of special tags will be text only. For feeds and other XML content (documents that don't consist of HTML), set this to true.*
9292

9393
### `decodeEntities`
94-
Type: `Boolean`
95-
Default: `false`
94+
Type: `Boolean`
95+
Default: `false`
9696
Description: *If set to true, entities within the document will be decoded.*
9797

9898
### `lowerCaseTags`
99-
Type: `Boolean`
100-
Default: `false`
99+
Type: `Boolean`
100+
Default: `false`
101101
Description: *If set to true, all tags will be lowercased. If `xmlMode` is disabled.*
102102

103103
### `lowerCaseAttributeNames`
104-
Type: `Boolean`
105-
Default: `false`
104+
Type: `Boolean`
105+
Default: `false`
106106
Description: *If set to true, all attribute names will be lowercased. This has noticeable impact on speed.*
107107

108108
### `recognizeCDATA`
109-
Type: `Boolean`
110-
Default: `false`
109+
Type: `Boolean`
110+
Default: `false`
111111
Description: *If set to true, CDATA sections will be recognized as text even if the `xmlMode` option is not enabled. NOTE: If `xmlMode` is set to `true` then CDATA sections will always be recognized as text.*
112112

113113
### `recognizeSelfClosing`
114-
Type: `Boolean`
115-
Default: `false`
114+
Type: `Boolean`
115+
Default: `false`
116116
Description: *If set to true, self-closing tags will trigger the `onclosetag` event even if `xmlMode` is not set to `true`. NOTE: If `xmlMode` is set to `true` then self-closing tags will always be recognized.*
117117

118-
### `sourceLocations`
119-
Type: `Boolean`
120-
Default: `false`
118+
### `sourceLocations`
119+
Type: `Boolean`
120+
Default: `false`
121121
Description: *If set to true, AST nodes will have a `location` property containing the `start` and `end` line and column position of the node.*
122122

123+
### `recognizeNoValueAttribute`
124+
Type: `Boolean`
125+
Default: `false`
126+
Description: *If set to true, AST nodes will recognize attribute with no value and mark as `true` which will be correctly rendered by `posthtml-render` package*
127+
123128
## License
124129

125130
[MIT](LICENSE)

Diff for: src/index.ts

+23
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export type Directive = {
1010
export type Options = {
1111
directives?: Directive[];
1212
sourceLocations?: boolean;
13+
recognizeNoValueAttribute?: boolean;
1314
} & ParserOptions;
1415

1516
export type Tag = string | boolean;
@@ -45,6 +46,7 @@ export const parser = (html: string, options: Options = {}): Node[] => {
4546
const bufArray: Node[] = [];
4647
const results: Node[] = [];
4748
let lastOpenTagEndIndex = 0;
49+
let noValueAttributes: Record<string, true> = {};
4850

4951
function bufferArrayLast(): Node {
5052
return bufArray[bufArray.length - 1];
@@ -70,6 +72,11 @@ export const parser = (html: string, options: Options = {}): Node[] => {
7072
const object: Attributes = {};
7173

7274
object[key] = String(attrs[key]).replace(/&quot;/g, '"');
75+
76+
if (options.recognizeNoValueAttribute && noValueAttributes[key]) {
77+
object[key] = true;
78+
}
79+
7380
Object.assign(result, object);
7481
});
7582

@@ -122,6 +129,17 @@ export const parser = (html: string, options: Options = {}): Node[] => {
122129
}
123130
}
124131

132+
function onattribute(name: string, value: string, quote?: string | undefined | null) {
133+
// Quote: Quotes used around the attribute.
134+
// `null` if the attribute has no quotes around the value,
135+
// `undefined` if the attribute has no value.
136+
if (quote === undefined) {
137+
// `true` is recognized by posthtml-render as attrubute without value
138+
// See: https://github.com/posthtml/posthtml-render/blob/master/src/index.ts#L268
139+
noValueAttributes[name] = true;
140+
}
141+
}
142+
125143
function onopentag(tag: string, attrs: Attributes) {
126144
const buf: NodeTag = { tag };
127145

@@ -137,6 +155,10 @@ export const parser = (html: string, options: Options = {}): Node[] => {
137155
buf.attrs = normalizeArributes(attrs);
138156
}
139157

158+
// Always reset after normalizeArributes
159+
// Reason: onopentag callback will fire after all attrubutes have been processed
160+
noValueAttributes = {};
161+
140162
bufArray.push(buf);
141163
}
142164

@@ -201,6 +223,7 @@ export const parser = (html: string, options: Options = {}): Node[] => {
201223
const parser = new Parser({
202224
onprocessinginstruction,
203225
oncomment,
226+
onattribute,
204227
onopentag,
205228
onclosetag,
206229
ontext

Diff for: test/test-core.spec.ts

+27
Original file line numberDiff line numberDiff line change
@@ -370,3 +370,30 @@ test('should parse with input in button', t => {
370370
];
371371
t.deepEqual(tree, expected);
372372
});
373+
374+
test('should parse no value attribute as `true` when `recognizeNoValueAttribute` is `true` ', t => {
375+
const tree = parser(
376+
'<div class="className" hasClass>Content</div>',
377+
{ recognizeNoValueAttribute: true }
378+
);
379+
const expected = [
380+
{
381+
tag: 'div',
382+
attrs: { class: 'className', hasClass: true },
383+
content: ['Content']
384+
}
385+
];
386+
t.deepEqual(tree, expected);
387+
});
388+
389+
test('should parse no value attribute as empty string when `recognizeNoValueAttribute` is `false` or not set ', t => {
390+
const tree = parser('<div class="className" hasClass>Content</div>');
391+
const expected = [
392+
{
393+
tag: 'div',
394+
attrs: { class: 'className', hasClass: '' },
395+
content: ['Content']
396+
}
397+
];
398+
t.deepEqual(tree, expected);
399+
});

0 commit comments

Comments
 (0)