@datagrok/sequence-translator 1.2.1 → 1.2.3

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.
Files changed (30) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/dist/package-test.js +1 -1
  3. package/dist/package-test.js.map +1 -1
  4. package/dist/package.js +1 -1
  5. package/dist/package.js.map +1 -1
  6. package/package.json +1 -1
  7. package/src/model/parsing-validation/format-handler.ts +147 -0
  8. package/src/model/translator-app/format-converter.ts +68 -0
  9. package/src/package.ts +3 -3
  10. package/src/plugins/mermade.ts +8 -8
  11. package/src/tests/formats-support.ts +1 -1
  12. package/src/tests/formats-to-helm.ts +1 -1
  13. package/src/tests/helm-to-nucleotides.ts +1 -1
  14. package/src/view/apps/oligo-pattern.ts +4 -4
  15. package/src/view/apps/oligo-structure.ts +16 -6
  16. package/src/view/apps/oligo-translator.ts +3 -3
  17. package/src/view/utils/colored-input/colored-text-input.ts +2 -2
  18. package/src/view/utils/draw-molecule.ts +1 -1
  19. package/src/model/format-translation/format-converter.ts +0 -109
  20. /package/src/model/{axolabs → pattern-app}/const.ts +0 -0
  21. /package/src/model/{axolabs → pattern-app}/draw-svg.ts +0 -0
  22. /package/src/model/{axolabs → pattern-app}/helpers.ts +0 -0
  23. /package/src/model/{axolabs/axolabs-tab.ts → pattern-app/oligo-pattern.ts} +0 -0
  24. /package/src/model/{sequence-to-structure-utils → structure-app}/const.ts +0 -0
  25. /package/src/model/{sequence-to-structure-utils → structure-app}/mol-transformations.ts +0 -0
  26. /package/src/model/{sequence-to-structure-utils → structure-app}/monomer-code-parser.ts +0 -0
  27. /package/src/model/{sequence-to-structure-utils/sdf-tab.ts → structure-app/oligo-structure.ts} +0 -0
  28. /package/src/model/{sequence-to-structure-utils → structure-app}/sequence-to-molfile.ts +0 -0
  29. /package/src/model/{format-translation → translator-app}/const.ts +0 -0
  30. /package/src/model/{format-translation → translator-app}/conversion-utils.ts +0 -0
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@datagrok/sequence-translator",
3
3
  "friendlyName": "Sequence Translator",
