@datagrok/sequence-translator 0.0.3 → 0.0.6

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/src/package.ts CHANGED
@@ -3,182 +3,435 @@ import * as grok from 'datagrok-api/grok';
3
3
  import * as ui from 'datagrok-api/ui';
4
4
  import * as DG from 'datagrok-api/dg';
5
5
  import * as OCL from 'openchemlib/full.js';
6
- import $ from "cash-dom";
7
- import {defineAxolabsPattern} from "./defineAxolabsPattern";
8
- import {map, stadardPhosphateLinkSMILES} from "./map";
6
+ import $ from 'cash-dom';
7
+ import {defineAxolabsPattern} from './defineAxolabsPattern';
8
+ import {saveSenseAntiSense} from './save-sense-antisense';
9
+ import {map, stadardPhosphateLinkSmiles, SYNTHESIZERS, TECHNOLOGIES, MODIFICATIONS} from './map';
9
10
 
10
- export let _package = new DG.Package();
11
+ export const _package = new DG.Package();
11
12
 
12
- const defaultInput = "AGGTCCTCTTGACTTAGGCC";
13
- const minimalValidNumberOfCharacters = 6;
14
- const smallNumberOfCharacters = "Length of input sequence should be at least " + minimalValidNumberOfCharacters + " characters";
15
- const undefinedInputSequence = "Type of input sequence is undefined";
16
- const noTranslationTableAvailable = "No translation table available";
13
+ const defaultInput = 'AGGTCCTCTTGACTTAGGCC';
14
+ const undefinedInputSequence = 'Type of input sequence is undefined';
15
+ const noTranslationTableAvailable = 'No translation table available';
17
16
  const sequenceWasCopied = 'Copied';
18
17
  const tooltipSequence = 'Copy sequence';
19
18
 
