url-template.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164
  1. // Based on https://github.com/bramstein/url-template, licensed under BSD
  2. // TODO: create separate package.
  3. //
  4. // Copyright (c) 2012-2014, Bram Stein
  5. // All rights reserved.
  6. // Redistribution and use in source and binary forms, with or without
  7. // modification, are permitted provided that the following conditions
  8. // are met:
  9. // 1. Redistributions of source code must retain the above copyright
  10. // notice, this list of conditions and the following disclaimer.
  11. // 2. Redistributions in binary form must reproduce the above copyright
  12. // notice, this list of conditions and the following disclaimer in the
  13. // documentation and/or other materials provided with the distribution.
  14. // 3. The name of the author may not be used to endorse or promote products
  15. // derived from this software without specific prior written permission.
  16. // THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED
  17. // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  18. // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  19. // EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
  20. // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  21. // BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  22. // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  23. // OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  24. // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
  25. // EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. /* istanbul ignore file */
  27. function encodeReserved(str) {
  28. return str
  29. .split(/(%[0-9A-Fa-f]{2})/g)
  30. .map(function (part) {
  31. if (!/%[0-9A-Fa-f]/.test(part)) {
  32. part = encodeURI(part).replace(/%5B/g, "[").replace(/%5D/g, "]");
  33. }
  34. return part;
  35. })
  36. .join("");
  37. }
  38. function encodeUnreserved(str) {
  39. return encodeURIComponent(str).replace(/[!'()*]/g, function (c) {
  40. return "%" + c.charCodeAt(0).toString(16).toUpperCase();
  41. });
  42. }
  43. function encodeValue(operator, value, key) {
  44. value =
  45. operator === "+" || operator === "#"
  46. ? encodeReserved(value)
  47. : encodeUnreserved(value);
  48. if (key) {
  49. return encodeUnreserved(key) + "=" + value;
  50. }
  51. else {
  52. return value;
  53. }
  54. }
  55. function isDefined(value) {
  56. return value !== undefined && value !== null;
  57. }
  58. function isKeyOperator(operator) {
  59. return operator === ";" || operator === "&" || operator === "?";
  60. }
  61. function getValues(context, operator, key, modifier) {
  62. var value = context[key], result = [];
  63. if (isDefined(value) && value !== "") {
  64. if (typeof value === "string" ||
  65. typeof value === "number" ||
  66. typeof value === "boolean") {
  67. value = value.toString();
  68. if (modifier && modifier !== "*") {
  69. value = value.substring(0, parseInt(modifier, 10));
  70. }
  71. result.push(encodeValue(operator, value, isKeyOperator(operator) ? key : ""));
  72. }
  73. else {
  74. if (modifier === "*") {
  75. if (Array.isArray(value)) {
  76. value.filter(isDefined).forEach(function (value) {
  77. result.push(encodeValue(operator, value, isKeyOperator(operator) ? key : ""));
  78. });
  79. }
  80. else {
  81. Object.keys(value).forEach(function (k) {
  82. if (isDefined(value[k])) {
  83. result.push(encodeValue(operator, value[k], k));
  84. }
  85. });
  86. }
  87. }
  88. else {
  89. const tmp = [];
  90. if (Array.isArray(value)) {
  91. value.filter(isDefined).forEach(function (value) {
  92. tmp.push(encodeValue(operator, value));
  93. });
  94. }
  95. else {
  96. Object.keys(value).forEach(function (k) {
  97. if (isDefined(value[k])) {
  98. tmp.push(encodeUnreserved(k));
  99. tmp.push(encodeValue(operator, value[k].toString()));
  100. }
  101. });
  102. }
  103. if (isKeyOperator(operator)) {
  104. result.push(encodeUnreserved(key) + "=" + tmp.join(","));
  105. }
  106. else if (tmp.length !== 0) {
  107. result.push(tmp.join(","));
  108. }
  109. }
  110. }
  111. }
  112. else {
  113. if (operator === ";") {
  114. if (isDefined(value)) {
  115. result.push(encodeUnreserved(key));
  116. }
  117. }
  118. else if (value === "" && (operator === "&" || operator === "?")) {
  119. result.push(encodeUnreserved(key) + "=");
  120. }
  121. else if (value === "") {
  122. result.push("");
  123. }
  124. }
  125. return result;
  126. }
  127. export function parseUrl(template) {
  128. return {
  129. expand: expand.bind(null, template),
  130. };
  131. }
  132. function expand(template, context) {
  133. var operators = ["+", "#", ".", "/", ";", "?", "&"];
  134. return template.replace(/\{([^\{\}]+)\}|([^\{\}]+)/g, function (_, expression, literal) {
  135. if (expression) {
  136. let operator = "";
  137. const values = [];
  138. if (operators.indexOf(expression.charAt(0)) !== -1) {
  139. operator = expression.charAt(0);
  140. expression = expression.substr(1);
  141. }
  142. expression.split(/,/g).forEach(function (variable) {
  143. var tmp = /([^:\*]*)(?::(\d+)|(\*))?/.exec(variable);
  144. values.push(getValues(context, operator, tmp[1], tmp[2] || tmp[3]));
  145. });
  146. if (operator && operator !== "+") {
  147. var separator = ",";
  148. if (operator === "?") {
  149. separator = "&";
  150. }
  151. else if (operator !== "#") {
  152. separator = operator;
  153. }
  154. return (values.length !== 0 ? operator : "") + values.join(separator);
  155. }
  156. else {
  157. return values.join(",");
  158. }
  159. }
  160. else {
  161. return encodeReserved(literal);
  162. }
  163. });
  164. }