Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
myliang committed Sep 6, 2018
0 parents commit 2982d02
Show file tree
Hide file tree
Showing 19 changed files with 682 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .babelrc
@@ -0,0 +1,9 @@
{
"presets": [
['env',{
"targets": {
"browsers": ['> 1%', 'last 2 versions']
}
}]
]
}
3 changes: 3 additions & 0 deletions .eslintrc.js
@@ -0,0 +1,3 @@
module.exports = {
"extends": "airbnb-base"
};
36 changes: 36 additions & 0 deletions package.json
@@ -0,0 +1,36 @@
{
"name": "x-spreadsheet",
"version": "1.0.0",
"description": "a javascript xpreadsheet",
"private": true,
"scripts": {
"dev": "webpack-dev-server --open --config webpack.dev.js",
"test": "./node_modules/mocha/bin/mocha --require babel-core/register test/*",
"build": "webpack --config webpack.prod.js"
},
"keywords": [
"javascript",
"spreadsheet"
],
"author": "myliang",
"license": "MIT",
"devDependencies": {
"babel-core": "^6.26.3",
"babel-loader": "^8.0.2",
"babel-preset-env": "^1.7.0",
"clean-webpack-plugin": "^0.1.19",
"css-loader": "^1.0.0",
"eslint": "^5.5.0",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-plugin-import": "^2.14.0",
"file-loader": "^2.0.0",
"html-webpack-plugin": "^3.2.0",
"mocha": "^5.2.0",
"style-loader": "^0.23.0",
"webpack": "^4.17.2",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.7",
"webpack-merge": "^4.1.4"
},
"dependencies": {}
}
27 changes: 27 additions & 0 deletions readme.txt
@@ -0,0 +1,27 @@
mkdir x-spreadsheet && cd x-spreadsheet
npm init -y
npm install webpack webpack-cli --save-dev

mkdir dist src
touch webpack.config.js


npm install --save-dev file-loader css-loader file-loader
npm install --save-dev html-webpack-plugin
npm install --save-dev clean-webpack-plugin
npm install --save-dev webpack-dev-server
npm install --save-dev webpack-merge

npm install eslint --save-dev
./node_modules/.bin/eslint --init # airbnb


# test mocha
npm install --save-dev mocha

# babel
npm install --save-dev babel-loader babel-core babel-preset-env
# for macha
npm install --save-dev babel-register
# npm install --save-dev babel-plugin-transform-runtime
# npm install --save babel-runtime
39 changes: 39 additions & 0 deletions src/algorithm/expression.js
@@ -0,0 +1,39 @@
// src: include chars: [0-9], +, -, *, /
// // 9+(3-1)*3+10/2 => 9 3 1-3*+ 10 2/+
const infix2suffix = (src) => {
const operatorStack = [];
const stack = [];
for (let i = 0; i < src.length; i += 1) {
const c = src.charAt(i);
if (c !== ' ') {
if (c >= '0' && c <= '9') {
stack.push(c);
} else if (c === ')') {
let c1 = operatorStack.pop();
while (c1 !== '(') {
stack.push(c1);
c1 = operatorStack.pop();
}
} else {
// priority: */ > +-
if (operatorStack.length > 0 && (c === '+' || c === '-')) {
const last = operatorStack[operatorStack.length - 1];
if (last === '*' || last === '/') {
while (operatorStack.length > 0) {
stack.push(operatorStack.pop());
}
}
}
operatorStack.push(c);
}
}
}
while (operatorStack.length > 0) {
stack.push(operatorStack.pop());
}
return stack;
};

export default {
infix2suffix,
};
26 changes: 26 additions & 0 deletions src/data/alphabet.js
@@ -0,0 +1,26 @@
const alphabets = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];

export default {
stringAt: (index) => {
let str = '';
let cindex = index;
while (cindex >= alphabets.length) {
cindex /= alphabets.length;
cindex -= 1;
str += alphabets[parseInt(cindex, 10) % alphabets.length];
}
const last = index % alphabets.length;
str += alphabets[last];
return str;
},
indexAt: (str) => {
let ret = 0;
for (let i = 0; i < str.length - 1; i += 1) {
const cindex = str.charCodeAt(i) - 65;
const exponet = str.length - 1 - i;
ret += (alphabets.length ** exponet) + alphabets.length * cindex;
}
ret += str.charCodeAt(str.length - 1) - 65;
return ret;
},
};
154 changes: 154 additions & 0 deletions src/data/cell.js
@@ -0,0 +1,154 @@
import alphabet from './alphabet';

// B10 => x,y
const expr2xy = (src) => {
let x = '';
let y = '';
for (let i = 0; i < src.length; i += 1) {
if (src.charAt(i) >= '0' && src.charAt(i) <= '9') {
y += src.charAt(i);
} else {
x += src.charAt(i);
}
}
return [alphabet.indexAt(x), parseInt(y, 10)];
};

