用多个正则表达式模式拆分字符串会返回不需要的值

JavaScript - Splitting string by multiple regex patterns returns undesired values

本文关键字:返回 不需要 字符串 拆分 正则表达式 模式      更新时间:2023-09-26

设置

我有一个字符串,类似于:

Sample Strings

val4[3].sub1[ 1 ][2].smth.esl
// or
val4.sub1
// or
val4.sub1[2].smth
// and so on...

字符串将始终遵循以下规则:

  • 字符串总是以属于A-Za-z0-9_-
  • 的字符开头
  • 每个句号后面必须至少有一个A-Za-z0-9_-
  • 每个左括号[都有一个匹配的右括号]
    • 每组括号包含一个整数,可选的前后空格

用与访问关联数组中的项相同的方式来考虑字符串。例如,上面列出的第一个字符串可能访问如下数组:

Sample Array Structure

val4 =  [
            0, 
            'some string', 
            2, 
            {
                sub1:   [
                            [1, 2, 3], 
                            [
                                'val',
                                1, 
                                {
                                    smth:   {
                                                esl: 'final value'
                                            }
                                }
                            ], 
                            [4, 5, 6]
                        ], 
                sub2: 'another str'
            }, 
            4
        ];

val4[3].sub1[ 1 ][2].smth.esl处的值为final value

虽然数组是一个噩梦,但只是为了帮助理解输入字符串中可接受的模式。

预期行为