20
- function sortByStringLengthInDescendingOrderToCheckForMatchWithLongerCodesFirst(array: string[]): string[] {
21
- return array.sort(function(a, b) { return b.length - a.length; });
19
+ function getAllCodesOfSynthesizer(synthesizer: string): string[] {
20
+ let codes: string[] = [];
21
+ for (const technology of Object.keys(map[synthesizer]))
22
+ codes = codes.concat(Object.keys(map[synthesizer][technology]));
23
+ return codes.concat(Object.keys(MODIFICATIONS));
22
24
  }
23
25
 
24
- function getObjectWithCodesAndSmiles() {
25
- let obj: {[code: string]: string} = {};
26
- for (let synthesizer of Object.keys(map))
27
- for (let technology of Object.keys(map[synthesizer]))
26
+ function getListOfPossibleSynthesizersByFirstMatchedCode(sequence: string): string[] {
27
+ const synthesizers: string[] = [];
28
+ Object.keys(map).forEach((synthesizer: string) => {
29
+ const codes = getAllCodesOfSynthesizer(synthesizer);
30
+ //TODO: get first non-dropdown code when there are two modifications
31
+ let start = 0;
32
+ for (let i = 0; i < sequence.length; i++) {
33
+ if (sequence[i] == ')') {
34
+ start = i + 1;
35
+ break;
36
+ }
37
+ }
38
+ if (codes.some((s: string) => s == sequence.slice(start, start + s.length)))
39
+ synthesizers.push(synthesizer);
40
+ });
41
+ return synthesizers;
42
+ }
43
+
44
+ function getListOfPossibleTechnologiesByFirstMatchedCode(sequence: string, synthesizer: string): string[] {
45
+ const technologies: string[] = [];
46
+ Object.keys(map[synthesizer]).forEach((technology: string) => {
47
+ const codes = Object.keys(map[synthesizer][technology]).concat(Object.keys(MODIFICATIONS));
48
+ if (codes.some((s) => s == sequence.slice(0, s.length)))
49
+ technologies.push(technology);
50
+ });
51
+ return technologies;
52
+ }
53
+
54
+ export function isValidSequence(sequence: string): {
55
+ indexOfFirstNotValidCharacter: number,
56
+ expectedSynthesizer: string | null,
57
+ expectedTechnology: string | null
58
+ } {
59
+ const possibleSynthesizers = getListOfPossibleSynthesizersByFirstMatchedCode(sequence);
60
+ if (possibleSynthesizers.length == 0)
61
+ return {indexOfFirstNotValidCharacter: 0, expectedSynthesizer: null, expectedTechnology: null};
62
+
63
+ let outputIndices = Array(possibleSynthesizers.length).fill(0);
64
+
65
+ const firstUniqueCharacters = ['r', 'd'];
66
+ const nucleotides = ['A', 'U', 'T', 'C', 'G'];
67
+
68
+ possibleSynthesizers.forEach((synthesizer, synthesizerIndex) => {
69
+ const codes = getAllCodesOfSynthesizer(synthesizer);
70
+ while (outputIndices[synthesizerIndex] < sequence.length) {
71
+ const matchedCode = codes
72
+ .find((c) => c == sequence.slice(outputIndices[synthesizerIndex], outputIndices[synthesizerIndex] + c.length));
73
+
74
+ if (matchedCode == null)
75
+ break;
76
+
77
+ if ( // for mistake pattern 'rAA'
78
+ outputIndices[synthesizerIndex] > 1 &&
79
+ nucleotides.includes(sequence[outputIndices[synthesizerIndex]]) &&
80
+ firstUniqueCharacters.includes(sequence[outputIndices[synthesizerIndex] - 2])
81
+ ) break;
82
+
83
+ if ( // for mistake pattern 'ArA'
84
+ firstUniqueCharacters.includes(sequence[outputIndices[synthesizerIndex] + 1]) &&
85
+ nucleotides.includes(sequence[outputIndices[synthesizerIndex]])
86
+ ) {
87
+ outputIndices[synthesizerIndex]++;
88
+ break;
89
+ }
90
+
91
+ outputIndices[synthesizerIndex] += matchedCode.length;
92
+ }
93
+ });
94
+
95
+ const indexOfExpectedSythesizer = Math.max.apply(Math, outputIndices);
96
+ const indexOfFirstNotValidCharacter = (indexOfExpectedSythesizer == sequence.length) ? -1 : indexOfExpectedSythesizer;
97
+ const expectedSynthesizer = possibleSynthesizers[outputIndices.indexOf(indexOfExpectedSythesizer)];
98
+ if (indexOfFirstNotValidCharacter != -1)
99
+ return {
100
+ indexOfFirstNotValidCharacter: indexOfFirstNotValidCharacter,
101
+ expectedSynthesizer: expectedSynthesizer,
102
+ expectedTechnology: null
103
+ };
104
+
105
+ let possibleTechnologies = getListOfPossibleTechnologiesByFirstMatchedCode(sequence, expectedSynthesizer);
106
+ if (possibleTechnologies.length == 0)
107
+ return { indexOfFirstNotValidCharacter: 0, expectedSynthesizer: null, expectedTechnology: null };
108
+
109
+ outputIndices = Array(possibleTechnologies.length).fill(0);
110
+
111
+ possibleTechnologies.forEach((technology: string, technologyIndex: number) => {
112
+ let codes = Object.keys(map[expectedSynthesizer][technology]);
113
+ while (outputIndices[technologyIndex] < sequence.length) {
114
+
115
+ let matchedCode = codes
116
+ .find((c) => c == sequence.slice(outputIndices[technologyIndex], outputIndices[technologyIndex] + c.length));
117
+
118
+ if (matchedCode == null)
119
+ break;
120
+
121
+ if ( // for mistake pattern 'rAA'
122
+ outputIndices[technologyIndex] > 1 &&
123
+ nucleotides.includes(sequence[outputIndices[technologyIndex]]) &&
124
+ firstUniqueCharacters.includes(sequence[outputIndices[technologyIndex] - 2])
125
+ ) break;
126
+
127
+ if ( // for mistake pattern 'ArA'
128
+ firstUniqueCharacters.includes(sequence[outputIndices[technologyIndex] + 1]) &&
129
+ nucleotides.includes(sequence[outputIndices[technologyIndex]])
130
+ ) {
131
+ outputIndices[technologyIndex]++;
132
+ break;
133
+ }
134
+
135
+ outputIndices[technologyIndex] += matchedCode.length;
136
+ }
137
+ });
138
+
139
+ const indexOfExpectedTechnology = Math.max.apply(Math, outputIndices);
140
+ const expectedTechnology = possibleTechnologies[outputIndices.indexOf(indexOfExpectedTechnology)];
141
+
142
+ return {
143
+ indexOfFirstNotValidCharacter: indexOfFirstNotValidCharacter,
144
+ expectedSynthesizer: expectedSynthesizer,
145
+ expectedTechnology: expectedTechnology
146
+ };
147
+ }
148
+
149
+ function sortByStringLengthInDescendingOrder(array: string[]): string[] {
150
+ return array.sort(function(a: string, b: string) { return b.length - a.length; });
151
+ }
152
+
153
+ function getObjectWithCodesAndSmiles(sequence: string) {
154
+ const obj: { [code: string]: string } = {};
155
+ for (const synthesizer of Object.keys(map))
156
+ for (const technology of Object.keys(map[synthesizer]))
28
157
  for (let code of Object.keys(map[synthesizer][technology]))
29
158
  obj[code] = map[synthesizer][technology][code].SMILES;
159
+ // TODO: create object based from synthesizer type to avoid key(codes) duplicates
160
+ const output = isValidSequence(sequence);
161
+ if (output.expectedSynthesizer == SYNTHESIZERS.MERMADE_12)
162
+ obj['g'] = map[SYNTHESIZERS.MERMADE_12][TECHNOLOGIES.SI_RNA]['g'].SMILES;
163
+ else if (output.expectedSynthesizer == SYNTHESIZERS.AXOLABS)
164
+ obj['g'] = map[SYNTHESIZERS.AXOLABS][TECHNOLOGIES.SI_RNA]['g'].SMILES;
30
165
  return obj;
31
166
  }
32
167
 
33
- function modifiedToSmiles(sequence: string) {
34
- const obj = getObjectWithCodesAndSmiles();
35
- const codes = sortByStringLengthInDescendingOrderToCheckForMatchWithLongerCodesFirst(Object.keys(obj));
36
- let i = 0, smiles = '', codesList = [];
168
+ export function sequenceToSmiles(sequence: string, inverted: boolean = false): string {
169
+ const obj = getObjectWithCodesAndSmiles(sequence);
170
+ let codes = sortByStringLengthInDescendingOrder(Object.keys(obj));
171
+ let i = 0;
172
+ let smiles = '';
173
+ const codesList = [];
37
174
  const links = ['s', 'ps', '*'];
175
+ const includesStandardLinkAlready = ['e', 'h', /*'g',*/ 'f', 'i', 'l', 'k', 'j'];
176
+ const dropdowns = Object.keys(MODIFICATIONS);
177
+ codes = codes.concat(dropdowns);
38
178
  while (i < sequence.length) {
39
- let code = codes.find((s) => s == sequence.slice(i, i + s.length))!;
179
+ const code = codes.find((s: string) => s == sequence.slice(i, i + s.length))!;
40
180
  i += code.length;
41
- codesList.push(code);
181
+ inverted ? codesList.unshift(code) : codesList.push(code);
42
182
  }
43
- for (let i = 0; i < codesList.length; i++)
44
- smiles += (links.includes(codesList[i]) || (i < codesList.length - 1 && links.includes(codesList[i+1]))) ?
45
- obj[codesList[i]] :
46
- obj[codesList[i]] + stadardPhosphateLinkSMILES;
47
- smiles = smiles.replace(/OO/g, 'O').replace(/SO/g, 'S');
48
- return codesList[codesList.length - 1] == 'ps' ? smiles : smiles.slice(0, smiles.length - stadardPhosphateLinkSMILES.length + 1);
183
+ for (let i = 0; i < codesList.length; i++) {
184
+ if (dropdowns.includes(codesList[i])) {
185
+ if (i == codesList.length -1 || (i < codesList.length - 1 && links.includes(codesList[i + 1]))) {
186
+ smiles += (i >= codesList.length / 2) ?
187
+ MODIFICATIONS[codesList[i]].right:
188
+ MODIFICATIONS[codesList[i]].left;
189
+ } else if (i < codesList.length - 1) {
190
+ smiles += (i >= codesList.length / 2) ?
191
+ MODIFICATIONS[codesList[i]].right + stadardPhosphateLinkSmiles:
192
+ MODIFICATIONS[codesList[i]].left + stadardPhosphateLinkSmiles;
193
+ }
194
+ } else {
195
+ if (links.includes(codesList[i]) ||
196
+ includesStandardLinkAlready.includes(codesList[i]) ||
197
+ (i < codesList.length - 1 && links.includes(codesList[i + 1]))
198
+ )
199
+ smiles += obj[codesList[i]];
200
+ else
201
+ smiles += obj[codesList[i]] + stadardPhosphateLinkSmiles;
202
+ }
203
+ }
204
+ smiles = smiles.replace(/OO/g, 'O');
205
+ return (
206
+ (
207
+ links.includes(codesList[codesList.length - 1]) &&
208
+ codesList.length > 1 &&
209
+ !includesStandardLinkAlready.includes(codesList[codesList.length - 2])
210
+ ) ||
211
+ dropdowns.includes(codesList[codesList.length - 1]) ||
212
+ includesStandardLinkAlready.includes(codesList[codesList.length - 1])
213
+ ) ?
214
+ smiles :
215
+ smiles.slice(0, smiles.length - stadardPhosphateLinkSmiles.length + 1);
49
216
  }
50
217
 
51
218
  //name: Sequence Translator
52
219
  //tags: app
53
- export function sequenceTranslator() {
54
-
55
- let windows = grok.shell.windows;
220
+ export function sequenceTranslator(): void {
221
+ const windows = grok.shell.windows;
56
222
  windows.showProperties = false;
57
223
  windows.showToolbox = false;
58
224
  windows.showHelp = false;
59
225
 
60
- function updateTableAndSVG(sequence: string) {
61
- moleculeSvgDiv.innerHTML = "";
62
- outputTableDiv.innerHTML = "";
63
- let outputSequenceObj = convertSequence(sequence);
64
- let tableRows = [];
65
- for (let key of Object.keys(outputSequenceObj).slice(1)) {
66
- //@ts-ignore
67
- tableRows.push({'key': key, 'value': ui.link(outputSequenceObj[key], () => navigator.clipboard.writeText(outputSequenceObj[key]).then(() => grok.shell.info(sequenceWasCopied)), tooltipSequence, '')})
68
- }
69
- outputTableDiv.append(
70
- ui.div([
71
- DG.HtmlTable.create(
72
- tableRows, (item: { key: string; value: string; }) => [item.key, item.value], ['Code', 'Sequence']
73
- ).root
74
- ], 'table')
75
- );
76
- semTypeOfInputSequence.textContent = 'Detected input type: ' + outputSequenceObj.type;
77
- if (!(outputSequenceObj.type == undefinedInputSequence || outputSequenceObj.type == smallNumberOfCharacters)) {
78
- let pi = DG.TaskBarProgressIndicator.create('Rendering molecule...');
79
- try {
80
- let flavor: string = (outputSequenceObj.Nucleotides.includes('U')) ? "RNA_both_caps" : "DNA_both_caps";
81
- (async () => {
82
- let smiles = (/^[ATGCU]{6,}$/.test(inputSequenceField.value.replace(/\s/g, ''))) ?
83
- await nucleotidesToSmiles(outputSequenceObj.Nucleotides, flavor) :
84
- modifiedToSmiles(inputSequenceField.value.replace(/\s/g, ''));
85
- smiles = smiles.replace(/@/g, ''); // Remove StereoChemistry on the Nucleic acid chain and remove the Chiral label
86
- moleculeSvgDiv.append(grok.chem.svgMol(smiles, 900, 300));
87
- })();
88
- } finally {
89
- pi.close();
226
+ function updateTableAndMolecule(sequence: string): void {
227
+ moleculeSvgDiv.innerHTML = '';
228
+ outputTableDiv.innerHTML = '';
229
+ const pi = DG.TaskBarProgressIndicator.create('Rendering table and molecule...');
230
+ let errorsExist = false;
231
+ try {
232
+ const outputSequenceObj = convertSequence(sequence);
233
+ const tableRows = [];
234
+
235
+ for (const key of Object.keys(outputSequenceObj).slice(1)) {
236
+ let indexOfFirstNotValidCharacter = ('indexOfFirstNotValidCharacter' in outputSequenceObj) ?
237
+ JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!).indexOfFirstNotValidCharacter :
238
+ -1;
239
+ if ('indexOfFirstNotValidCharacter' in outputSequenceObj) {
240
+ let indexOfFirstNotValidCharacter = ('indexOfFirstNotValidCharacter' in outputSequenceObj) ?
241
+ JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!).indexOfFirstNotValidCharacter :
242
+ -1;
243
+ if (indexOfFirstNotValidCharacter != -1)
244
+ errorsExist = true;
245
+ }
246
+
247
+ tableRows.push({
248
+ 'key': key,
249
+ 'value': ('indexOfFirstNotValidCharacter' in outputSequenceObj) ?
250
+ ui.divH([
251
+ ui.divText(sequence.slice(0, indexOfFirstNotValidCharacter), {style: {color: 'grey'}}),
252
+ ui.tooltip.bind(
253
+ ui.divText(sequence.slice(indexOfFirstNotValidCharacter), {style: {color: 'red'}}),
254
+ 'Expected format: ' + JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!).expectedSynthesizer + '. See tables with valid codes on the right'
255
+ ),
256
+ ]) : //@ts-ignore
257
+ ui.link(outputSequenceObj[key], () => navigator.clipboard.writeText(outputSequenceObj[key]).then(() => grok.shell.info(sequenceWasCopied)), tooltipSequence, '')
258
+ });
259
+ }
260
+
261
+ if (errorsExist) {
262
+ const expectedSynthesizer = JSON.parse(outputSequenceObj.indexOfFirstNotValidCharacter!).expectedSynthesizer.slice(0, -6);
263
+ asoGapmersGrid.onCellPrepare(function (gc) {
264
+ gc.style.backColor = (gc.gridColumn.name == expectedSynthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
265
+ });
266
+ omeAndFluoroGrid.onCellPrepare(function (gc) {
267
+ gc.style.backColor = (gc.gridColumn.name == expectedSynthesizer) ? 0xFFF00000 : 0xFFFFFFFF;
268
+ });
269
+ switchInput.enabled = true;
270
+ } else {
271
+ asoGapmersGrid.onCellPrepare(function (gc) {
272
+ gc.style.backColor = 0xFFFFFFFF;
273
+ });
274
+ omeAndFluoroGrid.onCellPrepare(function (gc) {
275
+ gc.style.backColor = 0xFFFFFFFF;
276
+ });
90
277
  }
278
+
279
+ outputTableDiv.append(
280
+ ui.div([
281
+ DG.HtmlTable.create(tableRows, (item: { key: string; value: string; }) => [item.key, item.value], ['Code', 'Sequence']).root
282
+ ], 'table')
283
+ );
284
+ semTypeOfInputSequence.textContent = 'Detected input type: ' + outputSequenceObj.type;
285
+
286
+ if (outputSequenceObj.type != undefinedInputSequence && outputSequenceObj.Error != undefinedInputSequence) {
287
+ let canvas = ui.canvas(300, 170);
288
+ canvas.addEventListener("click", () => {
289
+ let canv = ui.canvas($(window).width(), $(window).height());
290
+ const smiles = sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''));
291
+ // @ts-ignore
292
+ OCL.StructureView.drawMolecule(canv, OCL.Molecule.fromSmiles(smiles), { suppressChiralText: true });
293
+ ui.dialog('Molecule')
294
+ .add(canv)
295
+ .showModal(true);
296
+ });
297
+ $(canvas).on('mouseover', () => $(canvas).css('cursor', 'zoom-in'));
298
+ $(canvas).on('mouseout', () => $(canvas).css('cursor', 'default'));
299
+ const smiles = sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''));
300
+ // @ts-ignore
301
+ OCL.StructureView.drawMolecule(canvas, OCL.Molecule.fromSmiles(smiles), { suppressChiralText: true });
302
+ moleculeSvgDiv.append(canvas);
303
+ } else
304
+ moleculeSvgDiv.innerHTML = '';
305
+ } finally {
306
+ pi.close();
91
307
  }
92
308
  }
93
309
 
94
- const appMainDescription = ui.info([
95
- ui.divText('\n How to convert one sequence:',{style:{'font-weight':'bolder'}}),
96
- ui.divText("Paste sequence into the text field below"),
97
- ui.divText('\n How to convert many sequences:',{style:{'font-weight':'bolder'}}),
98
- ui.divText("1. Drag & drop an Excel or CSV file with sequences into Datagrok. The platform will automatically detect columns with sequences"),
99
- ui.divText('2. Right-click on the column header, then see the \'Convert\' menu'),
100
- ui.divText("This will add the result column to the right of the table"),
101
- ], 'Convert oligonucleotide sequences between Nucleotides, BioSpring, Axolabs, and GCRS representations.'
310
+ const semTypeOfInputSequence = ui.divText('');
311
+ const moleculeSvgDiv = ui.block([]);
312
+ const outputTableDiv = ui.div([], 'table');
313
+ const inputSequenceField = ui.textInput('', defaultInput, (sequence: string) => updateTableAndMolecule(sequence));
314
+
315
+ const asoDf = DG.DataFrame.fromObjects([
316
+ { Name: "2'MOE-5Me-rU", BioSpring: "5", "Janssen GCRS": "moeT" },
317
+ { Name: "2'MOE-rA", BioSpring: "6", "Janssen GCRS": "moeA" },
318
+ { Name: "2'MOE-5Me-rC", BioSpring: "7", "Janssen GCRS": "moe5mC" },
319
+ { Name: "2'MOE-rG", BioSpring: "8", "Janssen GCRS": "moeG" },
320
+ { Name: "5-Methyl-dC", BioSpring: "9", "Janssen GCRS": "5mC" },
321
+ { Name: "ps linkage", BioSpring: "*", "Janssen GCRS": "ps" },
322
+ { Name: "dA", BioSpring: "A", "Janssen GCRS": "A, dA" },
323
+ { Name: "dC", BioSpring: "C", "Janssen GCRS": "C, dC" },
324
+ { Name: "dG", BioSpring: "G", "Janssen GCRS": "G, dG" },
325
+ { Name: "dT", BioSpring: "T", "Janssen GCRS": "T, dT" },
326
+ { Name: "rA", BioSpring: "", "Janssen GCRS": "rA" },
327
+ { Name: "rC", BioSpring: "", "Janssen GCRS": "rC" },
328
+ { Name: "rG", BioSpring: "", "Janssen GCRS": "rG" },
329
+ { Name: "rU", BioSpring: "", "Janssen GCRS": "rU" }
330
+ ])!;
331
+ const asoGapmersGrid = DG.Viewer.grid(
332
+ asoDf, { showRowHeader: false, showCellTooltip: false }
102
333
  );
103
334
 
104
- let inputSequenceField = ui.textInput("", defaultInput, (sequence: string) => updateTableAndSVG(sequence));
105
- let outputSequenceObj = convertSequence(defaultInput);
106
- let semTypeOfInputSequence = ui.divText('Detected input type: ' + outputSequenceObj.type);
335
+ asoDf.onCurrentCellChanged.subscribe((_) => {
336
+ navigator.clipboard.writeText(asoDf.currentCell.value).then(() => grok.shell.info('Copied'))
337
+ });
107
338
 
108
- let tableRows = [];
109
- for (let key of Object.keys(outputSequenceObj).slice(1)) {
110
- //@ts-ignore
111
- tableRows.push({'key': key, 'value': ui.link(outputSequenceObj[key], () => navigator.clipboard.writeText(outputSequenceObj[key]).then(() => grok.shell.info(sequenceWasCopied)), tooltipSequence, '')})
112
- }
113
- let outputTableDiv = ui.div([], 'table');
114
- outputTableDiv.append(
115
- DG.HtmlTable.create(tableRows, (item: {key: string; value: string;}) => [item.key, item.value], ['Code', 'Sequence']).root
339
+ let omeAndFluoroGrid = DG.Viewer.grid(
340
+ DG.DataFrame.fromObjects([
341
+ { Name: "2'-fluoro-U", BioSpring: "1", Axolabs: "Uf", "Janssen GCRS": "fU" },
342
+ { Name: "2'-fluoro-A", BioSpring: "2", Axolabs: "Af", "Janssen GCRS": "fA" },
343
+ { Name: "2'-fluoro-C", BioSpring: "3", Axolabs: "Cf", "Janssen GCRS": "fC" },
344
+ { Name: "2'-fluoro-G", BioSpring: "4", Axolabs: "Gf", "Janssen GCRS": "fG" },
345
+ { Name: "2'OMe-rU", BioSpring: "5", Axolabs: "u", "Janssen GCRS": "mU" },
346
+ { Name: "2'OMe-rA", BioSpring: "6", Axolabs: "a", "Janssen GCRS": "mA" },
347
+ { Name: "2'OMe-rC", BioSpring: "7", Axolabs: "c", "Janssen GCRS": "mC" },
348
+ { Name: "2'OMe-rG", BioSpring: "8", Axolabs: "g", "Janssen GCRS": "mG" },
349
+ { Name: "ps linkage", BioSpring: "*", Axolabs: "s", "Janssen GCRS": "ps" }
350
+ ])!, { showRowHeader: false, showCellTooltip: false }
116
351
  );
117
352
 
118
- let tables = ui.divV([]);
119
- for (let synthesizer of Object.keys(map)) {
120
- for (let technology of Object.keys(map[synthesizer])) {
121
- let tableRows = [];
122
- for (let [key, value] of Object.entries(map[synthesizer][technology]))
123
- tableRows.push({'name': value.name, 'code': key});
124
- tables.append(
125
- DG.HtmlTable.create(
126
- tableRows,
127
- (item: {name: string; code: string;}) => [item['name'], item['code']],
128
- [synthesizer + ' ' + technology, 'Code']
129
- ).root,
130
- ui.div([], {style: {height: '30px'}})
131
- );
132
- }
133
- }
353
+ const overhangModificationsGrid = DG.Viewer.grid(
354
+ DG.DataFrame.fromObjects([
355
+ { Name: "(invabasic)" },
356
+ { Name: "(GalNAc-2-JNJ)" }
357
+ ])!, { showRowHeader: false, showCellTooltip: false }
358
+ );
359
+ updateTableAndMolecule(defaultInput);
134
360
 
135
- let showCodesButton = ui.button('SHOW CODES', () => ui.dialog('Codes').add(tables).show());
136
-
137
- let moleculeSvgDiv = ui.block([]);
138
-
139
- let flavor: string = (defaultInput.includes('U')) ? "RNA_both_caps" : "DNA_both_caps";
140
- (async () => moleculeSvgDiv.append(grok.chem.svgMol(<string> await nucleotidesToSmiles(defaultInput, flavor), 900, 300)))();
141
-
142
- let saveMolFileButton = ui.bigButton('SAVE MOL FILE', async() => {
143
- let outputSequenceObj = convertSequence(inputSequenceField.value);
144
- flavor = outputSequenceObj.Nucleotides.includes('U') ? "RNA_both_caps" : "DNA_both_caps";
145
- let smiles = (/^[ATGCU]{6,}$/.test(inputSequenceField.value.replace(/\s/g, ''))) ?
146
- await nucleotidesToSmiles(outputSequenceObj.Nucleotides, flavor) :
147
- modifiedToSmiles(inputSequenceField.value.replace(/\s/g, ''));
148
- smiles = smiles.replace(/@/g, ''); // Remove StereoChemistry on the Nucleic acid chain and remove the Chiral label
149
- let mol = OCL.Molecule.fromSmiles(smiles);
150
- let result = `${mol.toMolfile()}\n`;// + '$$$$';
151
- var element = document.createElement('a');
152
- element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
153
- element.setAttribute('download', inputSequenceField.value.replace(/\s/g, '') + '.mol');
154
- element.click();
155
- });
361
+ const appMainDescription = ui.info([
362
+ ui.divText('How to convert one sequence:',{style:{'font-weight':'bolder'}}),
363
+ ui.divText('Paste sequence into the text field below'),
364
+ ui.divText('\n How to convert many sequences:',{style:{'font-weight':'bolder'}}),
365
+ ui.divText('1. Drag & drop an Excel or CSV file with sequences into Datagrok. The platform will automatically detect columns with sequences'),
366
+ ui.divText('2. Right-click on the column header, then see the \'Convert\' menu'),
367
+ ui.divText('This will add the result column to the right of the table'),
368
+ ], 'Convert oligonucleotide sequences between Nucleotides, BioSpring, Axolabs, Mermade 12 and GCRS representations.'
369
+ );
156
370
 
157
- let v = grok.shell.newView('Sequence Translator', [
158
- ui.tabControl({
159
- 'MAIN': ui.div([
160
- appMainDescription,
161
- ui.panel([
162
- ui.div([
163
- ui.h1('Input sequence'),
371
+ const codesTablesDiv = ui.splitV([
372
+ ui.box(ui.h2('ASO Gapmers'), { style: {maxHeight: '40px'} }),
373
+ asoGapmersGrid.root,
374
+ ui.box(ui.h2("2'-OMe and 2'-F siRNA"), { style: {maxHeight: '40px'} }),
375
+ omeAndFluoroGrid.root,
376
+ ui.box(ui.h2('Overhang modifications'), { style: {maxHeight: '40px'} }),
377
+ overhangModificationsGrid.root
378
+ ], { style: { maxWidth: '350px' } });
379
+
380
+ const tabControl = ui.tabControl({
381
+ 'MAIN': ui.box(
382
+ ui.splitH([
383
+ ui.splitV([
384
+ ui.panel([
385
+ appMainDescription,
164
386
  ui.div([
165
- inputSequenceField.root
166
- ],'input-base')
167
- ], 'sequenceInput'),
168
- semTypeOfInputSequence,
169
- ui.block([
170
- ui.h1('Output'),
171
- outputTableDiv
172
- ]),
173
- moleculeSvgDiv,
174
- ui.divH([saveMolFileButton, showCodesButton])
175
- ], 'sequence')
176
- ]),
177
- 'AXOLABS': defineAxolabsPattern()
178
- })
179
- ]);
387
+ ui.h1('Input sequence'),
388
+ ui.div([
389
+ inputSequenceField.root,
390
+ ], 'input-base'),
391
+ ], 'sequenceInput'),
392
+ semTypeOfInputSequence,
393
+ ui.block([
394
+ ui.h1('Output'),
395
+ outputTableDiv,
396
+ ]),
397
+ moleculeSvgDiv,
398
+ ], 'sequence'),
399
+ ]),
400
+ codesTablesDiv,
401
+ ], {style: {height: '100%', width: '100%'}}),
402
+ ),
403
+ 'AXOLABS': defineAxolabsPattern(),
404
+ 'SDF': saveSenseAntiSense(),
405
+ });
406
+
407
+ let v = grok.shell.newView('Sequence Translator', [tabControl]);
180
408
  v.box = true;
181
409
 
410
+ const switchInput = ui.switchInput('Codes', true, (v: boolean) => (v) ?
411
+ $(codesTablesDiv).show() :
412
+ $(codesTablesDiv).hide()
413
+ );
414
+
415
+ const topPanel = [
416
+ ui.iconFA('download', () => {
417
+ const smiles = sequenceToSmiles(inputSequenceField.value.replace(/\s/g, ''));
418
+ const result = `${OCL.Molecule.fromSmiles(smiles).toMolfile()}\n`;
419
+ const element = document.createElement('a');
420
+ element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
421
+ element.setAttribute('download', inputSequenceField.value.replace(/\s/g, '') + '.mol');
422
+ element.click();
423
+ }, 'Save .mol file'),
424
+ ui.iconFA('copy', () => {
425
+ navigator.clipboard.writeText(sequenceToSmiles(inputSequenceField.value.replace(/\s/g, '')))
426
+ .then(() => grok.shell.info(sequenceWasCopied));
427
+ }, 'Copy SMILES'),
428
+ switchInput.root,
429
+ ];
430
+
431
+ tabControl.onTabChanged.subscribe((_) =>
432
+ v.setRibbonPanels([(tabControl.currentPane.name == 'MAIN') ? topPanel : []]));
433
+ v.setRibbonPanels([topPanel]);
434
+
182
435
  $('.sequence')
183
436
  .children().css('padding','5px 0');
184
437
  $('.sequenceInput .input-base')
@@ -187,109 +440,89 @@ export function sequenceTranslator() {
187
440
  .css('resize','none')
188
441
  .css('min-height','50px')
189
442
  .css('width','100%')
190
- .attr("spellcheck", "false");
443
+ .attr('spellcheck', 'false');
191
444
  $('.sequenceInput select')
192
445
  .css('width','100%');
193
446
  }
194
447
 
195
- export async function nucleotidesToSmiles(nucleotides: string, flavor: string) {
196
- return await grok.functions.call('SequenceTranslator:convertFastaToSmiles', {
197
- 'sequence_in_fasta_format': nucleotides,
198
- 'flavor': flavor
199
- });
200
- }
201
-
202
- export function isDnaNucleotidesCode(sequence: string) {return /^[ATGC]{6,}$/.test(sequence);}
203
- export function isRnaNucleotidesCode(sequence: string) {return /^[AUGC]{6,}$/.test(sequence);}
204
- export function isAsoGapmerBioSpringCode(sequence: string) {return /^[*56789ATGC]{6,}$/.test(sequence);}
205
- export function isAsoGapmerGcrsCode(sequence: string) {return /^(?=.*moe)(?=.*5mC)(?=.*ps){6,}/.test(sequence);}
206
- export function isSiRnaBioSpringCode(sequence: string) {return /^[*1-8]{6,}$/.test(sequence);}
207
- export function isSiRnaAxolabsCode(sequence: string) {return /^[fsACGUacgu]{6,}$/.test(sequence);}
208
- export function isSiRnaGcrsCode(sequence: string) {return /^[fmpsACGU]{6,}$/.test(sequence);}
209
- export function isGcrsCode(sequence: string) {return /^[fmpsACGU]{6,}$/.test(sequence);}
210
- export function isMM12Code(sequence: string) {return /^[IiJjKkLlEeFfGgHhQq]{6,}$/.test(sequence);}
211
-
212
- function convertSequence(seq: string) {
213
- seq = seq.replace(/\s/g, '');
214
- if (seq.length < minimalValidNumberOfCharacters)
448
+ function convertSequence(text: string) {
449
+ text = text.replace(/\s/g, '');
450
+ let seq = text;
451
+ let output = isValidSequence(seq);
452
+ if (output.indexOfFirstNotValidCharacter != -1)
215
453
  return {
216
- type: smallNumberOfCharacters,
217
- Nucleotides: smallNumberOfCharacters,
218
- BioSpring: smallNumberOfCharacters,
219
- Axolabs: smallNumberOfCharacters,
220
- GCRS: smallNumberOfCharacters
454
+ // type: '',
455
+ indexOfFirstNotValidCharacter: JSON.stringify(output),
456
+ Error: undefinedInputSequence
221
457
  };
222
- if (isDnaNucleotidesCode(seq))
458
+ if (output.expectedSynthesizer == SYNTHESIZERS.RAW_NUCLEOTIDES && output.expectedTechnology == TECHNOLOGIES.DNA)
223
459
  return {
224
- type: "DNA Nucleotides Code",
460
+ type: SYNTHESIZERS.RAW_NUCLEOTIDES + ' ' + TECHNOLOGIES.DNA,
225
461
  Nucleotides: seq,
226
462
  BioSpring: asoGapmersNucleotidesToBioSpring(seq),
227
- Axolabs: noTranslationTableAvailable,
228
463
  GCRS: asoGapmersNucleotidesToGcrs(seq)
229
464
  };
230
- if (isAsoGapmerBioSpringCode(seq))
465
+ if (output.expectedSynthesizer == SYNTHESIZERS.BIOSPRING && output.expectedTechnology == TECHNOLOGIES.ASO_GAPMERS)
231
466
  return {
232
- type: "ASO Gapmers / BioSpring Code",
467
+ type: SYNTHESIZERS.BIOSPRING + ' ' + TECHNOLOGIES.ASO_GAPMERS,
233
468
  Nucleotides: asoGapmersBioSpringToNucleotides(seq),
234
469
  BioSpring: seq,
235
- Axolabs: noTranslationTableAvailable,
236
470
  GCRS: asoGapmersBioSpringToGcrs(seq)
237
471
  };
238
- if (isAsoGapmerGcrsCode(seq))
472
+ if (output.expectedSynthesizer == SYNTHESIZERS.GCRS && output.expectedTechnology == TECHNOLOGIES.ASO_GAPMERS)
239
473
  return {
240
- type: "ASO Gapmers / GCRS Code",
474
+ type: SYNTHESIZERS.GCRS + ' ' + TECHNOLOGIES.ASO_GAPMERS,
241
475
  Nucleotides: asoGapmersGcrsToNucleotides(seq),
242
476
  BioSpring: asoGapmersGcrsToBioSpring(seq),
243
- Axolabs: noTranslationTableAvailable,
244
- MM12: gcrsToMM12(seq),
477
+ Mermade12: gcrsToMermade12(seq),
245
478
  GCRS: seq
246
479
  };
247
- if (isRnaNucleotidesCode(seq))
480
+ if (output.expectedSynthesizer == SYNTHESIZERS.RAW_NUCLEOTIDES && output.expectedTechnology == TECHNOLOGIES.RNA)
248
481
  return {
249
- type: "RNA Nucleotides Code",
482
+ type: SYNTHESIZERS.RAW_NUCLEOTIDES + ' ' + TECHNOLOGIES.RNA,
250
483
  Nucleotides: seq,
251
484
  BioSpring: siRnaNucleotideToBioSpringSenseStrand(seq),
252
485
  Axolabs: siRnaNucleotideToAxolabsSenseStrand(seq),
253
486
  GCRS: siRnaNucleotidesToGcrs(seq)
254
487
  };
255
- if (isSiRnaBioSpringCode(seq))
488
+ if (output.expectedSynthesizer == SYNTHESIZERS.BIOSPRING && output.expectedTechnology == TECHNOLOGIES.SI_RNA)
256
489
  return {
257
- type: "siRNA / bioSpring Code",
490
+ type: SYNTHESIZERS.BIOSPRING + ' ' + TECHNOLOGIES.SI_RNA,
258
491
  Nucleotides: siRnaBioSpringToNucleotides(seq),
259
492
  BioSpring: seq,
260
493
  Axolabs: siRnaBioSpringToAxolabs(seq),
261
494
  GCRS: siRnaBioSpringToGcrs(seq)
262
495
  };
263
- if (isSiRnaAxolabsCode(seq))
496
+ if (output.expectedSynthesizer == SYNTHESIZERS.AXOLABS && output.expectedTechnology == TECHNOLOGIES.SI_RNA)
264
497
  return {
265
- type: "siRNA / Axolabs Code",
498
+ type: SYNTHESIZERS.AXOLABS + ' ' + TECHNOLOGIES.SI_RNA,
266
499
  Nucleotides: siRnaAxolabsToNucleotides(seq),
267
500
  BioSpring: siRnaAxolabsToBioSpring(seq),
268
501
  Axolabs: seq,
269
502
  GCRS: siRnaAxolabsToGcrs(seq)
270
503
  };
271
- if (isSiRnaGcrsCode(seq))
504
+ if (output.expectedSynthesizer == SYNTHESIZERS.GCRS && output.expectedTechnology == TECHNOLOGIES.SI_RNA)
272
505
  return {
273
- type: "siRNA / GCRS Code",
506
+ type: SYNTHESIZERS.GCRS + ' ' + TECHNOLOGIES.SI_RNA,
274
507
  Nucleotides: siRnaGcrsToNucleotides(seq),
275
508
  BioSpring: siRnaGcrsToBioSpring(seq),
276
509
  Axolabs: siRnaGcrsToAxolabs(seq),
277
- MM12: gcrsToMM12(seq),
510
+ MM12: gcrsToMermade12(seq),
278
511
  GCRS: seq
279
512
  };
280
- if (isGcrsCode(seq))
513
+ if (output.expectedSynthesizer == SYNTHESIZERS.GCRS)
281
514
  return {
282
- type: "GCRS Code",
515
+ type: SYNTHESIZERS.GCRS,
283
516
  Nucleotides: gcrsToNucleotides(seq),
284
517
  GCRS: seq,
285
- MM12: gcrsToMM12(seq)
518
+ Mermade12: gcrsToMermade12(seq)
286
519
  }
287
- if (isMM12Code(seq))
520
+ if (output.expectedSynthesizer == SYNTHESIZERS.MERMADE_12)
288
521
  return {
289
- type: "MM12 Code",
522
+ type: SYNTHESIZERS.MERMADE_12,
290
523
  Nucleotides: noTranslationTableAvailable,
291
524
  GCRS: noTranslationTableAvailable,
292
- MM12: seq
525
+ Mermade12: seq
293
526
  };
294
527
  return {
295
528
  type: undefinedInputSequence,
@@ -300,49 +533,49 @@ function convertSequence(seq: string) {
300
533
  //name: asoGapmersNucleotidesToBioSpring
301
534
  //input: string nucleotides {semType: DNA nucleotides}
302
535
  //output: string result {semType: BioSpring / Gapmers}
303
- export function asoGapmersNucleotidesToBioSpring(nucleotides: string) {
536
+ export function asoGapmersNucleotidesToBioSpring(nucleotides: string): string {
304
537
  let count: number = -1;
305
- const objForEdges: {[index: string]: string} = {"T": "5*", "A": "6*", "C": "7*", "G": "8*"};
306
- const objForCenter: {[index: string]: string} = {"C": "9*", "A": "A*", "T": "T*", "G": "G*"};
307
- return nucleotides.replace(/[ATCG]/g, function (x: string) {
538
+ const objForEdges: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'T': '5*', 'A': '6*', 'C': '7*', 'G': '8*'};
539
+ const objForCenter: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'T': 'T*', 'A': 'A*', 'C': '9*', 'G': 'G*'};
540
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|A|T|C|G)/g, function (x: string) {
308
541
  count++;
309
542
  return (count > 4 && count < 15) ? objForCenter[x] : objForEdges[x];
310
- }).slice(0, 2 * count + 1);
543
+ }).slice(0, (nucleotides.endsWith('(invabasic)') || nucleotides.endsWith('(GalNAc-2-JNJ)')) ? nucleotides.length : 2 * count + 1);
311
544
  }
312
545
 
313
546
  //name: asoGapmersNucleotidesToGcrs
314
547
  //input: string nucleotides {semType: DNA nucleotides}
315
548
  //output: string result {semType: GCRS / Gapmers}
316
- export function asoGapmersNucleotidesToGcrs(nucleotides: string) {
549
+ export function asoGapmersNucleotidesToGcrs(nucleotides: string): string {
317
550
  let count: number = -1;
318
- const objForEdges: {[index: string]: string} = {"T": "moeUnps", "A": "moeAnps", "C": "moe5mCnps", "G": "moeGnps"};
319
- const objForCenter: {[index: string]: string} = {"C": "5mCps", "A": "Aps", "T": "Tps", "G": "Gps"};
320
- return nucleotides.replace(/[ATCG]/g, function (x: string) {
551
+ const objForEdges: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'T': 'moeUnps', 'A': 'moeAnps', 'C': 'moe5mCnps', 'G': 'moeGnps'};
552
+ const objForCenter: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'C': '5mCps', 'A': 'Aps', 'T': 'Tps', 'G': 'Gps'};
553
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|A|T|C|G)/g, function (x: string) {
321
554
  count++;
322
555
  if (count < 5) return (count == 4) ? objForEdges[x].slice(0, -3) + 'ps' : objForEdges[x];
323
556
  if (count < 15) return (count == 14) ? objForCenter[x].slice(0, -2) + 'nps' : objForCenter[x];
324
557
  return objForEdges[x];
325
- }).slice(0, -3);
558
+ }).slice(0, (nucleotides.endsWith('(invabasic)') || nucleotides.endsWith('(GalNAc-2-JNJ)')) ? nucleotides.length : -3);
326
559
  }
