【问题标题】:Parsing a Quickbook IIF format file解析 Quickbooks IIF 格式文件
【发布时间】:2009-01-10 16:27:57
【问题描述】:

我正在使用 Quickbook 的 IIF 文件格式,我需要编写一个解析器来读取和写入 IIF 文件,但我在读取文件时遇到了一些问题。

文件很简单,用制表符分隔。每一行要么是一个表定义,要么是一行。定义以“!”开头和表名,行仅以表名开头。这是我遇到的问题:某些字段允许换行。

当我第一次遇到这个问题时,我想,好吧,只需逐个标签而不是逐行解析它,但要做到这一点,我必须用制表符替换换行符,并且得到的值比列数多,但我最终将换行符分布在太多列中的值。

你会如何解析这样的文件?

编辑:一个例子

!CUST   NAME    REFNUM  TIMESTAMP   BADDR1  BADDR2  BADDR3  BADDR4  BADDR5  SADDR1  SADDR2  SADDR3  SADDR4  SADDR5  PHONE1  PHONE2  FAXNUM  CONT1   CONT2   CTYPE   TERMS   TAXABLE LIMIT   RESALENUM   REP TAXITEM NOTEPAD SALUTATION  COMPANYNAME FIRSTNAME   MIDINIT LASTNAME    CUSTFLD1    CUSTFLD2    CUSTFLD3    CUSTFLD4    CUSTFLD5    CUSTFLD6    CUSTFLD7    CUSTFLD8    CUSTFLD9    CUSTFLD10   CUSTFLD11   CUSTFLD12   CUSTFLD13   CUSTFLD14   CUSTFLD15   JOBDESC JOBTYPE JOBSTATUS   JOBSTART    JOBPROJEND  JOBEND  HIDDEN  DELCOUNT
CUST    St. Mark    359 1176670332  Saint Mark Catholic Church  609 W Main St   City, State Zip 
!CLASS  NAME    REFNUM  TIMESTAMP   HIDDEN  DELCOUNT
!INVITEM    NAME    REFNUM  TIMESTAMP   INVITEMTYPE DESC    PURCHASEDESC    ACCNT   ASSETACCNT  COGSACCNT   QNTY    QNTY    PRICE   COST    TAXABLE PAYMETH TAXVEND TAXDIST PREFVEND    REORDERPOINT    EXTRA   CUSTFLD1    CUSTFLD2    CUSTFLD3    CUSTFLD4    CUSTFLD5    DEP_TYPE    ISPASSEDTHRU    HIDDEN  DELCOUNT    USEID
INVITEM Labor   1   1119915308  SERV    Labor                                                                                               0
!TIMEACT    DATE    JOB EMP ITEM    PITEM   DURATION    PROJ    NOTE    XFERTOPAYROLL   BILLINGSTATUS
TIMEACT 3/8/08  876 Development Jane Doe {Consultant}   Labor       00:15       Renewing all domain name for 876 Development.
REIMBURSEMENT: 44.72 for one year renewal on all domain names.  N   1
TIMEACT 3/17/08 Greg:Bridge Jane Doe {Consultant}   Labor       01:00       Preparing Studio    N   1
TIMEACT 3/17/08 John Doe and Associates Jane Doe {Consultant}   Labor       00:06       REIMBURSEMENT: Toner cartridge on ebay & Fuser from FastPrinters- ask wendell before invoicing to see if this fixed the problem
49.99 (include tax) toner
$175.18 (include tax) fuser
    N   1
TIMEACT 3/17/08 John Doe II Jane Doe {Consultant}   Labor       01:00       Fixing Kandis's computer - replaced entire computer with similar system N   1

【问题讨论】:

  • 您能提供这样一个文件的样本吗?如果我们能看到它会容易得多。

标签: c# parsing io text-parsing fileparsing


【解决方案1】:

