Home Reference Source

src/utils/attr-list.ts

  1. import type { StringMap } from '../types/general';
  2.  
  3. const DECIMAL_RESOLUTION_REGEX = /^(\d+)x(\d+)$/; // eslint-disable-line no-useless-escape
  4. const ATTR_LIST_REGEX = /\s*(.+?)\s*=((?:\".*?\")|.*?)(?:,|$)/g; // eslint-disable-line no-useless-escape
  5.  
  6. // adapted from https://github.com/kanongil/node-m3u8parse/blob/master/attrlist.js
  7. class AttrList {
  8. [key: string]: any;
  9.  
  10. constructor(attrs: string | StringMap) {
  11. if (typeof attrs === 'string') {
  12. attrs = AttrList.parseAttrList(attrs);
  13. }
  14.  
  15. for (const attr in attrs) {
  16. if (attrs.hasOwnProperty(attr)) {
  17. this[attr] = attrs[attr];
  18. }
  19. }
  20. }
  21.  
  22. decimalInteger(attrName: string): number {
  23. const intValue = parseInt(this[attrName], 10);
  24. if (intValue > Number.MAX_SAFE_INTEGER) {
  25. return Infinity;
  26. }
  27.  
  28. return intValue;
  29. }
  30.  
  31. hexadecimalInteger(attrName: string) {
  32. if (this[attrName]) {
  33. let stringValue = (this[attrName] || '0x').slice(2);
  34. stringValue = (stringValue.length & 1 ? '0' : '') + stringValue;
  35.  
  36. const value = new Uint8Array(stringValue.length / 2);
  37. for (let i = 0; i < stringValue.length / 2; i++) {
  38. value[i] = parseInt(stringValue.slice(i * 2, i * 2 + 2), 16);
  39. }
  40.  
  41. return value;
  42. } else {
  43. return null;
  44. }
  45. }
  46.  
  47. hexadecimalIntegerAsNumber(attrName: string): number {
  48. const intValue = parseInt(this[attrName], 16);
  49. if (intValue > Number.MAX_SAFE_INTEGER) {
  50. return Infinity;
  51. }
  52.  
  53. return intValue;
  54. }
  55.  
  56. decimalFloatingPoint(attrName: string): number {
  57. return parseFloat(this[attrName]);
  58. }
  59.  
  60. optionalFloat(attrName: string, defaultValue: number): number {
  61. const value = this[attrName];
  62. return value ? parseFloat(value) : defaultValue;
  63. }
  64.  
  65. enumeratedString(attrName: string): string | undefined {
  66. return this[attrName];
  67. }
  68.  
  69. bool(attrName: string): boolean {
  70. return this[attrName] === 'YES';
  71. }
  72.  
  73. decimalResolution(
  74. attrName: string
  75. ):
  76. | {
  77. width: number;
  78. height: number;
  79. }
  80. | undefined {
  81. const res = DECIMAL_RESOLUTION_REGEX.exec(this[attrName]);
  82. if (res === null) {
  83. return undefined;
  84. }
  85.  
  86. return {
  87. width: parseInt(res[1], 10),
  88. height: parseInt(res[2], 10),
  89. };
  90. }
  91.  
  92. static parseAttrList(input: string): StringMap {
  93. let match;
  94. const attrs = {};
  95. const quote = '"';
  96. ATTR_LIST_REGEX.lastIndex = 0;
  97. while ((match = ATTR_LIST_REGEX.exec(input)) !== null) {
  98. let value = match[2];
  99.  
  100. if (
  101. value.indexOf(quote) === 0 &&
  102. value.lastIndexOf(quote) === value.length - 1
  103. ) {
  104. value = value.slice(1, -1);
  105. }
  106.  
  107. attrs[match[1]] = value;
  108. }
  109. return attrs;
  110. }
  111. }
  112.  
  113. export default AttrList;