327
560
 
328
561
  //name: asoGapmersBioSpringToNucleotides
329
562
  //input: string nucleotides {semType: BioSpring / Gapmers}
330
563
  //output: string result {semType: DNA nucleotides}
331
- export function asoGapmersBioSpringToNucleotides(nucleotides: string) {
332
- const obj: {[index: string]: string} = {"*": "", "5": "T", "6": "A", "7": "C", "8": "G", "9": "C"};
333
- return nucleotides.replace(/[*56789]/g, function (x: string) {return obj[x];});
564
+ export function asoGapmersBioSpringToNucleotides(nucleotides: string): string {
565
+ const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', '*': '', '5': 'T', '6': 'A', '7': 'C', '8': 'G', '9': 'C'};
566
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|\*|5|6|7|8|9)/g, function (x: string) {return obj[x];});
334
567
  }
335
568
 
336
569
  //name: asoGapmersBioSpringToGcrs
337
570
  //input: string nucleotides {semType: BioSpring / Gapmers}
338
571
  //output: string result {semType: GCRS / Gapmers}
339
- export function asoGapmersBioSpringToGcrs(nucleotides: string) {
572
+ export function asoGapmersBioSpringToGcrs(nucleotides: string): string {
340
573
  let count: number = -1;
341
- const obj: {[index: string]: string} = {
342
- "5*": "moeUnps", "6*": "moeAnps", "7*": "moe5mCnps", "8*": "moeGnps", "9*": "5mCps", "A*": "Aps", "T*": "Tps",
343
- "G*": "Gps", "C*": "Cps", "5": "moeU", "6": "moeA", "7": "moe5mC", "8": "moeG"
574
+ const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
575
+ '5*': 'moeUnps', '6*': 'moeAnps', '7*': 'moe5mCnps', '8*': 'moeGnps', '9*': '5mCps', 'A*': 'Aps', 'T*': 'Tps',
576
+ 'G*': 'Gps', 'C*': 'Cps', '5': 'moeU', '6': 'moeA', '7': 'moe5mC', '8': 'moeG'
344
577
  };
345
- return nucleotides.replace(/(5\*|6\*|7\*|8\*|9\*|A\*|T\*|G\*|C\*|5|6|7|8)/g, function (x: string) {
578
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|5\*|6\*|7\*|8\*|9\*|A\*|T\*|G\*|C\*|5|6|7|8)/g, function (x: string) {
346
579
  count++;
347
580
  return (count == 4) ? obj[x].slice(0, -3) + 'ps' : (count == 14) ? obj[x].slice(0, -2) + 'nps' : obj[x];
348
581
  });
@@ -351,103 +584,103 @@ export function asoGapmersBioSpringToGcrs(nucleotides: string) {
351
584
  //name: asoGapmersGcrsToBioSpring
352
585
  //input: string nucleotides {semType: GCRS / Gapmers}
353
586
  //output: string result {semType: BioSpring / Gapmers}
354
- export function asoGapmersGcrsToBioSpring(nucleotides: string) {
355
- const obj: {[index: string]: string} = {
356
- "moeT": "5", "moeA": "6", "moe5mC": "7", "moeG": "8", "moeU": "5", "5mC": "9", "nps": "*", "ps": "*", "U": "T"
587
+ export function asoGapmersGcrsToBioSpring(nucleotides: string): string {
588
+ const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
589
+ 'moeT': '5', 'moeA': '6', 'moe5mC': '7', 'moeG': '8', 'moeU': '5', '5mC': '9', 'nps': '*', 'ps': '*', 'U': 'T'
357
590
  };
358
- return nucleotides.replace(/(moeT|moeA|moe5mC|moeG|moeU|5mC|nps|ps|U)/g, function (x: string) {return obj[x];});
591
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|moeT|moeA|moe5mC|moeG|moeU|5mC|nps|ps|U)/g, function (x: string) {return obj[x];});
359
592
  }
360
593
 
361
594
  //name: asoGapmersGcrsToNucleotides
362
595
  //input: string nucleotides {semType: GCRS / Gapmers}
363
596
  //output: string result {semType: DNA nucleotides}
364
597
  export function asoGapmersGcrsToNucleotides(nucleotides: string) {
365
- const obj: {[index: string]: string} = {"moe": "", "5m": "", "n": "", "ps": "", "U": "T"};
366
- return nucleotides.replace(/(moe|5m|n|ps|U)/g, function (x: string) {return obj[x];});
598
+ const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'moe': '', '5m': '', 'n': '', 'ps': '', 'U': 'T'};
599
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|moe|5m|n|ps|U)/g, function (x: string) {return obj[x];});
367
600
  }
