@datagrok/sequence-translator 0.0.1 → 0.0.5

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
@@ -2,321 +2,527 @@
2
2
  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
- import $ from "cash-dom";
5
+ import * as OCL from 'openchemlib/full.js';
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';
10
+
11
+ export const _package = new DG.Package();
12
+
13
+ const defaultInput = 'AGGTCCTCTTGACTTAGGCC';
14
+ const undefinedInputSequence = 'Type of input sequence is undefined';
15
+ const noTranslationTableAvailable = 'No translation table available';
16
+ const sequenceWasCopied = 'Copied';
17
+ const tooltipSequence = 'Copy sequence';
18
+
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));
24
+ }
6
25
 
7
- import {defineAxolabsPattern} from "./defineAxolabsPattern";
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
+ }
8
43
 
9
- export let _package = new DG.Package();
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
+ }
10
53
 
11
- const undefinedInputSequence: string = "Type of input sequence is undefined";
12
- const smallNumberOfCharacters: string = "Length of input sequence should be at least 10 characters";
13
- const defaultNucleotidesInput: string = "AGGTCCTCTTGACTTAGGCC";
14
- const noTranslationTableAvailable: string = "No translation table available";
15
- const sequenceWasCopied: string = 'Copied!';
16
- const tooltipSequence: string = 'Copy sequence';
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
+ }
17
90
 
18
- //name: Sequence Translator
19
- //tags: app
20
- export function sequenceTranslator(): void {
91
+ outputIndices[synthesizerIndex] += matchedCode.length;
92
+ }
93
+ });
21
94
 
22
- let windows = grok.shell.windows;
23
- windows.showProperties = false;
24
- windows.showToolbox = false;
25
- windows.showHelp = false;
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
+ };
26
104
 
27
- let appMainDescription = ui.info(
28
- [
29
- ui.divText('\n How to convert one sequence:',{style:{'font-weight':'bolder'}}),
30
- ui.divText("Paste sequence into the text field below"),
31
- ui.divText('\n How to convert many sequences:',{style:{'font-weight':'bolder'}}),
32
- ui.divText("1. Drag & drop an Excel or CSV file with sequences into Datagrok. The platform will automatically detect columns with sequences"),
33
- ui.divText('2. Right-click on the column header, then see the \'Convert\' menu'),
34
- ui.divText("This will add the result column to the right of the table"),
35
- ], 'Convert oligonucleotide sequences between Nucleotides, BioSpring, Axolabs, and GCRS representations.'
36
- );
105
+ let possibleTechnologies = getListOfPossibleTechnologiesByFirstMatchedCode(sequence, expectedSynthesizer);
106
+ if (possibleTechnologies.length == 0)
107
+ return { indexOfFirstNotValidCharacter: 0, expectedSynthesizer: null, expectedTechnology: null };
37
108
 
