url-props-from-attribute.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. /**
  2. * Parse resource object for a given node from a specified attribute
  3. * @method urlPropsFromAttribute
  4. * @param {HTMLElement} node given node
  5. * @param {String} attribute attribute of the node from which resource should be parsed
  6. * @returns {Object}
  7. */
  8. function urlPropsFromAttribute(node, attribute) {
  9. if (!node.hasAttribute(attribute)) {
  10. return undefined;
  11. }
  12. const nodeName = node.nodeName.toUpperCase();
  13. let parser = node;
  14. /**
  15. * Note:
  16. * The need to create a parser, is to keep this function generic, to be able to parse resource from element(s) like `iframe` with `src` attribute,
  17. *
  18. * Also, when `a` or `area` is nested inside an svg document,
  19. * they do not have url properties as a HTML Node, hence the check for `ownerSVGElement`
  20. */
  21. if (!['A', 'AREA'].includes(nodeName) || node.ownerSVGElement) {
  22. parser = document.createElement('a');
  23. parser.href = node.getAttribute(attribute);
  24. }
  25. /**
  26. * Curate `https` and `ftps` to `http` and `ftp` as they will resolve to same resource
  27. */
  28. const protocol = [`https:`, `ftps:`].includes(parser.protocol)
  29. ? parser.protocol.replace(/s:$/, ':')
  30. : parser.protocol;
  31. /**
  32. * certain browser (in this case IE10 & 11)
  33. * does not resolve pathname with a beginning slash, thence prepending with a beginning slash
  34. */
  35. const parserPathname = /^\//.test(parser.pathname)
  36. ? parser.pathname
  37. : `/${parser.pathname}`;
  38. const { pathname, filename } = getPathnameOrFilename(parserPathname);
  39. return {
  40. protocol,
  41. hostname: parser.hostname,
  42. port: getPort(parser.port),
  43. pathname: /\/$/.test(pathname) ? pathname : `${pathname}/`,
  44. search: getSearchPairs(parser.search),
  45. hash: getHashRoute(parser.hash),
  46. filename
  47. };
  48. }
  49. /**
  50. * Resolve given port excluding default port(s)
  51. * @param {String} port port
  52. * @returns {String}
  53. */
  54. function getPort(port) {
  55. const excludePorts = [
  56. `443`, // default `https` port
  57. `80`
  58. ];
  59. return !excludePorts.includes(port) ? port : ``;
  60. }
  61. /**
  62. * Resolve if a given pathname has filename & resolve the same as parts
  63. * @method getPathnameOrFilename
  64. * @param {String} pathname pathname part of a given uri
  65. * @returns {Array<Object>}
  66. */
  67. function getPathnameOrFilename(pathname) {
  68. const filename = pathname.split('/').pop();
  69. if (!filename || filename.indexOf('.') === -1) {
  70. return {
  71. pathname,
  72. filename: ``
  73. };
  74. }
  75. return {
  76. // remove `filename` from `pathname`
  77. pathname: pathname.replace(filename, ''),
  78. // ignore filename when index.*
  79. filename: /index./.test(filename) ? `` : filename
  80. };
  81. }
  82. /**
  83. * Parse a given query string to key/value pairs sorted alphabetically
  84. * @param {String} searchStr search string
  85. * @returns {Object}
  86. */
  87. function getSearchPairs(searchStr) {
  88. const query = {};
  89. if (!searchStr || !searchStr.length) {
  90. return query;
  91. }
  92. // `substring` to remove `?` at the beginning of search string
  93. const pairs = searchStr.substring(1).split(`&`);
  94. if (!pairs || !pairs.length) {
  95. return query;
  96. }
  97. for (let index = 0; index < pairs.length; index++) {
  98. const pair = pairs[index];
  99. const [key, value = ''] = pair.split(`=`);
  100. query[decodeURIComponent(key)] = decodeURIComponent(value);
  101. }
  102. return query;
  103. }
  104. /**
  105. * Interpret a given hash
  106. * if `hash`
  107. * -> is `hashbang` -or- `hash` is followed by `slash`
  108. * -> it resolves to a different resource
  109. * @method getHashRoute
  110. * @param {String} hash hash component of a parsed uri
  111. * @returns {String}
  112. */
  113. function getHashRoute(hash) {
  114. if (!hash) {
  115. return ``;
  116. }
  117. /**
  118. * Check for any conventionally-formatted hashbang that may be present
  119. * eg: `#, #/, #!, #!/`
  120. */
  121. const hashRegex = /#!?\/?/g;
  122. const hasMatch = hash.match(hashRegex);
  123. if (!hasMatch) {
  124. return ``;
  125. }
  126. // do not resolve inline link as hash
  127. const [matchedStr] = hasMatch;
  128. if (matchedStr === '#') {
  129. return ``;
  130. }
  131. return hash;
  132. }
  133. export default urlPropsFromAttribute;