368
601
 
369
602
  //name: siRnaBioSpringToNucleotides
370
603
  //input: string nucleotides {semType: BioSpring / siRNA}
371
604
  //output: string result {semType: RNA nucleotides}
372
605
  export function siRnaBioSpringToNucleotides(nucleotides: string) {
373
- const obj: {[index: string]: string} = {"1": "U", "2": "A", "3": "C", "4": "G", "5": "U", "6": "A", "7": "C", "8": "G", "*": ""};
374
- return nucleotides.replace(/[12345678*]/g, function (x: string) {return obj[x];});
606
+ const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', '1': 'U', '2': 'A', '3': 'C', '4': 'G', '5': 'U', '6': 'A', '7': 'C', '8': 'G', '*': ''};
607
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|1|2|3|4|5|6|7|8|\*)/g, function (x: string) {return obj[x];});
375
608
  }
376
609
 
377
610
  //name: siRnaBioSpringToAxolabs
378
611
  //input: string nucleotides {semType: BioSpring / siRNA}
379
612
  //output: string result {semType: Axolabs / siRNA}
380
613
  export function siRnaBioSpringToAxolabs(nucleotides: string) {
381
- const obj: {[index: string]: string} = {"1": "Uf", "2": "Af", "3": "Cf", "4": "Gf", "5": "u", "6": "a", "7": "c", "8": "g", "*": "s"};
382
- return nucleotides.replace(/[12345678*]/g, function (x: string) {return obj[x];});
614
+ const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', '1': 'Uf', '2': 'Af', '3': 'Cf', '4': 'Gf', '5': 'u', '6': 'a', '7': 'c', '8': 'g', '*': 's'};
615
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|1|2|3|4|5|6|7|8|\*)/g, function (x: string) {return obj[x];});
383
616
  }
