<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
<?xml version="1.0"?> <!--這是註釋--> <workflow> <work name="1" switch="on"> <plugin name="echoplugin.so" switch="on" /> </work> </workflow>
我們來簡單觀察下上面的xml檔案,xml格式和html格式十分類似,一般用於儲存需要屬性的設定或者需要多個巢狀關係的設定。
xml一般使用於專案的組態檔,相比於其他的ini格式或者yaml格式,它的優勢在於可以將一個標籤擁有多個屬性,比如上述xml檔案格式是用於設定工作流的,其中有name屬性和switch屬性,且再work標籤中又巢狀了plugin標籤,相比較其他組態檔格式是要靈活很多的。
具體的應用場景有很多,比如使用過Java中Mybatis的同學應該清楚,Mybatis的組態檔就是xml格式,而且也可以通過xml格式進行sql語句的編寫,同樣Java的maven專案的組態檔也是採用的xml檔案進行設定。
而我為什麼要寫一個xml解析器呢?很明顯,我今後要寫的C++專案需要用到。
同樣回到之前的那段程式碼,實際上已經把xml檔案格式的不同情況都列出來了。
從整體上看,所有的xml標籤分為:
其中xml宣告和註釋都是非必須的。 而xml元素,至少需要一個成對標籤元素,而且在最外層有且只能有一個,它作為根元素。
從xml元素來看,分為:
根據之前的例子,很明顯,名稱是必須要有的而且是唯一的,其他內容則是可選。 根據元素的結束形式,我們把他們分為單標籤和雙標籤元素。
完整程式碼倉庫:xml-parser
程式碼如下:
namespace xml { using std::vector; using std::map; using std::string_view; using std::string; class Element { public: using children_t = vector<Element>; using attrs_t = map<string, string>; using iterator = vector<Element>::iterator; using const_iterator = vector<Element>::const_iterator; string &Name() { return m_name; } string &Text() { return m_text; } //迭代器方便遍歷子節點 iterator begin() { return m_children.begin(); } [[nodiscard]] const_iterator begin() const { return m_children.begin(); } iterator end() { return m_children.end(); } [[nodiscard]] const_iterator end() const { return m_children.end(); } void push_back(Element const &element)//方便子節點的存入 { m_children.push_back(element); } string &operator[](string const &key) //方便key-value的存取 { return m_attrs[key]; } string to_string() { return _to_string(); } private: string _to_string(); private: string m_name; string m_text; children_t m_children; attrs_t m_attrs; }; }
上述程式碼,我們主要看成員變數。
其餘的方法要麼是Getter/Setter,要麼是方便操作孩子節點和屬性。 當然還有一個to_string()方法這個待會講。
關於整體結構我們分解為下面的情形:
程式碼如下:
Element xml::Parser::Parse() { while (true) { char t = _get_next_token(); if (t != '<') { THROW_ERROR("invalid format", m_str.substr(m_idx, detail_len)); } //解析版本號 if (m_idx + 4 < m_str.size() && m_str.compare(m_idx, 5, "<?xml") == 0) { if (!_parse_version()) { THROW_ERROR("version parse error", m_str.substr(m_idx, detail_len)); } continue; } //解析註釋 if (m_idx + 3 < m_str.size() && m_str.compare(m_idx, 4, "<!--") == 0) { if (!_parse_comment()) { THROW_ERROR("comment parse error", m_str.substr(m_idx, detail_len)); } continue; } //解析element if (m_idx + 1 < m_str.size() && (isalpha(m_str[m_idx + 1]) || m_str[m_idx + 1] == '_')) { return _parse_element(); } //出現未定義情況直接丟擲異常 THROW_ERROR("error format", m_str.substr(m_idx, detail_len)); } }
上述程式碼我們用while迴圈進行巢狀的原因在於註釋可能有多個。
對應程式碼:
Element xml::Parser::_parse_element() { Element element; auto pre_pos = ++m_idx; //過掉< //判斷name首字元合法性 if (!(m_idx < m_str.size() && (std::isalpha(m_str[m_idx]) || m_str[m_idx] == '_'))) { THROW_ERROR("error occur in parse name", m_str.substr(m_idx, detail_len)); } //解析name while (m_idx < m_str.size() && (isalpha(m_str[m_idx]) || m_str[m_idx] == ':' || m_str[m_idx] == '-' || m_str[m_idx] == '_' || m_str[m_idx] == '.')) { m_idx++; } if (m_idx >= m_str.size()) THROW_ERROR("error occur in parse name", m_str.substr(m_idx, detail_len)); element.Name() = m_str.substr(pre_pos, m_idx - pre_pos); //正式解析內部 while (m_idx < m_str.size()) { char token = _get_next_token(); if (token == '/') //1.單元素,直接解析後結束 { if (m_str[m_idx + 1] == '>') { m_idx += 2; return element; } else { THROW_ERROR("parse single_element failed", m_str.substr(m_idx, detail_len)); } } if (token == '<')//2.對應三種情況:結束符、註釋、下個子節點 { //結束符 if (m_str[m_idx + 1] == '/') { if (m_str.compare(m_idx + 2, element.Name().size(), element.Name()) != 0) { THROW_ERROR("parse end tag error", m_str.substr(m_idx, detail_len)); } m_idx += 2 + element.Name().size(); char x = _get_next_token(); if (x != '>') { THROW_ERROR("parse end tag error", m_str.substr(m_idx, detail_len)); } m_idx++; //千萬注意把 '>' 過掉,防止下次解析被識別為初始的tag結束,實際上這個element已經解析完成 return element; } //是註釋的情況 if (m_idx + 3 < m_str.size() && m_str.compare(m_idx, 4, "<!--") == 0) { if (!_parse_comment()) { THROW_ERROR("parse comment error", m_str.substr(m_idx, detail_len)); } continue; } //其餘情況可能是註釋或子元素,直接呼叫parse進行解析得到即可 element.push_back(Parse()); continue; } if (token == '>') //3.對應兩種情況:該標籤的text內容,下個標籤的開始或者註釋(直接continue跳到到下次迴圈即可 { m_idx++; //判斷下個token是否為text,如果不是則continue char x = _get_next_token(); if (x == '<')//不可能是結束符,因為xml元素不能為空body,如果直接出現這種情況也有可能是中間夾雜了註釋 { continue; } //解析text再解析child auto pos = m_str.find('<', m_idx); if (pos == string::npos) THROW_ERROR("parse text error", m_str.substr(m_idx, detail_len)); element.Text() = m_str.substr(m_idx, pos - m_idx); m_idx = pos; //注意:有可能直接碰上結束符,所以需要continue,讓element裡的邏輯來進行判斷 continue; } //4.其餘情況都為屬性的解析 auto key = _parse_attr_key(); auto x = _get_next_token(); if (x != '=') { THROW_ERROR("parse attrs error", m_str.substr(m_idx, detail_len)); } m_idx++; auto value = _parse_attr_value(); element[key] = value; } THROW_ERROR("parse element error", m_str.substr(m_idx, detail_len)); }
無論是C++開發,還是各種其他語言的造輪子,在這個造輪子的過程中,不可能是一帆風順的,需要不斷的debug,然後再測試,然後再debug。。。實際上這類格式的解析,單純的進行程式的偵錯效率是非常低下的!
特別是你用的語言還是C++,那麼如果出現意外宕機行為,debug幾乎是不可能簡單的找出原因的,所以為了方便偵錯,或者是意外宕機行為,我們還是多做一些錯誤、例外處理的工作比較好。
比如上述的程式碼中,我們大量的用到了 THROW_ERROR 這個宏,實際上這個宏輸出的內容是有便於偵錯和快速定位的。 具體程式碼如下:
//用於返回較為詳細的錯誤資訊,方便錯誤追蹤 #define THROW_ERROR(error_info, error_detail) do{ string info = "parse error in "; string file_pos = __FILE__; file_pos.append(":"); file_pos.append(std::to_string(__LINE__)); info += file_pos; info += ", "; info += (error_info); info += "ndetail:"; info += (error_detail); throw std::logic_error(info); }while(false)
如果發生錯誤,這個異常攜帶的資訊如下:
列印出了兩個非常關鍵的資訊:
內部的C++程式碼解析丟擲異常的位置
解析發生錯誤的字串
按理來說這些資訊應該是用紀錄檔來進行記錄的,但是由於這個專案比較小型,直接用日常資訊當紀錄檔來方便偵錯也未嘗不可
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45