forked from plotly/dash-html-components
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathextract-attributes.js
146 lines (124 loc) · 5.17 KB
/
extract-attributes.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
'use strict';
const fs = require('fs');
const cheerio = require('cheerio');
const request = require('request');
const str = require('string');
const htmlURL = 'https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes';
const dataPath = './data/attributes.json';
const htmlPath = './data/attributes.html';
// From https://facebook.github.io/react/docs/tags-and-attributes.html#supported-attributes
// less the special `className` and `htmlFor` props,
// and `httpEquiv` + `acceptCharset` which are already correctly camelCased.
const supportedAttributes = ['accept', 'accessKey', 'action', 'allow',
'allowFullScreen', 'allowTransparency', 'alt', 'async', 'autoComplete',
'autoFocus', 'autoPlay', 'capture', 'cellPadding', 'cellSpacing', 'challenge',
'charSet', 'checked', 'cite', 'classID', 'colSpan', 'cols', 'content',
'contentEditable', 'contextMenu', 'controls', 'coords', 'crossOrigin', 'data',
'dateTime', 'default', 'defer', 'dir', 'disabled', 'download', 'draggable',
'encType', 'form', 'formAction', 'formEncType', 'formMethod', 'formNoValidate',
'formTarget', 'frameBorder', 'headers', 'height', 'hidden', 'high', 'href',
'hrefLang', 'icon', 'id', 'inputMode', 'integrity', 'is',
'keyParams', 'keyType', 'kind', 'label', 'lang', 'list', 'loop', 'low',
'manifest', 'marginHeight', 'marginWidth', 'max', 'maxLength', 'media',
'mediaGroup', 'method', 'min', 'minLength', 'multiple', 'muted', 'name',
'noValidate', 'nonce', 'open', 'optimum', 'pattern', 'placeholder', 'poster',
'preload', 'profile', 'radioGroup', 'readOnly', 'referrerPolicy', 'rel', 'required',
'reversed', 'role', 'rowSpan', 'rows', 'sandbox', 'scope', 'scoped', 'scrolling',
'seamless', 'selected', 'shape', 'size', 'sizes', 'span', 'spellCheck', 'src',
'srcDoc', 'srcLang', 'srcSet', 'start', 'step', 'style', 'summary', 'tabIndex',
'target', 'title', 'type', 'useMap', 'value', 'width', 'wmode', 'wrap'];
// Create a map of HTML attribute to React prop
// e.g. {"datetime": "dateTime"}
const attributeMap = supportedAttributes.reduce((map, reactAttribute) => {
const htmlAttribute = reactAttribute.toLowerCase();
map[htmlAttribute] = reactAttribute;
return map;
},
// Start the map with two attributes that have special names in React, and
// two attributes that already have camelCasing in HTML.
{
'class': 'className',
'for': 'htmlFor',
'httpEquiv': 'httpEquiv',
'acceptCharset': 'acceptCharset'
}
);
/**
* From the MDN attributes reference, extract a map of attributes with
* descriptions and supported elements.
*/
function extractAttributes($) {
const $table = $('#Attribute_list,#attribute_list').parent().find('table');
if($table.length !== 1) {
throw new Error('page structure changed at ' + htmlURL);
}
const attributes = {};
$table.find('tbody tr').each((i, row) => {
const $children = cheerio(row).find('td');
const attribute = $children.eq(0).text();
const elements = $children.eq(1).text()
.replace(/[<>\s]/g, '')
.split(',');
const description = $children.eq(2).text()
.replace(/\n/g, '')
// Fix irregular whitespace characters
.replace(' ', ' ')
.trim();
const htmlAttribute = str(attribute)
.trim()
// Convert e.g. `accept-charset` to `acceptCharset`
.camelize()
.toString();
// Skip `data-*` attributes
if (htmlAttribute.indexOf('data-') === 0) {
return;
}
// Ensure attribute is supported by React
if (!attributeMap[htmlAttribute]) {
return;
}
// maxlength -> maxLength; class -> className
const reactAttribute = attributeMap[htmlAttribute];
attributes[reactAttribute] = {
elements,
description
};
});
return attributes;
}
/**
* Create a map of elements to attributes e.g. {div: ['id', 'name']}
*/
function extractElements(attributes) {
return Object.keys(attributes)
.reduce((elementMap, attributeName) => {
const attribute = attributes[attributeName];
const elements = attribute.elements;
elements.forEach((element) => {
elementMap[element] = elementMap[element] || [];
elementMap[element].push(attributeName);
});
return elementMap;
}, {});
}
// A local copy of the MDN attributes web page has been saved for reference:
// fs.readFile('./data/attributes.html', 'utf-8', (error, html) => {
request(htmlURL, (error, response, html) => {
if (error) {
throw error;
}
const html2 = html.split(/\n\r|\r\n|\r|\n/).map(l => l.trimRight()).join('\n');
// write back to the saved copy of MDN attributes so we can see the diff
fs.writeFileSync(htmlPath, html2);
const $ = cheerio.load(html);
const attributes = extractAttributes($);
const elements = extractElements(attributes);
const out = {
attributes,
elements
};
// Print out JSON with 4-space indentation formatting.
// http://stackoverflow.com/a/11276104
const tabWidth = 4;
fs.writeFileSync(dataPath, JSON.stringify(out, null, tabWidth));
});