Reputation: 55273
This function loops through JavaScript nested arrays (recursively) and replaces the strings inside them:
function replaceRecur(tree, str, newStr) {
for (var i = 1; i < tree.length; i++) {
if (Array.isArray(tree[i])) {
replaceRecur(tree[i], str, newStr)
} else {
tree[i] = tree[i].replace(str, newStr)
}
}
}
Usage example:
function replaceQuotes(tree, callback) {
var str1 = /"(?=\b)/g
, str2 = /"(?!\b)/g
, newStr1 = '“'
, newStr2 = '”'
replaceRecur(tree, str1, newStr1)
replaceRecur(tree, str2, newStr2)
callback(null, tree)
}
How should I modify replaceRecur
so I allow two values per argument?
Example:
function replaceQuotes(tree, callback) {
var str = ['/"(?=\b)/g/', '"(?!\b)/g']
, newStr = '“ ”' // not sure whether to use arrays or strings
// what's more common?
replaceRecur(tree, str, newStr)
callback(null, tree)
}
(The reason is, I don't want to repeat replaceRecur
, str
, and newStr
twice. I want to keep the code DRY.)
EDIT:
Example input (just in case):
[ 'markdown',
[ 'para', '“a paragraph”' ],
[ 'hr' ],
[ 'para', '\'another paragraph\'' ],
[ 'para', 'test--test' ],
[ 'para', 'test---test' ],
[ 'bulletlist',
[ 'listitem', '“a list item”' ],
[ 'listitem', '“another list item”' ] ] ]
Upvotes: 1
Views: 125
Reputation: 214949
If you want to abstract your code a bit more, here's one possible way:
function maprec(x, callback) {
return x.map ? x.map(function(x) {
return maprec(x, callback);
}) : callback(x);
}
function pipe() {
var fns = arguments;
return function(x) {
return [].reduce.call(fns, function(a, f) { return f(a) }, x);
}
}
// test/demo:
tree = ['foo "bar" !', ['baz', ['ccc "hi" ', 'd']], 'eee', ['"foo?"']];
converted = maprec(tree, pipe(
function(x) { return x.replace(/"(?=\b)/g, '{') },
function(x) { return x.replace(/"(?=\B)/g, '}') }
));
document.write("<pre>" + JSON.stringify(converted));
Ok, what we did here? First, we define maprec
, a recursive mapper, which is the same as map
, but respects nested structures. The second utility, pipe
, is the function composer that takes a bunch of functions and returns a new function that applies these functions, in order, to the argument, in way similar to unix pipelines like grep | sort | uniq
(hence the name). Note that this is different from the usual compose
, which is right-associative. Finally, we use maprec(tree, pipe(replacer1, replacer2))
to do the actual job.
(I use {}
's instead of fancy quotes to make them look more obvious in the console window).
To illustrate the power of pipelining, here's a more advanced example:
fancyQuotes = pipe(
function(x) { return x.replace(/"(?=\b)/g, '«') },
function(x) { return x.replace(/"(?=\B)/g, '»') }
);
trim = "".trim.call.bind("".trim);
wrap = function(x) { return this.replace(/\$/g, x)};
wrap.in = wrap.bind;
converted = maprec(tree, pipe(
fancyQuotes,
trim,
wrap.in("<p>$</p>")));
Upvotes: 1
Reputation: 664433
not sure whether to use arrays or strings
Use arrays, you might want to replace multiple things. And you wouldn't alter replaceRecur
, it is fine as it is. Rather introduce a new function
function replaceMultipleRecur(tree, strArr, newStrArr) {
… // (in the simplest occasion a loop over the arrays with calls to replaceRecur)
}
function replaceQuotes(tree) {
return replaceMultipeRecur([/"(?=\b)/g, /"(?!\b)/g], ['“', '”']);
}
I don't want to repeat replaceRecur, str, and newStr twice
You can simply use your exsting function, by passing a regex that matches all your cases and a replacer callback instead of strings.
function replaceQuotes(tree) {
replaceRecur(tree, /("\b)|("\B)/g, function(m, l, r) {
return l ? '“' : '”';
});
return tree
}
Upvotes: 1
Reputation: 1074238
Preface: I'm with Bergi re his comment above, just calling a function twice doesn't mean you're repeating yourself. And adding complexity to a simple function to avoid calling it twice isn't necessarily a good idea.
But to your question: You could just add two arguments to the function, ignoring them if not given:
function replaceRecur(tree, str1, newStr1, str2, newStr2) {
for (var i = 1; i < tree.length; i++) {
if (Array.isArray(tree[i])) {
replaceRecur(tree[i], str1, newStr1, str2, newStr2);
} else {
tree[i] = tree[i].replace(str1, newStr1);
if (typeof str2 !== "undefined") {
tree[i] = tree[i].replace(str2, newStr2);
}
}
}
}
In fact, you can just keep doing this endlessly using a loop.
function replaceRecur(tree, str, newStr/*, str2, newStr2, ...*/) {
for (var i = 1; i < tree.length; i++) {
if (Array.isArray(tree[i])) {
replaceRecur.apply(this, arguments);
} else {
for (var arg = 1; arg + 1 < arguments.length; arg += 2) {
tree[i] = tree[i].replace(arguments[arg], arguments[arg + 1]);
}
}
}
}
Note that on some JavaScript engines using the arguments
pseudo-array has a negative impact on performance. On modern engines it doesn't matter at all.
Usage:
replaceRecur(tree, /"(?=\b)/g, '“', /"(?!\b)/g, '”');
Upvotes: 0
Reputation: 1556
Unfortunately there is no overloading in JavaScript.
One clean solution would to use replaceRecur
but internally check the typeof
arguments to determine whether you want to use _replaceRecur2
or _replaceRecur1
function replaceRecur(tree, arg1, arg2) {
if(typeof(arg1) === "function"){
_replaceRecur2(tree, arg1);
} else {
_replaceRecur1(tree, arg1, arg2);
}
}
function _replaceRecur1(tree, str, newStr) {
for (var i = 1; i < tree.length; i++) {
if (Array.isArray(tree[i])) {
_replaceRecur1(tree[i], str, newStr)
} else {
tree[i] = tree[i].replace(str, newStr)
}
}
}
function _replaceQuotes2(tree, callback) {
var str1 = /"(?=\b)/g
, str2 = /"(?!\b)/g
, newStr1 = '“'
, newStr2 = '”'
_replaceRecur1(tree, str1, newStr1)
_replaceRecur1(tree, str2, newStr2)
callback(null, tree)
}
Upvotes: 1
Reputation: 413720
Create a function that performs the recursive traversal of the structure and invokes callbacks for values. Then you can write your replacement functions as callbacks you pass in to that:
function traverse(tree, callback) {
for (var i = 0; i < tree.length; ++i) {
if (Array.isArray(tree[i]))
traverse(tree[i], callback);
else
tree[i] = callback(tree[i]);
}
}
function replaceTwo(tree, s1from, s1to, s2from, s2to) {
traverse(tree, function(element) {
return element.replace(s1from, s1to).replace(s2from, s2to);
});
}
You can now write all sorts of different functions to transform tree contents without having to rewrite the recursion part.
Upvotes: 3
Reputation: 1309
just alter the else
to apply each regex in the array.
else {
//array is the array of regex's passed
array.forEach(function(item) {
tree[i] = tree[i].replace(item, newStr);
});
}
Upvotes: 0