我做到了:

 public DataSet parseIIF(Stream file) {
            iifSet = new DataSet();
            String fileText;

            using (StreamReader sr = new StreamReader(file)) {
                fileText = sr.ReadToEnd();
            }
            //replace line breaks with tabs
            //fileText.Replace('\n', '\t');
            fileText = fileText.Replace("\r\n", "\n");
            fileText = fileText.Replace('\r', '\n');

            //split along tabs
            string[] lines = fileText.Split('\n');

            this.createTables(lines, iifSet);
            this.fillSet(lines, iifSet);

            return iifSet;
        }

        /// <summary>
        /// Reads an array of lines and parses them into tables for the dataset
        /// </summary>
        /// <param name="lines">String Array of lines from the iif file</param>
        /// <param name="iifSet">DataSet to be manipulated</param>
        private void fillSet(string[] lines, DataSet set) {
            //CODING HORROR
            //WARNING: I will monkey with the for loop index, be prepared!
            for (int i = 0; i < lines.Length; i++) {
                if (this.isTableHeader(lines[i])) {
                    //ignore this line, tables are alread defined
                    continue;
                }
                if (lines[i] == "" || lines[i] == "\r" || lines[i] == "\n\r" || lines[i] == "\n") {
                    //ignore lines that are empty if not inside a record
                    //probably the end of the file, it always ends with a blank line break
                    continue;
                }

                if (lines[i].IndexOf(";__IMPORTED__") != -1) {
                    continue;
                    //just signifying that it's been imported by quickbook's timer before, don't need it
                }

                string line = lines[i];
                while (!isFullLine(line, set)){
                    i++;            //<--------------------------- MONKEYING done here!
                    line += lines[i];       
                }
                //now, the line should be complete, we can parse it by tabs now
                this.parseRecord(line, set);
            }
        }

        private void parseRecord(string line, DataSet set) {
            if (isTableHeader(line)) {
                //we don't want to deal with headers here
                return;
            }

            String tablename = line.Split('\t')[0];
            //this just removes the first value and the line break from the last value
            String[] parameters = this.createDataRowParams(line);

            //add it to the dataset
            set.Tables[tablename].Rows.Add(parameters);
        }

        private bool isFullLine(string line, DataSet set) {
            if (isTableHeader(line)) {
                return true;    //assumes table headers won't have line breaks
            }
            int values = line.Split('\t').Length;
            string tableName = line.Split('\t')[0];
            int columns = set.Tables[tableName].Columns.Count;

            if (values < columns) {
                return false;
            } else {
                return true;
            }
        }

        private void createTables(string[] lines, DataSet set) {
            for (int index = 0; index < lines.Length; index++) {
                if (this.isTableHeader(lines[index])) {
                    set.Tables.Add(createTable(lines[index]));
                }
            }
        }

        private bool isTableHeader(string tab) {
            if (tab.StartsWith("!"))
                return true;
            else
                return false;
        }

        private bool isNewLine(string p) {
            if (p.StartsWith("!"))
                return true;
            if (iifSet.Tables[p.Split('\t')[0]] != null)    //that little mess there grabs the first record in the line, sorry about the mess
                return true;
            return false;
        }

    private DataTable createTable(string line) {
        String[] values = line.Split('\t');

        //first value is always the title
        //remove the ! from it
        values[0] = values[0].Substring(1);     //remove the first character
        DataTable dt = new DataTable(values[0]);
        values[0] = null;   //hide first title so it doesn't get used, cheaper than resetting array
        foreach (String name in values) {
            if (name == null || name == "")
                continue;
            DataColumn dc = new DataColumn(name, typeof(String));
            try {
                dt.Columns.Add(dc);
            } catch (DuplicateNameException) {
                //odd
                dc = new DataColumn(name + "_duplicateCol" + dt.Columns.Count.ToString());
                dt.Columns.Add(dc);
                //if there is a triple, just throw it
            }
        }

        return dt;
    }

   private string getTableName(string line) {
        String[] values = line.Split('\t');

        //first value is always the title
        if(values[0].StartsWith("!")){
            //remove the ! from it
            values[0] = values[0].Substring(1);     //remove the first character
        }
        return values[0];
    }

    private string[] createDataRowParams(string line) {
        string[] raw = line.Split('\t');
        string[] values = new string[raw.Length - 1];

        //copy all values except the first one
        for (int i = 0; i < values.Length; i++) {
            values[i] = raw[i + 1];
        }

        //remove last line break from the record
        if (values[values.Length - 1].EndsWith("\n")) {
            values[values.Length - 1] = values[values.Length - 1].Substring(0, values[values.Length - 1].LastIndexOf('\n'));
        } else if (values[values.Length - 1].EndsWith("\n\r")) {
            values[values.Length - 1] = values[values.Length - 1].Substring(0, values[values.Length - 1].LastIndexOf("\n\r"));
        } else if (values[values.Length - 1].EndsWith("\r")) {
            values[values.Length - 1] = values[values.Length - 1].Substring(0, values[values.Length - 1].LastIndexOf('\r'));
        }


        return values;
    }

    private string[] createDataRowParams(string line, int max) {
        string[] raw = line.Split('\t');

        int length = raw.Length - 1;
        if (length > max) {
            length = max;
        }

        string[] values = new string[length];
        for (int i = 0; i < length; i++) {
            values[i] = raw[i + 1];
        }

        if (values[values.Length - 1].EndsWith("\n")) {
            values[values.Length - 1] = values[values.Length - 1].Substring(0, values[values.Length - 1].LastIndexOf('\n'));
        } else if (values[values.Length - 1].EndsWith("\n\r")) {
            values[values.Length - 1] = values[values.Length - 1].Substring(0, values[values.Length - 1].LastIndexOf("\n\r"));
        } else if (values[values.Length - 1].EndsWith("\r")) {
            values[values.Length - 1] = values[values.Length - 1].Substring(0, values[values.Length - 1].LastIndexOf('\r'));
        }

        return values;
    }

