สามารถกำหนดรูปแบบ csv โดย regex ได้หรือไม่?


19

เพื่อนร่วมงานและฉันเพิ่งจะถกเถียงกันว่า regex บริสุทธิ์นั้นมีความสามารถในการห่อหุ้มรูปแบบ csv อย่างเต็มที่หรือไม่ซึ่งมันสามารถแยกวิเคราะห์ไฟล์ทั้งหมดด้วย escape char ที่ระบุไว้, ถ่าน char และถ่านคั่น

regex ไม่จำเป็นต้องสามารถเปลี่ยน chars เหล่านี้หลังจากการสร้าง แต่จะต้องไม่ล้มเหลวในกรณี edge อื่น ๆ

ฉันได้แย้งว่านี่เป็นไปไม่ได้สำหรับแค่ tokenizer regex เดียวที่อาจทำสิ่งนี้เป็นรูปแบบ PCRE ที่ซับซ้อนมากซึ่งเคลื่อนไปไกลกว่าเพียงแค่โทเค็น

ฉันกำลังมองหาบางอย่างตามแนวของ:

... รูปแบบ csv เป็นบริบทไวยากรณ์ฟรีและเป็นไปไม่ได้ที่จะแยกวิเคราะห์ด้วย regex เพียงอย่างเดียว ...

หรือฉันผิด เป็นไปได้หรือไม่ที่จะแยก csv ด้วย POSIX regex?

ตัวอย่างเช่นถ้าทั้ง char char และ quote char อยู่"ดังนั้นทั้งสองบรรทัดนี้จะเป็น csv ที่ถูกต้อง:

"""this is a test.""",""
"and he said,""What will be, will be."", to which I replied, ""Surely not!""","moving on to the next field here..."

มันไม่ได้เป็น CSV เป็นไม่มีที่ใดก็ได้ที่ทำรัง (IIRC)
วงล้อประหลาด

1
แต่กรณีขอบคืออะไร? อาจมี CSV มากกว่าที่ฉันเคยคิดหรือ
c69