38
- let inputSequenceField = ui.textInput("", defaultNucleotidesInput, async (seq: string) => {
39
- moleculeSvg.innerHTML = "";
40
- let outputSequencesObj = convertSequence(seq);
109
+ outputIndices = Array(possibleTechnologies.length).fill(0);
41
110
 
42
- let tableRows = [];
43
- for (let key of Object.keys(outputSequencesObj).slice(1)) {
44
- // @ts-ignore
45
- tableRows.push({'key': key, 'value': ui.link(outputSequencesObj[key], () => navigator.clipboard.writeText(outputSequencesObj[key]).then(() => grok.shell.info(sequenceWasCopied)), tooltipSequence, '')})
46
- }
111
+ possibleTechnologies.forEach((technology: string, technologyIndex: number) => {
112
+ let codes = Object.keys(map[expectedSynthesizer][technology]);
113
+ while (outputIndices[technologyIndex] < sequence.length) {
47
114
 
48
- outputTableDiv.innerHTML = "";
115
+ let matchedCode = codes
116
+ .find((c) => c == sequence.slice(outputIndices[technologyIndex], outputIndices[technologyIndex] + c.length));
49
117
 
50
- outputTableDiv.append(
51
- ui.div([
52
- DG.HtmlTable.create(
53
- tableRows,
54
- (item: {key: string; value: string;}) => [item.key, item.value],
55
- ['Code', 'Sequence']
56
- ).root
57
- ], 'table')
58
- );
59
- grok.shell.v
60
- semTypeOfInputSequence.textContent = 'Detected input type: ' + outputSequencesObj.type;
61
-
62
- if (!(outputSequencesObj.type == undefinedInputSequence || outputSequencesObj.type == smallNumberOfCharacters)) {
63
- let pi = DG.TaskBarProgressIndicator.create('Rendering molecule...');
64
- try {
65
- let flavor: string = (outputSequencesObj.Nucleotides.includes('U')) ? "RNA_both_caps" : "DNA_both_caps";
66
- let mol = grok.chem.svgMol(<string> await nucleotidesToSmiles(outputSequencesObj.Nucleotides, flavor), 900, 300);
67
- moleculeSvg.append(mol);
68
- } finally {
69
- pi.close();
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;
70
133
  }
134
+
135
+ outputIndices[technologyIndex] += matchedCode.length;
71
136
  }
72
137
  });
73
- let semTypeOfInputSequence = ui.divText('Detected input type: DNA Nucleotides Code');
74
-
75
- let outputTableDiv = ui.div([
76
- DG.HtmlTable.create([
77
- {key: 'Nucleotides', value: ui.link(defaultNucleotidesInput, () => navigator.clipboard.writeText(defaultNucleotidesInput).then(() => grok.shell.info(sequenceWasCopied)), tooltipSequence, '')},
78
- {key: 'BioSpring', value: ui.link(asoGapmersNucleotidesToBioSpring(defaultNucleotidesInput), () => navigator.clipboard.writeText(asoGapmersNucleotidesToBioSpring(defaultNucleotidesInput)).then(() => grok.shell.info(sequenceWasCopied)), tooltipSequence, '')},
79
- {key: 'Axolabs', value: ui.link(noTranslationTableAvailable, () => navigator.clipboard.writeText(defaultNucleotidesInput).then(() => grok.shell.info(sequenceWasCopied)), tooltipSequence, '')},
80
- {key: 'GCRS', value: ui.link(asoGapmersNucleotidesToGcrs(defaultNucleotidesInput), () => navigator.clipboard.writeText(asoGapmersNucleotidesToGcrs(defaultNucleotidesInput)).then(() => grok.shell.info(sequenceWasCopied)), tooltipSequence, '')}
81
- ], (item: {key: string; value: string;}) => [item.key, item.value], ['Code', 'Sequence']).root
82
- ], 'table');
83
-
84
- let accordionWithCmoCodes = ui.accordion();
85
- accordionWithCmoCodes.addPane('CMO Codes', () =>
86
- ui.divH([
87
- DG.HtmlTable.create(
88
- [
89
- {name: "2'MOE-5Me-rU", bioSpring: '5', gcrs: 'moeT'},
90
- {name: "2'MOE-rA", bioSpring: '6', gcrs: 'moeA'},
91
- {name: "2'MOE-5Me-rC", bioSpring: '7', gcrs: 'moe5mC'},
92
- {name: "2'MOE-rG", bioSpring: '8', gcrs: 'moeG'},
93
- {name: "5-Methyl-dC", bioSpring: '9', gcrs: '5mC'},
94
- {name: "ps linkage", bioSpring: '*', gcrs: 'ps'},
95
- {name: "dA", bioSpring: 'A', gcrs: 'A'},
96
- {name: "dC", bioSpring: 'C', gcrs: 'C'},
97
- {name: "dT", bioSpring: 'T', gcrs: 'T'},
98
- {name: "dG", bioSpring: 'G', gcrs: 'G'}
99
- ],
100
- (item: {name: string; bioSpring: string; gcrs: string}) => [item.name, item.bioSpring, item.gcrs],
101
- ['For ASO Gapmers', 'BioSpring', 'GCRS']
102
- ).root,
103
- ui.div([], {style: {width: '50px'}}),
104
- DG.HtmlTable.create(
105
- [
106
- {name: "2'-fluoro-U", axolabs: '1', bioSpring: 'Uf', gcrs: 'fU'},
107
- {name: "2'-fluoro-A", axolabs: '2', bioSpring: 'Af', gcrs: 'fA'},
108
- {name: "2'-fluoro-C", axolabs: '3', bioSpring: 'Cf', gcrs: 'fC'},
109
- {name: "2'-fluoro-G", axolabs: '4', bioSpring: 'Gf', gcrs: 'fG'},
110
- {name: "OMe-rU", axolabs: '5', bioSpring: 'u', gcrs: 'mU'},
111
- {name: "OMe-rA", axolabs: '6', bioSpring: 'a', gcrs: 'mA'},
112
- {name: "OMe-rC", axolabs: '7', bioSpring: 'c', gcrs: 'mC'},
113
- {name: "OMe-rG", axolabs: '8', bioSpring: 'g', gcrs: 'mG'},
114
- {name: "ps linkage", axolabs: '*', bioSpring: 's', gcrs: 'ps'}
115
- ],
116
- (item: {name: string; axolabs: string, bioSpring: string; gcrs: string}) => [item.name, item.bioSpring, item.axolabs, item.gcrs],
117
- ["For 2\'-OMe and 2\'-F modified siRNA", 'BioSpring', 'Axolabs', 'GCRS']
118
- ).root
119
- ]), false
120
- );
121
138
 
122
- let moleculeSvg = ui.block([
123
- grok.chem.svgMol('Cc1cn([C@H]2C[C@H](OP(=O)(O)OC[C@H]3O[C@@H](n4ccc(N)nc4=O)C[C@@H]3OP(=O)(O)OC[C@H]3O[C@@H](n' +
124
- '4cc(C)c(=O)[nH]c4=O)C[C@@H]3OP(=O)(O)OC[C@H]3O[C@@H](n4cc(C)c(=O)[nH]c4=O)C[C@@H]3OP(=O)(O)OC[C@H]3O[C@@H](n4cnc5' +
125
- 'c(=O)[nH]c(N)nc54)C[C@@H]3OP(=O)(O)OC[C@H]3O[C@@H](n4cnc5c(N)ncnc54)C[C@@H]3OP(=O)(O)OC[C@H]3O[C@@H](n4ccc(N)nc4=O)' +
126
- 'C[C@@H]3OP(=O)(O)OC[C@H]3O[C@@H](n4cc(C)c(=O)[nH]c4=O)C[C@@H]3OP(=O)(O)OC[C@H]3O[C@@H](n4cc(C)c(=O)[nH]c4=O)C[C@@H]3OP(=O)(O)' +
127
- 'OC[C@H]3O[C@@H](n4cnc5c(N)ncnc54)C[C@@H]3OP(=O)(O)OC[C@H]3O[C@@H](n4cnc5c(=O)[nH]c(N)nc54)C[C@@H]3OP(=O)(O)OC[C@H]' +
128
- '3O[C@@H](n4cnc5c(=O)[nH]c(N)nc54)C[C@@H]3OP(=O)(O)OC[C@H]3O[C@@H](n4ccc(N)nc4=O)C[C@@H]3OP(=O)(O)OC[C@H]3O[C@@H]' +
129
- '(n4ccc(N)nc4=O)C[C@@H]3OP(=O)(O)O)[C@@H](COP(=O)(O)O[C@H]3C[C@H](n4ccc(N)nc4=O)O[C@@H]3COP(=O)(O)O[C@H]3C[C@H]' +
130
- '(n4ccc(N)nc4=O)O[C@@H]3COP(=O)(O)O[C@H]3C[C@H](n4cc(C)c(=O)[nH]c4=O)O[C@@H]3COP(=O)(O)O[C@H]3C[C@H](n4cnc5c(=O)' +
131
- '[nH]c(N)nc54)O[C@@H]3COP(=O)(O)O[C@H]3C[C@H](n4cnc5c(=O)[nH]c(N)nc54)O[C@@H]3COP(=O)(O)O[C@H]3C[C@H](n4cnc5c(N)' +
132
- 'ncnc54)O[C@@H]3COP(=O)(O)O)O2)c(=O)[nH]c1=O', 900, 300
133
- )
134
- ]);
139
+ const indexOfExpectedTechnology = Math.max.apply(Math, outputIndices);
140
+ const expectedTechnology = possibleTechnologies[outputIndices.indexOf(indexOfExpectedTechnology)];
135
141
 
136
- let tab = ui.tabControl({
137
- 'MAIN': ui.div([
138
- appMainDescription,
139
- ui.panel([
140
- ui.div([
141
- ui.h1('Input sequence'),
142
- ui.div([
143
- inputSequenceField.root
144
- ],'input-base')
145
- ], 'sequenceInput'),
146
- semTypeOfInputSequence,
147
- ui.block([
148
- ui.h1('Output'),
149
- ui.h1('Output'),
150
- outputTableDiv
151
- ]),
152
- accordionWithCmoCodes.root,
153
- moleculeSvg,
154
- ui.button('SAVE SD FILE', async() => {
155
- let outputSequenceObj = convertSequence(inputSequenceField.value);
156
- let flavor: string = outputSequenceObj.Nucleotides.includes('U') ? "RNA_both_caps" : "DNA_both_caps";
157
- let smiles = await nucleotidesToSmiles(outputSequenceObj.Nucleotides, flavor);
158
- smiles = smiles.replace(/@/g, ''); // Remove StereoChemistry on the Nucleic acid chain and remove the Chiral label
159
- //@ts-ignore
160
- let mol = new OCL.Molecule.fromSmiles(smiles);
161
- let result = `${mol.toMolfile()}\n` + '$$$$';
162
- var element = document.createElement('a');
163
- element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(result));
164
- element.setAttribute('download', outputSequenceObj.Nucleotides + '.sdf');
165
- element.click();
166
- })
167
- ], 'sequence')!
168
- ]),
169
- 'AXOLABS': _defineAxolabsPattern()
170
- }).root;
171
- tab.style.height = '100%';
172
- tab.style.width = '100%';
173
-
174
- let v = grok.shell.newView('Sequence Translator', [
175
- tab
176
- ]);
177
- v.box = true;
142
+ return {
143
+ indexOfFirstNotValidCharacter: indexOfFirstNotValidCharacter,
144
+ expectedSynthesizer: expectedSynthesizer,
145
+ expectedTechnology: expectedTechnology
146
+ };
147
+ }
178
148
 
179
- $('.sequence')
180
- .children().css('padding','5px 0');
181
- $('.sequenceInput .input-base').css('margin','0');
182
- $('.sequenceInput textarea')
183
- .css('resize','none')
184
- .css('min-height','50px')
185
- .css('width','100%');
186
- $('.sequenceInput select')
187
- .css('width','100%');
149
+ function sortByStringLengthInDescendingOrder(array: string[]): string[] {
150
+ return array.sort(function(a: string, b: string) { return b.length - a.length; });
188
151
  }
189
152
 
190
- export async function nucleotidesToSmiles(nucleotides: string, flavor: string) {
191
- return await grok.functions.call('SequenceTranslator:convertFastaToSmiles', {
192
- 'sequence_in_fasta_format': nucleotides,
193
- 'flavor': flavor
194
- });
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]))
157
+ for (let code of Object.keys(map[synthesizer][technology]))
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;
165
+ return obj;
195
166
  }