// Converting infix expression to a suffix expression
// src: AVERAGE(SUM(A1,A2), B1) + 50 + B20
// return: [A1, A2], SUM[, B1],AVERAGE,50,+,B20,+
const infixExprToSuffixExpr = (src) => {
const operatorStack = [];
const stack = [];
let subStrs = []; // SUM, A1, B2, 50 ...
let fnArgType = 0; // 1 => , 2 => :
let fnArgsLen = 1; // A1,A2,A3...
for (let i = 0; i < src.length; i += 1) {
const c = src.charAt(i);
if (c !== ' ') {
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')) {
subStrs.push(c);
} else if (c === '"') {
i += 1;
while (src.charAt(i) !== '"') {
subStrs.push(src.charAt(i));
i += 1;
}
stack.push(`"${subStrs.join('')}`);
subStrs = [];
} else {
if (subStrs.length > 0) {
stack.push(subStrs.join(''));
}
if (c === ')') {
let c1 = operatorStack.pop();
if (fnArgType === 2) {
// fn argument range => A1:B5
const [ex, ey] = expr2xy(stack.pop());
const [sx, sy] = expr2xy(stack.pop());
// console.log('::', sx, sy, ex, ey);
let rangelen = 0;
for (let x = sx; x <= ex; x += 1) {
for (let y = sy; y <= ey; y += 1) {
stack.push(alphabet.stringAt(x) + y);
rangelen += 1;
}
}
stack.push([c1, rangelen]);
} else if (fnArgType === 1) {
// fn argument => A1,A2,B5
stack.push([c1, fnArgsLen]);
fnArgsLen = 1;
} else {
while (c1 !== '(') {
stack.push(c1);
c1 = operatorStack.pop();
}
}
fnArgType = 0;
} else if (c === ':') {
fnArgType = 2;
} else if (c === ',') {
fnArgType = 1;
fnArgsLen += 1;
} else if (c === '(' && subStrs.length > 0) {
// function
stack.pop();
operatorStack.push(subStrs.join(''));
} else {
// priority: */ > +-
if (operatorStack.length > 0 && (c === '+' || c === '-')) {
const last = operatorStack[operatorStack.length - 1];
if (last === '*' || last === '/') {
while (operatorStack.length > 0) {
stack.push(operatorStack.pop());
}
}
}
operatorStack.push(c);
}
subStrs = [];
}
}
}
if (subStrs.length > 0) {
stack.push(subStrs.join(''));
}
while (operatorStack.length > 0) {
stack.push(operatorStack.pop());
}
return stack;
};

const evalSubExpr = (subExpr, cellRender) => {
if (subExpr[0] >= '0' && subExpr[0] <= '9') {
return subExpr;
}
if (subExpr[0] === '"') {
return subExpr.substring(1);
}
const [x, y] = expr2xy(subExpr);
return cellRender(x, y);
};

// evaluate the suffix expression
// srcStack: <= infixExprToSufixExpr
// formulaMap: {'SUM': {}, ...}
// cellRender: (x, y) => {}
const evalSuffixExpr = (srcStack, formulaMap, cellRender) => {
const stack = [];
for (let i = 0; i < srcStack.length; i += 1) {
if (srcStack[i] === '+') {
stack.push(stack.pop() + stack.pop());
} else if (srcStack[i] === '-') {
stack.push(stack.pop() - stack.pop());
} else if (srcStack[i] === '*') {
stack.push(stack.pop() * stack.pop());
} else if (srcStack[i] === '/') {
stack.push(stack.pop() / stack.pop());
} else if (Array.isArray(srcStack[i])) {
const [formula, len] = srcStack[i];
stack.push(formulaMap[formula].render(srcStack.slice(i - len, i)));
} else {
stack.push(evalSubExpr(srcStack[i], cellRender));
}
}
return stack[0];
};

const cellRender = (src, formulaMap, getCellText) => {
if (src[0] === '=') {
const stack = infixExprToSuffixExpr(src);
console.log('suffixExpr:', stack);
const cb = (x, y) => cellRender(getCellText(x, y), formulaMap, getCellText);
return evalSuffixExpr(stack, formulaMap, cb);
}
return src;
};

export default {
render: cellRender,
};
export {
infixExprToSuffixExpr,
};
20 changes: 20 additions & 0 deletions src/data/font.js
@@ -0,0 +1,20 @@
const baseFonts = [
{ key: 'Comic Sans MS', title: 'Comic Sans MS' },
{ key: 'Arial', title: 'Arial' },
{ key: 'Courier New', title: 'Courier New' },
{ key: 'Verdana', title: 'Verdana' },
];

const fonts = (ary = []) => {
const map = {};
baseFonts.concat(ary).forEach((f) => {
map[f.key] = f;
});
return map;
};

export default {};
export {
fonts,
baseFonts,
};
62 changes: 62 additions & 0 deletions src/data/format.js
@@ -0,0 +1,62 @@
const formatStringRender = v => v;

const formatNumberRender = (v) => {
if (/^(-?\d*.?\d*)$/.test(v)) {
const v1 = Number(v).toFixed(2).toString();
const [first, ...parts] = v1.split('\\.');
return [first.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,'), ...parts];
}
return v;
};

const baseFormats = [
{
key: 'normal',
title: 'Normal',
render: formatStringRender,
},
{
key: 'text',
title: 'Text',
render: formatStringRender,
},
{
key: 'number',
title: 'Number',
label: '1,000.12',
render: formatNumberRender,
},
{
key: 'percent',
title: 'Percent',
label: '10.12%',
render: v => `${v}%`,
},
{
key: 'RMB',
title: 'RMB',
label: '¥10.00',
render: v => `¥${formatNumberRender(v)}`,
},
{
key: 'USD',
title: 'USD',
label: '$10.00',
render: v => `$${formatNumberRender(v)}`,
},
];

const formats = (ary = []) => {
const map = {};
baseFormats.concat(ary).forEach((f) => {
map[f.key] = f;
});
return map;
};

export default {
};
export {
formats,
baseFormats,
};

0 comments on commit 2982d02

Please sign in to comment.