Otclient  14/8/2020
otmlparser.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2010-2020 OTClient <https://github.com/edubart/otclient>
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20  * THE SOFTWARE.
21  */
22 
23 #include "otmlparser.h"
24 #include "otmldocument.h"
25 #include "otmlexception.h"
26 #include <boost/tokenizer.hpp>
27 
28 OTMLParser::OTMLParser(OTMLDocumentPtr doc, std::istream& in) :
29  currentDepth(0), currentLine(0),
30  doc(doc), currentParent(doc), previousNode(nullptr),
31  in(in)
32 {
33 }
34 
36 {
37  if(!in.good())
38  throw OTMLException(doc, "cannot read from input stream");
39 
40  while(!in.eof())
41  parseLine(getNextLine());
42 }
43 
44 std::string OTMLParser::getNextLine()
45 {
46  currentLine++;
47  std::string line;
48  std::getline(in, line);
49  return line;
50 }
51 
52 int OTMLParser::getLineDepth(const std::string& line, bool multilining)
53 {
54  // count number of spaces at the line beginning
55  std::size_t spaces = 0;
56  while(line[spaces] == ' ')
57  spaces++;
58 
59  // pre calculate depth
60  int depth = spaces / 2;
61 
62  if(!multilining || depth <= currentDepth) {
63  // check the next character is a tab
64  if(line[spaces] == '\t')
65  throw OTMLException(doc, "indentation with tabs are not allowed", currentLine);
66 
67  // must indent every 2 spaces
68  if(spaces % 2 != 0)
69  throw OTMLException(doc, "must indent every 2 spaces", currentLine);
70  }
71 
72  return depth;
73 }
74 
75 void OTMLParser::parseLine(std::string line)
76 {
77  int depth = getLineDepth(line);
78 
79  if(depth == -1)
80  return;
81 
82  // remove line sides spaces
83  stdext::trim(line);
84 
85  // skip empty lines
86  if(line.empty())
87  return;
88 
89  // skip comments
90  if(stdext::starts_with(line, "//"))
91  return;
92 
93  // a depth above, change current parent to the previous added node
94  if(depth == currentDepth+1) {
95  currentParent = previousNode;
96  // a depth below, change parent to previous parent
97  } else if(depth < currentDepth) {
98  for(int i=0;i<currentDepth-depth;++i)
99  currentParent = parentMap[currentParent];
100  // if it isn't the current depth, it's a syntax error
101  } else if(depth != currentDepth)
102  throw OTMLException(doc, "invalid indentation depth, are you indenting correctly?", currentLine);
103 
104  // sets current depth
105  currentDepth = depth;
106 
107  // alright, new depth is set, the line is not empty and it isn't a comment
108  // then it must be a node, so we parse it
109  parseNode(line);
110 }
111 
112 void OTMLParser::parseNode(const std::string& data)
113 {
114  std::string tag;
115  std::string value;
116  std::size_t dotsPos = data.find_first_of(':');
117  int nodeLine = currentLine;
118 
119  // node that has no tag and may have a value
120  if(!data.empty() && data[0] == '-') {
121  value = data.substr(1);
122  stdext::trim(value);
123  // node that has tag and possible a value
124  } else if(dotsPos != std::string::npos) {
125  tag = data.substr(0, dotsPos);
126  if(data.size() > dotsPos+1)
127  value = data.substr(dotsPos+1);
128  // node that has only a tag
129  } else {
130  tag = data;
131  }
132 
133  stdext::trim(tag);
134  stdext::trim(value);
135 
136  // process multitine values
137  if(value == "|" || value == "|-" || value == "|+") {
138  // reads next lines until we can a value below the same depth
139  std::string multiLineData;
140  do {
141  size_t lastPos = in.tellg();
142  std::string line = getNextLine();
143  int depth = getLineDepth(line, true);
144 
145  // depth above current depth, add the text to the multiline
146  if(depth > currentDepth) {
147  multiLineData += line.substr((currentDepth+1)*2);
148  // it has contents below the current depth
149  } else {
150  // if not empty, its a node
151  stdext::trim(line);
152  if(!line.empty()) {
153  // rewind and break
154  in.seekg(lastPos, std::ios::beg);
155  currentLine--;
156  break;
157  }
158  }
159  multiLineData += "\n";
160  } while(!in.eof());
161 
162  /* determine how to treat new lines at the end
163  * | strip all new lines at the end and add just a new one
164  * |- strip all new lines at the end
165  * |+ keep all the new lines at the end (the new lines until next node)
166  */
167  if(value == "|" || value == "|-") {
168  // remove all new lines at the end
169  int lastPos = multiLineData.length();
170  while(multiLineData[--lastPos] == '\n')
171  multiLineData.erase(lastPos, 1);
172 
173  if(value == "|")
174  multiLineData.append("\n");
175  } // else it's |+
176 
177  value = multiLineData;
178  }
179 
180  // create the node
181  OTMLNodePtr node = OTMLNode::create(tag);
182 
183  node->setUnique(dotsPos != std::string::npos);
184  node->setTag(tag);
185  node->setSource(doc->source() + ":" + stdext::unsafe_cast<std::string>(nodeLine));
186 
187  // ~ is considered the null value
188  if(value == "~")
189  node->setNull(true);
190  else {
191  if(stdext::starts_with(value, "[") && stdext::ends_with(value, "]")) {
192  std::string tmp = value.substr(1, value.length()-2);
193  boost::tokenizer<boost::escaped_list_separator<char>> tokens(tmp);
194  for(std::string v : tokens) {
195  stdext::trim(v);
196  node->writeIn(v);
197  }
198  } else
199  node->setValue(value);
200  }
201 
202  currentParent->addChild(node);
203  parentMap[node] = currentParent;
204  previousNode = node;
205 }
OTMLNode::writeIn
void writeIn(const T &v)
Definition: otmlnode.h:170
OTMLNode::setTag
void setTag(const std::string &tag)
Definition: otmlnode.h:50
OTMLNode::setSource
void setSource(const std::string &source)
Definition: otmlnode.h:54
OTMLNode::source
std::string source()
Definition: otmlnode.h:38
OTMLException
All OTML errors throw this exception.
Definition: otmlexception.h:29
OTMLParser::parse
void parse()
Parse the entire document.
Definition: otmlparser.cpp:35
otmlparser.h
otmlexception.h
OTMLParser::OTMLParser
OTMLParser(OTMLDocumentPtr doc, std::istream &in)
Definition: otmlparser.cpp:28
stdext::starts_with
bool starts_with(const std::string &str, const std::string &test)
Definition: string.cpp:263
OTMLNode::addChild
void addChild(const OTMLNodePtr &newChild)
Definition: otmlnode.cpp:91
otmldocument.h
OTMLNode::setNull
void setNull(bool null)
Definition: otmlnode.h:52
OTMLNode::setUnique
void setUnique(bool unique)
Definition: otmlnode.h:53
OTMLNode::setValue
void setValue(const std::string &value)
Definition: otmlnode.h:51
stdext::trim
void trim(std::string &str)
Definition: string.cpp:226
stdext::ends_with
bool ends_with(const std::string &str, const std::string &test)
Definition: string.cpp:258
stdext::shared_object_ptr< OTMLDocument >
OTMLNode::create
static OTMLNodePtr create(std::string tag="", bool unique=false)
Definition: otmlnode.cpp:27