196
167
 
197
- export function isDnaNucleotidesCode(sequence: string): boolean {return /^[ATGC]{10,}$/.test(sequence);}
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 = [];
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);
178
+ while (i < sequence.length) {
179
+ const code = codes.find((s: string) => s == sequence.slice(i, i + s.length))!;
180
+ i += code.length;
181
+ inverted ? codesList.unshift(code) : codesList.push(code);
182
+ }
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);
216
+ }
217
+
218
+ //name: Sequence Translator
219
+ //tags: app
220
+ export function sequenceTranslator(): void {
221
+ const windows = grok.shell.windows;
222
+ windows.showProperties = false;
223
+ windows.showToolbox = false;
224
+ windows.showHelp = false;
225
+
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
+ });
277
+ }
198
278
 
199
- export function isRnaNucleotidesCode(sequence: string): boolean {return /^[AUGC]{10,}$/.test(sequence);}
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();
307
+ }
308
+ }
309
+
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 }
333
+ );
200
334
 
201
- export function isAbiCode(sequence: string): boolean {return /^[5678ATGC]{10,}$/.test(sequence);}
335
+ asoDf.onCurrentCellChanged.subscribe((_) => {
336
+ navigator.clipboard.writeText(asoDf.currentCell.value).then(() => grok.shell.info('Copied'))
337
+ });
202
338
 
