簡體   English   中英

如何從 javascript 文件中提取 javascript function

[英]How to to extract a javascript function from a javascript file

我需要從腳本文件中提取整個 javascript function 。 我知道function的名字,但不知道function的內容可能是什么。 這個 function 可以嵌入到任意數量的閉包中。

我需要有兩個 output 值:

  1. 我在輸入腳本中找到的名為 function 的整個正文。
  2. 刪除了名為 function 的完整輸入腳本。

所以,假設我在這個輸入腳本中尋找findMe function:

function() {
  function something(x,y) {
    if (x == true) {
      console.log ("Something says X is true");
      // The regex should not find this:
      console.log ("function findMe(z) { var a; }");
    }
  }
  function findMe(z) {
    if (z == true) {
      console.log ("Something says Z is true");
    }
  }
  findMe(true);
  something(false,"hello");
}();

由此,我需要以下兩個結果值:

  1. 提取的findMe腳本

    function findMe(z) { if (z == true) { console.log ("Something says Z is true"); } }
  2. 刪除了findMe function 的輸入腳本

    function() { function something(x,y) { if (x == true) { console.log ("Something says X is true"); // The regex should not find this: console.log ("function findMe(z) { var a; }"); } } findMe(true); something(false,"hello"); }();

我正在處理的問題:

  1. 要查找的腳本正文中可能包含任何有效的 javascript 代碼。 查找此腳本的代碼或正則表達式必須能夠忽略字符串、多個嵌套塊級別等中的值。

  2. 如果在字符串中指定要查找的 function 定義,則應忽略它。

關於如何完成這樣的事情的任何建議?

更新:

看起來正則表達式不是這樣做的正確方法。 我願意接受指向可以幫助我完成此任務的解析器的指針。 我在看Jison ,但很想聽聽其他的。

如果腳本包含在您的頁面中(您不清楚的內容)並且 function 可以公開訪問,那么您可以通過以下方式獲取 function 的源代碼:

functionXX.toString();

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function/toString

其他想法:

1) 查看執行 JS 縮小或 JS 縮進的開源代碼。 在這兩種情況下,這些代碼都必須“理解” JS 語言才能以容錯的方式完成它們的工作。 我懷疑它會是純正則表達式,因為語言比這復雜一點。

2)如果您在服務器上控制源並且想要在其中修改特定的 function,那么只需插入一些新的 JS,在運行時將 function 替換為您自己的 ZC1C425268E681785D1AB4Z5074C。 這樣,您就可以讓 JS 編譯器為您識別 function,然后將其替換為您自己的版本。

3)對於正則表達式,這是我所做的並不是萬無一失的,但對我使用的一些構建工具有用:

我運行多遍(在python中使用正則表達式):

  1. 刪除所有用 /* 和 */ 描述的注釋。
  2. 刪除所有引用的字符串
  3. 現在,剩下的就是非字符串、非注釋 javascript 所以你應該能夠直接在你的 function 聲明上進行正則表達式
  4. 如果您需要帶有字符串和注釋的 function 源代碼,您必須從原始代碼中重新構建它,因為您知道 function 的開頭結尾

以下是我使用的正則表達式(以 python 的多行格式表示):

reStr = r"""
    (                               # capture the non-comment portion
        "(?:\\.|[^"\\])*"           # capture double quoted strings
        |
        '(?:\\.|[^'\\])*'           # capture single quoted strings
        |
        (?:[^/\n"']|/[^/*\n"'])+    # any code besides newlines or string literals
        |
        \n                          # newline
    )
    |
    (/\*  (?:[^*]|\*[^/])*   \*/)       # /* comment */
    |
    (?://(.*)$)                     # // single line comment
    $"""    

reMultiStart = r"""         # start of a multiline comment that doesn't terminate on this line
    (
        /\*                 # /* 
        (
            [^\*]           # any character that is not a *
            |               # or
            \*[^/]          # * followed by something that is not a /
        )*                  # any number of these
    )
    $"""

reMultiEnd = r"""           # end of a multiline comment that didn't start on this line
    (
        ^                   # start of the line
        (
            [^\*]           # any character that is not a *
            |               # or
            \*+[^/]         # * followed by something that is not a /
        )*                  # any number of these
        \*/                 # followed by a */
    )
"""

regExSingleKeep = re.compile("// /")                    # lines that have single lines comments that start with "// /" are single line comments we should keep
regExMain = re.compile(reStr, re.VERBOSE)
regExMultiStart = re.compile(reMultiStart, re.VERBOSE)
regExMultiEnd = re.compile(reMultiEnd, re.VERBOSE)

這對我來說聽起來很亂。 你最好解釋一下你真正想要解決的問題,這樣人們就可以幫助找到一個更優雅的解決實際問題的方法。

我使用普通的舊字符串方法(無正則表達式)在 C# 中構建了一個解決方案,它也適用於嵌套函數。 基本原理是計算大括號並檢查不平衡的右大括號。 警告:這不適用於大括號是注釋一部分的情況,但您可以通過在解析 function 邊界之前首先從代碼中刪除注釋來輕松增強此解決方案。

