1 /******************************************************************************
2  *
3  *  utilxml.cpp -	Implementaion of utility classes to handle
4  *			XML processing
5  *
6  * $Id: utilxml.cpp 3439 2016-10-23 08:32:02Z scribe $
7  *
8  * Copyright 2003-2013 CrossWire Bible Society (http://www.crosswire.org)
9  *	CrossWire Bible Society
10  *	P. O. Box 2528
11  *	Tempe, AZ  85280-2528
12  *
13  * This program is free software; you can redistribute it and/or modify it
14  * under the terms of the GNU General Public License as published by the
15  * Free Software Foundation version 2.
16  *
17  * This program is distributed in the hope that it will be useful, but
18  * WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * General Public License for more details.
21  *
22  */
23 
24 #include <utilxml.h>
25 #include <ctype.h>
26 #include <utilstr.h>
27 
28 
29 SWORD_NAMESPACE_START
30 
31 
parse() const32 void XMLTag::parse() const {
33 	int i;
34 	int start;
35 	char *name = 0;
36 	char *value = 0;
37 	attributes.clear();
38 
39 	if (!buf)
40 		return;
41 	for (i = 0; ((buf[i]) && (!isalpha(buf[i]))); i++);
42 	for (; buf[i]; i++) {
43 		if (strchr("\t\r\n ", buf[i])) {
44                         // Convert newlines, carriage returns and tabs to spaces
45 			buf[i] = ' ';
46 
47 			for (; ((buf[i]) && (!isalpha(buf[i]))); i++);
48 			if (buf[i]) {		// we have an attribute name
49 				start = i;
50 				// Deprecated: check for following whitespacee
51 				// Should be: for (; (buf[i] && buf[i] != '='; i++);
52 				for (; ((buf[i]) && (!strchr(" =", buf[i]))); i++);
53 
54 				if (i-start) {
55 					if (name)
56 						delete [] name;
57 					name = new char [ (i-start) + 1 ];
58 					strncpy(name, buf+start, i-start);
59 					name[i-start] = 0;
60 				}
61 
62 				// The following does not allow for empty attributes
63 				//for (; ((buf[i]) && (strchr(" =\"\'", buf[i]))); i++);
64 
65 				// skip space preceding the = sign
66 				// Deprecated: this is not part of the xml spec
67 				for (; buf[i] == ' '; i++) ;
68 
69 				// skip the = sign
70 				if (buf[i])
71 					i++;
72 
73 				// skip space following the = sign
74 				// Deprecated: this is not part of the xml spec
75 				for (; buf[i] == ' '; i++) ;
76 
77 				// remember and skip the quote sign
78 				char quoteChar = buf[i];
79 				if (quoteChar)
80 					i++;
81 
82 				if (buf[i]) {	// we have attribute value
83 					start = i;
84 					// Skip until matching quote character
85 					for (; ((buf[i]) && (buf[i] != quoteChar)); i++);
86 
87 					// Allow for empty quotes
88 					if (value)
89 						delete [] value;
90 					value = new char [ (i-start) + 1 ];
91 					if (i-start) {
92 						strncpy(value, buf+start, i-start);
93 					}
94 					value[i-start] = 0;
95 					attributes[name] = value;
96 				}
97 			}
98 		}
99 
100 		// if there are no more characters left then quit
101 		if (!buf[i])
102 			break;
103 
104 	}
105 	for (;i;i--) {
106 		if (buf[i] == '/')
107 			empty = true;
108 		if (!strchr(" \t\r\n>\t", buf[i]))
109 			break;
110 	}
111 
112 	parsed = true;
113 	if (name) delete [] name;
114 	if (value) delete [] value;
115 }
116 
117 
XMLTag(const char * tagString)118 XMLTag::XMLTag(const char *tagString) {
119 
120 	name   = 0;
121 	buf    = 0;
122 	setText(tagString);
123 }
124 
125 
XMLTag(const XMLTag & t)126 XMLTag::XMLTag(const XMLTag& t) : attributes(t.attributes)  {
127 	parsed = t.parsed;
128 	empty = t.empty;
129 	endTag = t.endTag;
130 	if (t.buf) {
131 		int len = (int)strlen(t.buf);
132 		buf = new char[len + 1];
133 		memcpy(buf, t.buf, len + 1);
134 	}
135 	if (t.name) {
136 		int len = (int)strlen(t.name);
137 		name = new char[len + 1];
138 		memcpy(name, t.name, len + 1);
139 	}
140 }
141 
142 
setText(const char * tagString)143 void XMLTag::setText(const char *tagString) {
144 	parsed = false;
145 	empty  = false;
146 	endTag = false;
147 
148 	if (buf) {
149 		delete [] buf;
150 		buf = 0;
151 	}
152 
153 	if (!tagString)		// assert tagString before proceeding
154 		return;
155 
156 	stdstr(&buf, tagString);
157 
158 	int start = 0;
159 	int i;
160 
161 	// skip beginning silliness
162 	for (i = 0; ((tagString[i]) && (!isalpha(tagString[i]))); i++) {
163 		if (tagString[i] == '/')
164 			endTag = true;
165 	}
166 	start = i;
167 	for (; ((tagString[i]) && (!strchr("\t\r\n />", tagString[i]))); i++);
168 	if (i-start) {
169 		if (name)
170 			delete [] name;
171 		name = new char [ (i-start) + 1 ];
172 		strncpy(name, tagString+start, i-start);
173 		name[i-start] = 0;
174 		if (tagString[i] == '/')
175 			empty = true;
176 	}
177 }
178 
179 
~XMLTag()180 XMLTag::~XMLTag() {
181 	if (buf)
182 		delete [] buf;
183 	if (name)
184 		delete [] name;
185 }
186 
187 
getAttributeNames() const188 const StringList XMLTag::getAttributeNames() const {
189 	StringList retVal;
190 
191 	if (!parsed)
192 		parse();
193 
194 	for (StringPairMap::const_iterator it = attributes.begin(); it != attributes.end(); it++)
195 		retVal.push_back(it->first.c_str());
196 
197 	return retVal;
198 }
199 
200 
getPart(const char * buf,int partNum,char partSplit) const201 const char *XMLTag::getPart(const char *buf, int partNum, char partSplit) const {
202 	for (; (buf && partNum); partNum--) {
203 		buf = strchr(buf, partSplit);
204 		if (buf)
205 			buf++;
206 	}
207 	if (buf) {
208 		const char *end = strchr(buf, partSplit);
209 		junkBuf = buf;
210 		if (end)
211 			junkBuf.setSize(end - buf);
212 		return junkBuf.c_str();
213 	}
214 	return 0;
215 }
216 
217 
getAttributePartCount(const char * attribName,char partSplit) const218 int XMLTag::getAttributePartCount(const char *attribName, char partSplit) const {
219 	int count;
220 	const char *buf = getAttribute(attribName);
221 	for (count = 0; buf; count++) {
222 		buf = strchr(buf, partSplit);
223 		if (buf)
224 			buf++;
225 	}
226 	return count;
227 }
228 
229 
getAttribute(const char * attribName,int partNum,char partSplit) const230 const char *XMLTag::getAttribute(const char *attribName, int partNum, char partSplit) const {
231 
232 	if (!parsed)
233 		parse();
234 
235 	StringPairMap::const_iterator it = attributes.find(attribName);
236 
237 	const char *retVal = 0;
238 	if (it != attributes.end())
239 		retVal = it->second.c_str();
240 
241 	if ((retVal) && (partNum > -1))
242 		retVal = getPart(retVal, partNum, partSplit);
243 
244 	return retVal;
245 }
246 
247 
setAttribute(const char * attribName,const char * attribValue,int partNum,char partSplit)248 const char *XMLTag::setAttribute(const char *attribName, const char *attribValue, int partNum, char partSplit) {
249 	if (!parsed)
250 		parse();
251 
252 	SWBuf newVal = "";
253 	// set part of an attribute
254 	if (partNum > -1) {
255 		const char *wholeAttr = getAttribute(attribName);
256 		int attrCount = getAttributePartCount(attribName, partSplit);
257 		for (int i = 0; i < attrCount; i++) {
258 			if (i == partNum) {
259 				if (attribValue) {
260 					newVal += attribValue;
261 					newVal += partSplit;
262 				}
263 				else {
264 					// discard this part per null attribValue
265 				}
266 			}
267 			else {
268 				newVal += getPart(wholeAttr, i, partSplit);
269 				newVal += partSplit;
270 			}
271 		}
272 		if (newVal.length()) newVal--;	// discard the last partSplit
273 		attribValue = (!attribValue && !newVal.length()) ? 0 : newVal.c_str();
274 	}
275 
276 	// perform the actual set
277 	if (attribValue)
278 		attributes[attribName] = attribValue;
279 	else	attributes.erase(attribName);
280 
281 	return attribValue;
282 }
283 
284 
toString() const285 const char *XMLTag::toString() const {
286 	SWBuf tag = "<";
287 	if (!parsed)
288 		parse();
289 
290 	if (isEndTag())
291 		tag.append('/');
292 
293 	tag.append(getName());
294 	for (StringPairMap::iterator it = attributes.begin(); it != attributes.end(); it++) {
295 		//tag.appendFormatted(" %s=\"%s\"", it->first.c_str(), it->second.c_str());
296 		tag.append(' ');
297 		tag.append(it->first.c_str());
298 		tag.append((strchr(it->second.c_str(), '\"')) ? "=\'" : "=\"");
299 		tag.append(it->second.c_str());
300 		tag.append((strchr(it->second.c_str(), '\"'))? '\'' : '\"');
301 	}
302 
303 	if (isEmpty())
304 		tag.append('/');
305 
306 	tag.append('>');
307 
308 
309 	if (buf)
310 		delete [] buf;
311 	buf = new char [ tag.length() + 1 ];
312 	strcpy(buf, tag.c_str());
313 
314 	return buf;
315 }
316 
317 
318 // if an eID is provided, then we check to be sure we have an attribute <tag eID="xxx"/> value xxx equiv to what is given us
319 // otherwise, we return if we're a simple XML end </tag>.
isEndTag(const char * eID) const320 bool XMLTag::isEndTag(const char *eID) const {
321 	if (eID) {
322 		return (SWBuf(eID) == getAttribute("eID"));
323 	}
324 	return endTag;
325 }
326 
327 
328 SWORD_NAMESPACE_END
329 
330