203
- export function isAsoGapmerBioSpringCode(sequence: string): boolean {return /^[*56789ATGC]{30,}$/.test(sequence);}
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 }
351
+ );
204
352
 
205
- export function isAsoGapmerGcrsCode(sequence: string): boolean {return /^(?=.*moe)(?=.*5mC)(?=.*ps){30,}/.test(sequence);}
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);
206
360
 
207
- export function isSiRnaBioSpringCode(sequence: string): boolean {return /^[*1-8]{30,}$/.test(sequence);}
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
+ );
208
370
 
209
- export function isSiRnaAxolabsCode(sequence: string): boolean {return /^[fsACGUacgu]{20,}$/.test(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,
386
+ ui.div([
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
+ });
210
406
 
211
- export function isSiRnaGcrsCode(sequence: string): boolean {return (sequence.slice(0, 3) == 'moe' && /^[fmpsACGU]{30,}$/.test(sequence));}
407
+ let v = grok.shell.newView('Sequence Translator', [tabControl]);
408
+ v.box = true;
212
409
 
213
- export function isGcrsCode(sequence: string): boolean {return /^[fmpsACGU]{30,}$/.test(sequence);}
410
+ const switchInput = ui.switchInput('Codes', true, (v: boolean) => (v) ?
411
+ $(codesTablesDiv).show() :
412
+ $(codesTablesDiv).hide()
413
+ );
214
414
 
215
- export function isOP100Code(sequence: string): boolean {return /^[acgu*]{10,}$/.test(sequence);}
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]);
216
434
 
217
- export function isMM12Code(sequence: string): boolean {return /^[IiJjKkLlEeFfGgHhQq]{10,}$/.test(sequence);}
435
+ $('.sequence')
436
+ .children().css('padding','5px 0');
437
+ $('.sequenceInput .input-base')
438
+ .css('margin','0');
439
+ $('.sequenceInput textarea')
440
+ .css('resize','none')
441
+ .css('min-height','50px')
442
+ .css('width','100%')
443
+ .attr('spellcheck', 'false');
444
+ $('.sequenceInput select')
445
+ .css('width','100%');
446
+ }
218
447
 
219
- function convertSequence(seq: string) {
220
- seq = seq.replace(/\s/g, '');
221
- if (seq.length < 10)
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)
222
453
  return {
223
- type: smallNumberOfCharacters,
224
- Nucleotides: smallNumberOfCharacters,
225
- BioSpring: smallNumberOfCharacters,
226
- Axolabs: smallNumberOfCharacters,
227
- GCRS: smallNumberOfCharacters
454
+ // type: '',
455
+ indexOfFirstNotValidCharacter: JSON.stringify(output),
456
+ Error: undefinedInputSequence
228
457
  };
229
- if (isDnaNucleotidesCode(seq))
458
+ if (output.expectedSynthesizer == SYNTHESIZERS.RAW_NUCLEOTIDES && output.expectedTechnology == TECHNOLOGIES.DNA)
230
459
  return {
231
- type: "DNA Nucleotides Code",
460
+ type: SYNTHESIZERS.RAW_NUCLEOTIDES + ' ' + TECHNOLOGIES.DNA,
232
461
  Nucleotides: seq,
233
462
  BioSpring: asoGapmersNucleotidesToBioSpring(seq),
234
- Axolabs: noTranslationTableAvailable,
235
463
  GCRS: asoGapmersNucleotidesToGcrs(seq)
236
464
  };
237
- if (isAbiCode(seq))
465
+ if (output.expectedSynthesizer == SYNTHESIZERS.BIOSPRING && output.expectedTechnology == TECHNOLOGIES.ASO_GAPMERS)
238
466
  return {
239
- type: "ABI Code",
240
- Nucleotides: noTranslationTableAvailable,
241
- GCRS: noTranslationTableAvailable,
242
- MM12: noTranslationTableAvailable,
243
- OP100: noTranslationTableAvailable,
244
- ABI: seq
245
- };
246
- if (isAsoGapmerBioSpringCode(seq))
247
- return {
248
- type: "ASO Gapmers / BioSpring Code",
467
+ type: SYNTHESIZERS.BIOSPRING + ' ' + TECHNOLOGIES.ASO_GAPMERS,
249
468
  Nucleotides: asoGapmersBioSpringToNucleotides(seq),
250
469
  BioSpring: seq,
251
- Axolabs: noTranslationTableAvailable,
252
470
  GCRS: asoGapmersBioSpringToGcrs(seq)
253
471
  };
254
- if (isAsoGapmerGcrsCode(seq))
472
+ if (output.expectedSynthesizer == SYNTHESIZERS.GCRS && output.expectedTechnology == TECHNOLOGIES.ASO_GAPMERS)
255
473
  return {
256
- type: "ASO Gapmers / GCRS Code",
474
+ type: SYNTHESIZERS.GCRS + ' ' + TECHNOLOGIES.ASO_GAPMERS,
257
475
  Nucleotides: asoGapmersGcrsToNucleotides(seq),
258
476
  BioSpring: asoGapmersGcrsToBioSpring(seq),
259
- Axolabs: noTranslationTableAvailable,
477
+ Mermade12: gcrsToMermade12(seq),
260
478
  GCRS: seq
261
479
  };