我首先添加了這個擴展方法來提取字符串中匹配的所有索引(來源: 更有效的方法來獲取字符串中字符的所有索引

    /// <summary>
    /// Source: https://stackoverflow.com/questions/12765819/more-efficient-way-to-get-all-indexes-of-a-character-in-a-string
    /// </summary>
    public static List<int> AllIndexesOf(this string str, string value)
    {
        if (String.IsNullOrEmpty(value))
            throw new ArgumentException("the string to find may not be empty", "value");
        List<int> indexes = new List<int>();
        for (int index = 0; ; index += value.Length)
        {
            index = str.IndexOf(value, index);
            if (index == -1)
                return indexes;
            indexes.Add(index);
        }
    }

我定義了這個結構以便於引用 function 邊界:

    private struct FuncLimits
    {
        public int StartIndex;
        public int EndIndex;
    }

這是我解析邊界的主要 function :

    public void Parse(string file)
    {
        List<FuncLimits> funcLimits = new List<FuncLimits>();

        List<int> allFuncIndices = file.AllIndexesOf("function ");
        List<int> allOpeningBraceIndices = file.AllIndexesOf("{");
        List<int> allClosingBraceIndices = file.AllIndexesOf("}");

        for (int i = 0; i < allFuncIndices.Count; i++)
        {
            int thisIndex = allFuncIndices[i];
            bool functionBoundaryFound = false;

            int testFuncIndex = i;
            int lastIndex = file.Length - 1;

            while (!functionBoundaryFound)
            {
                //find the next function index or last position if this is the last function definition
                int nextIndex = (testFuncIndex < (allFuncIndices.Count - 1)) ? allFuncIndices[testFuncIndex + 1] : lastIndex;

                var q1 = from c in allOpeningBraceIndices where c > thisIndex && c <= nextIndex select c;
                var qTemp = q1.Skip<int>(1); //skip the first element as it is the opening brace for this function

                var q2 = from c in allClosingBraceIndices where c > thisIndex && c <= nextIndex select c;

                int q1Count = qTemp.Count<int>();
                int q2Count = q2.Count<int>();

                if (q1Count == q2Count && nextIndex < lastIndex)
                    functionBoundaryFound = false; //next function is a nested function, move on to the one after this
                else if (q2Count > q1Count)
                {
                    //we found the function boundary... just need to find the closest unbalanced closing brace 
                    FuncLimits funcLim = new FuncLimits();
                    funcLim.StartIndex = q1.ElementAt<int>(0);
                    funcLim.EndIndex = q2.ElementAt<int>(q1Count);
                    funcLimits.Add(funcLim);

                    functionBoundaryFound = true;
                }
                testFuncIndex++;
            }
        }
    }

我幾乎擔心正則表達式無法完成這項工作。 我認為這與嘗試使用正則表達式解析 XML 或 HTML 相同,這個話題已經在這個論壇上引起了各種宗教辯論。

編輯:如果這與嘗試解析 XML 不同,請糾正我。

正則表達式不能做到這一點。 What you need is a tool that parses JavaScript in a compiler-accurate way, builds up a structure representing the shape of the JavaScript code, enables you to find the function you want and print it out, and enables you to remove the function definition from該結構並重新生成剩余的 javascript 文本。

我們的DMS 軟件再造工具包可以使用其JavaScript 前端來做到這一點。 DMS 提供通用解析、抽象語法樹構建/導航/操作以及來自修改后的 AST 的(有效)源文本的漂亮打印。 The JavaScript front end provides DMS with compiler-accurate definition of JavaScript, You can point DMS/JavaScript at a JavaScript file (or even various kinds of dynamic HTML with embedded script tags containing JavaScript). 讓它產生 AST:DMS 模式可用於查找您的功能:

  pattern find_my_function(r:type,a: arguments, b:body): declaration
     " \r my_function_name(\a) { \b } ";

DMS 可以在 AST 中搜索具有指定結構的匹配樹; 因為這是一個 AST 匹配而不是字符串匹配,所以換行符、空格、注釋和其他微不足道的差異不會欺騙它。 [你沒有說的是如果你有多個不同范圍的function怎么辦:你想要哪一個?]

找到匹配項后,您可以要求 DMS打印匹配的代碼作為提取步驟。 您還可以要求 DMS 使用重寫規則刪除 function:

  rule remove_my_function((r:type,a: arguments, b:body): declaration->declaration
     " \r my_function_name(\a) { \b } " -> ";";

然后漂亮打印生成的 AST。 DMS 將正確保留所有評論。

我想你必須為這項工作使用和構造一個 String-Tokenizer。

function tokenizer(str){
  var stack = array(); // stack of opening-tokens
  var last = ""; // last opening-token

  // token pairs: subblocks, strings, regex
  var matches = {
    "}":"{",
    "'":"'",
    '"':'"',
    "/":"/"
  };

  // start with function declaration
  var needle = str.match(/function[ ]+findme\([^\)]*\)[^\{]*\{/);

  // move everything before needle to result
  var result += str.slice(0,str.indexOf(needle));
  // everithing after needle goes to the stream that will be parsed
  var stream = str.slice(str.indexOf(needle)+needle.length);

  // init stack
  stack.push("{");
  last = "{";

  // while still in this function
  while(stack.length > 0){

    // determine next token
    needle = stream.match(/(?:\{|\}|"|'|\/|\\)/); 

    if(needle == "\\"){
      // if this is an escape character => remove escaped character
      stream = stream.slice(stream.indexOf(needle)+2);
      continue;

    }else if(last == matches[needle]){
      // if this ends something pop stack and set last
      stack.pop();
      last = stack[stack.length-1];

    }else if(last == "{"){  
      // if we are not inside a string (last either " or ' or /)
      // push needle to stack
      stack.push(needle);
      last = needle;
    }

    // cut away including token
    stream = stream.slice(stream.indexOf(needle)+1);
  }

  return result + stream;
}

哦,我忘記了評論的標記......但我想你現在知道它是如何工作的......

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM