| /** @file |
| |
| A brief file description |
| |
| @section license License |
| |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| /*************************************************************************** |
| InkXml.cc |
| |
| |
| ***************************************************************************/ |
| #include "libts.h" |
| |
| #include "InkXml.h" |
| |
| /*--- for stand-alone testing |
| #define xstrdup strdup |
| #define xfree free |
| #define xmalloc malloc |
| #define NEW(x) x |
| #define ink_isspace isspace |
| ---*/ |
| |
| /*------------------------------------------------------------------------- |
| InkXmlAttr |
| -------------------------------------------------------------------------*/ |
| |
| InkXmlAttr::InkXmlAttr(char *tag, char *value) |
| { |
| m_tag = xstrdup(tag); |
| m_value = xstrdup(value); |
| } |
| |
| InkXmlAttr::~InkXmlAttr() |
| { |
| xfree(m_tag); |
| xfree(m_value); |
| } |
| |
| void |
| InkXmlAttr::display(FILE * fd) |
| { |
| fprintf(fd, " <%s,%s>\n", m_tag, m_value); |
| } |
| |
| /*------------------------------------------------------------------------- |
| InkXmlObject |
| -------------------------------------------------------------------------*/ |
| |
| InkXmlObject::InkXmlObject(char *object_name, bool dup_attrs_allowed) |
| { |
| m_object_name = xstrdup(object_name); |
| m_dup_attrs_allowed = dup_attrs_allowed; |
| } |
| |
| InkXmlObject::~InkXmlObject() |
| { |
| xfree(m_object_name); |
| clear_tags(); |
| } |
| |
| void |
| InkXmlObject::clear_tags() |
| { |
| InkXmlAttr *attr; |
| while ((attr = m_tags.dequeue())) { |
| delete attr; |
| } |
| } |
| |
| int |
| InkXmlObject::add_tag(char *tag, char *value) |
| { |
| ink_assert(tag != NULL); |
| ink_assert(value != NULL); |
| |
| InkXmlAttr *attr = NEW(new InkXmlAttr(tag, value)); |
| return add_attr(attr); |
| } |
| |
| int |
| InkXmlObject::add_attr(InkXmlAttr * attr) |
| { |
| ink_assert(attr != NULL); |
| |
| if (!m_dup_attrs_allowed) { |
| for (InkXmlAttr * a = first(); a; a = next(a)) { |
| if (!strcmp(a->tag(), attr->tag())) { |
| Debug("xml", "tag %s already exists & dups not allowed", attr->tag()); |
| return -1; |
| } |
| } |
| } |
| m_tags.enqueue(attr); |
| return 0; |
| } |
| |
| char * |
| InkXmlObject::tag_value(char *tag_name) |
| { |
| ink_assert(tag_name != NULL); |
| |
| for (InkXmlAttr * a = first(); a; a = next(a)) { |
| if (!strcmp(a->tag(), tag_name)) { |
| return a->value(); |
| } |
| } |
| return NULL; |
| } |
| |
| void |
| InkXmlObject::display(FILE * fd) |
| { |
| fprintf(fd, "<%s>\n", m_object_name); |
| for (InkXmlAttr * a = first(); a; a = next(a)) { |
| a->display(fd); |
| } |
| } |
| |
| /*------------------------------------------------------------------------- |
| InkXmlConfigFile |
| -------------------------------------------------------------------------*/ |
| |
| InkXmlConfigFile::InkXmlConfigFile(char *config_file): |
| m_line(0), |
| m_col(0) |
| { |
| m_config_file = xstrdup(config_file); |
| } |
| |
| InkXmlConfigFile::~InkXmlConfigFile() |
| { |
| xfree(m_config_file); |
| clear_objects(); |
| } |
| |
| void |
| InkXmlConfigFile::clear_objects() |
| { |
| InkXmlObject *obj; |
| while ((obj = m_objects.dequeue())) { |
| delete obj; |
| } |
| } |
| |
| /* */ |
| int |
| InkXmlConfigFile::parse(int fd) |
| { |
| ink_assert(fd >= 0); |
| Debug("log", "Parsing XML config info from memory.."); |
| |
| m_line = 1; |
| m_col = 0; |
| |
| InkXmlObject *obj; |
| while ((obj = get_next_xml_object(fd)) != NULL) { |
| Debug("log", "Adding XML object <%s>", obj->object_name()); |
| add_object(obj); |
| } |
| |
| return 0; |
| } |
| |
| /* */ |
| |
| int |
| InkXmlConfigFile::parse() |
| { |
| ink_assert(m_config_file != NULL); |
| Debug("xml", "Parsing XML config file %s ...", m_config_file); |
| |
| int fd =::open(m_config_file, O_RDONLY); |
| if (fd < 0) { |
| Debug("xml", "Error opening %s: %d, %s", m_config_file, fd, strerror(errno)); |
| return -1; |
| } |
| |
| m_line = 1; |
| m_col = 0; |
| |
| InkXmlObject *obj; |
| while ((obj = get_next_xml_object(fd)) != NULL) { |
| Debug("xml", "Adding XML object <%s>", obj->object_name()); |
| add_object(obj); |
| } |
| |
| ::close(fd); |
| return 0; |
| } |
| |
| InkXmlObject * |
| InkXmlConfigFile::find_object(char *object_name) |
| { |
| for (InkXmlObject * obj = first(); obj; obj = next(obj)) { |
| if (!strcmp(object_name, obj->object_name())) { |
| return obj; |
| } |
| } |
| return NULL; |
| } |
| |
| void |
| InkXmlConfigFile::display(FILE * fd) |
| { |
| size_t i; |
| |
| fprintf(fd, "\n"); |
| for (i = 0; i < strlen(m_config_file) + 13; i++) |
| fputc('-', fd); |
| fprintf(fd, "\nConfig File: %s\n", m_config_file); |
| for (i = 0; i < strlen(m_config_file) + 13; i++) |
| fputc('-', fd); |
| fprintf(fd, "\n"); |
| for (InkXmlObject * obj = first(); obj; obj = next(obj)) { |
| obj->display(fd); |
| fprintf(fd, "\n"); |
| } |
| } |
| |
| void |
| InkXmlConfigFile::add_object(InkXmlObject * object) |
| { |
| ink_assert(object != NULL); |
| m_objects.enqueue(object); |
| } |
| |
| /*------------------------------------------------------------------------- |
| InkXmlConfigFile::get_next_xml_object() |
| |
| This routine (and its friends) does the real work of parsing the given |
| open file for the next XML object. |
| -------------------------------------------------------------------------*/ |
| |
| InkXmlObject * |
| InkXmlConfigFile::get_next_xml_object(int fd) |
| { |
| ink_assert(fd >= 0); |
| |
| char token; |
| bool start_object = false; |
| |
| while ((token = next_token(fd)) != EOF) { |
| switch (token) { |
| |
| case '<': |
| start_object = true; |
| break; |
| |
| case '!': |
| if (!start_object) |
| return parse_error(); |
| if ((token = scan_comment(fd)) == EOF) { |
| return NULL; |
| } |
| Debug("xml", "comment scanned"); |
| start_object = false; |
| break; |
| |
| default: |
| if (!start_object) |
| return parse_error(); |
| return scan_object(fd, token); |
| } |
| } |
| return NULL; |
| } |
| |
| InkXmlObject * |
| InkXmlConfigFile::parse_error() |
| { |
| Debug("xml", "Invalid XML tag, line %u, col %u", m_line, m_col); |
| return NULL; |
| } |
| |
| #define BAD_ATTR ((InkXmlAttr*)1) |
| |
| InkXmlObject * |
| InkXmlConfigFile::scan_object(int fd, char token) |
| { |
| // this routine is called just after the first '<' is read for a new |
| // object. |
| |
| const int max_ident_len = 2048; |
| char ident[max_ident_len]; |
| int ident_len = 0; |
| |
| while (token != '>' && ident_len < max_ident_len) { |
| ident[ident_len++] = token; |
| token = next_token(fd); |
| if (token == EOF) |
| return parse_error(); |
| } |
| if (!ident_len || ident_len >= max_ident_len) { |
| return parse_error(); |
| } |
| |
| ident[ident_len] = 0; |
| InkXmlObject *obj = new InkXmlObject(ident); |
| ink_assert(obj != NULL); |
| |
| InkXmlAttr *attr; |
| while ((attr = scan_attr(fd, ident)) != NULL) { |
| if (attr == BAD_ATTR) { |
| return parse_error(); |
| } |
| obj->add_attr(attr); |
| } |
| |
| return obj; |
| } |
| |
| InkXmlAttr * |
| InkXmlConfigFile::scan_attr(int fd, char *id) |
| { |
| // this routine is called after the object identifier has been scannedm |
| // and should attempt to scan for the next attribute set. When we see |
| // the end of the object (closing identifier), we scan it and return |
| // NULL for the attribute, signalling that there are no more |
| // attributes. |
| |
| char token, prev, next; |
| const int buf_size = 2048; |
| char name[buf_size]; |
| char value[buf_size]; |
| char ident[buf_size]; |
| char *write_to = NULL; |
| int write_len = 0; |
| bool start_attr = false; |
| bool in_quotes = false; |
| InkXmlAttr *attr = NULL; |
| |
| prev = next = 0; |
| while ((token = next_token(fd, !in_quotes)) != EOF) { |
| switch (token) { |
| case '<': |
| if (in_quotes && write_to) { |
| if (write_len >= buf_size) |
| return BAD_ATTR; |
| write_to[write_len++] = token; |
| break; |
| } |
| start_attr = true; |
| write_to = name; |
| write_len = 0; |
| break; |
| |
| case '=': |
| if (in_quotes && write_to) { |
| if (write_len >= buf_size) |
| return BAD_ATTR; |
| write_to[write_len++] = token; |
| break; |
| } |
| if (!start_attr) |
| return BAD_ATTR; |
| write_to[write_len] = 0; |
| write_to = value; |
| write_len = 0; |
| break; |
| |
| case '"': |
| if (in_quotes) { |
| if (prev == '\\') { |
| // escape the quote, just replace the backslash |
| // with it |
| write_to[write_len - 1] = token; |
| break; |
| } |
| } |
| in_quotes = !in_quotes; |
| break; |
| |
| case '/': |
| if (in_quotes && write_to) { |
| if (write_len >= buf_size) |
| return BAD_ATTR; |
| write_to[write_len++] = token; |
| break; |
| } |
| if (!start_attr) |
| return BAD_ATTR; |
| if (prev == '<') { |
| write_len = 0; |
| token = next_token(fd, !in_quotes); |
| while (token != '>' && write_len < buf_size) { |
| ident[write_len++] = token; |
| token = next_token(fd, !in_quotes); |
| if (token == EOF) |
| return BAD_ATTR; |
| } |
| if (!write_len || write_len >= buf_size) { |
| return BAD_ATTR; |
| } |
| ident[write_len] = 0; |
| if (strcmp(ident, id) != 0) |
| return BAD_ATTR; |
| return NULL; |
| } |
| |
| next = next_token(fd, !in_quotes); |
| if (next != '>') |
| return BAD_ATTR; |
| write_to[write_len] = 0; |
| attr = new InkXmlAttr(name, value); |
| ink_assert(attr != NULL); |
| return attr; |
| |
| case '>': |
| if (in_quotes && write_to) { |
| if (write_len >= buf_size) |
| return BAD_ATTR; |
| write_to[write_len++] = token; |
| break; |
| } |
| // seen at this point, this is an error, probably becase |
| // the person forgot the trailing '/'. |
| return BAD_ATTR; |
| |
| default: |
| if (!start_attr) |
| return BAD_ATTR; |
| if (write_len >= buf_size) |
| return BAD_ATTR; |
| write_to[write_len++] = token; |
| break; |
| } |
| prev = token; |
| } |
| return BAD_ATTR; |
| } |
| |
| char |
| InkXmlConfigFile::next_token(int fd, bool eat_whitespace) |
| { |
| char ch; |
| while (read(fd, &ch, 1) == 1) { |
| if (ch == '\n') { |
| m_line++; |
| m_col = 0; |
| continue; |
| } |
| m_col++; |
| if (eat_whitespace && ParseRules::is_space(ch)) |
| continue; |
| return ch; |
| } |
| return EOF; |
| } |
| |
| char |
| InkXmlConfigFile::scan_comment(int fd) |
| { |
| // this routine is called when we're just past a "<!" in the file. we |
| // need to skip until we find the matching '>'. |
| |
| int lt_stack = 1; // we've already seen one '<' character |
| char token; |
| while ((token = next_token(fd)) != EOF) { |
| switch (token) { |
| case '<': |
| lt_stack++; |
| break; |
| case '>': |
| lt_stack--; |
| if (lt_stack == 0) { |
| return token; |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| return EOF; |
| } |