262
- if (isRnaNucleotidesCode(seq))
480
+ if (output.expectedSynthesizer == SYNTHESIZERS.RAW_NUCLEOTIDES && output.expectedTechnology == TECHNOLOGIES.RNA)
263
481
  return {
264
- type: "RNA Nucleotides Code",
482
+ type: SYNTHESIZERS.RAW_NUCLEOTIDES + ' ' + TECHNOLOGIES.RNA,
265
483
  Nucleotides: seq,
266
484
  BioSpring: siRnaNucleotideToBioSpringSenseStrand(seq),
267
485
  Axolabs: siRnaNucleotideToAxolabsSenseStrand(seq),
268
486
  GCRS: siRnaNucleotidesToGcrs(seq)
269
487
  };
270
- if (isSiRnaBioSpringCode(seq))
488
+ if (output.expectedSynthesizer == SYNTHESIZERS.BIOSPRING && output.expectedTechnology == TECHNOLOGIES.SI_RNA)
271
489
  return {
272
- type: "siRNA / bioSpring Code",
490
+ type: SYNTHESIZERS.BIOSPRING + ' ' + TECHNOLOGIES.SI_RNA,
273
491
  Nucleotides: siRnaBioSpringToNucleotides(seq),
274
492
  BioSpring: seq,
275
493
  Axolabs: siRnaBioSpringToAxolabs(seq),
276
494
  GCRS: siRnaBioSpringToGcrs(seq)
277
495
  };
278
- if (isSiRnaAxolabsCode(seq))
496
+ if (output.expectedSynthesizer == SYNTHESIZERS.AXOLABS && output.expectedTechnology == TECHNOLOGIES.SI_RNA)
279
497
  return {
280
- type: "siRNA / Axolabs Code",
498
+ type: SYNTHESIZERS.AXOLABS + ' ' + TECHNOLOGIES.SI_RNA,
281
499
  Nucleotides: siRnaAxolabsToNucleotides(seq),
282
500
  BioSpring: siRnaAxolabsToBioSpring(seq),
283
501
  Axolabs: seq,
284
502
  GCRS: siRnaAxolabsToGcrs(seq)
285
503
  };
286
- if (isSiRnaGcrsCode(seq))
504
+ if (output.expectedSynthesizer == SYNTHESIZERS.GCRS && output.expectedTechnology == TECHNOLOGIES.SI_RNA)
287
505
  return {
288
- type: "siRNA / GCRS Code",
506
+ type: SYNTHESIZERS.GCRS + ' ' + TECHNOLOGIES.SI_RNA,
289
507
  Nucleotides: siRnaGcrsToNucleotides(seq),
290
508
  BioSpring: siRnaGcrsToBioSpring(seq),
291
509
  Axolabs: siRnaGcrsToAxolabs(seq),
510
+ MM12: gcrsToMermade12(seq),
292
511
  GCRS: seq
293
512
  };
294
- if (isGcrsCode(seq))
513
+ if (output.expectedSynthesizer == SYNTHESIZERS.GCRS)
295
514
  return {
296
- type: "GCRS Code",
515
+ type: SYNTHESIZERS.GCRS,
297
516
  Nucleotides: gcrsToNucleotides(seq),
298
517
  GCRS: seq,
299
- MM12: gcrsToMM12(seq),
300
- OP100: gcrsToOP100(seq),
301
- ABI: gcrsToABI(seq)
518
+ Mermade12: gcrsToMermade12(seq)
302
519
  }
303
- if (isMM12Code(seq))
304
- return {
305
- type: "MM12 Code",
306
- Nucleotides: noTranslationTableAvailable,
307
- GCRS: noTranslationTableAvailable,
308
- MM12: seq,
309
- OP100: noTranslationTableAvailable,
310
- ABI: noTranslationTableAvailable
311
- };
312
- if (isOP100Code(seq))
520
+ if (output.expectedSynthesizer == SYNTHESIZERS.MERMADE_12)
313
521
  return {
314
- type: "OP100 Code",
522
+ type: SYNTHESIZERS.MERMADE_12,
315
523
  Nucleotides: noTranslationTableAvailable,
316
524
  GCRS: noTranslationTableAvailable,
317
- MM12: noTranslationTableAvailable,
318
- OP100: seq,
319
- ABI: noTranslationTableAvailable
525
+ Mermade12: seq
320
526
  };
321
527
  return {
322
528
  type: undefinedInputSequence,
@@ -327,49 +533,49 @@ function convertSequence(seq: string) {
327
533
  //name: asoGapmersNucleotidesToBioSpring
328
534
  //input: string nucleotides {semType: DNA nucleotides}
329
535
  //output: string result {semType: BioSpring / Gapmers}
330
- export function asoGapmersNucleotidesToBioSpring(nucleotides: string) {
536
+ export function asoGapmersNucleotidesToBioSpring(nucleotides: string): string {
331
537
  let count: number = -1;
332
- const objForEdges: {[index: string]: string} = {"T": "5*", "A": "6*", "C": "7*", "G": "8*"};
333
- const objForCenter: {[index: string]: string} = {"C": "9*", "A": "A*", "T": "T*", "G": "G*"};
334
- 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) {
335
541
  count++;
336
542
  return (count > 4 && count < 15) ? objForCenter[x] : objForEdges[x];
337
- }).slice(0, 2 * count + 1);
543
+ }).slice(0, (nucleotides.endsWith('(invabasic)') || nucleotides.endsWith('(GalNAc-2-JNJ)')) ? nucleotides.length : 2 * count + 1);
338
544
  }
339
545
 
340
546
  //name: asoGapmersNucleotidesToGcrs