【讨论】:

  • 您的代码乍一看似乎很原始。例如,fileText.Replace("\r\n", "\n"); 无效,因为您没有处理返回的字符串。
【解决方案2】:

自从我完成 IIF 以来已经有一段时间了,但除非他们修复了它,否则 QuickBooks 无论如何都会在这些换行符上吐槽。似乎these folks 有同样的问题,他们用空格来处理它。

就我个人而言,当涉及到 QuickBooks 时,我会倾向于使用管道或可以清楚地描绘换行符的东西。如果您绝对肯定必须有换行符,请加入Intuit Developer Network,并在您的程序导入它们后使用 SDK 将这些值发送到 QB。

【讨论】:

  • 这些换行符来自 Quickbooks 本身,不会对它们产生影响。使用 qbXML 不是这个程序的选项,我希望它是 :(
  • 哦,好的。所以你是从 QB 出口的?对不起。每个人通常都在尝试导入,所以我假设你也在做同样的事情。在那种情况下,我可能会按令牌解析 - 只需捕获一个 TIMEACT 和下一个 TIMEACT 之间的所有内容。
  • 嗯,我正在做出口和进口。上面的代码片段来自 quickbooks 已经导入的文件。
【解决方案3】:

为什么不用空格而不是制表符替换换行符?

【讨论】:

  • 因为我想保留换行符
【解决方案4】:

我经常遇到这种事情。在进行解析时处理这种特殊情况的关键是用文本中极不可能出现的东西替换特殊情况,然后在完成后再次替换它。

例如,输出中有换行符,可以使用正则表达式轻松检测到。使用 Regex.Replace 将它们转换为类似 LINEBREAK 的内容。让它在编辑器中脱颖而出以进行调试。然后照常进行其余的解析,最后一步,将特殊标记替换为原始值(或新值)。

【讨论】:

    【解决方案5】:

    想法:

    1. 预处理您的文件,用一些高 ascii 字符替换换行符(假设它是单个 CR 或 LF)。然后通过tab解析,最后用换行符替换所说的high-ascii。

    2. 不是逐行处理,而是逐个字符处理。请注意,只有当嵌入的换行符与记录末尾的标准 CRLF 有所不同时,这仍然有效。

    【讨论】:

      【解决方案6】:

      我在 CodePlex 上找到了这个。您可以使用 nugget 包获取它。 https://qif.codeplex.com/

      我测试了它,我能够读写速成书格式。

      【讨论】:

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