【问题标题】:Parsing org-mode files in Javascript在 Javascript 中解析 org-mode 文件
【发布时间】:2011-06-17 18:12:13
【问题描述】:

现在已经有一段时间了,我试图让自己用 JavaScript 为org-mode 编写一个解析器。我在解析大纲时一点问题都没有(我在几分钟内就完成了),但解析实际内容要困难得多,例如,我在处理叠层列表时遇到了麻烦。

* This is a heading
  P1 Start a paragraph here but since it is the first indentation level
the paragraph may have a lower indentation on the next line
    or a greater one for that matter.

  + LI1.1 I am beginning a list here
  + LI1.2 Here begins another list item
    which continues here
      and also here
  P2 but is broken here (this line becomes a paragraph
  outside of the first list).
  + LI2.1 P1 Second list item.
    - LI2.1.1 Inner list with a simple item
    - LI2.1.2 P1 and with an item containing several paragraphs.
      Here is the second line in the item, and now

      LI2.1.2 P2 I begin a new paragraph still in the same item. 
        The indentation can be only higher
    LI2.1 P2 but if the indentation is lower, it breaks the item, 
    (and the whole list), and this is a paragraph in the LI2.1
    list item

    - LI 2.2.1 You get the picture
  P3 Just plain text outside of the list.

(在上面的例子中,PXLIX.Y 只是明确显示新块的开始,它们不会出现在实际文档中。P 代表段落,LI 代表列表项。在 HTML 世界中,PX 是 <p> 标记的开头。编号只是为了帮助跟踪列表的嵌套和变化。)

我想知道解析这种重要的空白叠瓦块的策略,显然我可以逐行解析而无需任何回溯或什么都没有,所以它一定很简单,但由于某种原因我无法做到做。我试图从 Markdown 解析器或应该具有类似重叠特性的东西中获得灵感,但在我看来(对于我看到的那些)它们非常hacky,充满了正则表达式,我希望我能写一些更干净的东西(org - 模式“语法”当你考虑它时非常庞大,它会一点一点地增长,我希望整个东西都可以维护并允许轻松插入新功能)。

哪位有解析这类东西的经验可以给我一些指点吗?

【问题讨论】:

  • AFAIK,没有简单的方法来解析它。这些类似 Wiki 的格式只是 @$$ 处理的痛苦。您是要手写解析器,还是编写/翻译语法并让解析器生成器为您创建解析器?
  • 好吧,您的评论表明我是手动编码的,我已经开始这样做但未能找到正确的方法。也许写一个语法会更容易,但我不知道如何处理重要的空格。我是解析新手,所以到目前为止我尝试过的所有东西都碰壁了。 :)
  • 某处是否有正式的语法?在我看来,问题在于您没有结束语句的令牌。几种语言使用空格格式而不是分号和大括号,但我想不出任何可以让您像 P1 示例那样在第一行之后有任何程度的缩进的格式。
  • @Samsdran : 没有我知道的语法...

标签: javascript parsing org-mode


【解决方案1】:

我喜欢解析器和编译器理论,所以我编写了一个小型解析器(手动),它能够将您的示例 sn-p 解析为 一个 XML DOM 文档对象。应该可以对其进行修改,使其生成其他类型的树结构,例如自定义 AST(抽象语法树)。

我试图让代码易于阅读,以便您了解这样的解析器是如何工作的。

问我是否需要更多解释,或者让我稍微修改一下。

以您的示例 sn-p 作为输入,语句 result = new OrgModParser().parse(input); result.xml 返回:

<org-mode-document indentLevel="-1">
    <section indentLevel="0">
        <header indentLevel="0">This is a heading</header>
            <paragraph indentLevel="1">P1 Start a paragraph here but since it is the first indentation level the paragraph may have a lower indentation on the next line or a greater one for that matter.</paragraph>
            <list indentLevel="1">
                <list-item indentLevel="1">
                    <paragraph indentLevel="2">LI1.1 I am beginning a list here</paragraph>
                </list-item>
                <list-item indentLevel="1">
                    <paragraph indentLevel="2">LI1.2 Here begins another list item which continues here and also here</paragraph>
                </list-item>
            </list>
        <paragraph indentLevel="1">P2 but is broken here (this line becomes a paragraph outside of the first list).</paragraph>
        <list indentLevel="1">
            <list-item indentLevel="1">
                <paragraph indentLevel="2">LI2.1 P1 Second list item.</paragraph>
                <list indentLevel="2">
                    <list-item indentLevel="2">
                        <paragraph indentLevel="3">LI2.1.1 Inner list with a simple item</paragraph>
                    </list-item>
                    <list-item indentLevel="2">
                        <paragraph indentLevel="3">LI2.1.2 P1 and with an item containing several paragraphs. Here is the second line in the item, and now</paragraph>
                        <paragraph indentLevel="3">LI2.1.2 P2 I begin a new paragraph still in the same item.  The indentation can be only higher</paragraph>
                    </list-item>
                </list>
                <paragraph indentLevel="2">LI2.1 P2 but if the indentation is lower, it breaks the item,  (and the whole list), and this is a paragraph in the LI2.1 list item</paragraph>
                <list indentLevel="2">
                    <list-item indentLevel="2">
                        <paragraph indentLevel="3">LI2.2.1 You get the picture</paragraph>
                    </list-item>
                </list>
            </list-item>
        </list>
        <paragraph indentLevel="1">P3 Just plain text outside of the list.</paragraph>
    </section>