341
547
  //input: string nucleotides {semType: DNA nucleotides}
342
548
  //output: string result {semType: GCRS / Gapmers}
343
- export function asoGapmersNucleotidesToGcrs(nucleotides: string) {
549
+ export function asoGapmersNucleotidesToGcrs(nucleotides: string): string {
344
550
  let count: number = -1;
345
- const objForEdges: {[index: string]: string} = {"T": "moeUnps", "A": "moeAnps", "C": "moe5mCnps", "G": "moeGnps"};
346
- const objForCenter: {[index: string]: string} = {"C": "5mCps", "A": "Aps", "T": "Tps", "G": "Gps"};
347
- 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) {
348
554
  count++;
349
555
  if (count < 5) return (count == 4) ? objForEdges[x].slice(0, -3) + 'ps' : objForEdges[x];
350
556
  if (count < 15) return (count == 14) ? objForCenter[x].slice(0, -2) + 'nps' : objForCenter[x];
351
557
  return objForEdges[x];
352
- }).slice(0, -3);
558
+ }).slice(0, (nucleotides.endsWith('(invabasic)') || nucleotides.endsWith('(GalNAc-2-JNJ)')) ? nucleotides.length : -3);
353
559
  }
354
560
 
355
561
  //name: asoGapmersBioSpringToNucleotides
356
562
  //input: string nucleotides {semType: BioSpring / Gapmers}
357
563
  //output: string result {semType: DNA nucleotides}
358
- export function asoGapmersBioSpringToNucleotides(nucleotides: string) {
359
- const obj: {[index: string]: string} = {"*": "", "5": "T", "6": "A", "7": "C", "8": "G", "9": "C"};
360
- 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];});
361
567
  }
362
568
 
363
569
  //name: asoGapmersBioSpringToGcrs
364
570
  //input: string nucleotides {semType: BioSpring / Gapmers}
365
571
  //output: string result {semType: GCRS / Gapmers}
366
- export function asoGapmersBioSpringToGcrs(nucleotides: string) {
572
+ export function asoGapmersBioSpringToGcrs(nucleotides: string): string {
367
573
  let count: number = -1;
368
- const obj: {[index: string]: string} = {
369
- "5*": "moeUnps", "6*": "moeAnps", "7*": "moe5mCnps", "8*": "moeGnps", "9*": "5mCps", "A*": "Aps", "T*": "Tps",
370
- "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'
371
577
  };
372
- 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) {
373
579
  count++;
374
580
  return (count == 4) ? obj[x].slice(0, -3) + 'ps' : (count == 14) ? obj[x].slice(0, -2) + 'nps' : obj[x];
375
581
  });
@@ -378,103 +584,103 @@ export function asoGapmersBioSpringToGcrs(nucleotides: string) {
378
584
  //name: asoGapmersGcrsToBioSpring
379
585
  //input: string nucleotides {semType: GCRS / Gapmers}
380
586
  //output: string result {semType: BioSpring / Gapmers}
381
- export function asoGapmersGcrsToBioSpring(nucleotides: string) {
382
- const obj: {[index: string]: string} = {
383
- "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'
384
590
  };
385
- 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];});
386
592
  }
387
593
 
388
594
  //name: asoGapmersGcrsToNucleotides
389
595
  //input: string nucleotides {semType: GCRS / Gapmers}
390
596
  //output: string result {semType: DNA nucleotides}
391
597
  export function asoGapmersGcrsToNucleotides(nucleotides: string) {
392
- const obj: {[index: string]: string} = {"moe": "", "5m": "", "n": "", "ps": "", "U": "T"};
393
- 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];});
394
600
  }
395
601
 
396
602
  //name: siRnaBioSpringToNucleotides
397
603
  //input: string nucleotides {semType: BioSpring / siRNA}
398
604
  //output: string result {semType: RNA nucleotides}
399
605
  export function siRnaBioSpringToNucleotides(nucleotides: string) {
400
- const obj: {[index: string]: string} = {"1": "U", "2": "A", "3": "C", "4": "G", "5": "U", "6": "A", "7": "C", "8": "G", "*": ""};
401
- 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];});
402
608
  }
403
609
 
404
610
  //name: siRnaBioSpringToAxolabs
405
611
  //input: string nucleotides {semType: BioSpring / siRNA}
406
612
  //output: string result {semType: Axolabs / siRNA}
407
613
  export function siRnaBioSpringToAxolabs(nucleotides: string) {
408
- const obj: {[index: string]: string} = {"1": "Uf", "2": "Af", "3": "Cf", "4": "Gf", "5": "u", "6": "a", "7": "c", "8": "g", "*": "s"};
409
- 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];});
410
616
  }
411
617
 
412
618
  //name: siRnaBioSpringToGcrs
413
619
  //input: string nucleotides {semType: BioSpring / siRNA}
414
620
  //output: string result {semType: GCRS}
415
621
  export function siRnaBioSpringToGcrs(nucleotides: string) {
416
- const obj: {[index: string]: string} = {"1": "fU", "2": "fA", "3": "fC", "4": "fG", "5": "mU", "6": "mA", "7": "mC", "8": "mG", "*": "ps"};
417
- 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];});
418
624
  }
419
625
 
420
626
  //name: siRnaAxolabsToGcrs
421
627
  //input: string nucleotides {semType: Axolabs / siRNA}
422
628
  //output: string result {semType: GCRS}
423
629
  export function siRnaAxolabsToGcrs(nucleotides: string) {
424
- const obj: {[index: string]: string} = {
425
- "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'
426
632
  };
427
- 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];});
428
634
  }