4
- "version": "1.2.1",
4
+ "version": "1.2.3",
5
5
  "author": {
6
6
  "name": "Alexey Choposky",
7
7
  "email": "achopovsky@datagrok.ai"
@@ -0,0 +1,147 @@
1
+ /* Do not change these import lines to match external modules in webpack configuration */
2
+ import * as grok from 'datagrok-api/grok';
3
+ import * as ui from 'datagrok-api/ui';
4
+ import * as DG from 'datagrok-api/dg';
5
+
6
+ import {codesToHelmDictionary} from '../data-loading-utils/json-loader';
7
+ import {CodesInfo} from '../data-loading-utils/types';
8
+ import {DEFAULT_FORMATS} from '../const';
9
+ import {GROUP_TYPE, PHOSPHATE_SYMBOL} from '../translator-app/const';
10
+
11
+ const inverseLengthComparator = (a: string, b: string) => b.length - a.length;
12
+
13
+ export class FormatHandler {
14
+ constructor() {
15
+ this.formats = this.getFormats();
16
+ }
17
+
18
+ /** Includes all formats except HELM (the "default" one) */
19
+ private formats: string[];
20
+
21
+ /** All format names except HELM (the "default" one) */
22
+ getFormatNames(): string[] {
23
+ return this.formats.sort();
24
+ };
25
+
26
+ getCodesByFormat(format: string): string[] {
27
+ this.validateFormat(format);
28
+
29
+ if (this.isHelm(format))
30
+ throw new Error(`Codes cannot be obtained for HELM`);
31
+ return this.getFormatCodes(format);
32
+ }
33
+
34
+ getHelmToFormatDict(format: string): {[key: string]: string} {
35
+ this.validateFormat(format);
36
+
37
+ const codesInfoObject = codesToHelmDictionary[format] as CodesInfo;
38
+ const dict = getHelmToCodeDict(codesInfoObject);
39
+ return dict;
40
+ }
41
+
42
+ getFormatToHelmDict(format: string): {[key: string]: string} {
43
+ this.validateFormat(format);
44
+
45
+ const codesInfoObject = codesToHelmDictionary[format] as CodesInfo;
46
+ const dict = Object.assign({}, ...Object.values(codesInfoObject)) as {[code: string]: string};
47
+ return dict;
48
+ }
49
+
50
+ /** Get helm codes for the specified format */
51
+ getTargetFormatHelmCodes(format: string): string[] {
52
+ this.validateFormat(format);
53
+
54
+ const dict = this.getHelmToFormatDict(format);
55
+ const helmCodes = Object.keys(dict).sort(inverseLengthComparator);
56
+ return helmCodes;
57
+ }
58
+
59
+ getTargetFormatHelmCodesRegExp(format: string): RegExp {
60
+ this.validateFormat(format);
61
+
62
+ const helmCodes = this.getTargetFormatHelmCodes(format);
63
+ const helmRegExp = new RegExp(getRegExpPattern(helmCodes) + '|.', 'g');
64
+ return helmRegExp;
65
+ }
66
+
67
+ getFormatRegExp(format: string): RegExp {
68
+ this.validateFormat(format);
69
+
70
+ if (this.isHelm(format))
71
+ throw new Error(`Helm RegExp can be built for non-HELM target formats`);
72
+ return this.getNonHelmFormatRegExp(format);
73
+ }
74
+
75
+ getPhosphateHelmCodesRegExp(format: string): RegExp {
76
+ this.validateFormat(format);
77
+
78
+ const codesInfoObject = codesToHelmDictionary[format] as CodesInfo;
79
+ const phosphateHELMCodes = Array.from(
80
+ new Set(Object.values(codesInfoObject[GROUP_TYPE.LINKAGE]))
81
+ ).sort(inverseLengthComparator);
82
+ const phosphateHELMPattern = getRegExpPattern(phosphateHELMCodes);
83
+ const phosphateRegExp = new RegExp(`${PHOSPHATE_SYMBOL}\.(${phosphateHELMPattern})`, 'g');
84
+ return phosphateRegExp;
85
+ }
86
+
87
+ isValidFormat(format: string): boolean {
88
+ return this.formats.includes(format);
89
+ }
90
+
91
+ private getFormats(): string[] {
92
+ return Object.keys(codesToHelmDictionary);
93
+ }
94
+
95
+ private validateFormat(format: string) {
96
+ if (!this.isValidFormat(format))
97
+ throw new Error(`Invalid format: ${format}`);
98
+ }
99
+
100
+ private isHelm(format: string): boolean {
101
+ return format === DEFAULT_FORMATS.HELM;
102
+ }
103
+
104
+ private getFormatCodes(format: string): string[] {
105
+ const dict = this.getFormatToHelmDict(format);
106
+ const formatCodes = Object.keys(dict).sort(inverseLengthComparator);
107
+ return formatCodes;
108
+ }
109
+
110
+ private getNonHelmFormatRegExp(format: string): RegExp {
111
+ const formatCodes = this.getCodesByFormat(format);
112
+ const formatRegExp = new RegExp(getRegExpPattern(formatCodes) + '|\\([^()]*\\)|.', 'g'); // the added group before '|.' is to avoid mismatch inside parenths
113
+ return formatRegExp;
114
+ }
115
+ }
116
+
117
+ export function getRegExpPattern(arr: string[]): string {
118
+ const negativeLookBehind = '(?<!\\([^()]*)'; // not '(' followed by non-parenths
119
+ const negativeLookAhead = '(?![^()]*\\))'; // not ')' preceded by non-parenths
120
+ const escaped = arr.map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
121
+ .map((key) => {
122
+ if (!key.includes('(') && !key.includes(')'))
123
+ return `${negativeLookBehind}${key}${negativeLookAhead}`;
124
+ return key;
125
+ });
126
+ const result = escaped.join('|');
127
+ return result;
128
+ }
129
+
130
+ function getHelmToCodeDict(infoObj: CodesInfo) {
131
+ const result: {[key: string]: string | string[]} = {};
132
+ Object.values(infoObj).forEach((obj: {[code: string]: string}) => {
133
+ Object.entries(obj).forEach(([code, helm]) => {
134
+ const key = helm.replace(/\)p/g, ')').replace(/\]p/g, ']');
135
+ if (result[key] === undefined) {
136
+ result[key] = [code];
137
+ } else {
138
+ (result[key] as string[]).push(code);
139
+ }
140
+ })
141
+ });
142
+ Object.entries(result).forEach(([key, value]) => {
143
+ const sorted = (value as string[]).sort(inverseLengthComparator);
144
+ result[key] = sorted[0] as string;
145
+ })
146
+ return result as {[key: string]: string};
147
+ }
@@ -0,0 +1,68 @@
1
+ import * as DG from 'datagrok-api/dg';
2
+ import {DEFAULT_FORMATS} from '../const';
3
+ import {PHOSPHATE_SYMBOL, UNKNOWN_SYMBOL} from './const';
4
+ import {FormatHandler, getRegExpPattern} from '../parsing-validation/format-handler';
5
+
6
+ const HELM_WRAPPER = {
7
+ LEFT: 'RNA1{',
8
+ RIGHT: '}$$$$',
9
+ };
10
+
11
+ export class FormatConverter {
12
+ constructor(private readonly sequence: string, private readonly sourceFormat: string) { };
13
+
14
+ private formats = new FormatHandler();
15
+
16
+ convertTo(targetFormat: string): string {
17
+ const formats = this.formats.getFormatNames();
18
+
19
+ if (this.sourceFormat === DEFAULT_FORMATS.HELM && formats.includes(targetFormat))
20
+ return this.helmToFormat(this.sequence, targetFormat);
21
+ else if (formats.includes(this.sourceFormat) && targetFormat === DEFAULT_FORMATS.HELM)
22
+ return this.formatToHelm(this.sequence, this.sourceFormat);
23
+ else if ([this.sourceFormat, targetFormat].every((el) => formats.includes(el))) {
24
+ const helm = this.formatToHelm(this.sequence, this.sourceFormat);
25
+ return this.helmToFormat(helm, targetFormat);
26
+ }
27
+ else {
28
+ throw new Error (`ST: unsupported translation direction ${this.sourceFormat} -> ${targetFormat}`);
29
+ }
30
+ }
31
+
32
+ private helmToFormat(helmSequence: string, targetFormat: string): string {
33
+ const wrapperRegExp = new RegExp(getRegExpPattern(Object.values(HELM_WRAPPER)), 'g')
34
+ let result = helmSequence.replace(wrapperRegExp, '');
35
+
36
+ const dict = this.formats.getHelmToFormatDict(targetFormat);
37
+ const helmCodes = this.formats.getTargetFormatHelmCodes(targetFormat);
38
+ const helmRegExp = this.formats.getTargetFormatHelmCodesRegExp(targetFormat);
39
+
40
+ result = result.replace(helmRegExp, (match) => {
41
+ return helmCodes.includes(match) ? dict[match] :
42
+ (match === 'p' || match === '.') ? match : '?';
43
+ }).replace(/\?+/g, UNKNOWN_SYMBOL).replace(/p\.|\./g, '');
44
+ result = result.replace(/<empty>/g, '');
45
+ // remove double slash in LCMS codes
46
+ result = result.replace(/\/\//g, '/');
47
+ return result;
48
+ }
49
+
50
+ private formatToHelm(sequence: string, sourceFormat: string): string {
51
+ const dict = this.formats.getFormatToHelmDict(sourceFormat);
52
+ const formatCodes = this.formats.getCodesByFormat(sourceFormat);
53
+ const formatRegExp = this.formats.getFormatRegExp(sourceFormat);
54
+ const phosphateRegExp = this.formats.getPhosphateHelmCodesRegExp(sourceFormat);
55
+
56
+ let helm = sequence.replace(formatRegExp, (match) => {
57
+ const result = formatCodes.includes(match) ? dict[match] + '.' : '?';
58
+ return result;
59
+ });
60
+ helm = helm.replace(/\?+/g, `${UNKNOWN_SYMBOL}.`);
61
+ helm = helm.slice(0, -1); // strip last dot
62
+ if (helm[helm.length - 1] === PHOSPHATE_SYMBOL)
63
+ helm = helm.slice(0, -1);
64
+ helm = helm.replace(phosphateRegExp, (match, group) => group);
65
+ helm = helm.replace(/<empty>/g, '');
66
+ return `${HELM_WRAPPER.LEFT + helm + HELM_WRAPPER.RIGHT}`;
67
+ }
68
+ }
package/src/package.ts CHANGED
@@ -8,13 +8,13 @@ import {LIB_PATH, DEFAULT_LIB_FILENAME} from './model/data-loading-utils/const';
8
8
  import {IMonomerLib} from '@datagrok-libraries/bio/src/types';
9
9
  import {getMonomerLibHelper, IMonomerLibHelper} from '@datagrok-libraries/bio/src/monomer-works/monomer-utils';
10
10
  import {getJsonData} from './model/data-loading-utils/json-loader';
11
- import {SequenceToMolfileConverter} from './model/sequence-to-structure-utils/sequence-to-molfile';
12
- import {linkStrandsV3000} from './model/sequence-to-structure-utils/mol-transformations';
11
+ import {SequenceToMolfileConverter} from './model/structure-app/sequence-to-molfile';
12
+ import {linkStrandsV3000} from './model/structure-app/mol-transformations';
13
13
  import {MonomerLibWrapper} from './model/monomer-lib/lib-wrapper';
14
14
  import {FormatDetector} from './model/parsing-validation/format-detector';
15
15
  import {SequenceValidator} from './model/parsing-validation/sequence-validator';
16
16
  import {demoOligoTranslatorUI, demoOligoPatternUI, demoOligoStructureUI} from './demo/demo-st-ui';
17
- import {FormatConverter} from './model/format-translation/format-converter';
17
+ import {FormatConverter} from './model/translator-app/format-converter';
18
18
  import {APP} from './view/const/ui';
19
19
  import {getExternalAppViewFactories} from './plugins/mermade';
20
20
 
@@ -23,16 +23,16 @@ export async function getExternalAppViewFactories(): Promise<{[name: string]: ()
23
23
  let div: HTMLDivElement;
24
24
  try {
25
25
  div = await grok.functions.call(pluginName, data.parameters);
26
- } catch (err) {
27
- console.error(`Plugin ${pluginName} not loaded, error:`, err)
28
- div = ui.divText('error loading');
29
- }
30
- const pluginUI = new ExternalPluginUI(data.tabName, div);
26
+ const pluginUI = new ExternalPluginUI(data.tabName, div);
31
27
 
32
- // intentonally don't await for the promise
33
- pluginUI.initView();
28
+ // intentonally don't await for the promise
29
+ pluginUI.initView();
34
30
 
35
- result[data.tabName] = () => pluginUI.getView();
31
+ result[data.tabName] = () => pluginUI.getView();
32
+ } catch (err) {
33
+ console.warn(`Plugin ${pluginName} not loaded, reason:`, err)
34
+ continue;
35
+ }
36
36
  }
