-
Notifications
You must be signed in to change notification settings - Fork 742
/
url-props-from-attribute.js
143 lines (124 loc) · 3.33 KB
/
url-props-from-attribute.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
/* global dom */
/**
* Parse resource object for a given node from a specified attribute
* @method urlPropsFromAttribute
* @param {HTMLElement} node given node
* @param {String} attribute attribute of the node from which resource should be parsed
* @returns {Object}
*/
dom.urlPropsFromAttribute = function urlPropsFromAttribute(node, attribute) {
const value = node[attribute];
if (!value) {
return undefined;
}
const nodeName = node.nodeName.toUpperCase();
let parser = node;
/**
* Note:
* The need to create a parser, is to keep this function generic, to be able to parse resource from element like `iframe` with `src` attribute
*/
if (!['A', 'AREA'].includes(nodeName)) {
parser = document.createElement('a');
parser.href = value;
}
/**
* Curate `https` and `ftps` to `http` and `ftp` as they will resolve to same resource
*/
const protocol = [`https:`, `ftps:`].includes(parser.protocol)
? parser.protocol.replace(/s:$/, ':')
: parser.protocol;
const { pathname, filename } = getPathnameOrFilename(parser.pathname);
return {
protocol,
hostname: parser.hostname,
port: getPort(parser.port),
pathname: /\/$/.test(pathname) ? pathname : `${pathname}/`,
search: getSearchPairs(parser.search),
hash: getHashRoute(parser.hash),
filename
};
};
/**
* Resolve given port excluding default port(s)
* @param {String} port port
* @returns {String}
*/
function getPort(port) {
const excludePorts = [
`443`, // default `https` port
`80`
];
return !excludePorts.includes(port) ? port : ``;
}
/**
* Resolve if a given pathname has filename & resolve the same as parts
* @method getPathnameOrFilename
* @param {String} pathname pathname part of a given uri
* @returns {Array<Object>}
*/
function getPathnameOrFilename(pathname) {
const filename = pathname.split('/').pop();
if (!filename || filename.indexOf('.') === -1) {
return {
pathname,
filename: ``
};
}
return {
// remove `filename` from `pathname`
pathname: pathname.replace(filename, ''),
// ignore filename when index.*
filename: /index./.test(filename) ? `` : filename
};
}
/**
* Parse a given query string to key/value pairs sorted alphabetically
* @param {String} searchStr search string
* @returns {Object}
*/
function getSearchPairs(searchStr) {
const query = {};
if (!searchStr || !searchStr.length) {
return query;
}
// `substring` to remove `?` at the beginning of search string
const pairs = searchStr.substring(1).split(`&`);
if (!pairs || !pairs.length) {
return query;
}
for (let index = 0; index < pairs.length; index++) {
const pair = pairs[index];
const [key, value = ''] = pair.split(`=`);
query[decodeURIComponent(key)] = decodeURIComponent(value);
}
return query;
}
/**
* Interpret a given hash
* if `hash`
* -> is `hashbang` -or- `hash` is followed by `slash`
* -> it resolves to a different resource
* @method getHashRoute
* @param {String} hash hash component of a parsed uri
* @returns {String}
*/
function getHashRoute(hash) {
if (!hash) {
return ``;
}
/**
* Check for any conventionally-formatted hashbang that may be present
* eg: `#, #/, #!, #!/`
*/
const hashRegex = /#!?\/?/g;
const hasMatch = hash.match(hashRegex);
if (!hasMatch) {
return ``;
}
// do not resolve inline link as hash
const [matchedStr] = hasMatch;
if (matchedStr === '#') {
return ``;
}
return hash;
}