我要做的是按以下方式分割字符串:

  • 句点及以下任何属于A-Za-z0-9_-的文本

    /'.([A-Za-z0-9_-]+)/g

  • 带整型内容和可选前后空格的括号对

    /'['s?['d]+'s?']/g

那么,再次以第一个字符串为例:

val4[3].sub1[ 1 ][2].smth.esl

会分裂成

Desired Output

val4
[3]
.sub1
[ 1 ]
[2]
.smth
.esl

您可以在这个RegExr中看到期望的分组应该是什么。

试验

jsfield代码

当我运行以下命令时:

Code Block #1

var str = 'val4[3].sub1[ 1 ][2].smth.esl';
var re = /('['s?['d]+'s?'])|(?='.([A-Za-z0-9_-]+))/g;   
var splits = str.split(re);

注意:在正则表达式中,我将第二个模式设置为正向前看(?=),以便句点与

后面的字符保持一致

我以以下内容结束:

Output #1

splits = [
            "val4",
            "[3]",
            null,
            ".sub1",
            "[ 1 ]",
            null,
            "",
            "[2]",
            null,
            ".smth",
            null,
            "esl",
            ".esl"
         ];

结果非常接近,有几个明显的例外:

  • splits[2]splits[5]splits[8]splits[10]为空
  • splits[6]为空字符串
  • splits[11]缺少前一个周期(并且实际上只是splits[12]的错误副本)

只使用括号部分

运行

Code Block #2

var re = /('['s?['d]+'s?'])/g;

的回报:

Output #2

splits = [
            "val4",
            "[3]",
            ".sub1",
            "[ 1 ]",
            "",
            "[2]",
            ".smth.esl"
         ];

运行这个命令,只有的句号/字符部分

Code Block #3

var re = /(?='.([A-Za-z0-9_-]+))/g;

的回报:

Output #3

splits = [
            "val4[3]",
            ".sub1[ 1 ][2]",
            "smth",
            ".smth",
            "esl",
            ".esl"
         ];

但是前面两种方法都有自己的缺点(空值,字符串不带句点)。

我以前用正则表达式说过"I'm so close",但离得太远了。但是,我觉得我正处在风口浪尖上。

那么,我如何修改正则表达式/分割来完成我所追求的?

基本上,不返回null或空值,只有字符串前面有的句点。

希望我已经说清楚了,如果有任何含糊之处请告诉我。

重要!

对于建议的解决方案,它需要是…

  1. 独立于浏览器的

    这意味着ES6可能不会有任何变化,因为浏览器对新内容的支持非常不一致。不能强迫人们仅仅为了运行这个而采用浏览器

  2. 易于移植到PHP(首选,但不是绝对关键)

    有一个为服务器端操作编写的姊妹脚本。避免使用javascript特有的技术将有助于在两种语言中实现

附加(非必需)

(对于那些对上下文和目的感兴趣的人)

剧透!确实存在一个数组。与上面定义的val4不完全相同,但没有不同,并且从来没有两次相同。

val4 =  [... and so on...];

提供一个字符串(必须是字符串类型)

str = 'val4[3].sub1[ 1 ][2].smth.esl';

返回值必须是val4中的值,在str中的地址。

var val4 =  [... and so on...];
var str = 'val4[3].sub1[ 1 ][2].smth.esl';
var result = getItem(val4, str);

会返回

return val4[3].sub1[1][2].smth.esl;

如果你有一个很好的方法来做我想做的事情,而不需要所有的正则表达式,我很高兴听到它。但仍然对问题中提出的主要问题感兴趣。

我猜最后你想构建的就是这个。

我稍微扩展了Regex来解析像["lorem ipsum"]

这样的东西

	//parses the path into an Array of keys
	var parsePath = (function(){
		var fetchParts = /'['s*('d+|"(?:''['s'S]|[^''"])+")'s*']|(?:'.|^)([A-Za-z_$][A-Za-z_$0-9]*)/g;
		var isValid = new RegExp("^(?:" + fetchParts.source.replace(/'((?:'?':)?/g, "(?:") + ")+$", "")
		
		return function(str){
			var s = str == null? "": String(str).trim();
			if(!isValid.test(s))
				throw new Error("invalid path: "+JSON.stringify(str));
			for(var m, keys = []; m = fetchParts.exec(s);)
				keys.push(m[1]? JSON.parse(m[1]): m[2]);
			return keys;
		}
	})();
	//takes a path or an Array of keys and returns a function that resolves the path from the passed object.
	//returns undefined if path can't be resolved;
	
	//v1
	var resolvePath = function(path){
		var keys = Array.isArray(path)? path: parsePath(path);
		return keys.reduceRight(
			(nextFn, key) => obj => obj != null && key in obj? nextFn(obj[key]): void 0,
			v => v
		);
	}
	//v2
	var resolvePath = function(path){
		var keys = Array.isArray(path)? path: parsePath(path);
		return function(obj){
			for(var i=0; i<keys.length; ++i){
				if(obj == null) return void 0;
				obj = obj[keys[i]]
			}
			return obj;
		}
	}
	//usage:
	var getSmthEsl = resolvePath('val4[3].sub1[ 1 ][2]["lorem''nipsum"].smth.esl');
	console.log("a", getSmthEsl({
		val4: [
			null,    //0
			null,    //1
			null,    //2
			{            //3
				sub1: [
					null,    //0
					[            //1
						null,    //0
						null,    //1
						{            //2
							"lorem'nipsum": {
								smth: {
									esl: {
										sucess: true
									}
								}
							}
						}
					]
				]
			}
		]
	}))
	
	console.log("b", getSmthEsl({ val4: [] }))

比你想象的简单多了:

window.val4 =  [
            0, 
            'some string', 
            2, 
            {
                sub1:   [
                            [1, 2, 3], 
                            [
                                'val',
                                1, 
                                {
                                    smth:   {
                                                esl: 'final value'
                                            }
                                }
                            ], 
                            [4, 5, 6]
                        ], 
                sub2: 'another str'
            }, 
            4
        ];
let ref = 'val4[3].sub1[ 1 ][2].smth.esl'
let result = window; // or 'global' in node
ref.replace(/('w+)|'['s*('d+)'s*']/g, (_, $1, $2) => result = result[$1 || $2]);
console.log(result)

对于这个作业,您需要能够动态设置对象属性。之前我已经为此目的编写了一个代码。Object.prototype.setNestedValue() .

setNestedValue([prop1[, prop2[, prop3…]]]],value)完全像它的孪生getNestedValue()一样工作,但最后一个参数是要设置的值。如果该属性不存在,它将根据所提供参数的类型创建一个对象或数组。同样,字符串类型实参将产生一个对象,而数字类型实参将产生一个相同大小的Array。

让我们看看代码。

Object.prototype.setNestedValue = function(...a) {
  a.length > 2 ? typeof this[a[0]] === "object" && this[a[0]] !== null ? this[a[0]].setNestedValue(...a.slice(1))
                                                                       : (this[a[0]] = typeof a[1] === "string" ? {} : new Array(a[1]),
                                                                         this[a[0]].setNestedValue(...a.slice(1)))
               : this[a[0]] = a[1];
  return this;
};
var str = "val4[3].sub1[ 1 ][2].smth.esl",
    arr = str.split(/'['s*('d+)'s*']|'./)
             .filter(prop => prop)
             .map(prop => Number.isNaN(+prop) ? prop : +prop)
             .concat("final value");
 result = {}.setNestedValue(...arr);
 console.log(JSON.stringify(arr));
 console.log(JSON.stringify(result,null,4));

我首先展示结果参数数组如何调用setNestedValue(...arr),然后是结果对象。

哦,我的错…我以为你想动态地构造对象,但是你喜欢从已经构造的对象中动态地获得嵌套值。Object.prototype.getNestedValue()是这项工作的理想选择。我们已经构造了对象,所以让我们再次使用上面的代码,动态地获取嵌套的值。

Object.prototype.getNestedValue = function(...a) {
  return a.length > 1 ? (this[a[0]] !== void 0 && this[a[0]].getNestedValue(...a.slice(1))) : this[a[0]];
};
Object.prototype.setNestedValue = function(...a) {
  a.length > 2 ? typeof this[a[0]] === "object" && this[a[0]] !== null ? this[a[0]].setNestedValue(...a.slice(1))
                                                                       : (this[a[0]] = typeof a[1] === "string" ? {} : new Array(a[1]),
                                                                         this[a[0]].setNestedValue(...a.slice(1)))
               : this[a[0]] = a[1];
  return this;
};
var str = "val4[3].sub1[ 1 ][2].smth.esl",
    arr = str.split(/'['s*('d+)'s*']|'./)
             .filter(prop => prop)
             .map(prop => Number.isNaN(+prop) ? prop : +prop)
             .concat("final value"),
  myObj = {}.setNestedValue(...arr),
  value;
arr.pop(); // lets remove the "final value" since now we will get it.
value = myObj.getNestedValue(...arr);
console.log(value);