</org-mode-document>

代码:

/*
 * File: orgmodparser.js
 * Basic usage: var object = new OrgModeParser().parse(input); 
 * Works on: JScript and JScript.Net.
 * - For other JavaScript platforms, just replace or override the .createRoot() method
 */

OrgModeParser = function (options) {
    if (typeof options == "object") {
        for (var i in options) {
            this[i] = options[i];
        }
    }
}

OrgModeParser.prototype = {

    "INDENT_WIDTH" :    2,  // Two spaces
    "LINE_SEPARATOR" :  "\r\n",

    /*
     * Each line in the input will be matched against this regexp.
     * Only spaces are allowed as indentation characters.
     * The symbols '*', '+' and '-' will be recognized, but only if they are followed by at least one space.
     * Add other symbols in this regexp if you want the parser to recognize them
     */
    "re" :    /^( *)([\+\-\*] +)?(.*)/,

    // This function must return a valid XML DOM document object
    createRoot :    function () {
        var err, progIDs = ["Msxml2.DOMDocument.6.0", "Msxml2.DOMDocument.5.0", "Msxml2.DOMDocument.4.0", "Msxml2.DOMDocument.3.0", "Msxml2.DOMDocument.2.0", "Msxml2.DOMDocument.1.0", "Msxml2.DOMDocument"];
        for (var i = 0; i < progIDs.length; i++) {
            try {
                return new ActiveXObject(progIDs[i]);
            }
            catch (err) {
            }
        }
        alert("Org-mode parser - Error - Failed to instantiate root object");
        return null;
    },

    parse : function (text) {

        function createNode (tagName, text) {
            var node = root.createElement(tagName);
            node.setAttribute("indentLevel", level);
            if (text) {
                var textNode = root.createTextNode(text);
                node.appendChild(textNode);
            }
            return node;
        }

        function getContainer () {
            if (lastNode.tagName == "section") { return lastNode; }
            var anc = lastNode.parentNode;
            while (anc) {
                if (modifier == "+" || modifier == "-") {
                    if (anc.getAttribute("indentLevel") == level && anc.tagName == "list") { return anc; }
                }
                if (anc.getAttribute("indentLevel") < level && anc.tagName != "paragraph") { return anc; }
                anc = anc.parentNode;
            }
            alert("Org-mode parser - Internal error at line: "+i);return null;
        }

        if (typeof text != "string") { alert("Org-mode - Type error - Input must be of type 'string'"); return null; }

        var body;
        var content;     // The text of the current line, without its indentation and modifier
        var lastNode;    // The node being processed
        var indent;      // The indentation of the current line
        var isAfterDubbleLineBreak;  // Indicates if the current line follows a dubble line break
        var line;        // The current line being processed
        var level;       // The current indentation level; given by indent.length / this.INDENT_WIDTH. Not to confuse with the nesting level 
        var lines;       // Array. Empty lines are included.
        var match;
        var modifier;    // This can be "*", "+", "-" or ""
        var root;

        isAfterDubbleLineBreak = false;
        level = -1;      // Indentation level is -1 initially; it will be 0 for the first "*"-bloc
        lines = text.split(this.LINE_SEPARATOR);
        root = this.createRoot();
        body = root.appendChild(createNode("org-mode-document"));
        lastNode = body;

        for (var i = 0; i < lines .length; i++) {
            line = lines[i];
            match = line.match(this.re);
            if (match === null) { alert("org-mode parse error at line: " + i); return null; }
            indent = match[1];
            level = indent.length / this.INDENT_WIDTH;
            modifier = match[2] && match[2].charAt(0);
            content = match[3];

            // These conditions tell the parser what to do when encountering a line with a given modifer
            if (content === "") { dubbleLineBreak(); continue; }
            else if (modifier == "+" || modifier == "-") { plus(); }
            else if (modifier == "*") { star(); }
            else if (modifier == "+") { plus(); }
            else if (modifier == "-") { minus(); }
            else if (modifier == "") { noModifier(); }
            isAfterDubbleLineBreak = false;
        }
        return root;


        function star() {
            // The '*' modifier is not allowed on an indented line
            if (indent) { alert("Org-mode parse error: unexpected '*' symbol at line " + i); return null; }
            lastNode = body.appendChild(createNode("section"));
            // The div remains the current node
            lastNode.appendChild(createNode("header", content));
        }

        function plus() {
            var container = getContainer();
            var tn = container.tagName;
            if (tn == "section" || tn == "list-item") {
                lastNode = container.appendChild(createNode("list"));
                lastNode = lastNode.appendChild(createNode("list-item"));
                lastNode = lastNode.appendChild(createNode("paragraph", content));
            } else if (tn == "list") {
                lastNode = container.appendChild(createNode("list-item"));
                lastNode = lastNode.appendChild(createNode("paragraph", content));
            }
            else alert("Org-mode parser - Internal error - Bad container tag name: " + tn);
            lastNode.setAttribute("indentLevel", Number(lastNode.getAttribute("indentLevel")) + 1);
        }

        function minus() { plus(); }

        function noModifier() {
            if (lastNode.tagName == "paragraph" && !isAfterDubbleLineBreak && (lastNode.getAttribute("indentLevel") == 1 || level >= lastNode.getAttribute("indentLevel"))) {
                lastNode.childNodes[0].appendData(" " + content);
            } else {
                var container = getContainer();
                lastNode = container.appendChild(createNode("paragraph", content));
            }
        }

        function dubbleLineBreak() {
            while (lines[i+1] && /^\s*$/.test(lines[i+1])) { i++; }
            isAfterDubbleLineBreak = true;
        }

    }
};