1
@ c69 วิธีการทั้งสองเกี่ยวกับการหลบหนีและอ้างถึงถ่านเป็น"อย่างไร จากนั้นข้อมูลต่อไปนี้จะถูกต้อง:"""this is a test.""",""
Spencer Rathbun

คุณลอง regexp จากตรงนี้เหรอ?
dasblinkenlight

1
คุณจำเป็นต้องระวังกรณีขอบ แต่ regex ควรสามารถ tokenize csv ตามที่คุณได้อธิบายไว้ regex ไม่จำเป็นต้องนับจำนวนอัญประกาศโดยพลการ แต่ต้องนับเป็น 3 เท่านั้นซึ่งนิพจน์ทั่วไปสามารถทำได้ เป็นคนอื่นได้กล่าวถึงคุณควรพยายามที่จะเขียนลงเป็นตัวแทนที่ดีที่กำหนดของสิ่งที่คุณคาดหวัง CSV โทเค็นจะ ...
comingstorm

คำตอบ:


20

ในทางทฤษฎีดีมากในทางปฏิบัติ

โดยCSVฉันจะถือว่าคุณหมายถึงการประชุมตามที่อธิบายในRFC 4180

ในขณะที่การจับคู่ข้อมูล CSV พื้นฐานเป็นเรื่องเล็กน้อย:

"data", "more data"

หมายเหตุ: BTW มันมีประสิทธิภาพมากขึ้นในการใช้ฟังก์ชัน. split ('/ n'). split ('"') สำหรับข้อมูลที่ง่ายและมีโครงสร้างที่ดีเช่นนี้นิพจน์ทั่วไปทำงานเป็น NDFSM (Non-Deterministic Finite) State Machine) ที่ทำให้เปลืองเวลาในการย้อนรอยมากเมื่อคุณเริ่มเพิ่มเคสแบบขอบอย่างเช่นตัวหลบหลีก

ตัวอย่างเช่นนี่คือสตริงการจับคู่นิพจน์ทั่วไปที่ครอบคลุมที่สุดที่ฉันพบ:

re_valid = r"""
# Validate a CSV string having single, double or un-quoted values.
^                                   # Anchor to start of string.
\s*                                 # Allow whitespace before value.
(?:                                 # Group for value alternatives.
  '[^'\\]*(?:\\[\S\s][^'\\]*)*'     # Either Single quoted string,
| "[^"\\]*(?:\\[\S\s][^"\\]*)*"     # or Double quoted string,
| [^,'"\s\\]*(?:\s+[^,'"\s\\]+)*    # or Non-comma, non-quote stuff.
)                                   # End group of value alternatives.
\s*                                 # Allow whitespace after value.
(?:                                 # Zero or more additional values
  ,                                 # Values separated by a comma.
  \s*                               # Allow whitespace before value.
  (?:                               # Group for value alternatives.
    '[^'\\]*(?:\\[\S\s][^'\\]*)*'   # Either Single quoted string,
  | "[^"\\]*(?:\\[\S\s][^"\\]*)*"   # or Double quoted string,
  | [^,'"\s\\]*(?:\s+[^,'"\s\\]+)*  # or Non-comma, non-quote stuff.
  )                                 # End group of value alternatives.
  \s*                               # Allow whitespace after value.
)*                                  # Zero or more additional values
$                                   # Anchor to end of string.
"""

มันมีเหตุผลจัดการค่าที่ยกมาเดี่ยวและคู่ แต่ไม่ขึ้นบรรทัดใหม่ในค่าที่หลีกหนีคำพูด ฯลฯ

ที่มา: Stack Overflow - ฉันจะแยกสตริงด้วย JavaScript ได้อย่างไร

มันกลายเป็นฝันร้ายเมื่อมีการเปิดตัวกล่องใส่ขอบทั่วไป ...

"such as ""escaped""","data"
"values that contain /n newline chars",""
"escaped, commas, like",",these"
"un-delimited data like", this
"","empty values"
"empty trailing values",        // <- this is completely valid
                                // <- trailing newline, may or may not be included

เคสขอบใหม่ตามมูลค่าเพียงอย่างเดียวก็เพียงพอที่จะแยก 99.9999% ของตัวแยกวิเคราะห์ RegEx ตามที่พบในป่า ทางเลือกเดียวที่สมเหตุสมผลคือการใช้การจับคู่ RegEx สำหรับอักขระควบคุม / ไม่ควบคุมขั้นพื้นฐาน (เช่นเทอร์มินัลเทียบกับที่ไม่ใช่เทอร์มินัล) จับคู่กับเครื่องสถานะที่ใช้สำหรับการวิเคราะห์ระดับที่สูงขึ้น

แหล่งที่มา: ประสบการณ์ที่รู้จักกันเป็นอย่างอื่นว่าความเจ็บปวดและความทุกข์ทรมานมากมาย

ฉันเป็นผู้เขียนjquery-CSVซึ่งเป็น javascript เท่านั้นที่ใช้ตัวแยกวิเคราะห์ CSV ที่สอดคล้องกับ RFC อย่างสมบูรณ์ที่สุดในโลก ฉันใช้เวลาหลายเดือนในการแก้ปัญหานี้พูดกับคนที่มีความคิดสร้างสรรค์จำนวนมากและลองใช้งานเป็นตันถ้าการใช้งานที่แตกต่างกันรวมถึงการเขียนใหม่ทั้งหมด 3 ครั้งของตัวแยกวิเคราะห์หลัก

tl; dr - คุณธรรมของเรื่องราว PCRE เพียงอย่างเดียวดูดการแยกวิเคราะห์อะไร แต่ไวยากรณ์ที่ง่ายและเข้มงวด (Ie Type-III) ธรรมดาที่สุด แม้ว่ามันจะมีประโยชน์สำหรับ tokenizing terminal และ non-terminal strings


1
ใช่นั่นเป็นประสบการณ์ของฉันเช่นกัน ความพยายามใด ๆ ที่จะแค็ปซูลมากกว่ารูปแบบ CSV ที่เรียบง่ายนั้นจะเข้ามาในสิ่งเหล่านี้แล้วคุณจะพบกับปัญหาด้านประสิทธิภาพและปัญหาความซับซ้อนของ regex ขนาดใหญ่ คุณดูไลบรารี่ node-csvหรือยัง? ดูเหมือนว่าจะตรวจสอบทฤษฎีนี้เช่นกัน ทุกการดำเนินการที่ไม่สำคัญใช้ parser ภายใน
Spencer Rathbun

@SpencerRathbun Yep ฉันแน่ใจว่าฉันได้ดูที่แหล่ง node-csv มาก่อน ดูเหมือนว่าจะใช้เครื่องสถานะ tokenization ตัวละครทั่วไปสำหรับการประมวลผล jquery-csv parser ทำงานบนแนวคิดพื้นฐานเดียวกันยกเว้นฉันใช้ regex สำหรับเทอร์มินัลโทเค็น / ไม่ใช่เทอร์มินัล แทนที่จะประเมินและเชื่อมต่อกับ char-by-char regex สามารถจับคู่อักขระที่ไม่ใช่เทอร์มินัลได้หลายตัวพร้อมกันและส่งกลับเป็นกลุ่ม (เช่นสตริง) ซึ่งช่วยลดการต่อข้อมูลที่ไม่จำเป็นและ 'ควร' เพิ่มประสิทธิภาพ
Evan Plaice

20

Regex สามารถแยกภาษาปกติและไม่สามารถแยกสิ่งแฟนซีเช่นไวยากรณ์ซ้ำ แต่ CSV ดูเหมือนจะค่อนข้างปกติดังนั้นจึงสามารถแยกวิเคราะห์ได้ด้วย regex

มาทำงานจากคำจำกัดความ : อนุญาตเป็นลำดับตัวเลือกรูปแบบทางเลือก ( |) และการทำซ้ำ (Kleene star, the *)

  • ค่าที่ไม่ได้อ้างอิงเป็นค่าปกติ: [^,]*# อักขระใด ๆ ยกเว้นเครื่องหมายจุลภาค
  • ค่าที่ยกมาเป็นปกติ: "([^\"]|\\\\|\\")*"# ลำดับของอะไรก็ได้ยกเว้น quote "หรือ\"escape quote หรือ escape escape\\
    • บางรูปแบบอาจรวมถึงการหลีกเลี่ยงการพูดด้วยคำพูดซึ่งจะเพิ่มตัวแปร("")*"ในการแสดงออกด้านบน
  • ค่าที่อนุญาตเป็นปกติ: <unquoted-value> |<quoted-value>
  • บรรทัด CSV บรรทัดเดียวเป็นปกติ: <value> (,<value>)*
  • ลำดับของเส้นที่คั่นด้วย\nเป็นปกติอย่างเห็นได้ชัด

ฉันไม่ได้ทดสอบแต่ละสำนวนอย่างพิถีพิถันและไม่เคยนิยามกลุ่มจับ ฉันยังกลบเกลื่อนศัพท์บางอย่างเช่นสายพันธุ์ของตัวละครที่สามารถนำมาใช้แทน,, "หรือแยกบรรทัด: เหล่านี้ไม่ได้ทำลายความสม่ำเสมอที่คุณเพิ่งได้รับภาษาที่แตกต่างออกไปเล็กน้อยหลาย

หากคุณสามารถพบปัญหาในการพิสูจน์นี้โปรดแสดงความคิดเห็น! :)

แต่อย่างไรก็ตามการแยกวิเคราะห์ไฟล์ CSV ที่ใช้งานจริงโดยนิพจน์ปกติล้วน ๆ อาจเป็นปัญหาได้ คุณจำเป็นต้องรู้ว่าตัวแปรใดบ้างที่ถูกป้อนให้กับ parser และไม่มีมาตรฐานสำหรับมัน คุณสามารถลองใช้ parsers หลาย ๆ ตัวต่อแต่ละบรรทัดได้จนกว่าจะประสบความสำเร็จ แต่สิ่งนี้อาจต้องใช้วิธีการอื่นนอกเหนือจากการแสดงออกปกติเพื่อทำอย่างมีประสิทธิภาพหรือเลย


4
+1 อย่างแน่นอนสำหรับจุดปฏิบัติ มีบางอย่างที่ฉันแน่ใจว่าที่ใดที่หนึ่งที่ลึกคือตัวอย่างของค่า (ที่วางแผนไว้) ที่จะทำให้รุ่นที่มีค่าที่ยกมานั้นฉันไม่ทราบว่ามันคืออะไร 'ความสนุก' กับตัวแยกวิเคราะห์หลายตัวจะเป็น "งานสองอย่างนี้ แต่ให้คำตอบที่ต่างออกไป"

1
เห็นได้ชัดว่าคุณจะต้องใช้ regexes ที่แตกต่างกันสำหรับเครื่องหมายแบ็กสแลช Regex สำหรับเขตข้อมูล csv ประเภทก่อนหน้าควรเป็นแบบ[^,"]*|"(\\(\\|")|[^\\"])*"นี้และแบบหลังควรเป็นแบบ[^,"]*|"(""|[^"])*"นี้ (ระวังเพราะฉันไม่ได้ทดสอบสิ่งเหล่านี้!)
พบ

ค้นหาสิ่งที่อาจเป็นมาตรฐานมีกรณีที่พลาด - ค่าที่มีตัวคั่นระเบียนล้อมรอบ สิ่งนี้ทำให้การแยกวิเคราะห์ภาคปฏิบัติสนุกยิ่งขึ้นเมื่อมีหลายวิธีที่จะจัดการกับมัน

คำตอบที่ดี แต่ถ้าฉันเรียกใช้perl -pi -e 's/"([^\"]|\\\\|\\")*"/yay/'และไปป์"I have here an item,\" that is a test\""ผลก็คือ `ใช่นั่นคือการทดสอบ \" " คิดว่า regex ของคุณมีข้อบกพร่อง
Spencer Rathbun

@SpencerRathbun: เมื่อฉันมีเวลามากขึ้นฉันจะทดสอบ regexes จริง ๆ และอาจวางรหัสการพิสูจน์แนวคิดที่ผ่านการทดสอบ ขออภัยวันทำงานกำลังดำเนินไป
9000

5

คำตอบง่ายๆ - อาจจะไม่

ปัญหาแรกคือการขาดมาตรฐาน ในขณะที่บางคนอาจอธิบาย csv ของพวกเขาในวิธีที่กำหนดไว้อย่างเคร่งครัด แต่ก็ไม่สามารถคาดหวังได้ว่าจะได้รับไฟล์ csv ที่กำหนดอย่างเคร่งครัด "จงระมัดระวังในสิ่งที่คุณทำจงเสรีในสิ่งที่คุณยอมรับจากผู้อื่น" - จอนไปรษณีย์

สมมติว่ามีมาตรฐานที่ยอมรับได้มีคำถามเกี่ยวกับอักขระเลี่ยงและหากจำเป็นต้องมีความสมดุล

สตริงในรูปแบบ CSV string value 1,string value 2จำนวนมากถูกกำหนดให้เป็น "string, value 1",string value 2แต่ถ้าสตริงที่มีเครื่องหมายจุลภาคก็คือตอนนี้ "string, ""value 1""",string value 2หากมีคำพูดมันจะกลายเป็น

ณ จุดนี้ฉันเชื่อว่ามันเป็นไปไม่ได้ ปัญหาที่คุณต้องพิจารณาจำนวนคำพูดที่คุณได้อ่านและถ้าเครื่องหมายจุลภาคอยู่ข้างในหรือด้านนอกของโหมดที่ยกมาเป็นสองเท่าของค่า การปรับสมดุลวงเล็บเป็นปัญหาที่เป็นไปไม่ได้ของ regex เอ็นจิ้นนิพจน์ทั่วไปแบบขยาย (PCRE) บางตัวสามารถจัดการกับมันได้ แต่ไม่ใช่นิพจน์ทั่วไป

คุณอาจพบว่า/programming/8629763/csv-parsing-with-a-context-free-grammarมีประโยชน์


แก้ไขเพิ่มเติม:

ฉันได้ดูรูปแบบของอักขระการหลบหนีและไม่พบสิ่งที่ต้องการการนับโดยพลการ - นั่นอาจไม่ใช่ปัญหา

อย่างไรก็ตามมีปัญหาเกี่ยวกับตัวละครยกเว้นและตัวคั่นบันทึก (เริ่มต้นด้วย) http://www.csvreader.com/csv_format.phpเป็นการอ่านที่ดีในรูปแบบที่แตกต่างในไวด์

  • กฎสำหรับสตริงที่ยกมา (ถ้ามันเป็นสตริงที่ยกมาเดียวหรือสตริงที่ยกมาคู่) แตกต่างกัน
    • 'This, is a value' VS "This, is a value"
  • กฎสำหรับตัวละครที่หลบหนี
    • "This ""is a value""" VS "This \"is a value\""
  • การจัดการตัวคั่นเรคคอร์ดฝังตัว ({rd})
    • (embeded ดิบ) "This {rd}is a value"vs (หนี) "This \{rd}is a value"vs (แปลแล้ว)"This {0x1C}is a value"

สิ่งสำคัญที่นี่คือเป็นไปได้ที่จะมีสตริงที่มักจะมีการตีความที่ถูกต้องหลายครั้ง

คำถามที่เกี่ยวข้อง (สำหรับกรณีขอบ) "เป็นไปได้ไหมที่จะมีสตริงที่ไม่ถูกต้องที่ยอมรับได้"

ฉันยังคงสงสัยอย่างมากว่ามีนิพจน์ทั่วไปที่สามารถจับคู่ CSV ที่ถูกต้องทั้งหมดที่สร้างโดยแอปพลิเคชันบางตัวและปฏิเสธ csv ทุกรายการที่ไม่สามารถแยกวิเคราะห์ได้


1
คำพูดภายในคำพูดไม่จำเป็นต้องมีความสมดุล ("")*"แต่ต้องมีจำนวนคู่ของคำพูดก่อนที่จะอ้างฝังตัวซึ่งเป็นปกติอย่างเห็นได้ชัด: หากราคาอยู่ภายในค่าปิดยอดคงเหลือแสดงว่าไม่ใช่ธุรกิจของเรา
9000

นี่คือตำแหน่งของฉันเมื่อเจอข้อแก้ตัวที่น่ากลัวสำหรับ "การถ่ายโอนข้อมูล" ในอดีต สิ่งเดียวที่จัดการได้อย่างเหมาะสมคือ parser, regex บริสุทธิ์ทำลายทุกสองสามสัปดาห์
Spencer Rathbun

2

ขั้นแรกให้กำหนดไวยากรณ์สำหรับ CSV ของคุณ (ตัวคั่นฟิลด์จะหลบหนีหรือเข้ารหัสอย่างใดหากปรากฏในข้อความหรือไม่) จากนั้นจะสามารถกำหนดได้หากสามารถแยกวิเคราะห์ด้วย regex ไวยากรณ์แรก: ตัวแยกวิเคราะห์ที่สอง: http://www.boyet.com/articles/csvparser.htmlควรสังเกตว่าวิธีนี้ใช้ tokenizer - แต่ฉันไม่สามารถผูก POSIX regex ที่จะตรงกับกรณีขอบทั้งหมด หากการใช้รูปแบบ CSV ไม่ใช่แบบปกติและไม่มีบริบท ... คำตอบคือคำถามของคุณ ภาพรวมที่ดีที่นี่: http://nikic.github.com/2012/06/15/The-true-power-of-regular-expressions.html


2

regexp นี้สามารถ tokenize CSV ปกติตามที่อธิบายไว้ใน RFC:

/("(?:[^"]|"")*"|[^,"\n\r]*)(,|\r?\n|\r)/

คำอธิบาย:

  • ("(?:[^"]|"")*"|[^,"\n\r]*) - ฟิลด์ CSV อ้างอิงหรือไม่
    • "(?:[^"]|"")*" - ฟิลด์ที่ยกมา
      • [^"]|""- ตัวละครแต่ละตัวไม่ได้"หรือ"หนีออกมาเป็น""
    • [^,"\n\r]* - ฟิลด์ที่ไม่มีเครื่องหมายซึ่งอาจไม่มี , " \n \r
  • (,|\r?\n|\r)- ตัวคั่นต่อไปนี้อย่างใดอย่างหนึ่ง,หรือขึ้นบรรทัดใหม่
    • \r?\n|\r - ขึ้นบรรทัดใหม่หนึ่งใน \r\n \n \r

ไฟล์ CSV ทั้งหมดสามารถจับคู่และตรวจสอบได้โดยใช้ regexp นี้ซ้ำ ๆ จากนั้นจึงจำเป็นต้องแก้ไขเขตข้อมูลที่ยกมาและแยกออกเป็นแถวตามตัวคั่น

นี่คือรหัสสำหรับตัวแยกวิเคราะห์ CSV ใน Javascript ตาม regexp:

var csv_tokens_rx = /("(?:[^"]|"")*"|[^,"\n\r]*)(,|\r?\n|\r)/y;
var csv_unescape_quote_rx = /""/g;
function csv_parse(s) {
    if (s && s.slice(-1) != '\n')
        s += '\n';
    var ok;
    var rows = [];
    var row = [];
    csv_tokens_rx.lastIndex = 0;
    while (true) {
        ok = csv_tokens_rx.lastIndex == s.length;
        var m = s.match(csv_tokens_rx);
        if (!m)
            break;
        var v = m[1], d = m[2];
        if (v[0] == '"') {
            v = v.slice(1, -1);
            v = v.replace(csv_unescape_quote_rx, '"');
        }
        if (d == ',' || v)
            row.push(v);
        if (d != ',') {
            rows.push(row)
            row = [];
        }
    }
    return ok ? rows : null;
}

คำตอบนี้ช่วยในการตัดสินข้อโต้แย้งของคุณหรือไม่ ฉันแค่ดีใจที่มีตัวแยกวิเคราะห์ CSV ขนาดเล็กเรียบง่ายและถูกต้อง

ในความคิดของฉันlexโปรแกรมมีขนาดใหญ่หรือน้อยกว่านิพจน์ทั่วไปและสิ่งเหล่านั้นสามารถโทเค็นรูปแบบที่ซับซ้อนมากขึ้นเช่นภาษาโปรแกรม C

โดยอ้างอิงถึงคำจำกัดความRFC 4180 :

  1. การขึ้นบรรทัดใหม่ (CRLF) - regexp มีความยืดหยุ่นมากกว่าช่วยให้ CRLF, LF หรือ CR
  2. ระเบียนสุดท้ายในไฟล์อาจมีหรือไม่มีตัวแบ่งบรรทัดสิ้นสุด - regexp ตามที่ต้องใช้ตัวแบ่งบรรทัดสุดท้าย แต่ parser ปรับสำหรับ
  3. อาจมีบรรทัดส่วนหัวเพิ่มเติม - ซึ่งไม่ส่งผลกระทบกับ parser
  4. แต่ละบรรทัดควรมีจำนวนฟิลด์เท่ากันตลอดทั้งไฟล์ - ไม่มีการบังคับใช้
    Spaces ถือเป็นส่วนหนึ่งของฟิลด์และไม่ควรเพิกเฉย - โอเค
    ฟิลด์สุดท้ายในเรคคอร์ดต้องไม่ใช้คอมม่า - ไม่บังคับใช้
  5. แต่ละฟิลด์อาจมีหรือไม่ใส่ในเครื่องหมายคำพูดคู่ ... - โอเค
  6. ฟิลด์ที่มีตัวแบ่งบรรทัด (CRLF) เครื่องหมายคำพูดคู่และเครื่องหมายจุลภาคควรอยู่ในเครื่องหมายคำพูดคู่ - โอเค
  7. ต้องใส่เครื่องหมายอัญประกาศคู่ที่ปรากฏภายในเขตข้อมูลโดยนำหน้าด้วยเครื่องหมายคำพูดคู่อื่น - โอเค

regexp นั้นเป็นไปตามข้อกำหนด RFC 4180 เกือบทั้งหมด ฉันไม่เห็นด้วยกับคนอื่น ๆ แต่มันง่ายที่จะปรับ parser เพื่อนำไปใช้


1
ดูเหมือนว่าการโปรโมตตนเองมากกว่าการตอบคำถามที่ถามดูวิธีตอบ
gnat

1
@ เร็ว ๆ นี้ฉันได้แก้ไขคำตอบของฉันเพื่อให้คำอธิบายเพิ่มเติมตรวจสอบ regexp เทียบกับ RFC 4180 และทำให้การส่งเสริมตนเองน้อยลง ฉันเชื่อว่าคำตอบนี้มีค่าเนื่องจากมี regexp ทดสอบซึ่งสามารถ tokenize รูปแบบที่พบมากที่สุดของ CSV ที่ใช้โดย Excel และสเปรดชีตอื่น ๆ ฉันคิดว่านี่เป็นคำถาม ตัวแยกวิเคราะห์ CSV ขนาดเล็กแสดงให้เห็นว่าการแยกวิเคราะห์ CSV ทำได้ง่ายโดยใช้ regexp นี้
Sam Watkins

นี่คือห้องสมุด csv และ tsv เล็ก ๆ ของฉันที่ฉันใช้เป็นส่วนหนึ่งของแอปสเปรดชีตเล็ก ๆ น้อย ๆ (แผ่นงาน Google รู้สึกหนักเกินไปสำหรับฉัน) นี่คือโอเพนซอร์ซ / โดเมนสาธารณะ / รหัส CC0 เช่นเดียวกับทุกสิ่งที่ฉันเผยแพร่ ฉันหวังว่านี่จะเป็นประโยชน์กับคนอื่น sam.aiki.info/code/js
Sam Watkins
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.