1 // SciTE - Scintilla based Text Editor
2 /** @file PropSetSimple.cxx
3  ** A Java style properties file module.
4  **/
5 // Copyright 1998-2010 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
7 
8 // Maintain a dictionary of properties
9 
10 #include <stdlib.h>
11 #include <string.h>
12 #include <stdio.h>
13 
14 #include <string>
15 #include <map>
16 
17 #include "PropSetSimple.h"
18 
19 #ifdef SCI_NAMESPACE
20 using namespace Scintilla;
21 #endif
22 
23 typedef std::map<std::string, std::string> mapss;
24 
PropSetSimple()25 PropSetSimple::PropSetSimple() {
26 	mapss *props = new mapss;
27 	impl = static_cast<void *>(props);
28 }
29 
~PropSetSimple()30 PropSetSimple::~PropSetSimple() {
31 	mapss *props = static_cast<mapss *>(impl);
32 	delete props;
33 	impl = 0;
34 }
35 
Set(const char * key,const char * val,int lenKey,int lenVal)36 void PropSetSimple::Set(const char *key, const char *val, int lenKey, int lenVal) {
37 	mapss *props = static_cast<mapss *>(impl);
38 	if (!*key)	// Empty keys are not supported
39 		return;
40 	if (lenKey == -1)
41 		lenKey = static_cast<int>(strlen(key));
42 	if (lenVal == -1)
43 		lenVal = static_cast<int>(strlen(val));
44 	(*props)[std::string(key, lenKey)] = std::string(val, lenVal);
45 }
46 
IsASpaceCharacter(unsigned int ch)47 static bool IsASpaceCharacter(unsigned int ch) {
48     return (ch == ' ') || ((ch >= 0x09) && (ch <= 0x0d));
49 }
50 
Set(const char * keyVal)51 void PropSetSimple::Set(const char *keyVal) {
52 	while (IsASpaceCharacter(*keyVal))
53 		keyVal++;
54 	const char *endVal = keyVal;
55 	while (*endVal && (*endVal != '\n'))
56 		endVal++;
57 	const char *eqAt = strchr(keyVal, '=');
58 	if (eqAt) {
59 		Set(keyVal, eqAt + 1, static_cast<int>(eqAt-keyVal),
60 			static_cast<int>(endVal - eqAt - 1));
61 	} else if (*keyVal) {	// No '=' so assume '=1'
62 		Set(keyVal, "1", static_cast<int>(endVal-keyVal), 1);
63 	}
64 }
65 
SetMultiple(const char * s)66 void PropSetSimple::SetMultiple(const char *s) {
67 	const char *eol = strchr(s, '\n');
68 	while (eol) {
69 		Set(s);
70 		s = eol + 1;
71 		eol = strchr(s, '\n');
72 	}
73 	Set(s);
74 }
75 
Get(const char * key) const76 const char *PropSetSimple::Get(const char *key) const {
77 	mapss *props = static_cast<mapss *>(impl);
78 	mapss::const_iterator keyPos = props->find(std::string(key));
79 	if (keyPos != props->end()) {
80 		return keyPos->second.c_str();
81 	} else {
82 		return "";
83 	}
84 }
85 
86 // There is some inconsistency between GetExpanded("foo") and Expand("$(foo)").
87 // A solution is to keep a stack of variables that have been expanded, so that
88 // recursive expansions can be skipped.  For now I'll just use the C++ stack
89 // for that, through a recursive function and a simple chain of pointers.
90 
91 struct VarChain {
VarChainVarChain92 	VarChain(const char *var_=NULL, const VarChain *link_=NULL): var(var_), link(link_) {}
93 
containsVarChain94 	bool contains(const char *testVar) const {
95 		return (var && (0 == strcmp(var, testVar)))
96 			|| (link && link->contains(testVar));
97 	}
98 
99 	const char *var;
100 	const VarChain *link;
101 };
102 
ExpandAllInPlace(const PropSetSimple & props,std::string & withVars,int maxExpands,const VarChain & blankVars)103 static int ExpandAllInPlace(const PropSetSimple &props, std::string &withVars, int maxExpands, const VarChain &blankVars) {
104 	size_t varStart = withVars.find("$(");
105 	while ((varStart != std::string::npos) && (maxExpands > 0)) {
106 		size_t varEnd = withVars.find(")", varStart+2);
107 		if (varEnd == std::string::npos) {
108 			break;
109 		}
110 
111 		// For consistency, when we see '$(ab$(cde))', expand the inner variable first,
112 		// regardless whether there is actually a degenerate variable named 'ab$(cde'.
113 		size_t innerVarStart = withVars.find("$(", varStart+2);
114 		while ((innerVarStart != std::string::npos) && (innerVarStart > varStart) && (innerVarStart < varEnd)) {
115 			varStart = innerVarStart;
116 			innerVarStart = withVars.find("$(", varStart+2);
117 		}
118 
119 		std::string var(withVars.c_str(), varStart + 2, varEnd - varStart - 2);
120 		std::string val = props.Get(var.c_str());
121 
122 		if (blankVars.contains(var.c_str())) {
123 			val = ""; // treat blankVar as an empty string (e.g. to block self-reference)
124 		}
125 
126 		if (--maxExpands >= 0) {
127 			maxExpands = ExpandAllInPlace(props, val, maxExpands, VarChain(var.c_str(), &blankVars));
128 		}
129 
130 		withVars.erase(varStart, varEnd-varStart+1);
131 		withVars.insert(varStart, val.c_str(), val.length());
132 
133 		varStart = withVars.find("$(");
134 	}
135 
136 	return maxExpands;
137 }
138 
GetExpanded(const char * key,char * result) const139 int PropSetSimple::GetExpanded(const char *key, char *result) const {
140 	std::string val = Get(key);
141 	ExpandAllInPlace(*this, val, 100, VarChain(key));
142 	const int n = static_cast<int>(val.size());
143 	if (result) {
144 		memcpy(result, val.c_str(), n+1);
145 	}
146 	return n;	// Not including NUL
147 }
148 
GetInt(const char * key,int defaultValue) const149 int PropSetSimple::GetInt(const char *key, int defaultValue) const {
150 	std::string val = Get(key);
151 	ExpandAllInPlace(*this, val, 100, VarChain(key));
152 	if (!val.empty()) {
153 		return atoi(val.c_str());
154 	}
155 	return defaultValue;
156 }
157