429
635
 
430
636
  //name: siRnaAxolabsToBioSpring
431
637
  //input: string nucleotides {semType: Axolabs / siRNA}
432
638
  //output: string result {semType: BioSpring / siRNA}
433
639
  export function siRnaAxolabsToBioSpring(nucleotides: string) {
434
- const obj: {[index: string]: string} = {
435
- "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': '*'
436
642
  };
437
- 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];});
438
644
  }
439
645
 
440
646
  //name: siRnaAxolabsToNucleotides
441
647
  //input: string nucleotides {semType: Axolabs / siRNA}
442
648
  //output: string result {semType: RNA nucleotides}
443
649
  export function siRnaAxolabsToNucleotides(nucleotides: string) {
444
- const obj: {[index: string]: string} = {
445
- "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': ''
446
652
  };
447
- 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];});
448
654
  }
449
655
 
450
656
  //name: siRnaGcrsToNucleotides
451
657
  //input: string nucleotides {semType: GCRS}
452
658
  //output: string result {semType: RNA nucleotides}
453
659
  export function siRnaGcrsToNucleotides(nucleotides: string) {
454
- const obj: {[index: string]: string} = {
455
- "fU": "U", "fA": "A", "fC": "C", "fG": "G", "mU": "U", "mA": "A", "mC": "C", "mG": "G", "ps": "", "s": ""
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': ''
456
662
  };
457
- return nucleotides.replace(/(fU|fA|fC|fG|mU|mA|mC|mG|ps|s)/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];});
458
664
  }
459
665
 
460
666
  //name: siRnaGcrsToBioSpring
461
667
  //input: string nucleotides {semType: GCRS}
462
668
  //output: string result {semType: BioSpring / siRNA}
463
669
  export function siRnaGcrsToBioSpring(nucleotides: string) {
464
- const obj: {[index: string]: string} = {
465
- "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': '*'
466
672
  };
467
- 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];});
468
674
  }
469
675
 
470
676
  //name: siRnaGcrsToAxolabs
471
677
  //input: string nucleotides {semType: GCRS}
472
678
  //output: string result {semType: Axolabs / siRNA}
473
679
  export function siRnaGcrsToAxolabs(nucleotides: string) {
474
- const obj: {[index: string]: string} = {
475
- "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'
476
682
  };
477
- 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];});
478
684
  }
479
685
 
480
686
  //name: siRnaNucleotideToBioSpringSenseStrand