37
37
  return result;
38
38
  }
@@ -8,7 +8,7 @@ import {DEFAULT_FORMATS} from '../model/const';
8
8
  import {getJsonData} from '../model/data-loading-utils/json-loader';
9
9
  import {formatsToHelm} from './const';
10
10
  import {SequenceValidator} from '../model/parsing-validation/sequence-validator';
11
- import {getTranslatedSequences} from '../model/format-translation/conversion-utils';
11
+ import {getTranslatedSequences} from '../model/translator-app/conversion-utils';
12
12
  import {_package} from '../package';
13
13
 
14
14
  function getTranslationObject(sequence: string, format: string): {[format: string]: string} {
@@ -5,7 +5,7 @@ import * as DG from 'datagrok-api/dg';
5
5
 
6
6
  import {before, category, expect, test} from '@datagrok-libraries/utils/src/test';
7
7
  import {DEFAULT_FORMATS} from '../model/const';
8
- import {FormatConverter} from '../model/format-translation/format-converter';
8
+ import {FormatConverter} from '../model/translator-app/format-converter';
9
9
  import {getJsonData} from '../model/data-loading-utils/json-loader';
10
10
  import {formatsToHelm} from './const';
11
11
  import {_package} from '../package';
@@ -4,7 +4,7 @@ import * as ui from 'datagrok-api/ui';
4
4
  import * as DG from 'datagrok-api/dg';
5
5
 
6
6
  import {before, category, expect, test} from '@datagrok-libraries/utils/src/test';
7
- import {getNucleotidesSequence} from '../model/format-translation/conversion-utils';
7
+ import {getNucleotidesSequence} from '../model/translator-app/conversion-utils';
8
8
  import {getJsonData} from '../model/data-loading-utils/json-loader';
9
9
  import {helmToNucleotides} from './const';
10
10
  import {_package} from '../package';
@@ -6,10 +6,10 @@ import * as DG from 'datagrok-api/dg';
6
6
  import {axolabsStyleMap} from '../../model/data-loading-utils/json-loader';
7
7
  import {
8
8
  DEFAULT_PTO, DEFAULT_SEQUENCE_LENGTH, MAX_SEQUENCE_LENGTH, USER_STORAGE_KEY, EXAMPLE_MIN_WIDTH, SS, AS, STRAND_NAME, STRANDS, TERMINAL, TERMINAL_KEYS, THREE_PRIME, FIVE_PRIME, JSON_FIELD as FIELD
9
- } from '../../model/axolabs/const';
10
- import {isOverhang} from '../../model/axolabs/helpers';
11
- import {generateExample, translateSequence, getShortName, isCurrentUserCreatedThisPattern, findDuplicates, addColumnWithIds, addColumnWithTranslatedSequences} from '../../model/axolabs/axolabs-tab';
12
- import {drawAxolabsPattern} from '../../model/axolabs/draw-svg';
9
+ } from '../../model/pattern-app/const';
10
+ import {isOverhang} from '../../model/pattern-app/helpers';
11
+ import {generateExample, translateSequence, getShortName, isCurrentUserCreatedThisPattern, findDuplicates, addColumnWithIds, addColumnWithTranslatedSequences} from '../../model//pattern-app/oligo-pattern';
12
+ import {drawAxolabsPattern} from '../../model/pattern-app/draw-svg';
13
13
  // todo: remove ts-ignore