384
617
 
385
618
  //name: siRnaBioSpringToGcrs
386
619
  //input: string nucleotides {semType: BioSpring / siRNA}
387
620
  //output: string result {semType: GCRS}
388
621
  export function siRnaBioSpringToGcrs(nucleotides: string) {
389
- const obj: {[index: string]: string} = {"1": "fU", "2": "fA", "3": "fC", "4": "fG", "5": "mU", "6": "mA", "7": "mC", "8": "mG", "*": "ps"};
390
- return nucleotides.replace(/[12345678*]/g, function (x: string) {return obj[x];});
622
+ const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', '1': 'fU', '2': 'fA', '3': 'fC', '4': 'fG', '5': 'mU', '6': 'mA', '7': 'mC', '8': 'mG', '*': 'ps'};
623
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|1|2|3|4|5|6|7|8|\*)/g, function (x: string) {return obj[x];});
391
624
  }
392
625
 
393
626
  //name: siRnaAxolabsToGcrs
394
627
  //input: string nucleotides {semType: Axolabs / siRNA}
395
628
  //output: string result {semType: GCRS}
396
629
  export function siRnaAxolabsToGcrs(nucleotides: string) {
397
- const obj: {[index: string]: string} = {
398
- "Uf": "fU", "Af": "fA", "Cf": "fC", "Gf": "fG", "u": "mU", "a": "mA", "c": "mC", "g": "mG", "s": "ps"
630
+ const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
631
+ 'Uf': 'fU', 'Af': 'fA', 'Cf': 'fC', 'Gf': 'fG', 'u': 'mU', 'a': 'mA', 'c': 'mC', 'g': 'mG', 's': 'ps'
399
632
  };
400
- return nucleotides.replace(/(Uf|Af|Cf|Gf|u|a|c|g|s)/g, function (x: string) {return obj[x];});
633
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|Uf|Af|Cf|Gf|u|a|c|g|s)/g, function (x: string) {return obj[x];});
401
634
  }
