首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >解析半结构化值

解析半结构化值
EN

Stack Overflow用户
提问于 2014-06-16 21:16:14
回答 5查看 579关注 0票数 9

这是我的第一个问题。我试图找到一个答案,但是,诚实地说,我无法弄清楚我应该使用哪些术语,所以如果之前有人问过,我很抱歉。

如下所示:我在一个.txt文件中以这种格式保存了数千条记录:

代码语言:javascript
运行
复制
(1, 3, 2, 1, 'John (Finances)'),
(2, 7, 2, 1, 'Mary Jane'),
(3, 7, 3, 2, 'Gerald (Janitor), Broflowski'),

..。诸若此类。第一个值是PK,另外3个是外键,第5个是字符串。

我需要在Javascript中将它们解析为JSON (或其他什么),但是我遇到了麻烦,因为有些字符串有parentheses+comma (例如,在第三条记录上,“看门人”),所以我不能使用子字符串.也许可以修剪正确的部分,但我想知道是否有更明智的方法来解析它。

任何帮助都会很感激的。

谢谢!

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2014-06-16 21:31:48

您不能(阅读可能不应该)为此使用正则表达式。如果括号中包含另一对或一个不匹配怎么办?

好消息是,您可以轻松地为此构造一个标记器/解析器。这样做的目的是跟踪你目前的状况,并采取相应的行动。

这是我在这里写的解析器的草图,重点是向您展示一般的想法。如果你对此有任何概念上的疑问,请告诉我。

它可以使用这里的演示,但我请求您在理解和修补它之前不要在生产中使用它。

它是如何工作的

那么,我们如何构建一个解析器:

代码语言:javascript
运行
复制
var State = { // remember which state the parser is at.
     BeforeRecord:0, // at the (
     DuringInts:1, // at one of the integers
     DuringString:2, // reading the name string
     AfterRecord:3 // after the )
};

我们需要跟踪输出和当前的工作对象,因为我们将一次解析这些对象。

代码语言:javascript
运行
复制
var records = []; // to contain the results
var state = State.BeforeRecord;

现在,我们迭代字符串,继续在其中进行,并读取下一个字符。

代码语言:javascript
运行
复制
for(var i = 0;i < input.length; i++){
    if(state === State.BeforeRecord){
        // handle logic when in (
    }
    ...
    if(state === State.AfterRecord){
        // handle that state
    }
}

现在,剩下的就是在每个状态下将其消耗到对象中:

  • 如果是在(,我们就开始解析,跳过任何空白空间
  • 读取所有整数并丢弃,
  • 在四个整数之后,将字符串从'读取到到达其末尾的下一个'
  • 在字符串之后,读取到),存储对象,然后再次启动循环。

执行起来也不是很困难。

解析器

代码语言:javascript
运行
复制
var State = { // keep track of the state
     BeforeRecord:0,
     DuringInts:1,
     DuringString:2,
     AfterRecord:3
};
var records = []; // to contain the results
var state = State.BeforeRecord;
var input = " (1, 3, 2, 1, 'John (Finances)'), (2, 7, 2, 1, 'Mary Jane'), (3, 7, 3, 2, 'Gerald (Janitor), Broflowski')," // sample input

var workingRecord = {}; // what we're reading into.