@@ -482,11 +688,11 @@ export function siRnaGcrsToAxolabs(nucleotides: string) {
482
688
  //output: string result {semType: BioSpring / siRNA}
483
689
  export function siRnaNucleotideToBioSpringSenseStrand(nucleotides: string) {
484
690
  let count: number = -1;
485
- const objForLeftEdge: {[index: string]: string} = {"A": "6*", "U": "5*", "G": "8*", "C": "7*"};
486
- const objForRightEdge: {[index: string]: string} = {"A": "*6", "U": "*5", "G": "*8", "C": "*7"};
487
- const objForOddIndices: {[index: string]: string} = {"A": "6", "U": "5", "G": "8", "C": "7"};
488
- const objForEvenIndices: {[index: string]: string} = {"A": "2", "U": "1", "G": "4", "C": "3"};
489
- 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) {
490
696
  count++;
491
697
  if (count < 2) return objForLeftEdge[x];
492
698
  if (count > nucleotides.length - 3) return objForRightEdge[x];
@@ -499,11 +705,11 @@ export function siRnaNucleotideToBioSpringSenseStrand(nucleotides: string) {
499
705
  //output: string result {semType: GCRS}
500
706
  export function siRnaNucleotidesToGcrs(nucleotides: string) {
501
707
  let count: number = -1;
502
- const objForLeftEdge: {[index: string]: string} = {"A": "mAps", "U": "mUps", "G": "mGps", "C": "mCps"};
503
- const objForRightEdge: {[index: string]: string} = {"A": "psmA", "U": "psmU", "G": "psmG", "C": "psmC"};
504
- const objForEvenIndices: {[index: string]: string} = {"A": "fA", "U": "fU", "G": "fG", "C": "fC"};
505
- const objForOddIndices: {[index: string]: string} = {"A": "mA", "U": "mU", "G": "mG", "C": "mC"};
506
- 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) {
507
713
  count++;
508
714
  if (count < 2) return objForLeftEdge[x];
509
715
  if (count > nucleotides.length - 3) return objForRightEdge[x];
@@ -516,10 +722,10 @@ export function siRnaNucleotidesToGcrs(nucleotides: string) {
516
722
  //output: string result {semType: Axolabs}
517
723
  export function siRnaNucleotideToAxolabsSenseStrand(nucleotides: string) {
518
724
  let count: number = -1;
519
- const objForLeftEdge: {[index: string]: string} = {"A": "as", "U": "us", "G": "gs", "C": "cs"};
520
- const objForSomeIndices: {[index: string]: string} = {"A": "Af", "U": "Uf", "G": "Gf", "C": "Cf"};
521
- const obj: {[index: string]: string} = {"A": "a", "U": "u", "G": "g", "C": "c"};
522
- 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) {
523
729
  count++;
524
730
  if (count < 2) return objForLeftEdge[x];
525
731
  if (count == 6 || (count > 7 && count < 11)) return objForSomeIndices[x]
@@ -533,11 +739,11 @@ export function siRnaNucleotideToAxolabsSenseStrand(nucleotides: string) {
533
739
  //output: string result {semType: Axolabs}
534
740
  export function siRnaNucleotideToAxolabsAntisenseStrand(nucleotides: string) {
535
741
  let count: number = -1;
536
- const objForSmallLinkages: {[index: string]: string} = {"A": "as", "U": "us", "G": "gs", "C": "cs"};
537
- const objForBigLinkages: {[index: string]: string} = {"A": "Afs", "U": "Ufs", "G": "Gfs", "C": "Cfs"};
538
- const objForSomeIndices: {[index: string]: string} = {"A": "Af", "U": "Uf", "G": "Gf", "C": "Cf"};
539
- const obj: {[index: string]: string} = {"A": "a", "U": "u", "G": "g", "C": "c"};
540
- 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) {
541
747
  count++;
542
748
  if (count > 19 && count < 22) return objForSmallLinkages[x];
543
749
  if (count == 0) return 'us';
@@ -550,69 +756,141 @@ export function siRnaNucleotideToAxolabsAntisenseStrand(nucleotides: string) {
550
756
  //input: string nucleotides {semType: GCRS}
551
757
  //output: string result {semType: RNA nucleotides}
552
758
  export function gcrsToNucleotides(nucleotides: string) {
553
- const obj: {[index: string]: string} = {
554
- "mAps": "A", "mUps": "U", "mGps": "G", "mCps": "C", "fAps": "A", "fUps": "U", "fGps": "G", "fCps": "C",
555
- "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'
556
762
  };
557
- 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];});
558
764
  }
559
765
 
560
- //name: gcrsToOP100
766
+ //name: gcrsToMermade12
561
767
  //input: string nucleotides {semType: GCRS}
562
- //output: string result {semType: OP100}
563
- export function gcrsToOP100(nucleotides: string) {
564
- let count: number = -1;
565
- const objForEvenIndicesAtLeftEdge: {[index: string]: string} = {
566
- "mAps": "a", "mUps": "u", "mGps": "g", "mCps": "c", "fAps": "a", "fUps": "u", "fGps": "g", "fCps": "c"
567
- };
568
- const objForOddIndicesAtLeftEdge: {[index: string]: string} = {
569
- "mAps": "a*", "mUps": "u*", "mGps": "g*", "mCps": "c*", "fAps": "a*", "fUps": "u*", "fGps": "g*", "fCps": "c*"
570
- };
571
- // const objForEvenIndicesAtRightEdge: {[index: string]: string} = {
572
- // "fU": "u*", "fA": "a*", "fC": "c*", "fG": "g*", "mU": "u*", "mA": "a*", "mC": "c*", "mG": "g*"
573
- // };
574
- const objForOddIndicesAtRightEdge: {[index: string]: string} = {
575
- "mAps": "a", "mUps": "u", "mGps": "g", "mCps": "c", "fAps": "a", "fUps": "u", "fGps": "g", "fCps": "c"
576
- };
577
- const objForEvenIndicesAtCenter: {[index: string]: string} = {
578
- "fU": "u*", "fA": "a*", "fC": "c*", "fG": "g*", "mU": "u*", "mA": "a*", "mC": "c*", "mG": "g*"
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'
579
773
  };
580
- const objForOddIndicesAtCenter: {[index: string]: string} = {
581
- "fU": "u", "fA": "a", "fC": "c", "fG": "g", "mU": "u", "mA": "a", "mC": "c", "mG": "g"
582
- };
583
- return nucleotides.replace(/(mAps|mUps|mGps|mCps|fAps|fUps|fGps|fCps|fU|fA|fC|fG|mU|mA|mC|mG)/g, function (x: string) {
584
- count++;
585
- if (count < 3) return (count % 2 == 0) ? objForEvenIndicesAtLeftEdge[x] : objForOddIndicesAtLeftEdge[x];
586
- if (count == 19) return objForOddIndicesAtRightEdge[x];
587
- return (count % 2 == 1) ? objForEvenIndicesAtCenter[x] : objForOddIndicesAtCenter[x];
588
- });
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]});
589
775
  }
590
776
 
591
- //name: gcrsToMM12
592
- //input: string nucleotides {semType: GCRS}
593
- //output: string result {semType: MM12}
594
- export function gcrsToMM12(nucleotides: string) {
595
- const obj: {[index: string]: string} = {
596
- "mAps": "e", "mUps": "h", "mGps": "g", "mCps": "f", "fAps": "i", "fUps": "l", "fGps": "k", "fCps": "j", "fU": "L",
597
- "fA": "I", "fC": "J", "fG": "K", "mU": "H", "mA": "E", "mC": "F", "mG": "G"
598
- };
599
- 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]});
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();
600
800
  }
601
801
 
602
- //name: gcrsToABI
603
- //input: string nucleotides {semType: GCRS}
604
- //output: string result {semType: ABI}
605
- export function gcrsToABI(nucleotides: string) {
606
- let count: number = -1;
607
- const objForEdges: {[index: string]: string} = {"moeA": "5", "(5m)moeC": "6", "moeG": "7", "moeT": "8"};
608
- const objForCenter: {[index: string]: string} = {"A": "A", "T": "T", "(5m)C": "C", "G": "G"};
609
- return nucleotides.replace(/(moeA|\(5m\)moeC|moeG|moeT|A|T|\(5m\)C|G)/g, function (x: string) {
610
- count++;
611
- return (5 < count || count < 15) ? objForCenter[x] : objForEdges[x];
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);
612
814
  });
613
815
  }
614
816
 
615
- //name: defineAxolabsPattern
616
- export function _defineAxolabsPattern() {
617
- return defineAxolabsPattern();
618
- }
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
+ }