index.js 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. 'use strict';
  2. var escapeRegExp = require('escape-string-regexp');
  3. var objectAssign = require('object-assign');
  4. var Transform = require('readable-stream/transform');
  5. module.exports = function ReplaceStream(search, replace, options) {
  6. var tail = '';
  7. var totalMatches = 0;
  8. var isRegex = search instanceof RegExp;
  9. options = objectAssign({
  10. limit: Infinity,
  11. encoding: 'utf8',
  12. maxMatchLen: 100
  13. }, options);
  14. var replaceFn = replace;
  15. replaceFn = createReplaceFn(replace, isRegex);
  16. var match;
  17. if (isRegex) {
  18. match = matchFromRegex(search, options)
  19. } else {
  20. match = matchFromString(search, options);
  21. options.maxMatchLen = search.length;
  22. }
  23. function transform(buf, enc, cb) {
  24. var matches;
  25. var lastPos = 0;
  26. var runningMatch = '';
  27. var matchCount = 0;
  28. var rewritten = '';
  29. var haystack = tail + buf.toString(options.encoding);
  30. tail = '';
  31. while (totalMatches < options.limit &&
  32. (matches = match.exec(haystack)) !== null) {
  33. matchCount++;
  34. var before = haystack.slice(lastPos, matches.index);
  35. var regexMatch = matches;
  36. lastPos = matches.index + regexMatch[0].length;
  37. if (lastPos > haystack.length && regexMatch[0].length < options.maxMatchLen) {
  38. tail = regexMatch[0]
  39. } else {
  40. var dataToAppend = getDataToAppend(before,regexMatch);
  41. rewritten += dataToAppend;
  42. }
  43. }
  44. if (tail.length < 1)
  45. tail = haystack.slice(lastPos).length > options.maxMatchLen ? haystack.slice(lastPos).slice(0 - options.maxMatchLen) : haystack.slice(lastPos)
  46. var dataToQueue = getDataToQueue(matchCount,haystack,rewritten,lastPos);
  47. cb(null, dataToQueue);
  48. }
  49. function getDataToAppend(before, match) {
  50. var dataToAppend = before;
  51. totalMatches++;
  52. dataToAppend += isRegex ? replaceFn.apply(this, match.concat([match.index, match.input])) : replaceFn(match[0]);
  53. return dataToAppend;
  54. }
  55. function getDataToQueue(matchCount, haystack, rewritten, lastPos) {
  56. if (matchCount > 0) {
  57. if (haystack.length > tail.length) {
  58. return rewritten + haystack.slice(lastPos, haystack.length - tail.length);
  59. }
  60. return rewritten;
  61. }
  62. return haystack.slice(0, haystack.length - tail.length);
  63. }
  64. function flush(cb) {
  65. if (tail) {
  66. this.push(tail);
  67. }
  68. cb();
  69. }
  70. return new Transform({
  71. transform: transform,
  72. flush: flush
  73. });
  74. };
  75. function createReplaceFn(replace, isRegEx) {
  76. var regexReplaceFunction = function () {
  77. var newReplace = replace;
  78. // ability to us $1 with captures
  79. // Start at 1 and end at length - 2 to avoid the match parameter and offset
  80. // And string parameters
  81. var paramLength = arguments.length - 2;
  82. for (var i = 1; i < paramLength; i++) {
  83. newReplace = newReplace.replace(new RegExp('\\$' + i, 'g'), arguments[i] || '')
  84. }
  85. return newReplace;
  86. };
  87. if (isRegEx && !(replace instanceof Function)) {
  88. return regexReplaceFunction;
  89. }
  90. if (!(replace instanceof Function)) {
  91. return function stringReplaceFunction() {
  92. return replace;
  93. };
  94. }
  95. return replace;
  96. }
  97. function matchFromRegex(regex, options) {
  98. if (options.regExpOptions) {
  99. regex = new RegExp(regex.source, options.regExpOptions)
  100. }
  101. // If there is no global flag then there can only be one match
  102. if (!regex.global) {
  103. options.limit = 1;
  104. }
  105. return regex;
  106. }
  107. function matchFromString(s, options) {
  108. if (options.regExpOptions) {
  109. return new RegExp(escapeRegExp(s), options.regExpOptions);
  110. }
  111. return new RegExp(escapeRegExp(s), options.ignoreCase === false ? 'gm' : 'gmi');
  112. }