【讨论】:

  • 不错,这比我所在的地方好一点(因为它(几乎)按我预期的那样工作:)),但我非常不愿意依赖 DOM,首先因为我希望使用浏览器外部的解析器,其次是因为 org-mode 的一些其他功能无法按原样插入到 DOM 模型中。 (我说的几乎如我所料,因为 LI2.1.2 应该有两个段落:段落在双换行符上被打破。)
  • 感谢您的反馈。我已经更正了段落的重复换行错误并将输出类型更改为 XML 对象,因此它不需要浏览器即可工作。
  • 谢谢。你会发现我很烦人,但是依赖 Windows ActiveX 并不比依赖浏览器好多少...... :) 不过我明白你的建议的要点。
  • 不,我不觉得你很烦人......我发布的代码并不是适用于每个 js 平台的完整解决方案,只是如何处理叠瓦块和空格的示例,不使用许多正则表达式。这就是我使用 DOM 或 XML 对象的原因,它允许我编写更少的方法,从而使代码更短且更易于理解。无论如何,很容易移植到任何支持 xml 的平台。但是如果您需要一个完全可移植的解析器,它将使用纯 JS 树结构而不是 XML 对象。如果你想让我写它,或者发布关于算法的解释,请告诉我。
  • 我为给定的努力奖励了这个答案,我真的很感激。我对这个答案的问题是它与我所处的位置非常相似,并且向解析器添加更多功能可能会变得非常混乱。但正如 Bart 所说,这种语言的语法无论如何都必须是混乱的,我对我的问题的回答是:“嗯,它很复杂,尝试更多”。 :) 非常感谢 Luc 和 Bart。
【解决方案2】:

就像我在 cmets 中所说的那样,解析它会很痛苦,就像许多类似 Wiki 的语言一样。

如果您要编写语法并让解析器生成器为您创建解析器,而不是手动编写解析器,则有多种选择。列举几个:

我知道 ANTLR 可以做到这一点,但这并非易事,最重要的是,您需要掌握该工具(这需要一些时间!)。我没有在其他两个工具上花费太多时间,但怀疑它们是否能胜任使用如此讨厌的语言的工作。

使用手写解析器可以让您快速入门,但调试、增强或重写它会很困难。编写语法并让解析器生成器为您创建解析器将使解析器的调试、增强和重写(通过语法)更容易,但您将需要(相当)一些时间来学习使用该工具。

如果编写得当,手写解析器(很可能)会比生成的解析器更快,当然。但它们之间的差异可能只有在大块源代码中才会明显。

抱歉,我没有如何使用手写解析器处理此问题的通用策略。

祝你好运!

【讨论】:

  • +1 表示吉森。你只需要使用一个好的旧 lex/yacc 端口来进行解析。
  • @Raynos,好的,但这并不能完全回答如何处理重要的空白,特别是当 LI 项目具有这种特殊性时,它们的第一行和后续行有不同的缩进(请参阅问题中的示例)。
  • @subtenante Buy the dragon book。然后阅读它。然后用你惊人的编译器知识解决你的问题。
  • @Raynos 为什么要输入这么多字符?您可以只说“解决您的问题”。 :p
  • @subtenante,如果您有兴趣,我可以发布一个关于如何使用 ANTLR(当然还有 JavaScript 目标)解析它的小演示。小,我的意思是不是完整的组织模式,而是你发布的小例子 sn-p。但是,如果您已经知道自己不会使用 ANTLR,我不会打扰。请告诉我,今晚我会尝试做点什么(我在 CET)。
【解决方案3】:

有一个可用的 Javascript 组织模式解析器 here

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多