@doist/react-interpolate 0.0.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +1 -2
- package/.github/workflows/nodejs.yml +1 -9
- package/README.md +114 -23
- package/__test__/Interpolate.test.js +65 -32
- package/__test__/parser.test.js +3 -1
- package/dist/react-interpolate.cjs +117 -47
- package/dist/react-interpolate.min.cjs +1 -1
- package/dist/react-interpolate.min.cjs.gz +0 -0
- package/dist/react-interpolate.min.mjs +1 -1
- package/dist/react-interpolate.min.mjs.gz +0 -0
- package/dist/react-interpolate.mjs +89 -33
- package/jest.config.js +3 -0
- package/package.json +32 -28
- package/src/index.js +8 -0
- package/src/interpolate.js +23 -7
- package/src/lexer.js +7 -19
- package/src/parser.js +9 -3
- package/src/syntax.js +38 -0
package/.eslintrc.js
CHANGED
|
@@ -3,7 +3,6 @@ const config = {
|
|
|
3
3
|
"eslint:recommended",
|
|
4
4
|
"plugin:react/recommended",
|
|
5
5
|
"prettier",
|
|
6
|
-
"prettier/react",
|
|
7
6
|
"plugin:compat/recommended"
|
|
8
7
|
],
|
|
9
8
|
env: {
|
|
@@ -18,7 +17,7 @@ const config = {
|
|
|
18
17
|
sourceType: "module"
|
|
19
18
|
},
|
|
20
19
|
plugins: ["react", "prettier", "react-hooks", "jest"],
|
|
21
|
-
parser: "babel-
|
|
20
|
+
parser: "@babel/eslint-parser",
|
|
22
21
|
rules: {
|
|
23
22
|
semi: ["error", "never"],
|
|
24
23
|
"arrow-parens": ["error", "as-needed"],
|
|
@@ -1,17 +1,9 @@
|
|
|
1
|
-
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
|
2
|
-
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
|
3
|
-
|
|
4
1
|
name: CI
|
|
5
2
|
|
|
6
|
-
on:
|
|
7
|
-
push:
|
|
8
|
-
branches: [ master ]
|
|
9
|
-
pull_request:
|
|
10
|
-
branches: [ master ]
|
|
3
|
+
on: [push, pull_request]
|
|
11
4
|
|
|
12
5
|
jobs:
|
|
13
6
|
build:
|
|
14
|
-
|
|
15
7
|
runs-on: ubuntu-latest
|
|
16
8
|
|
|
17
9
|
strategy:
|
package/README.md
CHANGED
|
@@ -10,13 +10,15 @@ A string interpolation component that formats and interpolates a template string
|
|
|
10
10
|
import Interpolate from "@doist/react-interpolate"
|
|
11
11
|
|
|
12
12
|
function Greeting() {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
return (
|
|
14
|
+
<Interpolate
|
|
15
|
+
string="<h1>Hello {name}. Here is <a>your order info</a></h1>"
|
|
16
|
+
mapping={{
|
|
17
|
+
name: "William",
|
|
18
|
+
a: <a href="https://orderinfo.com" />
|
|
19
|
+
}}
|
|
20
|
+
/>
|
|
21
|
+
)
|
|
20
22
|
}
|
|
21
23
|
```
|
|
22
24
|
|
|
@@ -39,20 +41,26 @@ Please see the [Interpolation syntax](./#interpolation-syntax) section below for
|
|
|
39
41
|
#### `mapping`
|
|
40
42
|
An object that defines the values to be injected for placeholder and tags defined in the template string. Optional.
|
|
41
43
|
|
|
42
|
-
-
|
|
43
|
-
- For open & close tag,
|
|
44
|
+
- For placeholder or self-closing tag, the mapping value could be any valid element value
|
|
45
|
+
- For open & close tag, the mapping value could be either renderer function or an element.
|
|
44
46
|
|
|
45
47
|
```jsx
|
|
46
48
|
<Interpolate
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
49
|
+
string="Hello {name}. Here is <orderLink>your order info</orderLink><hr/>. \
|
|
50
|
+
Please contact <supportLink>support</supportLink> for help"
|
|
51
|
+
mapping={{
|
|
52
|
+
// you can map placholder and self-closing tag to any valid element value
|
|
53
|
+
name: "William",
|
|
54
|
+
hr: <hr className="break" />,
|
|
55
|
+
|
|
56
|
+
// you can map open & close tag to a rendering function
|
|
57
|
+
orderLink: text => (
|
|
58
|
+
<a href="https://orderinfo.com">{text}</a>
|
|
59
|
+
),
|
|
60
|
+
|
|
61
|
+
// or you can map open & close tag to an element
|
|
62
|
+
supportLink: <a href="https://orderinfo.com" />
|
|
63
|
+
}}
|
|
56
64
|
/>
|
|
57
65
|
```
|
|
58
66
|
|
|
@@ -71,6 +79,10 @@ Optional. `true` by default.
|
|
|
71
79
|
/>
|
|
72
80
|
```
|
|
73
81
|
|
|
82
|
+
#### `syntax`
|
|
83
|
+
|
|
84
|
+
Optional. `syntax` props allow use of react-Interpolate with different string formatting syntax. Please see the ["Custom syntax support"](#custom-syntax-support) section for more detail.
|
|
85
|
+
|
|
74
86
|
|
|
75
87
|
## Interpolation syntax
|
|
76
88
|
|
|
@@ -96,17 +108,51 @@ Placeholder name should be alphanumeric (`[A-Za-z0-9_]`). Placeholders could be
|
|
|
96
108
|
"Here is <a><b>you order info {name}</b></a>"
|
|
97
109
|
```
|
|
98
110
|
|
|
99
|
-
Tag name should be alphanumeric (`[A-Za-z0-9_]`).
|
|
111
|
+
Tag name should be alphanumeric (`[A-Za-z0-9_]`).
|
|
112
|
+
|
|
113
|
+
Open & close tag could be mapped to an element value.
|
|
114
|
+
|
|
115
|
+
```jsx
|
|
116
|
+
<Interpolate
|
|
117
|
+
string="Here is <a>your order info</a>"
|
|
118
|
+
mapping={{
|
|
119
|
+
a: <a href="https://orderinfo.com" />
|
|
120
|
+
}}
|
|
121
|
+
/>
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
// Invalid; the mapping value element should not contain children
|
|
125
|
+
<Interpolate
|
|
126
|
+
string="Here is <a>your order info</a>"
|
|
127
|
+
mapping={{
|
|
128
|
+
a: (
|
|
129
|
+
<a href="https://orderinfo.com">
|
|
130
|
+
<b />
|
|
131
|
+
<br />
|
|
132
|
+
</a>
|
|
133
|
+
)
|
|
134
|
+
}}
|
|
135
|
+
/>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Open & close tag could be mapped to a rendering function, which would take a single argument that contains the enclosing text.
|
|
100
139
|
|
|
101
140
|
```jsx
|
|
102
141
|
<Interpolate
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
142
|
+
string="Here is <a>your order info</a>"
|
|
143
|
+
mapping={{
|
|
144
|
+
a: text => (
|
|
145
|
+
<a href="https://orderinfo.com">
|
|
146
|
+
<b>{text}</b>
|
|
147
|
+
<br />
|
|
148
|
+
</a>
|
|
149
|
+
)
|
|
150
|
+
}}
|
|
107
151
|
/>
|
|
108
152
|
```
|
|
109
153
|
|
|
154
|
+
|
|
155
|
+
|
|
110
156
|
Unclosed tag or incorrect nesting of tag would result in syntax error.
|
|
111
157
|
|
|
112
158
|
```js
|
|
@@ -134,3 +180,48 @@ When tags are used the string but there are no correponding mapped value, it wou
|
|
|
134
180
|
// would render: <h1>Hellow</h1><br/>World
|
|
135
181
|
<Interpolate string="<h1>Hello</h1><br/>world"/>
|
|
136
182
|
```
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
## Custom syntax support
|
|
187
|
+
|
|
188
|
+
You may already be using a formatting syntax in your string that is different than the built-in syntax support from Interpolate. You can configure Interpolate so that it could recognize the formatting syntax that you use.
|
|
189
|
+
|
|
190
|
+
For instance, you may be using [i18next](https://www.i18next.com/) which has a slightly different placeholder syntax.
|
|
191
|
+
```
|
|
192
|
+
hello {{name}}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
You can define the formatting syntax of your string via `syntax` props.
|
|
196
|
+
|
|
197
|
+
```jsx
|
|
198
|
+
import Interpolate, { TOKEN_PLACEHOLDER } from "react-interpolate"
|
|
199
|
+
|
|
200
|
+
const i18nNextSyntax = [
|
|
201
|
+
{
|
|
202
|
+
type: TOKEN_PLACEHOLDER,
|
|
203
|
+
regex: /{{\s*(\w+)\s*}}/g
|
|
204
|
+
}
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
// will output "hi steven"
|
|
208
|
+
<Interpolate
|
|
209
|
+
syntax={i18nNextSyntax}
|
|
210
|
+
string="hi {{name}}"
|
|
211
|
+
mapping={{
|
|
212
|
+
name: "steven"
|
|
213
|
+
}}
|
|
214
|
+
/>
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
react-interpolate comes with i18next syntax support, and you can enable it via
|
|
218
|
+
|
|
219
|
+
```jsx
|
|
220
|
+
import { SYNTAX_I18NEXT } from "react-interpolate"
|
|
221
|
+
|
|
222
|
+
<Interpolate
|
|
223
|
+
syntax={SYNTAX_I18NEXT}
|
|
224
|
+
...
|
|
225
|
+
/>
|
|
226
|
+
```
|
|
227
|
+
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* eslint-disable react/display-name */
|
|
2
2
|
import React from "react"
|
|
3
3
|
import { render } from "@testing-library/react"
|
|
4
|
-
import Interpolate from "../src
|
|
4
|
+
import Interpolate, { SYNTAX_I18NEXT } from "../src"
|
|
5
5
|
|
|
6
6
|
Interpolate.defaultProps = {
|
|
7
7
|
graceful: false
|
|
@@ -19,10 +19,8 @@ const surpressConsole = () => {
|
|
|
19
19
|
|
|
20
20
|
describe("Interpolate", () => {
|
|
21
21
|
function renderTest({ expected, ...props }) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
expect(container.innerHTML).toEqual(expected)
|
|
25
|
-
}
|
|
22
|
+
const { container } = render(<Interpolate {...props} />)
|
|
23
|
+
expect(container.innerHTML).toEqual(expected)
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
test("when no mapping is provide", () => {
|
|
@@ -36,8 +34,7 @@ describe("Interpolate", () => {
|
|
|
36
34
|
restore()
|
|
37
35
|
})
|
|
38
36
|
|
|
39
|
-
test(
|
|
40
|
-
"tag mapping",
|
|
37
|
+
test("tag mapping", () =>
|
|
41
38
|
renderTest({
|
|
42
39
|
string: "<h1>hello <b>steven</b></h1>. welcome to todoist",
|
|
43
40
|
mapping: {
|
|
@@ -45,11 +42,19 @@ describe("Interpolate", () => {
|
|
|
45
42
|
h1: child => <h2>{child}</h2>
|
|
46
43
|
},
|
|
47
44
|
expected: "<h2>hello <i>steven</i></h2>. welcome to todoist"
|
|
48
|
-
})
|
|
49
|
-
)
|
|
45
|
+
}))
|
|
50
46
|
|
|
51
|
-
test(
|
|
52
|
-
|
|
47
|
+
test("tag mapping: should accept element directly", () =>
|
|
48
|
+
renderTest({
|
|
49
|
+
string: "<h1>hello <b>steven</b></h1>. welcome to todoist",
|
|
50
|
+
mapping: {
|
|
51
|
+
b: <i />,
|
|
52
|
+
h1: <h2 />
|
|
53
|
+
},
|
|
54
|
+
expected: "<h2>hello <i>steven</i></h2>. welcome to todoist"
|
|
55
|
+
}))
|
|
56
|
+
|
|
57
|
+
test("placholder mapping", () =>
|
|
53
58
|
renderTest({
|
|
54
59
|
string: "{greeting} <b>{name}</b>. welcome to todoist",
|
|
55
60
|
mapping: {
|
|
@@ -57,22 +62,18 @@ describe("Interpolate", () => {
|
|
|
57
62
|
name: () => <i>steven</i>
|
|
58
63
|
},
|
|
59
64
|
expected: "hi <b><i>steven</i></b>. welcome to todoist"
|
|
60
|
-
})
|
|
61
|
-
)
|
|
65
|
+
}))
|
|
62
66
|
|
|
63
|
-
test(
|
|
64
|
-
"void tag mapping",
|
|
67
|
+
test("void tag mapping", () =>
|
|
65
68
|
renderTest({
|
|
66
69
|
string: "hello <br/>",
|
|
67
70
|
mapping: {
|
|
68
71
|
br: <hr />
|
|
69
72
|
},
|
|
70
73
|
expected: "hello <hr>"
|
|
71
|
-
})
|
|
72
|
-
)
|
|
74
|
+
}))
|
|
73
75
|
|
|
74
|
-
test(
|
|
75
|
-
"combination of mapping",
|
|
76
|
+
test("combination of mapping", () =>
|
|
76
77
|
renderTest({
|
|
77
78
|
string: "<h1>hello <b>{name}</b></h1>.<br/> welcome to todoist",
|
|
78
79
|
mapping: {
|
|
@@ -82,8 +83,7 @@ describe("Interpolate", () => {
|
|
|
82
83
|
br: <hr />
|
|
83
84
|
},
|
|
84
85
|
expected: "<h2>hello <i>steven</i></h2>.<hr> welcome to todoist"
|
|
85
|
-
})
|
|
86
|
-
)
|
|
86
|
+
}))
|
|
87
87
|
|
|
88
88
|
test("combination of mapping with function component", () => {
|
|
89
89
|
// eslint-disable-next-line
|
|
@@ -104,8 +104,7 @@ describe("Interpolate", () => {
|
|
|
104
104
|
})
|
|
105
105
|
})
|
|
106
106
|
|
|
107
|
-
test(
|
|
108
|
-
"spacing in the void tag and placeholder should be allowed",
|
|
107
|
+
test("spacing in the void tag and placeholder should be allowed", () =>
|
|
109
108
|
renderTest({
|
|
110
109
|
string: "hello { name }<br /> welcome to todoist",
|
|
111
110
|
mapping: {
|
|
@@ -113,8 +112,7 @@ describe("Interpolate", () => {
|
|
|
113
112
|
br: <hr />
|
|
114
113
|
},
|
|
115
114
|
expected: "hello steven<hr> welcome to todoist"
|
|
116
|
-
})
|
|
117
|
-
)
|
|
115
|
+
}))
|
|
118
116
|
|
|
119
117
|
test("the mapping value should be interpolate corrected with proper html escape", () => {
|
|
120
118
|
renderTest({
|
|
@@ -125,7 +123,7 @@ describe("Interpolate", () => {
|
|
|
125
123
|
},
|
|
126
124
|
expected:
|
|
127
125
|
"hello <script>window.xss = 1</script><script>window.xss = 1</script> welcome to todoist"
|
|
128
|
-
})
|
|
126
|
+
})
|
|
129
127
|
|
|
130
128
|
expect(window.css).toBeUndefined()
|
|
131
129
|
})
|
|
@@ -137,13 +135,28 @@ describe("Interpolate", () => {
|
|
|
137
135
|
string: "</h1>",
|
|
138
136
|
expected: "</h1>",
|
|
139
137
|
graceful: true
|
|
140
|
-
})
|
|
138
|
+
})
|
|
141
139
|
|
|
142
140
|
restore()
|
|
143
141
|
})
|
|
142
|
+
|
|
143
|
+
test("using SYNTAX_I18NEXT", () => {
|
|
144
|
+
renderTest({
|
|
145
|
+
syntax: SYNTAX_I18NEXT,
|
|
146
|
+
string: "<0>hello <b>{{name}}</b></0>.<br/> welcome to todoist",
|
|
147
|
+
mapping: {
|
|
148
|
+
0: child => <h2 className="subheader">{child}</h2>,
|
|
149
|
+
b: child => <i>{child}</i>,
|
|
150
|
+
name: "steven",
|
|
151
|
+
br: <hr />
|
|
152
|
+
},
|
|
153
|
+
expected:
|
|
154
|
+
'<h2 class="subheader">hello <i>steven</i></h2>.<hr> welcome to todoist'
|
|
155
|
+
})
|
|
156
|
+
})
|
|
144
157
|
})
|
|
145
158
|
|
|
146
|
-
describe("Interpolate: error", () => {
|
|
159
|
+
describe("Interpolate: error cases", () => {
|
|
147
160
|
let restore
|
|
148
161
|
beforeAll(() => {
|
|
149
162
|
restore = surpressConsole()
|
|
@@ -152,19 +165,39 @@ describe("Interpolate: error", () => {
|
|
|
152
165
|
restore()
|
|
153
166
|
})
|
|
154
167
|
|
|
155
|
-
function renderTest({ props }) {
|
|
168
|
+
function renderTest({ expectedError, ...props }) {
|
|
156
169
|
return () => {
|
|
157
|
-
expect(() => render(<Interpolate {...props} />)).toThrow(
|
|
170
|
+
expect(() => render(<Interpolate {...props} />)).toThrow(
|
|
171
|
+
expectedError
|
|
172
|
+
)
|
|
158
173
|
}
|
|
159
174
|
}
|
|
160
175
|
|
|
161
|
-
test(
|
|
176
|
+
test(
|
|
177
|
+
"non-closing tag",
|
|
178
|
+
renderTest({
|
|
179
|
+
string: "<b>",
|
|
180
|
+
expectedError:
|
|
181
|
+
"Syntax error. Please check if each open tag is closed correctly"
|
|
182
|
+
})
|
|
183
|
+
)
|
|
162
184
|
|
|
163
185
|
test(
|
|
164
186
|
"mapping value for tag should always be a function",
|
|
165
187
|
renderTest({
|
|
166
188
|
string: "<h1>hello</h1>. welcome to todoist",
|
|
167
|
-
mapping: { h1: "hi" }
|
|
189
|
+
mapping: { h1: "hi" },
|
|
190
|
+
expectedError: "Invalid mapping value for"
|
|
191
|
+
})
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
test(
|
|
195
|
+
"when passing element as value but the element contains children, error should be thrown",
|
|
196
|
+
renderTest({
|
|
197
|
+
string: "<h1>hello</h1>. welcome to todoist",
|
|
198
|
+
mapping: { h1: <h1>hi</h1> },
|
|
199
|
+
expectedError:
|
|
200
|
+
"when passing an element as value, the element should not contains children"
|
|
168
201
|
})
|
|
169
202
|
)
|
|
170
203
|
})
|
package/__test__/parser.test.js
CHANGED
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
var React =
|
|
6
|
-
var PropTypes =
|
|
7
|
-
var _classCallCheck =
|
|
8
|
-
var _createClass =
|
|
9
|
-
var _objectWithoutProperties =
|
|
10
|
-
|
|
5
|
+
var React = require('react');
|
|
6
|
+
var PropTypes = require('prop-types');
|
|
7
|
+
var _classCallCheck = require('@babel/runtime/helpers/classCallCheck');
|
|
8
|
+
var _createClass = require('@babel/runtime/helpers/createClass');
|
|
9
|
+
var _objectWithoutProperties = require('@babel/runtime/helpers/objectWithoutProperties');
|
|
10
|
+
|
|
11
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
12
|
+
|
|
13
|
+
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
|
|
14
|
+
var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes);
|
|
15
|
+
var _classCallCheck__default = /*#__PURE__*/_interopDefaultLegacy(_classCallCheck);
|
|
16
|
+
var _createClass__default = /*#__PURE__*/_interopDefaultLegacy(_createClass);
|
|
17
|
+
var _objectWithoutProperties__default = /*#__PURE__*/_interopDefaultLegacy(_objectWithoutProperties);
|
|
11
18
|
|
|
12
19
|
var TOKEN_PLACEHOLDER = "TOKEN_PLACEHOLDER";
|
|
13
20
|
var TOKEN_OPEN_TAG = "TOKEN_OPEN_TAG";
|
|
@@ -20,13 +27,15 @@ var NODE_VOID_ELEMENT = "NODE_VOID_ELEMENT";
|
|
|
20
27
|
var NODE_PLACEHOLDER = "NODE_PLACEHOLDER";
|
|
21
28
|
var NODE_TEXT = "NODE_TEXT";
|
|
22
29
|
|
|
30
|
+
var _excluded = ["type", "children"];
|
|
31
|
+
|
|
23
32
|
var Node = /*#__PURE__*/function () {
|
|
24
33
|
function Node(_ref) {
|
|
25
34
|
var type = _ref.type,
|
|
26
35
|
children = _ref.children,
|
|
27
|
-
fields =
|
|
36
|
+
fields = _objectWithoutProperties__default["default"](_ref, _excluded);
|
|
28
37
|
|
|
29
|
-
|
|
38
|
+
_classCallCheck__default["default"](this, Node);
|
|
30
39
|
|
|
31
40
|
this.type = type;
|
|
32
41
|
this.children = children || [];
|
|
@@ -40,7 +49,7 @@ var Node = /*#__PURE__*/function () {
|
|
|
40
49
|
}
|
|
41
50
|
}
|
|
42
51
|
|
|
43
|
-
|
|
52
|
+
_createClass__default["default"](Node, [{
|
|
44
53
|
key: "appendChild",
|
|
45
54
|
value: function appendChild(child) {
|
|
46
55
|
this.children.push(child);
|
|
@@ -95,29 +104,69 @@ Node.createPlaceholderNode = function (token) {
|
|
|
95
104
|
});
|
|
96
105
|
};
|
|
97
106
|
|
|
107
|
+
var SYNTAX_BUILT_IN = [{
|
|
108
|
+
type: TOKEN_PLACEHOLDER,
|
|
109
|
+
regex: /{\s*(\w+)\s*}/g
|
|
110
|
+
}, {
|
|
111
|
+
type: TOKEN_OPEN_TAG,
|
|
112
|
+
regex: /<(\w+)>/g
|
|
113
|
+
}, {
|
|
114
|
+
type: TOKEN_CLOSE_TAG,
|
|
115
|
+
regex: /<\/(\w+)>/g
|
|
116
|
+
}, {
|
|
117
|
+
type: TOKEN_SELF_TAG,
|
|
118
|
+
regex: /<(\w+)\s*\/>/g
|
|
119
|
+
}];
|
|
120
|
+
var SYNTAX_I18NEXT = [{
|
|
121
|
+
type: TOKEN_PLACEHOLDER,
|
|
122
|
+
regex: /{{\s*(\w+)\s*}}/g
|
|
123
|
+
}, {
|
|
124
|
+
type: TOKEN_OPEN_TAG,
|
|
125
|
+
regex: /<(\w+)>/g
|
|
126
|
+
}, {
|
|
127
|
+
type: TOKEN_CLOSE_TAG,
|
|
128
|
+
regex: /<\/(\w+)>/g
|
|
129
|
+
}, {
|
|
130
|
+
type: TOKEN_SELF_TAG,
|
|
131
|
+
regex: /<(\w+)\s*\/>/g
|
|
132
|
+
}];
|
|
133
|
+
|
|
134
|
+
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
|
|
135
|
+
|
|
136
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
137
|
+
|
|
138
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
|
|
98
139
|
/*
|
|
99
140
|
* return a array of token object
|
|
100
141
|
*/
|
|
101
142
|
|
|
102
|
-
function lexer(string) {
|
|
103
|
-
var _regxMap;
|
|
104
|
-
|
|
105
|
-
var regxMap = (_regxMap = {}, _defineProperty(_regxMap, TOKEN_PLACEHOLDER, /{\s*(\w+)\s*}/g), _defineProperty(_regxMap, TOKEN_OPEN_TAG, /<(\w+)>/g), _defineProperty(_regxMap, TOKEN_CLOSE_TAG, /<\/(\w+)>/g), _defineProperty(_regxMap, TOKEN_SELF_TAG, /<(\w+)\s*\/>/g), _regxMap);
|
|
143
|
+
function lexer(string, syntax) {
|
|
106
144
|
var matches = [];
|
|
107
145
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
146
|
+
var _iterator = _createForOfIteratorHelper(syntax),
|
|
147
|
+
_step;
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
151
|
+
var rule = _step.value;
|
|
152
|
+
var type = rule.type,
|
|
153
|
+
regex = rule.regex;
|
|
154
|
+
var res;
|
|
155
|
+
|
|
156
|
+
while ((res = regex.exec(string)) !== null) {
|
|
157
|
+
matches.push({
|
|
158
|
+
type: type,
|
|
159
|
+
string: res[0],
|
|
160
|
+
name: res[1],
|
|
161
|
+
start: res.index,
|
|
162
|
+
end: res.index + res[0].length
|
|
163
|
+
});
|
|
164
|
+
}
|
|
120
165
|
}
|
|
166
|
+
} catch (err) {
|
|
167
|
+
_iterator.e(err);
|
|
168
|
+
} finally {
|
|
169
|
+
_iterator.f();
|
|
121
170
|
}
|
|
122
171
|
|
|
123
172
|
matches.sort(function (a, b) {
|
|
@@ -159,12 +208,16 @@ function lexer(string) {
|
|
|
159
208
|
return tokens;
|
|
160
209
|
}
|
|
161
210
|
|
|
162
|
-
function parser(string) {
|
|
163
|
-
|
|
211
|
+
function parser(string, syntax) {
|
|
212
|
+
if (!syntax) {
|
|
213
|
+
syntax = SYNTAX_BUILT_IN;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
var tokens = lexer(string, syntax);
|
|
164
217
|
var p = new Parser(tokens);
|
|
165
218
|
return p.parse();
|
|
166
219
|
}
|
|
167
|
-
var SYNTAX_ERROR = "
|
|
220
|
+
var SYNTAX_ERROR = "Syntax error. Please check if each open tag is closed correctly"; // A special token representing end of the tream
|
|
168
221
|
|
|
169
222
|
var EPSILON = {
|
|
170
223
|
type: "EPSILON"
|
|
@@ -198,13 +251,13 @@ var EPSILON = {
|
|
|
198
251
|
|
|
199
252
|
var Parser = /*#__PURE__*/function () {
|
|
200
253
|
function Parser(tokens) {
|
|
201
|
-
|
|
254
|
+
_classCallCheck__default["default"](this, Parser);
|
|
202
255
|
|
|
203
256
|
this.tokens = [].concat(tokens);
|
|
204
257
|
this.tags = [];
|
|
205
258
|
}
|
|
206
259
|
|
|
207
|
-
|
|
260
|
+
_createClass__default["default"](Parser, [{
|
|
208
261
|
key: "parse",
|
|
209
262
|
value: function parse() {
|
|
210
263
|
var tree = this.document();
|
|
@@ -304,6 +357,11 @@ var Parser = /*#__PURE__*/function () {
|
|
|
304
357
|
|
|
305
358
|
this.match(TOKEN_CLOSE_TAG);
|
|
306
359
|
}
|
|
360
|
+
}, {
|
|
361
|
+
key: "lookahead",
|
|
362
|
+
get: function get() {
|
|
363
|
+
return this.tokens.length === 0 ? EPSILON : this.tokens[0];
|
|
364
|
+
}
|
|
307
365
|
}, {
|
|
308
366
|
key: "predict",
|
|
309
367
|
value: function predict() {
|
|
@@ -328,11 +386,6 @@ var Parser = /*#__PURE__*/function () {
|
|
|
328
386
|
value: function pushTag(token) {
|
|
329
387
|
this.tags.push(token);
|
|
330
388
|
}
|
|
331
|
-
}, {
|
|
332
|
-
key: "lookahead",
|
|
333
|
-
get: function get() {
|
|
334
|
-
return this.tokens.length === 0 ? EPSILON : this.tokens[0];
|
|
335
|
-
}
|
|
336
389
|
}]);
|
|
337
390
|
|
|
338
391
|
return Parser;
|
|
@@ -340,7 +393,7 @@ var Parser = /*#__PURE__*/function () {
|
|
|
340
393
|
|
|
341
394
|
var createElement = function createElement(node, mapping, keyPrefix) {
|
|
342
395
|
var children = node.children.map(function (c, i) {
|
|
343
|
-
return /*#__PURE__*/
|
|
396
|
+
return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, {
|
|
344
397
|
key: keyPrefix + i
|
|
345
398
|
}, createElement(c, mapping, keyPrefix));
|
|
346
399
|
});
|
|
@@ -353,7 +406,7 @@ var createElement = function createElement(node, mapping, keyPrefix) {
|
|
|
353
406
|
|
|
354
407
|
case NODE_FRAGMENT:
|
|
355
408
|
{
|
|
356
|
-
return
|
|
409
|
+
return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, children);
|
|
357
410
|
}
|
|
358
411
|
|
|
359
412
|
case NODE_VOID_ELEMENT:
|
|
@@ -364,22 +417,32 @@ var createElement = function createElement(node, mapping, keyPrefix) {
|
|
|
364
417
|
return val();
|
|
365
418
|
}
|
|
366
419
|
|
|
367
|
-
return val ||
|
|
420
|
+
return val || /*#__PURE__*/React__default["default"].createElement(node.name, null);
|
|
368
421
|
}
|
|
369
422
|
|
|
370
423
|
case NODE_TAG_ELEMENT:
|
|
371
424
|
{
|
|
372
425
|
var _val = mapping[node.name];
|
|
373
426
|
|
|
427
|
+
if (_val === undefined) {
|
|
428
|
+
return /*#__PURE__*/React__default["default"].createElement(node.name, null, children);
|
|
429
|
+
}
|
|
430
|
+
|
|
374
431
|
if (typeof _val === "function") {
|
|
375
432
|
return _val(children);
|
|
376
433
|
}
|
|
377
434
|
|
|
378
|
-
if (_val
|
|
379
|
-
|
|
435
|
+
if ( /*#__PURE__*/React__default["default"].isValidElement(_val)) {
|
|
436
|
+
if (React__default["default"].Children.count(_val.props.children) !== 0) {
|
|
437
|
+
throw new Error("when passing an element as value, the element should not contains children");
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return /*#__PURE__*/React__default["default"].cloneElement(_val, {
|
|
441
|
+
children: children
|
|
442
|
+
});
|
|
380
443
|
}
|
|
381
444
|
|
|
382
|
-
|
|
445
|
+
throw new Error("Invalid mapping value for \"".concat(node.name, "\". Only element or render function are accepted"));
|
|
383
446
|
}
|
|
384
447
|
|
|
385
448
|
case NODE_PLACEHOLDER:
|
|
@@ -405,13 +468,14 @@ var createElement = function createElement(node, mapping, keyPrefix) {
|
|
|
405
468
|
|
|
406
469
|
function Interpolate(_ref) {
|
|
407
470
|
var string = _ref.string,
|
|
471
|
+
syntax = _ref.syntax,
|
|
408
472
|
_ref$mapping = _ref.mapping,
|
|
409
473
|
mapping = _ref$mapping === void 0 ? {} : _ref$mapping,
|
|
410
474
|
_ref$graceful = _ref.graceful,
|
|
411
475
|
graceful = _ref$graceful === void 0 ? true : _ref$graceful;
|
|
412
476
|
|
|
413
477
|
try {
|
|
414
|
-
var tree = parser(string);
|
|
478
|
+
var tree = parser(string, syntax);
|
|
415
479
|
return createElement(tree, mapping, string);
|
|
416
480
|
} catch (e) {
|
|
417
481
|
if (graceful) {
|
|
@@ -423,9 +487,15 @@ function Interpolate(_ref) {
|
|
|
423
487
|
}
|
|
424
488
|
}
|
|
425
489
|
Interpolate.propTypes = {
|
|
426
|
-
string:
|
|
427
|
-
mapping:
|
|
428
|
-
graceful:
|
|
490
|
+
string: PropTypes__default["default"].string.isRequired,
|
|
491
|
+
mapping: PropTypes__default["default"].object,
|
|
492
|
+
graceful: PropTypes__default["default"].bool
|
|
429
493
|
};
|
|
430
494
|
|
|
431
|
-
|
|
495
|
+
exports.SYNTAX_BUILT_IN = SYNTAX_BUILT_IN;
|
|
496
|
+
exports.SYNTAX_I18NEXT = SYNTAX_I18NEXT;
|
|
497
|
+
exports.TOKEN_CLOSE_TAG = TOKEN_CLOSE_TAG;
|
|
498
|
+
exports.TOKEN_OPEN_TAG = TOKEN_OPEN_TAG;
|
|
499
|
+
exports.TOKEN_PLACEHOLDER = TOKEN_PLACEHOLDER;
|
|
500
|
+
exports.TOKEN_SELF_TAG = TOKEN_SELF_TAG;
|
|
501
|
+
exports["default"] = Interpolate;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0});var React=require("react"),PropTypes=require("prop-types"),_classCallCheck=require("@babel/runtime/helpers/classCallCheck"),_createClass=require("@babel/runtime/helpers/createClass"),_objectWithoutProperties=require("@babel/runtime/helpers/objectWithoutProperties");function _interopDefaultLegacy(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var React__default=_interopDefaultLegacy(React),PropTypes__default=_interopDefaultLegacy(PropTypes),_classCallCheck__default=_interopDefaultLegacy(_classCallCheck),_createClass__default=_interopDefaultLegacy(_createClass),_objectWithoutProperties__default=_interopDefaultLegacy(_objectWithoutProperties),TOKEN_PLACEHOLDER="TOKEN_PLACEHOLDER",TOKEN_OPEN_TAG="TOKEN_OPEN_TAG",TOKEN_CLOSE_TAG="TOKEN_CLOSE_TAG",TOKEN_SELF_TAG="TOKEN_SELF_TAG",TOKEN_TEXT="TOKEN_TEXT",NODE_FRAGMENT="NODE_FRAGMENT",NODE_TAG_ELEMENT="NODE_TAG_ELEMENT",NODE_VOID_ELEMENT="NODE_VOID_ELEMENT",NODE_PLACEHOLDER="NODE_PLACEHOLDER",NODE_TEXT="NODE_TEXT",_excluded=["type","children"],Node=function(){function e(t){var r=t.type,n=t.children,a=_objectWithoutProperties__default.default(t,_excluded);for(var o in _classCallCheck__default.default(this,e),this.type=r,this.children=n||[],a)this[o]=a[o];a.token&&(this.string=a.token.string)}return _createClass__default.default(e,[{key:"appendChild",value:function(e){this.children.push(e)}},{key:"isLeaf",get:function(){return 0===this.children.length}}]),e}();Node.createTagNode=function(e,t){return new Node({type:NODE_TAG_ELEMENT,children:t,name:e.name,token:e})},Node.createFragmentNode=function(e){return new Node({type:NODE_FRAGMENT,children:e})},Node.createVoidNode=function(e){return new Node({type:NODE_VOID_ELEMENT,name:e.name,token:e})},Node.createTextNode=function(e){return new Node({type:NODE_TEXT,text:e.string,token:e})},Node.createPlaceholderNode=function(e){return new Node({type:NODE_PLACEHOLDER,name:e.name,token:e})};var SYNTAX_BUILT_IN=[{type:TOKEN_PLACEHOLDER,regex:/{\s*(\w+)\s*}/g},{type:TOKEN_OPEN_TAG,regex:/<(\w+)>/g},{type:TOKEN_CLOSE_TAG,regex:/<\/(\w+)>/g},{type:TOKEN_SELF_TAG,regex:/<(\w+)\s*\/>/g}],SYNTAX_I18NEXT=[{type:TOKEN_PLACEHOLDER,regex:/{{\s*(\w+)\s*}}/g},{type:TOKEN_OPEN_TAG,regex:/<(\w+)>/g},{type:TOKEN_CLOSE_TAG,regex:/<\/(\w+)>/g},{type:TOKEN_SELF_TAG,regex:/<(\w+)\s*\/>/g}];function _createForOfIteratorHelper(e,t){var r="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(!r){if(Array.isArray(e)||(r=_unsupportedIterableToArray(e))||t&&e&&"number"==typeof e.length){r&&(e=r);var n=0,a=function(){};return{s:a,n:function(){return n>=e.length?{done:!0}:{done:!1,value:e[n++]}},e:function(e){throw e},f:a}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,i=!0,u=!1;return{s:function(){r=r.call(e)},n:function(){var e=r.next();return i=e.done,e},e:function(e){u=!0,o=e},f:function(){try{i||null==r.return||r.return()}finally{if(u)throw o}}}}function _unsupportedIterableToArray(e,t){if(e){if("string"==typeof e)return _arrayLikeToArray(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?_arrayLikeToArray(e,t):void 0}}function _arrayLikeToArray(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);r<t;r++)n[r]=e[r];return n}function lexer(e,t){var r,n=[],a=_createForOfIteratorHelper(t);try{for(a.s();!(r=a.n()).done;)for(var o,i=r.value,u=i.type,l=i.regex;null!==(o=l.exec(e));)n.push({type:u,string:o[0],name:o[1],start:o.index,end:o.index+o[0].length})}catch(e){a.e(e)}finally{a.f()}n.sort(function(e,t){return e.start-t.start});var c=[],s=0;n.forEach(function(t){if(t.start!==s){var r=t.start,n=e.substring(s,r);c.push({type:TOKEN_TEXT,string:n,text:n,start:s,end:r}),s=t.end}else s=t.end});var _=e.substring(s);return""!==_&&c.push({type:TOKEN_TEXT,string:_,start:s,end:e.length}),[].concat(c,n).sort(function(e,t){return e.start-t.start})}function parser(e,t){t||(t=SYNTAX_BUILT_IN);var r=lexer(e,t);return new Parser(r).parse()}var SYNTAX_ERROR="Syntax error. Please check if each open tag is closed correctly",EPSILON={type:"EPSILON"},Parser=function(){function e(t){_classCallCheck__default.default(this,e),this.tokens=[].concat(t),this.tags=[]}return _createClass__default.default(e,[{key:"parse",value:function(){var e=this.document();if(!this.predict(EPSILON.type))throw new Error(SYNTAX_ERROR);return e}},{key:"document",value:function(){var e=this.elementOrData();return Node.createFragmentNode(e)}},{key:"element",value:function(){var e=this.openTag(),t=this.elementOrData();return this.endTag(),Node.createTagNode(e,t)}},{key:"openTag",value:function(){if(!this.predict(TOKEN_OPEN_TAG))throw new Error(SYNTAX_ERROR);return this.pushTag(this.lookahead),this.match(TOKEN_OPEN_TAG)}},{key:"elementOrData",value:function(){var e=[];if(this.predict(TOKEN_OPEN_TAG)&&(e.push(this.element()),e=e.concat(this.elementOrData())),this.predict(TOKEN_SELF_TAG,TOKEN_TEXT,TOKEN_PLACEHOLDER)){var t,r=this.match(this.lookahead.type);switch(r.type){case TOKEN_SELF_TAG:t=Node.createVoidNode(r);break;case TOKEN_TEXT:t=Node.createTextNode(r);break;case TOKEN_PLACEHOLDER:t=Node.createPlaceholderNode(r)}e.push(t),e=e.concat(this.elementOrData())}return e}},{key:"endTag",value:function(){if(!this.predict(TOKEN_CLOSE_TAG))throw new Error(SYNTAX_ERROR);if(this.tags.pop().name!==this.lookahead.name)throw new Error(SYNTAX_ERROR);this.match(TOKEN_CLOSE_TAG)}},{key:"lookahead",get:function(){return 0===this.tokens.length?EPSILON:this.tokens[0]}},{key:"predict",value:function(){for(var e=arguments.length,t=new Array(e),r=0;r<e;r++)t[r]=arguments[r];return t.includes(this.lookahead.type)}},{key:"match",value:function(e){if(!this.predict(e))throw new Error(SYNTAX_ERROR);return this.tokens.shift()}},{key:"pushTag",value:function(e){this.tags.push(e)}}]),e}(),createElement=function e(t,r,n){var a=t.children.map(function(t,a){return React__default.default.createElement(React__default.default.Fragment,{key:n+a},e(t,r,n))});switch(t.type){case NODE_TEXT:return t.text;case NODE_FRAGMENT:return React__default.default.createElement(React__default.default.Fragment,null,a);case NODE_VOID_ELEMENT:var o=r[t.name];return"function"==typeof o?o():o||React__default.default.createElement(t.name,null);case NODE_TAG_ELEMENT:var i=r[t.name];if(void 0===i)return React__default.default.createElement(t.name,null,a);if("function"==typeof i)return i(a);if(React__default.default.isValidElement(i)){if(0!==React__default.default.Children.count(i.props.children))throw new Error("when passing an element as value, the element should not contains children");return React__default.default.cloneElement(i,{children:a})}throw new Error('Invalid mapping value for "'.concat(t.name,'". Only element or render function are accepted'));case NODE_PLACEHOLDER:var u=r[t.name];return void 0===u?(console.warn('missing "'.concat(t.name,'" in mapping')),t.string):"function"==typeof u?u():u;default:return}};function Interpolate(e){var t=e.string,r=e.syntax,n=e.mapping,a=void 0===n?{}:n,o=e.graceful,i=void 0===o||o;try{var u=parser(t,r);return createElement(u,a,t)}catch(e){if(i)return console.error(e),t;throw e}}Interpolate.propTypes={string:PropTypes__default.default.string.isRequired,mapping:PropTypes__default.default.object,graceful:PropTypes__default.default.bool},exports.SYNTAX_BUILT_IN=SYNTAX_BUILT_IN,exports.SYNTAX_I18NEXT=SYNTAX_I18NEXT,exports.TOKEN_CLOSE_TAG=TOKEN_CLOSE_TAG,exports.TOKEN_OPEN_TAG=TOKEN_OPEN_TAG,exports.TOKEN_PLACEHOLDER=TOKEN_PLACEHOLDER,exports.TOKEN_SELF_TAG=TOKEN_SELF_TAG,exports.default=Interpolate;
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import React from"react";import PropTypes from"prop-types";import _classCallCheck from"@babel/runtime/helpers/classCallCheck";import _createClass from"@babel/runtime/helpers/createClass";import _objectWithoutProperties from"@babel/runtime/helpers/objectWithoutProperties";
|
|
1
|
+
import React from"react";import PropTypes from"prop-types";import _classCallCheck from"@babel/runtime/helpers/classCallCheck";import _createClass from"@babel/runtime/helpers/createClass";import _objectWithoutProperties from"@babel/runtime/helpers/objectWithoutProperties";var TOKEN_PLACEHOLDER="TOKEN_PLACEHOLDER",TOKEN_OPEN_TAG="TOKEN_OPEN_TAG",TOKEN_CLOSE_TAG="TOKEN_CLOSE_TAG",TOKEN_SELF_TAG="TOKEN_SELF_TAG",TOKEN_TEXT="TOKEN_TEXT",NODE_FRAGMENT="NODE_FRAGMENT",NODE_TAG_ELEMENT="NODE_TAG_ELEMENT",NODE_VOID_ELEMENT="NODE_VOID_ELEMENT",NODE_PLACEHOLDER="NODE_PLACEHOLDER",NODE_TEXT="NODE_TEXT",_excluded=["type","children"],Node=function(){function e(t){var r=t.type,n=t.children,a=_objectWithoutProperties(t,_excluded);for(var o in _classCallCheck(this,e),this.type=r,this.children=n||[],a)this[o]=a[o];a.token&&(this.string=a.token.string)}return _createClass(e,[{key:"appendChild",value:function(e){this.children.push(e)}},{key:"isLeaf",get:function(){return 0===this.children.length}}]),e}();Node.createTagNode=function(e,t){return new Node({type:NODE_TAG_ELEMENT,children:t,name:e.name,token:e})},Node.createFragmentNode=function(e){return new Node({type:NODE_FRAGMENT,children:e})},Node.createVoidNode=function(e){return new Node({type:NODE_VOID_ELEMENT,name:e.name,token:e})},Node.createTextNode=function(e){return new Node({type:NODE_TEXT,text:e.string,token:e})},Node.createPlaceholderNode=function(e){return new Node({type:NODE_PLACEHOLDER,name:e.name,token:e})};var SYNTAX_BUILT_IN=[{type:TOKEN_PLACEHOLDER,regex:/{\s*(\w+)\s*}/g},{type:TOKEN_OPEN_TAG,regex:/<(\w+)>/g},{type:TOKEN_CLOSE_TAG,regex:/<\/(\w+)>/g},{type:TOKEN_SELF_TAG,regex:/<(\w+)\s*\/>/g}],SYNTAX_I18NEXT=[{type:TOKEN_PLACEHOLDER,regex:/{{\s*(\w+)\s*}}/g},{type:TOKEN_OPEN_TAG,regex:/<(\w+)>/g},{type:TOKEN_CLOSE_TAG,regex:/<\/(\w+)>/g},{type:TOKEN_SELF_TAG,regex:/<(\w+)\s*\/>/g}];function _createForOfIteratorHelper(e,t){var r="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(!r){if(Array.isArray(e)||(r=_unsupportedIterableToArray(e))||t&&e&&"number"==typeof e.length){r&&(e=r);var n=0,a=function(){};return{s:a,n:function(){return n>=e.length?{done:!0}:{done:!1,value:e[n++]}},e:function(e){throw e},f:a}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var o,i=!0,c=!1;return{s:function(){r=r.call(e)},n:function(){var e=r.next();return i=e.done,e},e:function(e){c=!0,o=e},f:function(){try{i||null==r.return||r.return()}finally{if(c)throw o}}}}function _unsupportedIterableToArray(e,t){if(e){if("string"==typeof e)return _arrayLikeToArray(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);return"Object"===r&&e.constructor&&(r=e.constructor.name),"Map"===r||"Set"===r?Array.from(e):"Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?_arrayLikeToArray(e,t):void 0}}function _arrayLikeToArray(e,t){(null==t||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);r<t;r++)n[r]=e[r];return n}function lexer(e,t){var r,n=[],a=_createForOfIteratorHelper(t);try{for(a.s();!(r=a.n()).done;)for(var o,i=r.value,c=i.type,E=i.regex;null!==(o=E.exec(e));)n.push({type:c,string:o[0],name:o[1],start:o.index,end:o.index+o[0].length})}catch(e){a.e(e)}finally{a.f()}n.sort(function(e,t){return e.start-t.start});var s=[],u=0;n.forEach(function(t){if(t.start!==u){var r=t.start,n=e.substring(u,r);s.push({type:TOKEN_TEXT,string:n,text:n,start:u,end:r}),u=t.end}else u=t.end});var l=e.substring(u);return""!==l&&s.push({type:TOKEN_TEXT,string:l,start:u,end:e.length}),[].concat(s,n).sort(function(e,t){return e.start-t.start})}function parser(e,t){t||(t=SYNTAX_BUILT_IN);var r=lexer(e,t);return new Parser(r).parse()}var SYNTAX_ERROR="Syntax error. Please check if each open tag is closed correctly",EPSILON={type:"EPSILON"},Parser=function(){function e(t){_classCallCheck(this,e),this.tokens=[].concat(t),this.tags=[]}return _createClass(e,[{key:"parse",value:function(){var e=this.document();if(!this.predict(EPSILON.type))throw new Error(SYNTAX_ERROR);return e}},{key:"document",value:function(){var e=this.elementOrData();return Node.createFragmentNode(e)}},{key:"element",value:function(){var e=this.openTag(),t=this.elementOrData();return this.endTag(),Node.createTagNode(e,t)}},{key:"openTag",value:function(){if(!this.predict(TOKEN_OPEN_TAG))throw new Error(SYNTAX_ERROR);return this.pushTag(this.lookahead),this.match(TOKEN_OPEN_TAG)}},{key:"elementOrData",value:function(){var e=[];if(this.predict(TOKEN_OPEN_TAG)&&(e.push(this.element()),e=e.concat(this.elementOrData())),this.predict(TOKEN_SELF_TAG,TOKEN_TEXT,TOKEN_PLACEHOLDER)){var t,r=this.match(this.lookahead.type);switch(r.type){case TOKEN_SELF_TAG:t=Node.createVoidNode(r);break;case TOKEN_TEXT:t=Node.createTextNode(r);break;case TOKEN_PLACEHOLDER:t=Node.createPlaceholderNode(r)}e.push(t),e=e.concat(this.elementOrData())}return e}},{key:"endTag",value:function(){if(!this.predict(TOKEN_CLOSE_TAG))throw new Error(SYNTAX_ERROR);if(this.tags.pop().name!==this.lookahead.name)throw new Error(SYNTAX_ERROR);this.match(TOKEN_CLOSE_TAG)}},{key:"lookahead",get:function(){return 0===this.tokens.length?EPSILON:this.tokens[0]}},{key:"predict",value:function(){for(var e=arguments.length,t=new Array(e),r=0;r<e;r++)t[r]=arguments[r];return t.includes(this.lookahead.type)}},{key:"match",value:function(e){if(!this.predict(e))throw new Error(SYNTAX_ERROR);return this.tokens.shift()}},{key:"pushTag",value:function(e){this.tags.push(e)}}]),e}(),createElement=function e(t,r,n){var a=t.children.map(function(t,a){return React.createElement(React.Fragment,{key:n+a},e(t,r,n))});switch(t.type){case NODE_TEXT:return t.text;case NODE_FRAGMENT:return React.createElement(React.Fragment,null,a);case NODE_VOID_ELEMENT:var o=r[t.name];return"function"==typeof o?o():o||React.createElement(t.name,null);case NODE_TAG_ELEMENT:var i=r[t.name];if(void 0===i)return React.createElement(t.name,null,a);if("function"==typeof i)return i(a);if(React.isValidElement(i)){if(0!==React.Children.count(i.props.children))throw new Error("when passing an element as value, the element should not contains children");return React.cloneElement(i,{children:a})}throw new Error('Invalid mapping value for "'.concat(t.name,'". Only element or render function are accepted'));case NODE_PLACEHOLDER:var c=r[t.name];return void 0===c?(console.warn('missing "'.concat(t.name,'" in mapping')),t.string):"function"==typeof c?c():c;default:return}};function Interpolate(e){var t=e.string,r=e.syntax,n=e.mapping,a=void 0===n?{}:n,o=e.graceful,i=void 0===o||o;try{var c=parser(t,r);return createElement(c,a,t)}catch(e){if(i)return console.error(e),t;throw e}}Interpolate.propTypes={string:PropTypes.string.isRequired,mapping:PropTypes.object,graceful:PropTypes.bool};export{SYNTAX_BUILT_IN,SYNTAX_I18NEXT,TOKEN_CLOSE_TAG,TOKEN_OPEN_TAG,TOKEN_PLACEHOLDER,TOKEN_SELF_TAG,Interpolate as default};
|
|
Binary file
|
|
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
|
|
3
3
|
import _classCallCheck from '@babel/runtime/helpers/classCallCheck';
|
|
4
4
|
import _createClass from '@babel/runtime/helpers/createClass';
|
|
5
5
|
import _objectWithoutProperties from '@babel/runtime/helpers/objectWithoutProperties';
|
|
6
|
-
import _defineProperty from '@babel/runtime/helpers/defineProperty';
|
|
7
6
|
|
|
8
7
|
var TOKEN_PLACEHOLDER = "TOKEN_PLACEHOLDER";
|
|
9
8
|
var TOKEN_OPEN_TAG = "TOKEN_OPEN_TAG";
|
|
@@ -16,11 +15,13 @@ var NODE_VOID_ELEMENT = "NODE_VOID_ELEMENT";
|
|
|
16
15
|
var NODE_PLACEHOLDER = "NODE_PLACEHOLDER";
|
|
17
16
|
var NODE_TEXT = "NODE_TEXT";
|
|
18
17
|
|
|
18
|
+
var _excluded = ["type", "children"];
|
|
19
|
+
|
|
19
20
|
var Node = /*#__PURE__*/function () {
|
|
20
21
|
function Node(_ref) {
|
|
21
22
|
var type = _ref.type,
|
|
22
23
|
children = _ref.children,
|
|
23
|
-
fields = _objectWithoutProperties(_ref,
|
|
24
|
+
fields = _objectWithoutProperties(_ref, _excluded);
|
|
24
25
|
|
|
25
26
|
_classCallCheck(this, Node);
|
|
26
27
|
|
|
@@ -91,29 +92,69 @@ Node.createPlaceholderNode = function (token) {
|
|
|
91
92
|
});
|
|
92
93
|
};
|
|
93
94
|
|
|
95
|
+
var SYNTAX_BUILT_IN = [{
|
|
96
|
+
type: TOKEN_PLACEHOLDER,
|
|
97
|
+
regex: /{\s*(\w+)\s*}/g
|
|
98
|
+
}, {
|
|
99
|
+
type: TOKEN_OPEN_TAG,
|
|
100
|
+
regex: /<(\w+)>/g
|
|
101
|
+
}, {
|
|
102
|
+
type: TOKEN_CLOSE_TAG,
|
|
103
|
+
regex: /<\/(\w+)>/g
|
|
104
|
+
}, {
|
|
105
|
+
type: TOKEN_SELF_TAG,
|
|
106
|
+
regex: /<(\w+)\s*\/>/g
|
|
107
|
+
}];
|
|
108
|
+
var SYNTAX_I18NEXT = [{
|
|
109
|
+
type: TOKEN_PLACEHOLDER,
|
|
110
|
+
regex: /{{\s*(\w+)\s*}}/g
|
|
111
|
+
}, {
|
|
112
|
+
type: TOKEN_OPEN_TAG,
|
|
113
|
+
regex: /<(\w+)>/g
|
|
114
|
+
}, {
|
|
115
|
+
type: TOKEN_CLOSE_TAG,
|
|
116
|
+
regex: /<\/(\w+)>/g
|
|
117
|
+
}, {
|
|
118
|
+
type: TOKEN_SELF_TAG,
|
|
119
|
+
regex: /<(\w+)\s*\/>/g
|
|
120
|
+
}];
|
|
121
|
+
|
|
122
|
+
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
|
|
123
|
+
|
|
124
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
125
|
+
|
|
126
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) { arr2[i] = arr[i]; } return arr2; }
|
|
94
127
|
/*
|
|
95
128
|
* return a array of token object
|
|
96
129
|
*/
|
|
97
130
|
|
|
98
|
-
function lexer(string) {
|
|
99
|
-
var _regxMap;
|
|
100
|
-
|
|
101
|
-
var regxMap = (_regxMap = {}, _defineProperty(_regxMap, TOKEN_PLACEHOLDER, /{\s*(\w+)\s*}/g), _defineProperty(_regxMap, TOKEN_OPEN_TAG, /<(\w+)>/g), _defineProperty(_regxMap, TOKEN_CLOSE_TAG, /<\/(\w+)>/g), _defineProperty(_regxMap, TOKEN_SELF_TAG, /<(\w+)\s*\/>/g), _regxMap);
|
|
131
|
+
function lexer(string, syntax) {
|
|
102
132
|
var matches = [];
|
|
103
133
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
134
|
+
var _iterator = _createForOfIteratorHelper(syntax),
|
|
135
|
+
_step;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
139
|
+
var rule = _step.value;
|
|
140
|
+
var type = rule.type,
|
|
141
|
+
regex = rule.regex;
|
|
142
|
+
var res;
|
|
143
|
+
|
|
144
|
+
while ((res = regex.exec(string)) !== null) {
|
|
145
|
+
matches.push({
|
|
146
|
+
type: type,
|
|
147
|
+
string: res[0],
|
|
148
|
+
name: res[1],
|
|
149
|
+
start: res.index,
|
|
150
|
+
end: res.index + res[0].length
|
|
151
|
+
});
|
|
152
|
+
}
|
|
116
153
|
}
|
|
154
|
+
} catch (err) {
|
|
155
|
+
_iterator.e(err);
|
|
156
|
+
} finally {
|
|
157
|
+
_iterator.f();
|
|
117
158
|
}
|
|
118
159
|
|
|
119
160
|
matches.sort(function (a, b) {
|
|
@@ -155,12 +196,16 @@ function lexer(string) {
|
|
|
155
196
|
return tokens;
|
|
156
197
|
}
|
|
157
198
|
|
|
158
|
-
function parser(string) {
|
|
159
|
-
|
|
199
|
+
function parser(string, syntax) {
|
|
200
|
+
if (!syntax) {
|
|
201
|
+
syntax = SYNTAX_BUILT_IN;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
var tokens = lexer(string, syntax);
|
|
160
205
|
var p = new Parser(tokens);
|
|
161
206
|
return p.parse();
|
|
162
207
|
}
|
|
163
|
-
var SYNTAX_ERROR = "
|
|
208
|
+
var SYNTAX_ERROR = "Syntax error. Please check if each open tag is closed correctly"; // A special token representing end of the tream
|
|
164
209
|
|
|
165
210
|
var EPSILON = {
|
|
166
211
|
type: "EPSILON"
|
|
@@ -300,6 +345,11 @@ var Parser = /*#__PURE__*/function () {
|
|
|
300
345
|
|
|
301
346
|
this.match(TOKEN_CLOSE_TAG);
|
|
302
347
|
}
|
|
348
|
+
}, {
|
|
349
|
+
key: "lookahead",
|
|
350
|
+
get: function get() {
|
|
351
|
+
return this.tokens.length === 0 ? EPSILON : this.tokens[0];
|
|
352
|
+
}
|
|
303
353
|
}, {
|
|
304
354
|
key: "predict",
|
|
305
355
|
value: function predict() {
|
|
@@ -324,11 +374,6 @@ var Parser = /*#__PURE__*/function () {
|
|
|
324
374
|
value: function pushTag(token) {
|
|
325
375
|
this.tags.push(token);
|
|
326
376
|
}
|
|
327
|
-
}, {
|
|
328
|
-
key: "lookahead",
|
|
329
|
-
get: function get() {
|
|
330
|
-
return this.tokens.length === 0 ? EPSILON : this.tokens[0];
|
|
331
|
-
}
|
|
332
377
|
}]);
|
|
333
378
|
|
|
334
379
|
return Parser;
|
|
@@ -349,7 +394,7 @@ var createElement = function createElement(node, mapping, keyPrefix) {
|
|
|
349
394
|
|
|
350
395
|
case NODE_FRAGMENT:
|
|
351
396
|
{
|
|
352
|
-
return React.createElement(React.Fragment, null, children);
|
|
397
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, children);
|
|
353
398
|
}
|
|
354
399
|
|
|
355
400
|
case NODE_VOID_ELEMENT:
|
|
@@ -360,22 +405,32 @@ var createElement = function createElement(node, mapping, keyPrefix) {
|
|
|
360
405
|
return val();
|
|
361
406
|
}
|
|
362
407
|
|
|
363
|
-
return val || React.createElement(node.name, null);
|
|
408
|
+
return val || /*#__PURE__*/React.createElement(node.name, null);
|
|
364
409
|
}
|
|
365
410
|
|
|
366
411
|
case NODE_TAG_ELEMENT:
|
|
367
412
|
{
|
|
368
413
|
var _val = mapping[node.name];
|
|
369
414
|
|
|
415
|
+
if (_val === undefined) {
|
|
416
|
+
return /*#__PURE__*/React.createElement(node.name, null, children);
|
|
417
|
+
}
|
|
418
|
+
|
|
370
419
|
if (typeof _val === "function") {
|
|
371
420
|
return _val(children);
|
|
372
421
|
}
|
|
373
422
|
|
|
374
|
-
if (_val
|
|
375
|
-
|
|
423
|
+
if ( /*#__PURE__*/React.isValidElement(_val)) {
|
|
424
|
+
if (React.Children.count(_val.props.children) !== 0) {
|
|
425
|
+
throw new Error("when passing an element as value, the element should not contains children");
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return /*#__PURE__*/React.cloneElement(_val, {
|
|
429
|
+
children: children
|
|
430
|
+
});
|
|
376
431
|
}
|
|
377
432
|
|
|
378
|
-
|
|
433
|
+
throw new Error("Invalid mapping value for \"".concat(node.name, "\". Only element or render function are accepted"));
|
|
379
434
|
}
|
|
380
435
|
|
|
381
436
|
case NODE_PLACEHOLDER:
|
|
@@ -401,13 +456,14 @@ var createElement = function createElement(node, mapping, keyPrefix) {
|
|
|
401
456
|
|
|
402
457
|
function Interpolate(_ref) {
|
|
403
458
|
var string = _ref.string,
|
|
459
|
+
syntax = _ref.syntax,
|
|
404
460
|
_ref$mapping = _ref.mapping,
|
|
405
461
|
mapping = _ref$mapping === void 0 ? {} : _ref$mapping,
|
|
406
462
|
_ref$graceful = _ref.graceful,
|
|
407
463
|
graceful = _ref$graceful === void 0 ? true : _ref$graceful;
|
|
408
464
|
|
|
409
465
|
try {
|
|
410
|
-
var tree = parser(string);
|
|
466
|
+
var tree = parser(string, syntax);
|
|
411
467
|
return createElement(tree, mapping, string);
|
|
412
468
|
} catch (e) {
|
|
413
469
|
if (graceful) {
|
|
@@ -424,4 +480,4 @@ Interpolate.propTypes = {
|
|
|
424
480
|
graceful: PropTypes.bool
|
|
425
481
|
};
|
|
426
482
|
|
|
427
|
-
export default
|
|
483
|
+
export { SYNTAX_BUILT_IN, SYNTAX_I18NEXT, TOKEN_CLOSE_TAG, TOKEN_OPEN_TAG, TOKEN_PLACEHOLDER, TOKEN_SELF_TAG, Interpolate as default };
|
package/jest.config.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doist/react-interpolate",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A string interpolation component that formats and interpolates a template string in a safe way",
|
|
5
5
|
"main": "dist/react-interpolate.cjs",
|
|
6
6
|
"module": "dist/react-interpolate.mjs",
|
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
"build": "del dist && rollup -c && npm run mini-cjs && npm run mini-mjs && npm run gzip",
|
|
11
11
|
"mini-cjs": "uglifyjs dist/react-interpolate.cjs --compress --mangle --enclose --output dist/react-interpolate.min.cjs",
|
|
12
12
|
"mini-mjs": "uglifyjs dist/react-interpolate.mjs --compress --mangle --enclose --output dist/react-interpolate.min.mjs",
|
|
13
|
-
"gzip": "gzip dist/**/*.min.*js --extension=gz"
|
|
13
|
+
"gzip": "gzip dist/**/*.min.*js --extension=gz",
|
|
14
|
+
"publish": "npm run build && npm publish",
|
|
15
|
+
"release": "git checkout master && git pull && release-it",
|
|
16
|
+
"release:auto": "git checkout master && git pull && release-it --ci"
|
|
14
17
|
},
|
|
15
18
|
"repository": {
|
|
16
19
|
"type": "git",
|
|
@@ -27,37 +30,38 @@
|
|
|
27
30
|
"author": "Steven Kao",
|
|
28
31
|
"license": "ISC",
|
|
29
32
|
"peerDependencies": {
|
|
30
|
-
"react": "^
|
|
31
|
-
"react-dom": "^
|
|
33
|
+
"react": "^17.0.2",
|
|
34
|
+
"react-dom": "^17.0.2"
|
|
32
35
|
},
|
|
33
36
|
"devDependencies": {
|
|
34
|
-
"@babel/cli": "^7.
|
|
35
|
-
"@babel/core": "^7.
|
|
36
|
-
"@babel/
|
|
37
|
-
"@babel/
|
|
38
|
-
"@babel/preset-
|
|
39
|
-
"@
|
|
40
|
-
"
|
|
41
|
-
"babel-jest": "^
|
|
42
|
-
"core-js": "^3.
|
|
43
|
-
"del-cli": "^
|
|
44
|
-
"eslint": "^
|
|
45
|
-
"eslint-config-prettier": "^
|
|
46
|
-
"eslint-plugin-compat": "^
|
|
47
|
-
"eslint-plugin-jest": "^
|
|
48
|
-
"eslint-plugin-prettier": "^
|
|
49
|
-
"eslint-plugin-react": "^7.
|
|
50
|
-
"eslint-plugin-react-hooks": "^3.0
|
|
51
|
-
"gzip-cli": "^1.
|
|
52
|
-
"jest": "^
|
|
53
|
-
"prettier": "^2.
|
|
54
|
-
"react": "^
|
|
55
|
-
"react-dom": "^
|
|
56
|
-
"
|
|
37
|
+
"@babel/cli": "^7.16.0",
|
|
38
|
+
"@babel/core": "^7.16.0",
|
|
39
|
+
"@babel/eslint-parser": "^7.16.3",
|
|
40
|
+
"@babel/plugin-transform-runtime": "^7.16.4",
|
|
41
|
+
"@babel/preset-env": "^7.16.4",
|
|
42
|
+
"@babel/preset-react": "^7.16.0",
|
|
43
|
+
"@testing-library/react": "^12.1.2",
|
|
44
|
+
"babel-jest": "^27.3.1",
|
|
45
|
+
"core-js": "^3.19.1",
|
|
46
|
+
"del-cli": "^4.0.1",
|
|
47
|
+
"eslint": "^8.2.0",
|
|
48
|
+
"eslint-config-prettier": "^8.3.0",
|
|
49
|
+
"eslint-plugin-compat": "^4.0.0",
|
|
50
|
+
"eslint-plugin-jest": "^25.2.4",
|
|
51
|
+
"eslint-plugin-prettier": "^4.0.0",
|
|
52
|
+
"eslint-plugin-react": "^7.27.0",
|
|
53
|
+
"eslint-plugin-react-hooks": "^4.3.0",
|
|
54
|
+
"gzip-cli": "^1.2.0",
|
|
55
|
+
"jest": "^27.3.1",
|
|
56
|
+
"prettier": "^2.4.1",
|
|
57
|
+
"react": "^17.0.2",
|
|
58
|
+
"react-dom": "^17.0.2",
|
|
59
|
+
"release-it": "^14.11.7",
|
|
60
|
+
"rollup": "^2.60.0",
|
|
57
61
|
"rollup-plugin-babel": "^4.4.0",
|
|
58
62
|
"uglify-es": "^3.3.9"
|
|
59
63
|
},
|
|
60
64
|
"dependencies": {
|
|
61
|
-
"@babel/runtime": "^7.
|
|
65
|
+
"@babel/runtime": "^7.16.3"
|
|
62
66
|
}
|
|
63
67
|
}
|
package/src/index.js
CHANGED
package/src/interpolate.js
CHANGED
|
@@ -33,17 +33,28 @@ const createElement = (node, mapping, keyPrefix) => {
|
|
|
33
33
|
}
|
|
34
34
|
case NODE_TAG_ELEMENT: {
|
|
35
35
|
const val = mapping[node.name]
|
|
36
|
+
|
|
37
|
+
if (val === undefined) {
|
|
38
|
+
return React.createElement(node.name, null, children)
|
|
39
|
+
}
|
|
40
|
+
|
|
36
41
|
if (typeof val === "function") {
|
|
37
42
|
return val(children)
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
if (val
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
45
|
+
if (React.isValidElement(val)) {
|
|
46
|
+
if (React.Children.count(val.props.children) !== 0) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
"when passing an element as value, the element should not contains children"
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return React.cloneElement(val, { children })
|
|
44
53
|
}
|
|
45
54
|
|
|
46
|
-
|
|
55
|
+
throw new Error(
|
|
56
|
+
`Invalid mapping value for "${node.name}". Only element or render function are accepted`
|
|
57
|
+
)
|
|
47
58
|
}
|
|
48
59
|
case NODE_PLACEHOLDER: {
|
|
49
60
|
const val = mapping[node.name]
|
|
@@ -63,9 +74,14 @@ const createElement = (node, mapping, keyPrefix) => {
|
|
|
63
74
|
}
|
|
64
75
|
}
|
|
65
76
|
|
|
66
|
-
export default function Interpolate({
|
|
77
|
+
export default function Interpolate({
|
|
78
|
+
string,
|
|
79
|
+
syntax,
|
|
80
|
+
mapping = {},
|
|
81
|
+
graceful = true
|
|
82
|
+
}) {
|
|
67
83
|
try {
|
|
68
|
-
const tree = parser(string)
|
|
84
|
+
const tree = parser(string, syntax)
|
|
69
85
|
return createElement(tree, mapping, string)
|
|
70
86
|
} catch (e) {
|
|
71
87
|
if (graceful) {
|
package/src/lexer.js
CHANGED
|
@@ -1,31 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
TOKEN_OPEN_TAG,
|
|
4
|
-
TOKEN_CLOSE_TAG,
|
|
5
|
-
TOKEN_SELF_TAG,
|
|
6
|
-
TOKEN_TEXT
|
|
7
|
-
} from "./constants"
|
|
1
|
+
import { TOKEN_TEXT } from "./constants"
|
|
2
|
+
export { SYNTAX_I18NEXT, SYNTAX_BUILT_IN } from "./syntax"
|
|
8
3
|
|
|
9
4
|
/*
|
|
10
5
|
* return a array of token object
|
|
11
6
|
*/
|
|
12
|
-
export default function lexer(string) {
|
|
13
|
-
const regxMap = {
|
|
14
|
-
[TOKEN_PLACEHOLDER]: /{\s*(\w+)\s*}/g,
|
|
15
|
-
[TOKEN_OPEN_TAG]: /<(\w+)>/g,
|
|
16
|
-
[TOKEN_CLOSE_TAG]: /<\/(\w+)>/g,
|
|
17
|
-
[TOKEN_SELF_TAG]: /<(\w+)\s*\/>/g
|
|
18
|
-
}
|
|
19
|
-
|
|
7
|
+
export default function lexer(string, syntax) {
|
|
20
8
|
const matches = []
|
|
21
9
|
|
|
22
|
-
for (const
|
|
23
|
-
const
|
|
10
|
+
for (const rule of syntax) {
|
|
11
|
+
const { type, regex } = rule
|
|
24
12
|
|
|
25
13
|
var res
|
|
26
|
-
while ((res =
|
|
14
|
+
while ((res = regex.exec(string)) !== null) {
|
|
27
15
|
matches.push({
|
|
28
|
-
type
|
|
16
|
+
type,
|
|
29
17
|
string: res[0],
|
|
30
18
|
name: res[1],
|
|
31
19
|
start: res.index,
|
package/src/parser.js
CHANGED
|
@@ -7,14 +7,20 @@ import {
|
|
|
7
7
|
} from "./constants"
|
|
8
8
|
import Node from "./node.js"
|
|
9
9
|
import lexer from "./lexer"
|
|
10
|
+
import { SYNTAX_BUILT_IN } from "./syntax"
|
|
10
11
|
|
|
11
|
-
export default function parser(string) {
|
|
12
|
-
|
|
12
|
+
export default function parser(string, syntax) {
|
|
13
|
+
if (!syntax) {
|
|
14
|
+
syntax = SYNTAX_BUILT_IN
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const tokens = lexer(string, syntax)
|
|
13
18
|
const p = new Parser(tokens)
|
|
14
19
|
return p.parse()
|
|
15
20
|
}
|
|
16
21
|
|
|
17
|
-
const SYNTAX_ERROR =
|
|
22
|
+
const SYNTAX_ERROR =
|
|
23
|
+
"Syntax error. Please check if each open tag is closed correctly"
|
|
18
24
|
|
|
19
25
|
// A special token representing end of the tream
|
|
20
26
|
const EPSILON = {
|
package/src/syntax.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TOKEN_PLACEHOLDER,
|
|
3
|
+
TOKEN_OPEN_TAG,
|
|
4
|
+
TOKEN_CLOSE_TAG,
|
|
5
|
+
TOKEN_SELF_TAG
|
|
6
|
+
} from "./constants"
|
|
7
|
+
|
|
8
|
+
export const SYNTAX_BUILT_IN = [
|
|
9
|
+
{
|
|
10
|
+
type: TOKEN_PLACEHOLDER,
|
|
11
|
+
regex: /{\s*(\w+)\s*}/g
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
type: TOKEN_OPEN_TAG,
|
|
15
|
+
regex: /<(\w+)>/g
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
type: TOKEN_CLOSE_TAG,
|
|
19
|
+
regex: /<\/(\w+)>/g
|
|
20
|
+
},
|
|
21
|
+
{ type: TOKEN_SELF_TAG, regex: /<(\w+)\s*\/>/g }
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
export const SYNTAX_I18NEXT = [
|
|
25
|
+
{
|
|
26
|
+
type: TOKEN_PLACEHOLDER,
|
|
27
|
+
regex: /{{\s*(\w+)\s*}}/g
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: TOKEN_OPEN_TAG,
|
|
31
|
+
regex: /<(\w+)>/g
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
type: TOKEN_CLOSE_TAG,
|
|
35
|
+
regex: /<\/(\w+)>/g
|
|
36
|
+
},
|
|
37
|
+
{ type: TOKEN_SELF_TAG, regex: /<(\w+)\s*\/>/g }
|
|
38
|
+
]
|