/* Copyright (C) 2014 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. */ namespace Bird { /** * Representation of one XML tag. */ public class Tag : GLib.Object { public int tag_index; public int attribute_index; public bool has_tags; public bool has_attributes; public XmlString name; public XmlString data; public XmlString attributes; public Tag? next_tag = null; public Attribute? next_attribute = null; public bool error = false; public int log_level = WARNINGS; public int refcount = 1; internal Tag (XmlString name, XmlString attributes, XmlString content, int log_level) { this.log_level = log_level; this.name = name; this.data = content; this.attributes = attributes; reparse (); reparse_attributes (); } internal Tag.empty () { data = new XmlString ("", 0); attributes = new XmlString ("", 0); name = new XmlString ("", 0); } /** * Get tag attributes for this tag. * @return a container with all the attributes */ public Attributes get_attributes () { return new Attributes (this); } /** * Iterate over all tags inside of this tag. */ public Iterator iterator () { return new Iterator(this); } /** * Reset the parser and start from the beginning XML tag. */ public void reparse () { tag_index = 0; next_tag = obtain_next_tag (); } internal void reparse_attributes () { attribute_index = 0; next_attribute = obtain_next_attribute (); } /** * Obtain the name of the tag. * @return the name of this tag. */ public string get_name () { return name.to_string (); } /** * Obtain tag content. * @return data between the start and end tags. */ public string get_content () { return data.to_string (); } /** * @return true if there is one more tags left */ internal bool has_more_tags () { return has_tags; } /** @return the next tag. **/ internal Tag get_next_tag () { Tag r = next_tag == null ? new Tag.empty () : (!) next_tag; next_tag = obtain_next_tag (); return r; } /** @return true is there is one or more attributes to obtain with get_next_attribute */ internal bool has_more_attributes () { return has_attributes; } /** @return next attribute. */ internal Attribute get_next_attribute () { Attribute r = next_attribute == null ? new Attribute.empty () : (!) next_attribute; next_attribute = obtain_next_attribute (); return r; } internal bool has_failed () { return error; } Tag obtain_next_tag () { int end_tag_index; Tag tag; tag = find_next_tag (tag_index, out end_tag_index); if (end_tag_index != -1) { tag_index = end_tag_index; has_tags = true; return tag; } has_tags = false; return new Tag.empty (); } Tag find_next_tag (int start, out int end_tag_index) { int index; unichar c; int separator; int end; int closing_tag; XmlString? d; XmlString name; XmlString attributes; XmlString content; end_tag_index = -1; if (start < 0) { warn ("Negative index."); return new Tag.empty (); } index = start; d = data; if (d == null) { warn ("No data in xml string."); return new Tag.empty (); } while (data.get_next_char (ref index, out c)) { if (c == '<') { separator = find_next_separator (index); if (separator < 0) { error = true; warn ("Expecting a separator."); return new Tag.empty (); } name = data.substring (index, separator - index); if (name.has_prefix ("!")) { continue; } end = data.index_of (">", start); attributes = data.substring (separator, end - separator); if (attributes.has_suffix ("/")) { content = new XmlString ("", 0); end_tag_index = data.index_of (">", index); data.get_next_char (ref end_tag_index, out c); } else { if (!data.get_next_char (ref end, out c)) {; // skip > warn ("Unexpected end of data."); error = true; break; } if (c != '>') { warn ("Expecting '>'"); error = true; break; } closing_tag = find_closing_tag (name, end); if (closing_tag == -1) { warn ("No closing tag."); error = true; break; } content = data.substring (end, closing_tag - end); end_tag_index = data.index_of (">", closing_tag); data.get_next_char (ref end_tag_index, out c); } return new Tag (name, attributes, content, log_level); } } return new Tag.empty (); } int find_next_separator (int start) { int index = start; int previous_index = start; unichar c; while (true) { previous_index = index; if (!data.get_next_char (ref index, out c)) { break; } if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '>' || c == '/') { return previous_index; } } return -1; } int find_closing_tag (XmlString name, int start) { int index = start; int slash_index = start; int previous_index; unichar c, slash; int start_count = 1; if (name.length == 0) { error = true; warn ("No name for tag."); return -1; } while (true) { previous_index = index; if (!data.get_next_char (ref index, out c)) { warn (@"Unexpected end of file"); break; } if (c == '<') { slash_index = index; data.get_next_char (ref slash_index, out slash); if (slash == '/' && is_tag (name, slash_index)) { if (start_count == 1) { return previous_index; } else { start_count--; if (start_count == 0) { return previous_index; } } } else if (is_tag (name, index)) { start_count++; } } } error = true; warn (@"No closing tag for $(name.to_string ())"); return -1; } bool is_tag (XmlString name, int start) { int index = 0; int data_index = start; unichar c; unichar c_data; while (name.get_next_char (ref index, out c)) { if (data.get_next_char (ref data_index, out c_data)) { if (c_data != c) { return false; } } } if (data.get_next_char (ref data_index, out c_data)) { return c_data == '>' || c_data == ' ' || c_data == '\t' || c_data == '\n' || c_data == '\r' || c_data == '/'; } return false; } internal Attribute obtain_next_attribute () { int previous_index; int index = attribute_index; int name_start; XmlString attribute_name; XmlString ns; XmlString content; int ns_separator; int content_start; int content_stop; unichar quote; unichar c; // skip space and other separators while (true) { previous_index = index; if (!attributes.get_next_char (ref index, out c)) { has_attributes = false; return new Attribute.empty (); } if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '/')) { break; } } name_start = previous_index; // read attribute name while (true) { previous_index = index; if (!attributes.get_next_char (ref index, out c)) { error = true; warn (@"Unexpected end of attributes in tag $(this.name)"); has_attributes = false; return new Attribute.empty (); } if (c == ' ' || c == '\t' || c == '=' || c == '\n' || c == '\r') { break; } } attribute_name = attributes.substring (name_start, previous_index - name_start); index = name_start + attribute_name.length; ns = new XmlString ("", 0); ns_separator = attribute_name.index_of (":"); if (ns_separator != -1) { ns = attribute_name.substring (0, ns_separator); attribute_name = attribute_name.substring (ns_separator + 1); } // equal sign and space around it while (attributes.get_next_char (ref index, out c)) { if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r')) { if (c == '=') { break; } else { has_attributes = false; error = true; warn (@"Expecting equal sign for attribute $(attribute_name)."); return new Attribute.empty (); } } } while (attributes.get_next_char (ref index, out c)) { if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r')) { if (c == '"' || c == '\'') { break; } else { has_attributes = false; error = true; warn (@"Expecting quote for attribute $(attribute_name)."); return new Attribute.empty (); } } } quote = c; content_start = index; while (true) { if (!attributes.get_next_char (ref index, out c)) { has_attributes = false; error = true; warn (@"Expecting end quote for attribute $(attribute_name)."); return new Attribute.empty (); } if (c == quote) { break; } } content_stop = index - 1; content = attributes.substring (content_start, content_stop - content_start); has_attributes = true; attribute_index = content_stop + 1; return new Attribute (ns, attribute_name, content); } public class Iterator : GLib.Object { public Tag tag; public Tag? next_tag = null; public int iterator_efcount = 1; internal Iterator (Tag t) { tag = t; tag.reparse (); } public bool next () { if (tag.error) { return false; } if (tag.has_more_tags ()) { next_tag = tag.get_next_tag (); } else { next_tag = null; } return next_tag != null; } public new Tag get () { if (next_tag == null) { XmlParser.warning ("No tag is parsed yet."); return new Tag.empty (); } return (!) next_tag; } } internal void warn (string message) { if (log_level == WARNINGS) { XmlParser.warning (message); } } } }