Skip to content

问题正文标准录入格式

ben7th edited this page Jan 16, 2014 · 9 revisions

服务端持久化存储中(目前使用MongoDB)保存着所有的问题数据。
问题正文是问题数据的组成部分(另外一部分是问题选项),具有以下特征:

  • 问题正文由图片,代码段,普通文本组成。“图片”,“代码段”,“普通文本”被称为不同的“内容类型”。将来还有可能扩展其他的内容类型,例如内嵌音频,内嵌视频,录音控件,二维条码扫描控件等。
  • 不同的“内容类型”在问题正文内按照一维数组方式排列。目前没有排版信息描述。
  • 问题正文在录入时,为方便录入者,需要遵循标准格式规定,保存在持久化存储中,再由 question.make_content 整理为问题正文标准JSON格式,以供客户端使用。

标准录入格式范例

text:
请看以下代码段,再根据要求回答问题:

code:javascript:
var a = 'Hello';
var b = 'World';
var c = a + ' ' + b;

console.log(a, b, c);

text:
请问以下图片

image:
http://ww2.sinaimg.cn/bmiddle/548cebedjw1eca640hqlhj20h50bqq6k.jpg

text:
是否代表了这段代码正确的输出结果?

标准录入格式说明

一段标准录入格式问题文本,每种内容类型段落以类型标识开始,以空行后的下一个类型标识,或EOF结束。

text: image: code: 被称为类型标识。目前只有这三种。

text: 普通文本类型标识

text:
我是普通文本。到这里不算结束。

普通文本就是我。到这里才结束,因为后面有下一个类型标识。

text:
对不起,第二个类型标识还是文本

image:
http://ww2.sinaimg.cn/bmiddle/548cebedjw1eca640hqlhj20h50bqq6k.jpg

以上正文应该被解析成三个类型段落。
其中第一个类型段落中包含空行,但中间的空行并不是这个类型段落的结束。因为后面没有出现新的类型标识。
直到遇到空行紧接着一个类型标识时,才认为这段文本结束了。因此,第一段文本被解析为:

我是普通文本。到这里不算结束。

普通文本就是我。到这里才结束,因为后面有下一个类型标识。

image: 图片类型标识

后面紧跟着图片的url(包含 http:// https:// 部分)。否则就是不合法的。

image:
http://ww2.sinaimg.cn/bmiddle/548cebedjw1eca640hqlhj20h50bqq6k.jpg

code: 代码段类型标识

和普通文本一样,中间的空行不被视作结束。

特殊情况,转义:

有时候,文本中本身就包含 text: image: 等字符串,但是又不想被识别为类型标识。则使用转义方法。

text:
我是第一行

raw:text:
这里声明了转义。不是新段落。

text:
这里才是新段落。

以 raw:在行首作为转义。以上文本将被识别为:

我是第一行

text:
这里声明了转义。不是新段落。
这里才是新段落。

两个段落。


解析脚本

将标准录入格式解析成问题正文标准JSON格式的脚本如下:

class QuestionContentParser
  def initialize(content)
    @content = content
  end

  def output
    @line_num = 0
    @token = nil
    @output = []
    @lines_stack = []
    @code_lang = ''

    lines = @content.lines
    @lc = lines.length

    lines.each do |line|
      @line_num += 1
      s = line.strip

      case @token
      
      when nil
        deal_nil(s)        

      when 'image:'
        deal_image(s)

      when 'text:'
        deal_text(s)

      when 'code:'
        deal_code(s)

      end
    end

    @output
  end

  def is_token(s)
    return 'image:' if s == 'image:'
    return 'text:' if s == 'text:'

    if m = s.match(/code\:(.+)\:/)
      @code_lang = m[1]
      return 'code:'
    end

    return nil
  end

  def deal_nil(s)
    return if s == ''

    it = is_token(s)

    if it
      @token = s
      @lines_stack = []
    end

    # raise "第#{line_num}行:无法找到合理的段落类型标识"
  end

  def deal_image(s)
    url = s
    @output << {
      :type => :image,
      :data => {
        :url => url
      }
    }
    @token = nil
    @lines_stack = []
  end

  def deal_text(s)
    if _is_end_of_block?(s)
      @output << {
        :type => :text,
        :data => {
          :content => pack_lines_stack
        }
      }

      @token = is_token(s)
      @lines_stack = []
      return
    end

    @lines_stack << s
  end

  def deal_code(s)
    if _is_end_of_block?(s)
      @output << {
        :type => :code,
        :data => {
          :lang => @code_lang,
          :content => QuestionCodeFormatter.new(pack_lines_stack, @code_lang).get_content
        }
      }

      @token = is_token(s)
      return
    end

    @lines_stack << s
  end

  def pack_lines_stack
    str = @lines_stack.map { |x|
      x.sub /^raw\:/, ''
    }[0..-2] * "\n"

    @lines_stack = []
    return str
  end

  def _is_end_of_block?(s)
    return true if is_token(s) && @lines_stack.last == ''
    if @line_num == @lc
      @lines_stack << s
      @lines_stack << ''
      return true
    end
    return false
  end
end

使用方法:

QuestionContentParser.new(content).output

范例:以下字符串:

text:
我是普通文本。到这里不算结束。

普通文本就是我。到这里才结束,因为后面有下一个类型标识。

text:
对不起,第二个类型标识还是文本

code:javascript:
var a = 'Hello';
var b = 'World';
var c = a + ' ' + b;

console.log(a, b, c);

text:
raw:image:
haha

image:
http://ww2.sinaimg.cn/bmiddle/548cebedjw1eca640hqlhj20h50bqq6k.jpg

通过以上脚本处理将被解析为:

{:type=>:text, :data=>{:content=>"我是普通文本。到这里不算结束。\n\n普通文本就是我。到这里才结束,因为后面有下一个类型标识。"}}
{:type=>:text, :data=>{:content=>"对不起,第二个类型标识还是文本"}}
{:type=>:code, :data=>{:lang=>"javascript", :content=>[["keyword", "var"], ["space", " "], ["ident", "a"], ["space", " "], ["operator", "="], ["space", " "], ["delimiter", "'", "string"], ["content", "Hello", "string"], ["delimiter", "'", "string"], ["operator", ";"], ["space", "\n"], ["keyword", "var"], ["space", " "], ["ident", "b"], ["space", " "], ["operator", "="], ["space", " "], ["delimiter", "'", "string"], ["content", "World", "string"], ["delimiter", "'", "string"], ["operator", ";"], ["space", "\n"], ["keyword", "var"], ["space", " "], ["ident", "c"], ["space", " "], ["operator", "="], ["space", " "], ["ident", "a"], ["space", " "], ["operator", "+"], ["space", " "], ["delimiter", "'", "string"], ["content", " ", "string"], ["delimiter", "'", "string"], ["space", " "], ["operator", "+"], ["space", " "], ["ident", "b"], ["operator", ";"], ["space", "\n\n"], ["ident", "console"], ["operator", "."], ["ident", "log"], ["operator", "("], ["ident", "a"], ["operator", ","], ["space", " "], ["ident", "b"], ["operator", ","], ["space", " "], ["ident", "c"], ["operator", ")"], ["operator", ";"]]}}
{:type=>:text, :data=>{:content=>"image:\nhaha"}}
{:type=>:image, :data=>{:url=>"http://ww2.sinaimg.cn/bmiddle/548cebedjw1eca640hqlhj20h50bqq6k.jpg"}}
Clone this wiki locally