Skip to content

Commit 2bef3ad

Browse files
Baseline functionality.
0 parents  commit 2bef3ad

File tree

8 files changed

+558
-0
lines changed

8 files changed

+558
-0
lines changed

conditional.html

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<html>
2+
<head>
3+
<title>Conditional Parser</title>
4+
<link rel="stylesheet" type="text/css" href="css/style.css">
5+
</head>
6+
<body>
7+
<textarea id="input"></textarea>
8+
<a href="javascript:void(0)" id="example1">1</a>
9+
<a href="javascript:void(0)" id="example2">2</a>
10+
<a href="javascript:void(0)" id="example3">3</a>
11+
<a href="javascript:void(0)" id="example4">4</a>
12+
<a href="javascript:void(0)" id="example5">5</a>
13+
<a href="javascript:void(0)" id="example6">6</a>
14+
15+
<div id="output"></div>
16+
17+
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.15/require.min.js"></script>
18+
<script type="text/javascript">
19+
require.config({
20+
baseUrl: 'js/',
21+
paths: {
22+
'jquery': 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min',
23+
'lodash': 'https://cdnjs.cloudflare.com/ajax/libs/lodash.js/2.4.1/lodash.min'
24+
},
25+
shim: {
26+
'underscore': {
27+
exports: '_'
28+
},
29+
'jquery': {
30+
exports: '$'
31+
}
32+
}
33+
});
34+
require(['main']);
35+
</script>
36+
</body>
37+
</html>

css/style.css

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#input {
2+
width: 80%;
3+
height: 30%;
4+
}
5+
#output {
6+
margin-top: 2em;
7+
}
8+
TABLE {
9+
border-collapse: collapse;
10+
}
11+
TH, TD {
12+
text-align: center;
13+
border: 1px solid black;
14+
}
15+
.condition.depth-1 {
16+
background-color: #66A3FF;
17+
}
18+
.condition.depth-2 {
19+
background-color: #B2D1FF;
20+
}
21+
.condition.depth-3 {
22+
background-color: #E0EDFF;
23+
}
24+
.operator {
25+
background-color: #E6E6E6;
26+
}
27+
.begin-expression {
28+
border-left: 3px solid black;
29+
}
30+
.end-expression {
31+
border-right: 3px solid black;
32+
}

js/condition.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
define(function(require, exports, module) {
2+
'use strict';
3+
4+
function Condition(text) {
5+
this.text = text;
6+
7+
return this;
8+
}
9+
10+
Condition.prototype.toString = function() {
11+
return this.text;
12+
};
13+
14+
return Condition;
15+
16+
});