for(var i = 0;i < input.length; i++){
    var token = input[i]; // read the current input
    if(state === State.BeforeRecord){ // before reading a record
        if(token === ' ') continue; // ignore whitespaces between records
        if(token === '('){ state = State.DuringInts; continue; }
        throw new Error("Expected ( before new record");
    }
    if(state === State.DuringInts){
        if(token === ' ') continue; // ignore whitespace
        for(var j = 0; j < 4; j++){
            if(token === ' ') {token = input[++i]; j--; continue;} // ignore whitespace 
             var curNum = '';
             while(token != ","){
                  if(!/[0-9]/.test(token)) throw new Error("Expected number, got " + token);
                  curNum += token;
                  token = input[++i]; // get the next token
             }
             workingRecord[j] = Number(curNum); // set the data on the record
             token = input[++i]; // remove the comma
        }
        state = State.DuringString;
        continue; // progress the loop
    }
    if(state === State.DuringString){
         if(token === ' ') continue; // skip whitespace
         if(token === "'"){
             var str = "";
             token = input[++i];
             var lenGuard = 1000;
             while(token !== "'"){
                 str+=token;
                 if(lenGuard-- === 0) throw new Error("Error, string length bounded by 1000");
                 token = input[++i];
             }
             workingRecord.str = str;
             token = input[++i]; // remove )
             state = State.AfterRecord;
             continue;
         }
    }
    if(state === State.AfterRecord){
        if(token === ' ') continue; // ignore whitespace
        if(token === ',') { // got the "," between records
            state = State.BeforeRecord;
            records.push(workingRecord);
            workingRecord = {}; // new record;
            continue;
        }
        throw new Error("Invalid token found " + token);
    }
}
console.log(records); // logs [Object, Object, Object]
                      // each object has four numbers and a string, for example
                      // records[0][0] is 1, records[0][1] is 3 and so on,
                      // records[0].str is "John (Finances)"
票数 14
EN

Stack Overflow用户

发布于 2014-06-16 21:48:37

我重复本的感情关于正则表达式通常对此不利的观点,我完全同意他的观点,即标记器是这里最好的工具。

但是,考虑到一些注意事项,您可以在这里使用正则表达式。这是因为您的(),'中的任何歧义都可以归因于您的最后一列;因为所有其他列都是整数。

So,给定:

  1. 输入是完美的(没有意外的(),')。
  2. 根据您的编辑,每条记录都在一条新行上。
  3. 您输入的唯一新行将是打破下一个记录。

..。下面的内容应该可以工作(注意“新行”,这里是\n。如果它们是\r\n,则相应地更改它们):

代码语言:javascript
运行
复制
var input = /* Your input */;
var output = input.split(/\n/g).map(function (cols) {
    cols = cols.match(/^\((\d+), (\d+), (\d+), (\d+), '(.*)'\)/).slice(1);

    return cols.slice(0, 4).map(Number).concat(cols[4]);
});

代码在新行上拆分,然后逐行遍历,然后使用正则表达式将其分割成单元格,该表达式尽可能多地将其属性分配给最终的单元格。然后,它将前4个元素转换为整数,并将第5个元素(字符串)插入末尾。

这给出了一个记录数组,其中每个记录本身都是一个数组。前4个元素是PK元素(作为整数),第5个元素是字符串。

例如,给定输入,使用output[0][4]获取"Gerald (Janitor), Broflowski",使用output[1][0]获取第二条记录的第一个PK 2 (不要忘记JavaScript数组是零索引的)。

您可以看到它在这里工作:http://jsfiddle.net/56ThR/

票数 4
EN

Stack Overflow用户

发布于 2014-06-16 22:19:49

另一种选择是将其转换为类似于Arrayeval的东西。我知道不建议使用eval,但这是一个很酷的解决方案:)

代码语言:javascript
运行
复制
var lines = input.split("\n");
var output = [];

for(var v in lines){

    // Remove opening ( 
    lines[v] = lines[v].slice(1);

    // Remove closing ) and what is after
    lines[v] = lines[v].slice(0, lines[v].lastIndexOf(')'));

    output[v] = eval("[" + lines[v] + "]");       
}

所以,eval parameter看起来应该是:[1, 3, 2, 1, 'John (Finances)'],它确实是一个数组。

演示:http://jsfiddle.net/56ThR/3/

而且,它也可以写得更短,像这样:

代码语言:javascript
运行
复制
var lines = input.split("\n");
var output = lines.map( function(el) { 
    return eval("[" + el.slice(1).slice(0, el.lastIndexOf(')') - 1) + "]");
});

演示:http://jsfiddle.net/56ThR/4/

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/24252352

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档