402
635
 
403
636
  //name: siRnaAxolabsToBioSpring
404
637
  //input: string nucleotides {semType: Axolabs / siRNA}
405
638
  //output: string result {semType: BioSpring / siRNA}
406
639
  export function siRnaAxolabsToBioSpring(nucleotides: string) {
407
- const obj: {[index: string]: string} = {
408
- "Uf": "1", "Af": "2", "Cf": "3", "Gf": "4", "u": "5", "a": "6", "c": "7", "g": "8", "s": "*"
640
+ const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
641
+ 'Uf': '1', 'Af': '2', 'Cf': '3', 'Gf': '4', 'u': '5', 'a': '6', 'c': '7', 'g': '8', 's': '*'
409
642
  };
410
- return nucleotides.replace(/(Uf|Af|Cf|Gf|u|a|c|g|s)/g, function (x: string) {return obj[x];});
643
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|Uf|Af|Cf|Gf|u|a|c|g|s)/g, function (x: string) {return obj[x];});
411
644
  }
412
645
 
413
646
  //name: siRnaAxolabsToNucleotides
414
647
  //input: string nucleotides {semType: Axolabs / siRNA}
415
648
  //output: string result {semType: RNA nucleotides}
416
649
  export function siRnaAxolabsToNucleotides(nucleotides: string) {
417
- const obj: {[index: string]: string} = {
418
- "Uf": "U", "Af": "A", "Cf": "C", "Gf": "G", "u": "U", "a": "A", "c": "C", "g": "G", "s": ""
650
+ const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
651
+ 'Uf': 'U', 'Af': 'A', 'Cf': 'C', 'Gf': 'G', 'u': 'U', 'a': 'A', 'c': 'C', 'g': 'G', 's': ''
419
652
  };
420
- return nucleotides.replace(/(Uf|Af|Cf|Gf|u|a|c|g|s)/g, function (x: string) {return obj[x];});
653
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|Uf|Af|Cf|Gf|u|a|c|g|s)/g, function (x: string) {return obj[x];});
421
654
  }
422
655
 
423
656
  //name: siRnaGcrsToNucleotides
424
657
  //input: string nucleotides {semType: GCRS}
425
658
  //output: string result {semType: RNA nucleotides}
426
659
  export function siRnaGcrsToNucleotides(nucleotides: string) {
427
- const obj: {[index: string]: string} = {
428
- "fU": "U", "fA": "A", "fC": "C", "fG": "G", "mU": "U", "mA": "A", "mC": "C", "mG": "G", "ps": ""
660
+ const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
661
+ 'fU': 'U', 'fA': 'A', 'fC': 'C', 'fG': 'G', 'mU': 'U', 'mA': 'A', 'mC': 'C', 'mG': 'G', 'ps': ''
429
662
  };
430
- return nucleotides.replace(/(fU|fA|fC|fG|mU|mA|mC|mG|ps)/g, function (x: string) {return obj[x];});
663
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|fU|fA|fC|fG|mU|mA|mC|mG|ps)/g, function (x: string) {return obj[x];});
431
664
  }
432
665
 
433
666
  //name: siRnaGcrsToBioSpring
434
667
  //input: string nucleotides {semType: GCRS}
435
668
  //output: string result {semType: BioSpring / siRNA}
436
669
  export function siRnaGcrsToBioSpring(nucleotides: string) {
437
- const obj: {[index: string]: string} = {
438
- "fU": "1", "fA": "2", "fC": "3", "fG": "4", "mU": "5", "mA": "6", "mC": "7", "mG": "8", "ps": "*"
670
+ const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
671
+ 'fU': '1', 'fA': '2', 'fC': '3', 'fG': '4', 'mU': '5', 'mA': '6', 'mC': '7', 'mG': '8', 'ps': '*'
439
672
  };
440
- return nucleotides.replace(/(fU|fA|fC|fG|mU|mA|mC|mG|ps)/g, function (x: string) {return obj[x];});
673
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|fU|fA|fC|fG|mU|mA|mC|mG|ps)/g, function (x: string) {return obj[x];});
441
674
  }