js/expression.js

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
define(function(require, exports, module) {
2+
'use strict';
3+
4+
var Utils = require('utils'),
5+
Condition = require('condition'),
6+
Operator = require('operator');
7+
8+
function Expression(text) {
9+
this.operators = [/*Operator*/];
10+
this.conditions = [/*Condition*/];
11+
this.truePaths = [/*{ condition, result }*/];
12+
13+
// trim surrounding space and parenthesis pairs
14+
var textToParse = Utils.trimParenthesisPairs(text);
15+
var topLevelParenthesis = Utils.findTopLevelParenthesis(textToParse);
16+
17+
var textChunks = [],
18+
lastPosition = 0,
19+
i, j, k, l, m; // iterators
20+
21+
// Break the text into sub-expressions and top-level expressions
22+
// TODO: Identify when a ! precedes an Expression, and pass that into the constructor
23+
if(topLevelParenthesis.length === 0) {
24+
// There are no sub-expressions to extract. Store the entire string
25+
textChunks.push(textToParse);
26+
} else {
27+
for(i = 0; i < topLevelParenthesis.length; i++) {
28+
// Store the text between previous chunk and start of this Expression
29+
textChunks.push(textToParse.substring(lastPosition, topLevelParenthesis[i].start));
30+
// Store the sub-expression
31+
textChunks.push(new Expression(textToParse.substring(topLevelParenthesis[i].start, topLevelParenthesis[i].end + 1)));
32+
// Advance the pointer
33+
lastPosition = topLevelParenthesis[i].end + 1;
34+
}
35+
// Store any trailing text
36+
if(lastPosition < textToParse.length - 1) {
37+
textChunks.push(textToParse.substring(lastPosition));
38+
}
39+
}
40+
41+
var conditionChunks = [],
42+
matchAndOr = new RegExp(
43+
'(\\s|\\b)(?=' + Utils.tokensAndOr.join('|') + ')'
44+
, 'ig'),
45+
captureLeadingAnd = new RegExp(
46+
'^(' + Utils.tokensAnd.join('|') + ')'
47+
, 'ig'),
48+
captureLeadingOr = new RegExp(
49+
'^(' + Utils.tokensOr.join('|') + ')'
50+
, 'ig'),
51+
condition, leadingAndMatch, leadingOrMatch;
52+
53+
// TODO: Identify when the condition is preceded by a ! or has a negative comparison
54+
for(j = 0; j < textChunks.length; j++) {
55+
// If this chunk is a sub-expression, just store it without parsing
56+
if(textChunks[j] instanceof Expression) {
57+
this.conditions.push(textChunks[j]);
58+
} else {
59+
conditionChunks = textChunks[j].split(matchAndOr);
60+
for(k = 0; k < conditionChunks.length; k++) {
61+
condition = conditionChunks[k];
62+
63+
// Determine if an AND operator or an OR operator was found.
64+
// If so, store which was found and then remove it.
65+
if((leadingAndMatch = condition.match(captureLeadingAnd)) !== null) {
66+
this.operators.push(new Operator(true)); // AND operator
67+
condition = condition.substring(leadingAndMatch[0].length);
68+
} else if((leadingOrMatch = condition.match(captureLeadingOr)) !== null) {
69+
this.operators.push(new Operator(false)); // OR operator
70+
condition = condition.substring(leadingOrMatch[0].length);
71+
}
72+
73+
// Store anything that's not still empty.
74+
condition = condition.trim();
75+
if(condition !== '') {
76+
this.conditions.push(new Condition(condition));
77+
}
78+
}
79+
}
80+
}
81+
82+
this.hasMixedOperators = Utils.hasMixedOperators(this.operators);
83+
84+
// Calculate the combinations of Conditions that will resolve to true
85+
var truePath;
86+
if(!this.hasMixedOperators) { // TODO: Can we calculate when operators are mixed?
87+
if(this.conditions.length === 1) {
88+
// There's only one condition, so it must be true
89+
this.truePaths.push([{ condition: this.conditions[0], result: true }]);
90+
} else {
91+
if(this.operators[0].isAnd()) {
92+
// Create one truePath where every condition is true
93+
truePath = [];
94+
for(l = 0; l < this.conditions.length; l++) {
95+
truePath.push({ condition: this.conditions[l], result: true });
96+
}
97+
this.truePaths.push(truePath);
98+
} else {
99+
// Create a separate truePath for each Condition where one is true
100+
for(l = 0; l < this.conditions.length; l++) {
101+
truePath = [];
102+
for(m = 0; m < this.conditions.length; m++) {
103+
truePath.push({ condition: this.conditions[m], result: (l === m) });
104+
}
105+
this.truePaths.push(truePath);
106+
}
107+
}
108+
}
109+
}
110+
111+
return this;
112+
}
113+
114+
Expression.prototype.expandTruePaths = function() {
115+
116+
var replaceSubExpressionWithTruePath = function(appendInto, toAppend, subExpressionPos) {
117+
var resultToInsert = appendInto[subExpressionPos].result,
118+
tempAppendInfo, tempToAppend, i, spliceArgs;
119+
120+
// Create a copy of the objects so that modifications will not leak in or out by reference
121+
tempAppendInfo = Utils.cloneDeep(appendInto);
122+
tempToAppend = Utils.cloneDeep(toAppend);
123+
124+
// If the Expression does not need to be true in this path, set all its conditions to false
125+
// TODO: Move this to the (expandedTruePaths[k][i].result === false) condition
126+
if(!resultToInsert) {
127+
for(i = 0; i < tempToAppend.length; i++) {
128+
tempToAppend[i].result = false;
129+
}
130+
}
131+
132+
spliceArgs = [subExpressionPos, 1].concat(tempToAppend);
133+
Array.prototype.splice.apply(tempAppendInfo, spliceArgs);
134+
135+
return tempAppendInfo;
136+
};
137+
138+
var expandedTruePaths = Utils.cloneDeep(this.truePaths),
139+
i, j, k, kMax, subTruePaths, tempTruePath;
140+
141+
// Iterate through the first truePath, as a template of the conditions and sub-expressions.
142+
// Step through it backwards so expanded paths will not throw off the upcoming indicies.
143+
for(i = this.truePaths[0].length - 1; i >= 0; i--) {
144+
if(this.truePaths[0][i].condition instanceof Expression) {
145+
subTruePaths = this.truePaths[0][i].condition.expandTruePaths();
146+
147+
// Cross-apply the child true paths to the one for this Expression
148+
for(k = expandedTruePaths.length - 1; k >= 0; k--) {
149+
150+
if(expandedTruePaths[k][i].result === false) {
151+
// If the sub-expression doesn't need to be true, then we can replace it with a single entry where all conditions are false
152+
tempTruePath = replaceSubExpressionWithTruePath(expandedTruePaths[k], subTruePaths[0], i);
153+
// Append the new true path after the existing one. Because k counts down, it won't be processed.
154+
expandedTruePaths.splice(k + 1, 0, tempTruePath);
155+
} else {
156+
// The sub-expression needs to be true, so insert each of its true paths
157+
for(j = subTruePaths.length - 1; j >= 0; j--) {
158+
// Update this true path to replace the sub-expression with its expanded conditions
159+
tempTruePath = replaceSubExpressionWithTruePath(expandedTruePaths[k], subTruePaths[j], i);
160+
// Append the new true path after the existing one. Because k counts down, it won't be processed.
161+
expandedTruePaths.splice(k + 1, 0, tempTruePath);
162+
}
163+
}
164+
165+
// Remove the sub-expression, now that it's been replaced above by the expansion(s)
166+
expandedTruePaths.splice(k, 1);
167+
168+
}
169+
}
170+
}
171+
172+
return expandedTruePaths;
173+
};
174+
175+
/*Expression.prototype.toArray = function() {
176+
var toArray = [],
177+
i;
178+
for(i = 0; i < this.conditions.length; i++) {
179+
toArray.push(this.conditions[i]);
180+
if(i < this.operators.length) {
181+
toArray.push(this.operators[i]);
182+
}
183+
}
184+
return toArray;
185+
};*/
186+
187+
return Expression;
188+
189+
});