14
14
  //@ts-ignore
15
15
  import * as svg from 'save-svg-as-png';
@@ -10,10 +10,10 @@ import $ from 'cash-dom';
10
10
  import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
11
11
 
12
12
  import {highlightInvalidSubsequence} from '../utils/colored-input/input-painters';
13
- import {getLinkedMolfile, saveSdf} from '../../model/sequence-to-structure-utils/sdf-tab';
13
+ import {getLinkedMolfile, saveSdf} from '../../model/structure-app/oligo-structure';
14
14
  import {ColoredTextInput} from '../utils/colored-input/colored-text-input';
15
15
  import {MoleculeImage} from '../utils/molecule-img';
16
- import {StrandData} from '../../model/sequence-to-structure-utils/sdf-tab';
16
+ import {StrandData} from '../../model/structure-app/oligo-structure';
17
17
 
18
18
  const enum DIRECTION {
19
19
  STRAIGHT = '5′ → 3′',
@@ -63,10 +63,10 @@ export class StructureLayoutHandler {
63
63
  const bottomDiv = ui.divH([boolInputsAndButton, this.moleculeImgDiv]);
64
64
  $(bottomDiv).addClass('st-structure-bottom');
65
65
 
66
- const sdfTabBody = ui.divV([tableLayout, bottomDiv]);
67
- $(sdfTabBody).addClass('st-structure-body');
66
+ const layout = ui.divV([tableLayout, bottomDiv]);
67
+ $(layout).addClass('st-structure-body');
68
68
 
69
- return sdfTabBody;
69
+ return layout;
70
70
  }
71
71
 
72
72
  private getBoolInputsAndButton(): HTMLDivElement {
@@ -123,15 +123,25 @@ export class StructureLayoutHandler {
123
123
  )
124
124
  );
125
125
 
126
+ const clearBlock = Object.fromEntries(
127
+ STRANDS.map(
128
+ (key) => {
129
+ const icon = ui.iconFA('eraser', () => {coloredInput[key].inputBase.value = ''});
130
+ ui.tooltip.bind(icon, `Clear ${key.toUpperCase()}`);
131
+ return [key, icon];
132
+ }
133
+ ));
134
+
126
135
  const tableRows = STRANDS.map((strand) => {
127
136
  return {
128
137
  label: label[strand],
129
138
  textInput: coloredInput[strand].root,
139
+ clear: clearBlock[strand],
130
140
  choiceInput: directionChoiceInput[strand].root,
131
141
  };
132
142
  });
133
143
  const tableLayout = ui.table(
134
- tableRows, (item) => [item.label, item.textInput, item.choiceInput]);
144
+ tableRows, (item) => [item.label, item.textInput, item.clear, item.choiceInput]);
135
145
  $(tableLayout).css('margin-top', '10px');