442
675
 
443
676
  //name: siRnaGcrsToAxolabs
444
677
  //input: string nucleotides {semType: GCRS}
445
678
  //output: string result {semType: Axolabs / siRNA}
446
679
  export function siRnaGcrsToAxolabs(nucleotides: string) {
447
- const obj: {[index: string]: string} = {
448
- "fU": "Uf", "fA": "Af", "fC": "Cf", "fG": "Gf", "mU": "u", "mA": "a", "mC": "c", "mG": "g", "ps": "s"
680
+ const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
681
+ 'fU': 'Uf', 'fA': 'Af', 'fC': 'Cf', 'fG': 'Gf', 'mU': 'u', 'mA': 'a', 'mC': 'c', 'mG': 'g', 'ps': 's'
449
682
  };
450
- return nucleotides.replace(/(fU|fA|fC|fG|mU|mA|mC|mG|ps)/g, function (x: string) {return obj[x];});
683
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|fU|fA|fC|fG|mU|mA|mC|mG|ps)/g, function (x: string) {return obj[x];});
451
684
  }
452
685
 
453
686
  //name: siRnaNucleotideToBioSpringSenseStrand
@@ -455,11 +688,11 @@ export function siRnaGcrsToAxolabs(nucleotides: string) {
455
688
  //output: string result {semType: BioSpring / siRNA}
456
689
  export function siRnaNucleotideToBioSpringSenseStrand(nucleotides: string) {
457
690
  let count: number = -1;
458
- const objForLeftEdge: {[index: string]: string} = {"A": "6*", "U": "5*", "G": "8*", "C": "7*"};
459
- const objForRightEdge: {[index: string]: string} = {"A": "*6", "U": "*5", "G": "*8", "C": "*7"};
460
- const objForOddIndices: {[index: string]: string} = {"A": "6", "U": "5", "G": "8", "C": "7"};
461
- const objForEvenIndices: {[index: string]: string} = {"A": "2", "U": "1", "G": "4", "C": "3"};
462
- return nucleotides.replace(/[AUGC]/g, function (x: string) {
691
+ const objForLeftEdge: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': '6*', 'U': '5*', 'G': '8*', 'C': '7*'};
692
+ const objForRightEdge: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': '*6', 'U': '*5', 'G': '*8', 'C': '*7'};
693
+ const objForOddIndices: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': '6', 'U': '5', 'G': '8', 'C': '7'};
694
+ const objForEvenIndices: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': '2', 'U': '1', 'G': '4', 'C': '3'};
695
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|A|U|G|C)/g, function (x: string) {
463
696
  count++;
464
697
  if (count < 2) return objForLeftEdge[x];
465
698
  if (count > nucleotides.length - 3) return objForRightEdge[x];
@@ -472,11 +705,11 @@ export function siRnaNucleotideToBioSpringSenseStrand(nucleotides: string) {
472
705
  //output: string result {semType: GCRS}
473
706
  export function siRnaNucleotidesToGcrs(nucleotides: string) {
474
707
  let count: number = -1;
475
- const objForLeftEdge: {[index: string]: string} = {"A": "mAps", "U": "mUps", "G": "mGps", "C": "mCps"};
476
- const objForRightEdge: {[index: string]: string} = {"A": "psmA", "U": "psmU", "G": "psmG", "C": "psmC"};
477
- const objForEvenIndices: {[index: string]: string} = {"A": "fA", "U": "fU", "G": "fG", "C": "fC"};
478
- const objForOddIndices: {[index: string]: string} = {"A": "mA", "U": "mU", "G": "mG", "C": "mC"};
479
- return nucleotides.replace(/[AUGC]/g, function (x: string) {
708
+ const objForLeftEdge: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'mAps', 'U': 'mUps', 'G': 'mGps', 'C': 'mCps'};
709
+ const objForRightEdge: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'psmA', 'U': 'psmU', 'G': 'psmG', 'C': 'psmC'};
710
+ const objForEvenIndices: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'fA', 'U': 'fU', 'G': 'fG', 'C': 'fC'};
711
+ const objForOddIndices: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'mA', 'U': 'mU', 'G': 'mG', 'C': 'mC'};
712
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|A|U|G|C)/g, function (x: string) {
480
713
  count++;
481
714
  if (count < 2) return objForLeftEdge[x];
482
715
  if (count > nucleotides.length - 3) return objForRightEdge[x];
@@ -489,10 +722,10 @@ export function siRnaNucleotidesToGcrs(nucleotides: string) {
489
722
  //output: string result {semType: Axolabs}
490
723
  export function siRnaNucleotideToAxolabsSenseStrand(nucleotides: string) {
491
724
  let count: number = -1;
492
- const objForLeftEdge: {[index: string]: string} = {"A": "as", "U": "us", "G": "gs", "C": "cs"};
493
- const objForSomeIndices: {[index: string]: string} = {"A": "Af", "U": "Uf", "G": "Gf", "C": "Cf"};
494
- const obj: {[index: string]: string} = {"A": "a", "U": "u", "G": "g", "C": "c"};
495
- return nucleotides.replace(/[AUGC]/g, function (x: string) {
725
+ const objForLeftEdge: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'as', 'U': 'us', 'G': 'gs', 'C': 'cs'};
726
+ const objForSomeIndices: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'Af', 'U': 'Uf', 'G': 'Gf', 'C': 'Cf'};
727
+ const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'a', 'U': 'u', 'G': 'g', 'C': 'c'};
728
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|A|U|G|C)/g, function (x: string) {
496
729
  count++;
497
730
  if (count < 2) return objForLeftEdge[x];
498
731
  if (count == 6 || (count > 7 && count < 11)) return objForSomeIndices[x]
@@ -506,11 +739,11 @@ export function siRnaNucleotideToAxolabsSenseStrand(nucleotides: string) {
506
739
  //output: string result {semType: Axolabs}
507
740
  export function siRnaNucleotideToAxolabsAntisenseStrand(nucleotides: string) {
508
741
  let count: number = -1;
509
- const objForSmallLinkages: {[index: string]: string} = {"A": "as", "U": "us", "G": "gs", "C": "cs"};
510
- const objForBigLinkages: {[index: string]: string} = {"A": "Afs", "U": "Ufs", "G": "Gfs", "C": "Cfs"};
511
- const objForSomeIndices: {[index: string]: string} = {"A": "Af", "U": "Uf", "G": "Gf", "C": "Cf"};
512
- const obj: {[index: string]: string} = {"A": "a", "U": "u", "G": "g", "C": "c"};
513
- return nucleotides.replace(/[AUGC]/g, function (x: string) {
742
+ const objForSmallLinkages: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'as', 'U': 'us', 'G': 'gs', 'C': 'cs'};
743
+ const objForBigLinkages: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'Afs', 'U': 'Ufs', 'G': 'Gfs', 'C': 'Cfs'};
744
+ const objForSomeIndices: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'Af', 'U': 'Uf', 'G': 'Gf', 'C': 'Cf'};
745
+ const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)', 'A': 'a', 'U': 'u', 'G': 'g', 'C': 'c'};
746
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|A|U|G|C)/g, function (x: string) {
514
747
  count++;
515
748
  if (count > 19 && count < 22) return objForSmallLinkages[x];
516
749
  if (count == 0) return 'us';
@@ -523,48 +756,141 @@ export function siRnaNucleotideToAxolabsAntisenseStrand(nucleotides: string) {
523
756
  //input: string nucleotides {semType: GCRS}
524
757
  //output: string result {semType: RNA nucleotides}
525
758
  export function gcrsToNucleotides(nucleotides: string) {
526
- const obj: {[index: string]: string} = {
527
- "mAps": "A", "mUps": "U", "mGps": "G", "mCps": "C", "fAps": "A", "fUps": "U", "fGps": "G", "fCps": "C",
528
- "fU": "U", "fA": "A", "fC": "C", "fG": "G", "mU": "U", "mA": "A", "mC": "C", "mG": "G"
759
+ const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
760
+ 'mAps': 'A', 'mUps': 'U', 'mGps': 'G', 'mCps': 'C', 'fAps': 'A', 'fUps': 'U', 'fGps': 'G', 'fCps': 'C',
761
+ 'fU': 'U', 'fA': 'A', 'fC': 'C', 'fG': 'G', 'mU': 'U', 'mA': 'A', 'mC': 'C', 'mG': 'G'
529
762
  };
530
- return nucleotides.replace(/(mAps|mUps|mGps|mCps|fAps|fUps|fGps|fCps|fU|fA|fC|fG|mU|mA|mC|mG)/g, function (x: string) {return obj[x];});
763
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|mAps|mUps|mGps|mCps|fAps|fUps|fGps|fCps|fU|fA|fC|fG|mU|mA|mC|mG)/g, function (x: string) {return obj[x];});
531
764
  }
532
765
 
533
- //name: gcrsToOP100
766
+ //name: gcrsToMermade12
534
767
  //input: string nucleotides {semType: GCRS}
535
- //output: string result {semType: OP100}
536
- export function gcrsToOP100(nucleotides: string) {
537
- let count: number = -1;
538
- const objForEvenIndicesAtLeftEdge: {[index: string]: string} = {
539
- "mAps": "a", "mUps": "u", "mGps": "g", "mCps": "c", "fAps": "a", "fUps": "u", "fGps": "g", "fCps": "c"
540
- };
541
- const objForOddIndicesAtLeftEdge: {[index: string]: string} = {
542
- "mAps": "a*", "mUps": "u*", "mGps": "g*", "mCps": "c*", "fAps": "a*", "fUps": "u*", "fGps": "g*", "fCps": "c*"
543
- };
544
- const objForOddIndicesAtRightEdge: {[index: string]: string} = {
545
- "mAps": "a", "mUps": "u", "mGps": "g", "mCps": "c", "fAps": "a", "fUps": "u", "fGps": "g", "fCps": "c"
768
+ //output: string result {semType: Mermade 12 / siRNA}
769
+ export function gcrsToMermade12(nucleotides: string) {
770
+ const obj: {[index: string]: string} = {'(invabasic)': '(invabasic)', '(GalNAc-2-JNJ)': '(GalNAc-2-JNJ)',
771
+ 'mAps': 'e', 'mUps': 'h', 'mGps': 'g', 'mCps': 'f', 'fAps': 'i', 'fUps': 'l', 'fGps': 'k', 'fCps': 'j', 'fU': 'L',
772
+ 'fA': 'I', 'fC': 'J', 'fG': 'K', 'mU': 'H', 'mA': 'E', 'mC': 'F', 'mG': 'G'
546
773
  };
547
- const objForEvenIndicesAtCenter: {[index: string]: string} = {
548
- "fU": "u*", "fA": "a*", "fC": "c*", "fG": "g*", "mU": "u*", "mA": "a*", "mC": "c*", "mG": "g*"
549
- };
550
- const objForOddIndicesAtCenter: {[index: string]: string} = {
551
- "fU": "u", "fA": "a", "fC": "c", "fG": "g", "mU": "u", "mA": "a", "mC": "c", "mG": "g"
552
- };
553
- return nucleotides.replace(/(mAps|mUps|mGps|mCps|fAps|fUps|fGps|fCps|fU|fA|fC|fG|mU|mA|mC|mG)/g, function (x: string) {
554
- count++;
555
- if (count < 3) return (count % 2 == 0) ? objForEvenIndicesAtLeftEdge[x] : objForOddIndicesAtLeftEdge[x];
556
- if (count == 19) return objForOddIndicesAtRightEdge[x];
557
- return (count % 2 == 1) ? objForEvenIndicesAtCenter[x] : objForOddIndicesAtCenter[x];
774
+ return nucleotides.replace(/(\(invabasic\)|\(GalNAc-2-JNJ\)|mAps|mUps|mGps|mCps|fAps|fUps|fGps|fCps|fU|fA|fC|fG|mU|mA|mC|mG)/g, function (x: string) {return obj[x]});
775
+ }
776
+
777
+ async function saveTableAsSdFile(table: DG.DataFrame) {
778
+ if (!table.columns.contains('Compound Name'))
779
+ grok.shell.warning("File was saved without columns 'Compound Name', 'Compound Components', 'Cpd MW', 'Salt mass', 'Batch MW'!");
780
+ let structureColumn = table.columns.byName('Sequence');
781
+ let result = '';
782
+ for (let i = 0; i < table.rowCount; i++) {
783
+ try {
784
+ let smiles = sequenceToSmiles(structureColumn.get(i));
785
+ //@ts-ignore
786
+ let mol = new OCL.Molecule.fromSmiles(smiles);
787
+ result += `\n${mol.toMolfile()}\n`;
788
+ for (let col of table.columns)
789
+ result += `> <${col.name}>\n${col.get(i)}\n\n`;
790
+ result += '$$$$';
791
+ }
792
+ catch (error) {
793
+ console.error(error);
794
+ }
795
+ }
796
+ let element = document.createElement('a');
797
+ element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
798
+ element.setAttribute('download', table.name + '.sdf');
799
+ element.click();
800
+ }
801
+
802
+ function parseNumber(saltName: string) {
803
+ let i = saltName.length;
804
+ while (saltName.length > -1 && saltName[i] != '(')
805
+ i--;
806
+ return parseInt(saltName.slice(i + 2));
807
+ }
808
+
809
+ //tags: autostart
810
+ export function autostartOligoSdFileSubscription() {
811
+ grok.events.onViewAdded.subscribe((v: any) => {
812
+ if (v.type == 'TableView' && v.dataFrame.columns.contains('Type'))
813
+ oligoSdFile(v.dataFrame);
558
814
  });
559
815
  }
560
816
 
561
- //name: gcrsToMM12
562
- //input: string nucleotides {semType: GCRS}
563
- //output: string result {semType: MM12}
564
- export function gcrsToMM12(nucleotides: string) {
565
- const obj: {[index: string]: string} = {
566
- "mAps": "e", "mUps": "h", "mGps": "g", "mCps": "f", "fAps": "i", "fUps": "l", "fGps": "k", "fCps": "j", "fU": "L",
567
- "fA": "I", "fC": "J", "fG": "K", "mU": "H", "mA": "E", "mC": "F", "mG": "G"
568
- };
569
- return nucleotides.replace(/(mAps|mUps|mGps|mCps|fAps|fUps|fGps|fCps|fU|fA|fC|fG|mU|mA|mC|mG)/g, function (x: string) {return obj[x]});
570
- }
817
+ let weightsObj: {[code: string]: number} = {};
818
+ for (let synthesizer of Object.keys(map))
819
+ for (let technology of Object.keys(map[synthesizer]))
820
+ for (let code of Object.keys(map[synthesizer][technology]))
821
+ weightsObj[code] = map[synthesizer][technology][code].weight;
822
+
823
+ function molecularWeight(sequence: string): number {
824
+ const codes = sortByStringLengthInDescendingOrder(Object.keys(weightsObj));
825
+ let weight = 0, i = 0;
826
+ while (i < sequence.length) {
827
+ let matchedCode = codes.find((s) => s == sequence.slice(i, i + s.length))!;
828
+ weight += weightsObj[sequence.slice(i, i + matchedCode.length)];
829
+ i += matchedCode!.length;
830
+ }
831
+ return weight - 61.97;
832
+ }
833
+
834
+ export function oligoSdFile(table: DG.DataFrame) {
835
+
836
+ function addColumns(t: DG.DataFrame) {
837
+ if (t.columns.contains('Compound Name'))
838
+ return grok.shell.error('Columns already exist!');
839
+
840
+ table.col('Source')?.init('Johnson and Johnson Pharma');
841
+ table.col('ICD')?.init('No Contract');
842
+
843
+ let sequence = t.col('Sequence')!;
844
+ let salt = t.col('Salt')!;
845
+ let equivalents = t.col('Equivalents')!;
846
+
847
+ t.columns.addNewString('Compound Name').init((i: number) => sequence.get(i));
848
+ t.columns.addNewString('Compound Comments').init((i: number) => (i > 0 && i % 2 == 0) ?
849
+ sequence.getString(i) + '; duplex of SS: ' + sequence.getString(i - 2) + ' and AS: ' + sequence.getString(i - 1) :
850
+ sequence.getString(i)
851
+ );
852
+ t.columns.addNewFloat('Cpd MW').init((i: number) => ((i + 1) % 3 == 0) ? DG.FLOAT_NULL : molecularWeight(sequence.get(i)));
853
+ t.columns.addNewFloat('Salt mass').init((i: number) => parseNumber(salt.get(i)) * equivalents.get(i));
854
+ t.columns.addNewCalculated('Batch MW', '${Cpd MW} + ${Salt mass}', DG.COLUMN_TYPE.FLOAT, false);
855
+
856
+ addColumnsPressed = true;
857
+ return newDf = t;
858
+ }
859
+
860
+ const columnsOrder = ["Chemistry", "Number", "Type", "Chemistry Name", "Internal compound ID", "IDP", "Sequence", "Compound Name",
861
+ "Compound Comments", "Salt", "Equivalents", "Purity", "Cpd MW", "Salt mass", "Batch MW", "Source", "ICD", "Owner"];
862
+ let newDf: DG.DataFrame;
863
+ let addColumnsPressed = false;
864
+
865
+ let d = ui.div([
866
+ ui.icons.edit(() => {
867
+ d.innerHTML = '';
868
+ d.append(
869
+ ui.link('Add Columns', async () => {
870
+ await addColumns(table);
871
+ grok.shell.tableView(table.name).grid.columns.setOrder(columnsOrder);
872
+ }, 'Add columns: Compound Name, Compound Components, Cpd MW, Salt mass, Batch MW', ''),
873
+ ui.button('Save SD file', () => saveTableAsSdFile(addColumnsPressed ? newDf : table))
874
+ );
875
+ let view = grok.shell.getTableView(table.name);
876
+ let typeCol = view.grid.col('Type')!;
877
+ let saltCol = view.grid.col('Salt')!;
878
+ saltCol.cellType = 'html';
879
+ typeCol.cellType = 'html';
880
+ view.grid.onCellPrepare(function (gc: DG.GridCell) {
881
+ if (gc.isTableCell) {
882
+ if (gc.gridColumn.name == 'Type')
883
+ gc.style.element = ui.choiceInput('', gc.cell.value, ['AS', 'SS', 'Duplex']).root;
884
+ else if (gc.gridColumn.name == 'Salt')
885
+ gc.style.element = ui.choiceInput('', gc.cell.value, ['Sodium (+1)', 'Sodium (+2)']).root;
886
+ }
887
+ });
888
+
889
+ table.onDataChanged.subscribe((_) => {
890
+ if (table.currentCol.name == 'IDP' && typeof table.currentCell.value != 'number')
891
+ grok.shell.error('Value should be numeric');
892
+ });
893
+ })
894
+ ]);
895
+ grok.shell.v.setRibbonPanels([[d]]);
896
+ }