js/main.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// TODO: The ability to flip a condition from == to != or vice versa to make it easier to understand. Don't alter the input.
2+
3+
define(function(require, exports, module) {
4+
'use strict';
5+
6+
var $ = require('jquery'),
7+
Expression = require('expression'),
8+
Submission = require('submission');
9+
10+
function printHeadings(/*Expression*/ expression, newDepth) {
11+
var headings = '',
12+
depth = newDepth || 0,
13+
i, headingClass, operatorClass;
14+
for(i = 0; i < expression.conditions.length; i++) {
15+
if(expression.conditions[i] instanceof Expression) {
16+
// Recursively print sub-expressions
17+
headings += printHeadings(expression.conditions[i], depth + 1);
18+
} else {
19+
headingClass = 'condition depth-' + ((depth % 4) + 1);
20+
if(i === 0) { headingClass += ' begin-expression'; }
21+
if(i === expression.conditions.length - 1) { headingClass += ' end-expression'; }
22+
headings += '<th class="' + headingClass + '">' + expression.conditions[i] + '<\/th>';
23+
}
24+
25+
// Print the operator
26+
if(i < expression.operators.length) {
27+
operatorClass = 'operator depth-' + ((depth % 4) + 1);
28+
headings += '<th class="' + operatorClass + '">' + expression.operators[i] + '<\/th>';
29+
}
30+
}
31+
return headings;
32+
}
33+
34+
function printCells(/*Expression*/ expression, newDepth) {
35+
var cells = '',
36+
depth = newDepth || 0,
37+
expandedTruePaths = expression.expandTruePaths(),
38+
i, j, cellClass, operatorClass;
39+
for(i = 0; i < expandedTruePaths.length; i++) {
40+
cells += '<tr>';
41+
for(j = 0; j < expandedTruePaths[i].length; j++) {
42+
if(j !== 0) { cells += '<td class="operator">&nbsp;<\/td>'; } //
43+
cells += '<td class="condition">' + (expandedTruePaths[i][j].result ? 'true' : '') + '<\/td>';
44+
}
45+
cells += '<\/tr>';
46+
}
47+
return cells;
48+
}
49+
50+
$(function() {
51+
var $input = $('#input'),
52+
$output = $('#output'),
53+
$example1 = $('#example1'),
54+
$example2 = $('#example2'),
55+
$example3 = $('#example3'),
56+
$example4 = $('#example4'),
57+
$example5 = $('#example5'),
58+
$example6 = $('#example6');
59+
60+
$input.change(function() {
61+
var expression = (new Submission($input.val())).expression;
62+
63+
var output = '<table>' +
64+
'<thead><tr>' + printHeadings(expression) + '<\/tr><\/thead>' +
65+
'<tbody>' + printCells(expression) + '<\/tbody>' +
66+
'<\/table>';
67+
68+
$output.html(output);
69+
});
70+
71+
$example1.click(function() {
72+
$input.val(
73+
'!HasGenerousDeadlinePassed && Active && SelectedPaymentPortalInfo.Portal != PaymentPortal.NoPortal ' + "\r\n" +
74+
'&& ( !IsPayPalPortalInUse || (PayPalAccountType != "CannotValidate" && !String.IsNullOrWhiteSpace(PayPalAccountType)));'
75+
).change();
76+
});
77+
78+
$example2.click(function() {
79+
$input.val(
80+
'!HasGenerousDeadlinePassed && Active && SelectedPaymentPortalInfo.Portal != PaymentPortal.NoPortal ' + "\r\n" +
81+
'&& ( !IsPayPalPortalInUse && (PayPalAccountType != "CannotValidate" && !String.IsNullOrWhiteSpace(PayPalAccountType)));'
82+
).change();
83+
});
84+
85+
$example3.click(function() {
86+
$input.val(
87+
'if (PaymentPortalInUse == PaymentPortal.PayPal.GetHashCode() || (!PaymentPortalInUse.HasValue || !WePayPortalInUse || !String.IsNullOrWhiteSpace(PayPalID)))' + "\r\n"
88+
).change();
89+
});
90+
91+
$example4.click(function() {
92+
$input.val(
93+
'if (PaymentPortalInUse == PaymentPortal.PayPal.GetHashCode() || (!PaymentPortalInUse.HasValue && !WePayPortalInUse && !String.IsNullOrWhiteSpace(PayPalID)))' + "\r\n"
94+
).change();
95+
});
96+
97+
$example5.click(function() {
98+
$input.val(
99+
'if (a || (b && c) )' + "\r\n"
100+
).change();
101+
});
102+
});
103+
104+
});

0 commit comments

Comments
 (0)