136
146
 
137
147
  for (const strand of STRANDS) {
@@ -9,15 +9,15 @@ import $ from 'cash-dom';
9
9
 
10
10
  import {highlightInvalidSubsequence} from '../utils/colored-input/input-painters';
11
11
  import {ColoredTextInput} from '../utils/colored-input/colored-text-input';
12
- import {SequenceToMolfileConverter} from '../../model/sequence-to-structure-utils/sequence-to-molfile';
13
- import {getTranslatedSequences} from '../../model/format-translation/conversion-utils';
12
+ import {SequenceToMolfileConverter} from '../../model/structure-app/sequence-to-molfile';
13
+ import {getTranslatedSequences} from '../../model/translator-app/conversion-utils';
14
14
  import {MoleculeImage} from '../utils/molecule-img';
15
15
  import {download} from '../../model/helpers';
16
16
  import {SEQUENCE_COPIED_MSG, SEQ_TOOLTIP_MSG} from '../const/oligo-translator';
17
17
  import {DEFAULT_AXOLABS_INPUT} from '../const/ui';
18
18
  import {FormatDetector} from '../../model/parsing-validation/format-detector';
19
19
  import {SequenceValidator} from '../../model/parsing-validation/sequence-validator';
20
- import {FormatConverter} from '../../model/format-translation/format-converter';
20
+ import {FormatConverter} from '../../model/translator-app/format-converter';
21
21
  import {codesToHelmDictionary} from '../../model/data-loading-utils/json-loader';
22
22
  import {DEFAULT_FORMATS} from '../../model/const';
23
23
 
@@ -22,7 +22,7 @@ export class ColoredTextInput {
22
22
  $(this.root).addClass('colored-text-input');
23
23
  if (resizeable) {
24
24
  // make input field automatically resizeable
25
- this.textInputBase.onInput(
25
+ this.textInputBase.onChanged(
26
26
  () => {
27
27
  // necessary for the field to be squeezable, not only expandable
28
28
  $(this.textArea).css('height', 0);
@@ -34,7 +34,7 @@ export class ColoredTextInput {
34
34
  this.root.appendChild(this.highlights);
35
35
  this.colorize();
36
36
 
37
- this.textInputBase.onInput(() => this.colorize());
37
+ this.textInputBase.onChanged(() => this.colorize());
38
38
  }
39
39
 
40
40
  private highlights: HTMLDivElement;
@@ -6,7 +6,7 @@ import {errorToConsole} from '@datagrok-libraries/utils/src/to-console';
6
6
 
7
7
  import $ from 'cash-dom';
8
8
 
9
- import {extractAtomDataV3000} from '../../model/sequence-to-structure-utils/mol-transformations';
9
+ import {extractAtomDataV3000} from '../../model/structure-app/mol-transformations';
10
10
 
11
11
  /** Draw molecule on the canvas and append it to the specified div, with the
12
12
  * option of zoom-in */
@@ -1,109 +0,0 @@
1
- import * as DG from 'datagrok-api/dg';
2
- import {DEFAULT_FORMATS} from '../const';
3
- import {GROUP_TYPE, PHOSPHATE_SYMBOL, UNKNOWN_SYMBOL} from './const';
4
- import {CodesInfo} from '../data-loading-utils/types';
5
- import {codesToHelmDictionary} from '../data-loading-utils/json-loader';
6
-
7
- const HELM_WRAPPER = {
8
- LEFT: 'RNA1{',
9
- RIGHT: '}$$$$',
10
- };
11
-
12
- export class FormatConverter {
13
- constructor(private readonly sequence: string, private readonly sourceFormat: string) { };
14
-
15
- convertTo(targetFormat: string): string {
16
- const formats = Object.keys(codesToHelmDictionary);
17
-
18
- if (this.sourceFormat === DEFAULT_FORMATS.HELM && formats.includes(targetFormat))
19
- return helmToFormat(this.sequence, targetFormat);
20
- else if (formats.includes(this.sourceFormat) && targetFormat === DEFAULT_FORMATS.HELM)
21
- return formatToHelm(this.sequence, this.sourceFormat);
22
- else if ([this.sourceFormat, targetFormat].every((el) => formats.includes(el))) {
23
- const helm = formatToHelm(this.sequence, this.sourceFormat);
24
- return helmToFormat(helm, targetFormat);
25
- }
26
- else {
27
- throw new Error (`ST: unsupported translation direction ${this.sourceFormat} -> ${targetFormat}`);
28
- }
29
- }
30
- }
31
-
32
- function getRegExpPattern(arr: string[]): string {
33
- const negativeLookBehind = '(?<!\\([^()]*)'; // not '(' followed by non-parenths
34
- const negativeLookAhead = '(?![^()]*\\))'; // not ')' preceded by non-parenths
35
- const escaped = arr.map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
36
- .map((key) => {
37
- if (!key.includes('(') && !key.includes(')'))
38
- return `${negativeLookBehind}${key}${negativeLookAhead}`;
39
- return key;
40
- });
41
- const result = escaped.join('|');
42
- return result;
43
- }
44
-
45
- function sortCallback(a: string, b: string) {return b.length - a.length};
46
-
47
- function getHelmToCodeDict(infoObj: CodesInfo) {
48
- const result: {[key: string]: string | string[]} = {};
49
- Object.values(infoObj).forEach((obj: {[code: string]: string}) => {
50
- Object.entries(obj).forEach(([code, helm]) => {
51
- const key = helm.replace(/\)p/g, ')').replace(/\]p/g, ']');
52
- if (result[key] === undefined) {
53
- result[key] = [code];
54
- } else {
55
- (result[key] as string[]).push(code);
56
- }
57
- })
58
- });
59
- Object.entries(result).forEach(([key, value]) => {
60
- const sorted = (value as string[]).sort(sortCallback);
61
- result[key] = sorted[0] as string;
62
- })
63
- return result as {[key: string]: string};
64
- }
65
-
66
- function helmToFormat(helmSequence: string, targetFormat: string): string {
67
- const codesInfoObject = codesToHelmDictionary[targetFormat] as CodesInfo;
68
- const dict = getHelmToCodeDict(codesInfoObject);
69
- const wrapperRegExp = new RegExp(getRegExpPattern(Object.values(HELM_WRAPPER)), 'g')
70
- let result = helmSequence.replace(wrapperRegExp, '');
71
-
72
- const helmCodes = Object.keys(dict)
73
- .sort(sortCallback);
74
- const helmRegExp = new RegExp(getRegExpPattern(helmCodes) + '|.', 'g');
75
- result = result.replace(helmRegExp, (match) => {
76
- return helmCodes.includes(match) ? dict[match] :
77
- (match === 'p' || match === '.') ? match : '?';
78
- }).replace(/\?+/g, UNKNOWN_SYMBOL).replace(/p\.|\./g, '');
79
- result = result.replace(/<empty>/g, '');
80
- // remove double slash in LCMS codes
81
- result = result.replace(/\/\//g, '/');
82
- return result;
83
- }
84
-
85
- function formatToHelm(sequence: string, sourceFormat: string): string {
86
- const codesInfoObject = codesToHelmDictionary[sourceFormat] as CodesInfo;
87
- const dict = Object.assign({}, ...Object.values(codesInfoObject)) as {[code: string]: string};
88
-
89
- const formatCodes = Object.keys(dict).sort(sortCallback);
90
- const formatRegExp = new RegExp(getRegExpPattern(formatCodes) + '|\\([^()]*\\)|.', 'g'); // the added group before '|.' is to avoid mismatch inside parenths
91
-
92
- const phosphateHELMCodes = Array.from(
93
- new Set(Object.values(codesInfoObject[GROUP_TYPE.LINKAGE]))
94
- ).sort(sortCallback);
95
- const phosphateHELMPattern = getRegExpPattern(phosphateHELMCodes);
96
- const phosphateRegExp = new RegExp(`${PHOSPHATE_SYMBOL}\.(${phosphateHELMPattern})`, 'g');
97
-
98
- let helm = sequence.replace(formatRegExp, (match) => {
99
- const result = formatCodes.includes(match) ? dict[match] + '.' : '?';
100
- return result;
101
- });
102
- helm = helm.replace(/\?+/g, `${UNKNOWN_SYMBOL}.`);
103
- helm = helm.slice(0, -1); // strip last dot
104
- if (helm[helm.length - 1] === PHOSPHATE_SYMBOL)
105
- helm = helm.slice(0, -1);
106
- helm = helm.replace(phosphateRegExp, (match, group) => group);
107
- helm = helm.replace(/<empty>/g, '');
108
- return `${HELM_WRAPPER.LEFT + helm + HELM_WRAPPER.RIGHT}`;
109
- }
File without changes
File without changes
File without changes