The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

Tag.vala in libbirdxml

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

View the latest version of libbirdxml/Tag.vala.
End of tag name in XML parser
1 /* 2 Copyright (C) 2014 Johan Mattsson 3 4 This library is free software; you can redistribute it and/or modify 5 it under the terms of the GNU Lesser General Public License as 6 published by the Free Software Foundation; either version 3 of the 7 License, or (at your option) any later version. 8 9 This library is distributed in the hope that it will be useful, but 10 WITHOUT ANY WARRANTY; without even the implied warranty of 11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 Lesser General Public License for more details. 13 */ 14 15 namespace Bird { 16 17 /** 18 * Representation of one XML tag. 19 */ 20 public class Tag : GLib.Object { 21 public XmlData entire_file; 22 23 public int tag_index; 24 public int attribute_index; 25 26 public bool has_tags; 27 public bool has_attributes; 28 29 public XmlString name; 30 public XmlString data; 31 public XmlString attributes; 32 33 public Tag? next_tag = null; 34 public Attribute? next_attribute = null; 35 36 public bool error = false; 37 public int log_level = WARNINGS; 38 39 public int refcount = 1; 40 41 internal Tag (XmlString name, XmlString attributes, XmlString content, 42 int log_level, XmlData entire_file) { 43 44 this.entire_file = entire_file; 45 this.log_level = log_level; 46 this.name = name; 47 this.data = content; 48 this.attributes = attributes; 49 50 reparse (); 51 reparse_attributes (); 52 } 53 54 internal Tag.empty () { 55 entire_file = new XmlData ("", 0); 56 data = new XmlString ("", 0); 57 attributes = new XmlString ("", 0); 58 name = new XmlString ("", 0); 59 } 60 61 /** 62 * Get tag attributes for this tag. 63 * @return a container with all the attributes 64 */ 65 public Attributes get_attributes () { 66 return new Attributes (this); 67 } 68 69 /** 70 * Iterate over all tags inside of this tag. 71 */ 72 public Iterator iterator () { 73 return new Iterator(this); 74 } 75 76 /** 77 * Reset the parser and start from the beginning XML tag. 78 */ 79 public void reparse () { 80 tag_index = 0; 81 next_tag = obtain_next_tag (); 82 } 83 84 internal void reparse_attributes () { 85 attribute_index = 0; 86 next_attribute = obtain_next_attribute (); 87 } 88 89 /** 90 * Obtain the name of the tag. 91 * @return the name of this tag. 92 */ 93 public string get_name () { 94 return name.to_string (); 95 } 96 97 /** 98 * Obtain tag content. 99 * @return data between the start and end tags. 100 */ 101 public string get_content () { 102 return data.to_string (); 103 } 104 105 /** 106 * @return true if there is one more tags left 107 */ 108 internal bool has_more_tags () { 109 return has_tags; 110 } 111 112 /** @return the next tag. **/ 113 internal Tag get_next_tag () { 114 Tag r = next_tag == null ? new Tag.empty () : (!) next_tag; 115 next_tag = obtain_next_tag (); 116 return r; 117 } 118 119 /** @return true is there is one or more attributes to obtain with get_next_attribute */ 120 internal bool has_more_attributes () { 121 return has_attributes; 122 } 123 124 /** @return next attribute. */ 125 internal Attribute get_next_attribute () { 126 Attribute r = next_attribute == null ? new Attribute.empty () : (!) next_attribute; 127 next_attribute = obtain_next_attribute (); 128 return r; 129 } 130 131 internal bool has_failed () { 132 return error; 133 } 134 135 Tag obtain_next_tag () { 136 int end_tag_index; 137 Tag tag; 138 139 tag = find_next_tag (tag_index, out end_tag_index); 140 141 if (end_tag_index != -1) { 142 tag_index = end_tag_index; 143 has_tags = true; 144 return tag; 145 } 146 147 has_tags = false; 148 return new Tag.empty (); 149 } 150 151 Tag find_next_tag (int start, out int end_tag_index) { 152 int index; 153 unichar c; 154 int separator; 155 int end; 156 int closing_tag; 157 XmlString? d; 158 159 XmlString name; 160 XmlString attributes; 161 XmlString content; 162 163 end_tag_index = -1; 164 165 if (start < 0) { 166 warn ("Negative index."); 167 return new Tag.empty (); 168 } 169 170 index = start; 171 172 d = data; 173 if (d == null) { 174 warn ("No data in xml string."); 175 return new Tag.empty (); 176 } 177 178 while (data.get_next_ascii_char (ref index, out c)) { 179 180 if (c == '<') { 181 separator = find_next_separator (index); 182 183 if (separator < 0) { 184 error = true; 185 warn ("Expecting a separator."); 186 return new Tag.empty (); 187 } 188 189 name = data.substring (index, separator - index); 190 191 if (name.has_prefix ("!")) { 192 continue; 193 } 194 195 end = data.index_of (">", start); 196 attributes = data.substring (separator, end - separator); 197 198 if (attributes.has_suffix ("/")) { 199 content = new XmlString ("", 0); 200 end_tag_index = data.index_of (">", index); 201 data.get_next_ascii_char (ref end_tag_index, out c); 202 } else { 203 if (!data.get_next_ascii_char (ref end, out c)) {; // skip > 204 warn ("Unexpected end of data."); 205 error = true; 206 break; 207 } 208 209 if (c != '>') { 210 warn ("Expecting '>'"); 211 error = true; 212 break; 213 } 214 215 closing_tag = find_closing_tag (name, end); 216 217 if (closing_tag == -1) { 218 warn ("No closing tag."); 219 error = true; 220 break; 221 } 222 223 content = data.substring (end, closing_tag - end); 224 end_tag_index = data.index_of (">", closing_tag); 225 data.get_next_ascii_char (ref end_tag_index, out c); 226 } 227 228 return new Tag (name, attributes, content, log_level, entire_file); 229 } 230 } 231 232 return new Tag.empty (); 233 } 234 235 int find_next_separator (int start) { 236 int index = start; 237 int previous_index = start; 238 unichar c; 239 240 while (true) { 241 242 previous_index = index; 243 if (!data.get_next_ascii_char (ref index, out c)) { 244 break; 245 } 246 247 if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '>' || c == '/') { 248 return previous_index; 249 } 250 } 251 252 return -1; 253 } 254 255 int find_closing_tag (XmlString name, int start) { 256 int index = start; 257 int slash_index = start; 258 int previous_index; 259 unichar c, slash; 260 int start_count = 1; 261 int next_tag; 262 263 if (name.length == 0) { 264 error = true; 265 warn ("No name for tag."); 266 return -1; 267 } 268 269 index = entire_file.get_index (data) + start; 270 while (true) { 271 while (!entire_file.substring (index).has_prefix ("</")) { 272 index = entire_file.find_next_tag_token (entire_file, index); 273 274 if (index == -1) { 275 warning (@"No end tag for $(name)"); 276 return -1; 277 } 278 } 279 280 previous_index = index - entire_file.get_index (data); 281 282 if (!entire_file.get_next_ascii_char (ref index, out c)) { 283 warn ("Unexpected end of file"); 284 break; 285 } 286 287 if (c == '<') { 288 slash_index = index; 289 entire_file.get_next_ascii_char (ref slash_index, out slash); 290 291 if (slash == '/' && entire_file.substring (slash_index).has_prefix (name.to_string ())) { 292 if (start_count == 1) { 293 return previous_index; 294 } else { 295 start_count--; 296 if (start_count == 0) { 297 return previous_index; 298 } 299 } 300 } else if (entire_file.substring (index).has_prefix (name.to_string ())) { 301 start_count++; 302 } 303 } 304 } 305 306 error = true; 307 warn (@"No closing tag for $(name.to_string ())"); 308 309 return -1; 310 } 311 312 bool is_tag (XmlString name, int start) { 313 int index = 0; 314 int data_index = start; 315 unichar c; 316 unichar c_data; 317 318 while (name.get_next_ascii_char (ref index, out c)) { 319 if (data.get_next_ascii_char (ref data_index, out c_data)) { 320 if (c_data != c) { 321 return false; 322 } 323 } 324 } 325 326 if (data.get_next_ascii_char (ref data_index, out c_data)) { 327 return c_data == '>' || c_data == ' ' || c_data == '\t' 328 || c_data == '\n' || c_data == '\r' || c_data == '/'; 329 } 330 331 return false; 332 } 333 334 internal Attribute obtain_next_attribute () { 335 int previous_index; 336 int index = attribute_index; 337 int name_start; 338 XmlString attribute_name; 339 XmlString ns; 340 XmlString content; 341 int ns_separator; 342 int content_start; 343 int content_stop; 344 unichar quote; 345 unichar c; 346 347 // skip space and other separators 348 while (true) { 349 previous_index = index; 350 351 if (!attributes.get_next_ascii_char (ref index, out c)) { 352 has_attributes = false; 353 return new Attribute.empty (); 354 } 355 356 if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '/')) { 357 break; 358 } 359 } 360 361 name_start = previous_index; 362 363 // read attribute name 364 while (true) { 365 previous_index = index; 366 if (!attributes.get_next_ascii_char (ref index, out c)) { 367 error = true; 368 warn (@"Unexpected end of attributes in tag $(this.name)"); 369 has_attributes = false; 370 return new Attribute.empty (); 371 } 372 373 if (c == ' ' || c == '\t' || c == '=' || c == '\n' || c == '\r') { 374 break; 375 } 376 } 377 378 attribute_name = attributes.substring (name_start, previous_index - name_start); 379 index = name_start + attribute_name.length; 380 ns = new XmlString ("", 0); 381 ns_separator = attribute_name.index_of (":"); 382 if (ns_separator != -1) { 383 ns = attribute_name.substring (0, ns_separator); 384 attribute_name = attribute_name.substring (ns_separator + 1); 385 } 386 387 // equal sign and space around it 388 while (attributes.get_next_ascii_char (ref index, out c)) { 389 if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r')) { 390 if (c == '=') { 391 break; 392 } else { 393 has_attributes = false; 394 error = true; 395 warn (@"Expecting equal sign for attribute $(attribute_name)."); 396 warn (@"Around: $(attributes.substring (index, 10))."); 397 warn (@"Row: $(get_row (((size_t) attributes.data) + index))"); 398 399 return new Attribute.empty (); 400 } 401 } 402 } 403 404 while (attributes.get_next_ascii_char (ref index, out c)) { 405 if (!(c == ' ' || c == '\t' || c == '\n' || c == '\r')) { 406 if (c == '"' || c == '\'') { 407 break; 408 } else { 409 has_attributes = false; 410 error = true; 411 warn (@"Expecting quote for attribute $(attribute_name)."); 412 return new Attribute.empty (); 413 } 414 } 415 } 416 417 quote = c; 418 content_start = index; 419 420 while (true) { 421 if (!attributes.get_next_ascii_char (ref index, out c)) { 422 has_attributes = false; 423 error = true; 424 warn (@"Expecting end quote for attribute $(attribute_name)."); 425 return new Attribute.empty (); 426 } 427 428 if (c == quote) { 429 break; 430 } 431 } 432 433 content_stop = index - 1; 434 content = attributes.substring (content_start, content_stop - content_start); 435 436 has_attributes = true; 437 438 attribute_index = content_stop + 1; 439 return new Attribute (ns, attribute_name, content); 440 } 441 442 public class Iterator : GLib.Object { 443 public Tag tag; 444 public Tag? next_tag = null; 445 public int iterator_efcount = 1; 446 447 internal Iterator (Tag t) { 448 tag = t; 449 tag.reparse (); 450 } 451 452 public bool next () { 453 if (tag.error) { 454 return false; 455 } 456 457 if (tag.has_more_tags ()) { 458 next_tag = tag.get_next_tag (); 459 } else { 460 next_tag = null; 461 } 462 463 return next_tag != null; 464 } 465 466 public new Tag get () { 467 if (next_tag == null) { 468 XmlParser.warning ("No tag is parsed yet."); 469 return new Tag.empty (); 470 } 471 return (!) next_tag; 472 } 473 } 474 475 internal int get_row (size_t pos) { 476 int index = 0; 477 unichar c; 478 int row = 1; 479 size_t p, e; 480 481 e = (size_t) entire_file.data; 482 while (entire_file.get_next_ascii_char (ref index, out c)) { 483 if (c == '\n') { 484 row++; 485 } 486 487 if (e + index >= pos) { 488 break; 489 } 490 } 491 492 return row; 493 } 494 495 internal void warn (string message) { 496 if (log_level == WARNINGS) { 497 XmlParser.warning (message); 498 } 499 } 500 } 501 502 } 503