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