comparison mqparam.py @ 3:2d67fb758956 draft

"planemo upload for repository https://github.com/galaxyproteomics/tools-galaxyp/tree/master/tools/maxquant commit da342a782ccc391b87fb4fead956b7b3cbd21258"
author galaxyp
date Sat, 11 Apr 2020 11:50:09 -0400
parents 256cc0e17454
children 9cb7dcc07dae
comparison
equal deleted inserted replaced
2:3fc2116ac6d9 3:2d67fb758956
1 """ 1 """
2 Create a project-specific MaxQuant parameter file. 2 Create a project-specific MaxQuant parameter file.
3
4 TODO: check validity of parsed experimental design template
5 add support for parameter groups
6 add reporter ion MS2
7
8 Author: Damian Glaetzer <d.glaetzer@mailbox.org>
9 """ 3 """
10 4
5 import copy
11 import ntpath 6 import ntpath
12 import os 7 import os
13 import re 8 import re
9 import yaml
14 import xml.etree.ElementTree as ET 10 import xml.etree.ElementTree as ET
15 from itertools import zip_longest 11 from itertools import zip_longest
16 from xml.dom import minidom 12 from xml.dom import minidom
13
14
15 def et_add_child(el, name, text, attrib=None):
16 "Add a child element to an xml.etree.ElementTree.Element"
17 child = ET.SubElement(el, name, attrib=attrib if attrib else {})
18 child.text = str(text)
19 return child
20
21
22 class ParamGroup:
23 """Represents one parameter Group
24 """
25
26 def __init__(self, root):
27 """Initialize with its xml.etree.ElementTree root Element.
28 """
29 self._root = copy.deepcopy(root)
30
31 def set_list_param(self, key, vals):
32 """Set a list parameter.
33 """
34 node = self._root.find(key)
35 if node is None:
36 raise ValueError('Element {} not found in parameter file'
37 .format(key))
38 node.clear()
39 node.tag = key
40 for e in vals:
41 et_add_child(node, name='string', text=e)
42
43 def set_simple_param(self, key, value):
44 """Set a simple parameter.
45 """
46 node = self._root.find(key)
47 if node is None:
48 raise ValueError('Element {} not found in parameter file'
49 .format(key))
50 node.text = str(value)
51
52 def set_silac(self, light_labels, medium_labels, heavy_labels):
53 """Set label modifications.
54 """
55 if medium_labels and not (heavy_labels or light_labels): # medium omly with heavy and light
56 raise Exception("Incorrect SILAC specification. Use medium only together with light and heavy labels.")
57 multiplicity = 3 if medium_labels else 2 if heavy_labels else 1
58 max_label = str(max(len(light_labels) if light_labels else 0,
59 len(medium_labels) if medium_labels else 0,
60 len(heavy_labels) if heavy_labels else 0))
61 self._root.find('multiplicity').text = str(multiplicity)
62 self._root.find('maxLabeledAa').text = max_label
63 node = self._root.find('labelMods')
64 node[0].text = ';'.join(light_labels) if light_labels else ''
65 if multiplicity == 3:
66 et_add_child(node, name='string', text=';'.join(medium_labels))
67 if multiplicity > 1:
68 et_add_child(node, name='string',
69 text=';'.join(heavy_labels) if heavy_labels else '')
70
71 def set_isobaric_label(self, internalLabel, terminalLabel,
72 cm2, cm1, cp1, cp2, tmtLike):
73 """Add isobaric label info.
74 Args:
75 internalLabel: string
76 terminalLabel: string
77 cm2: (float) correction factor
78 cm1: (float) correction factor
79 cp1: (float) correction factor
80 cp2: (float) correction factor
81 tmtLike: bool or string
82 Returns:
83 None
84 """
85 iso_labels_node = self._root.find('isobaricLabels')
86 label = et_add_child(iso_labels_node, 'IsobaricLabelInfo', '')
87 et_add_child(label, 'internalLabel', internalLabel)
88 et_add_child(label, 'terminalLabel', terminalLabel)
89 for num, factor in (('M2', cm2), ('M1', cm1), ('P1', cp1), ('P2', cp2)):
90 et_add_child(label, 'correctionFactor' + num,
91 str(float(factor) if factor % 1 else int(factor)))
92 et_add_child(label, 'tmtLike', str(tmtLike))
17 93
18 94
19 class MQParam: 95 class MQParam:
20 """Represents a mqpar.xml and provides methods to modify 96 """Represents a mqpar.xml and provides methods to modify
21 some of its parameters. 97 some of its parameters.
22 """ 98 """
23 99
24 fasta_template = """<FastaFileInfo> 100 def __init__(self, mqpar_in, exp_design=None, yaml=None, substitution_rx=r'[^\s\S]'): # no sub by default
25 <fastaFilePath></fastaFilePath>
26 <identifierParseRule></identifierParseRule>
27 <descriptionParseRule></descriptionParseRule>
28 <taxonomyParseRule></taxonomyParseRule>
29 <variationParseRule></variationParseRule>
30 <modificationParseRule></modificationParseRule>
31 <taxonomyId></taxonomyId>
32 </FastaFileInfo>"""
33
34 def __init__(self, mqpar_out, mqpar_in, exp_design,
35 substitution_rx=r'[^\s\S]'): # no sub by default
36 """Initialize MQParam class. mqpar_in can either be a template 101 """Initialize MQParam class. mqpar_in can either be a template
37 or a already suitable mqpar file. 102 or a already suitable mqpar file.
38 >>> t = MQParam("test", './test-data/template.xml', None) 103 Args:
39 >>> t.root.tag 104 mqpar_in: a template parameter file
40 'MaxQuantParams' 105 exp_design: a experimental design template (see MaxQuant documentation),
41 >>> (t.root.find('maxQuantVersion')).text 106 can be None
42 '1.6.3.4' 107 substitution_rx: a regular expression for replacements in the file names.
43 """ 108 It is applied before comparing input file names (e.g. from the exp. design)
44 109 """
45 self.orig_mqpar = mqpar_in 110 self.orig_mqpar = mqpar_in
46 self.exp_design = exp_design 111 self.exp_design = exp_design
47 self.mqpar_out = mqpar_out 112 self._root = ET.parse(mqpar_in).getroot()
48 self.root = ET.parse(mqpar_in).getroot() 113 self.version = self._root.find('maxQuantVersion').text
49 self.version = self.root.find('maxQuantVersion').text
50 # regex for substitution of certain file name characters 114 # regex for substitution of certain file name characters
51 self.substitution_rx = substitution_rx 115 self.substitution_rx = substitution_rx
116 self.pg_node = copy.deepcopy(self._root.find('parameterGroups')[0])
117 self._paramGroups = []
118 self.fasta_file_node = copy.deepcopy(self._root.find('fastaFiles')[0])
119 if yaml:
120 self._from_yaml(yaml)
121
122 def __getitem__(self, index):
123 """Return paramGroup if indexed with integer, else try to find
124 matching Element in XML root and return its text or None.
125 """
126 try:
127 return self._paramGroups[index]
128 except TypeError:
129 ret = self._root.find(index)
130 return ret.text if ret is not None else None
52 131
53 @staticmethod 132 @staticmethod
54 def _add_child(el, name, text, attrib=None): 133 def _check_validity(design, len_infiles):
55 """Add a child element to an element. 134 """Perform some checks on the exp. design template"""
56 135 design_len = len(design['Name'])
57 >>> t = MQParam("test", './test-data/template.xml', None) 136 # 'Name' can be None, we need at least len_infiles valid entries
58 >>> MQParam._add_child(t.root, "test", "test") 137 match = len(list(filter(lambda x: bool(x), design['Name'])))
59 >>> t.root.find('test').text == "test" 138 if match < len_infiles:
60 True 139 raise Exception(' '.join(["Error parsing experimental design template:",
61 """ 140 "Found only {} matching entries".format(match),
62 141 "for {} input files".format(len_infiles)]))
63 child = ET.SubElement(el, name, attrib=attrib if attrib else {}) 142 for i in range(0, design_len):
64 child.text = str(text) 143 msg = "(in line " + str(i + 2) + " of experimental design) "
65 144 if not design['Experiment'][i]:
66 def _make_exp_design(self, infiles): 145 raise ValueError(msg + " Experiment is empty.")
67 """Create a dict representing an experimental design from 146 if design['PTM'][i].lower() not in ('true', 'false'):
68 an experimental design template and a list of input files. 147 raise ValueError(msg + "Defines invalid PTM value, should be 'True' or 'False'.")
148 try:
149 int(design['Fraction'][i])
150 except ValueError as e:
151 raise ValueError(msg + str(e))
152
153 def _make_exp_design(self, groups, files):
154 """Create a dict representing an experimental design from an
155 experimental design template and a list input files.
69 If the experimental design template is None, create a default 156 If the experimental design template is None, create a default
70 design with one experiment for each input file, no fractions and 157 design with one experiment for each input file and no fractions
71 parameter group 0 for all files. 158 for all files.
72 >>> t2 = MQParam("test", './test-data/template.xml', \ 159 Args:
73 './test-data/two/exp_design_template.txt') 160 files: list of input file paths
74 >>> design = t2._make_exp_design(['./test-data/BSA_min_21.mzXML', \ 161 groups: list of parameter group indices
75 './test-data/BSA_min_22.mzXML']) 162 Returns:
76 >>> design['Name'] 163 dict: The (complete) experimental design template
77 ['./test-data/BSA_min_21.mzXML', './test-data/BSA_min_22.mzXML'] 164 """
78 >>> design['Fraction'] 165 design = {s: [] for s in ("Name", "PTM", "Fraction", "Experiment", "paramGroup")}
79 ['1', '2']
80 """
81 design = {s: [] for s in ("Name", "PTM", "Fraction", "Experiment")}
82 if not self.exp_design: 166 if not self.exp_design:
83 design["Name"] = infiles 167 design["Name"] = files
84 design["Fraction"] = ('32767',) * len(infiles) 168 design["Fraction"] = ('32767',) * len(files)
85 design["Experiment"] = [os.path.split(f)[1] for f in infiles] 169 design["Experiment"] = [os.path.split(f)[1] for f in files]
86 design["PTM"] = ('False',) * len(infiles) 170 design["PTM"] = ('False',) * len(files)
171 design["paramGroup"] = groups
87 else: 172 else:
88 with open(self.exp_design) as design_file: 173 with open(self.exp_design) as design_file:
89 index_line = design_file.readline().strip() 174 index_line = design_file.readline().strip()
90 index = [] 175 index = []
91 for i in index_line.split('\t'): 176 for i in index_line.split('\t'):
92 if i in design: 177 if i in design:
93 index.append(i) 178 index.append(i)
94 else: 179 else:
95 raise Exception("Invalid comlumn index in experimental" 180 raise Exception("Invalid column index in experimental design template: {}".format(i))
96 + " design template: {}".format(i))
97 for line in design_file: 181 for line in design_file:
98 row = line.strip().split('\t') 182 row = line.strip().split('\t')
99 for e, i in zip_longest(row, index): 183 for e, i in zip_longest(row, index):
184 if i == "Fraction" and not e:
185 e = '32767'
186 elif i == "PTM" and not e:
187 e = 'False'
100 design[i].append(e) 188 design[i].append(e)
101 189 # map files to names in exp. design template
102 # map infiles to names in exp. design template
103 names = [] 190 names = []
104 names_to_paths = {} 191 names_to_paths = {}
105 # strip path and extension 192 # strip path and extension
106 for f in infiles: 193 for f in files:
107 b = os.path.basename(f) 194 b = os.path.basename(f)
108 basename = b[:-6] if b.endswith('.mzXML') else b[:-11] 195 basename = b[:-11] if b.lower().endswith('.thermo.raw') else b.rsplit('.', maxsplit=1)[0]
109 names_to_paths[basename] = f 196 names_to_paths[basename] = f
110 for name in design['Name']: 197 for name in design['Name']:
111 # same substitution as in maxquant.xml, 198 # same substitution as in maxquant.xml,
112 # when passing the element identifiers 199 # when passing the element identifiers
113 fname = re.sub(self.substitution_rx, '_', name) 200 fname = re.sub(self.substitution_rx, '_', name)
114 names.append(names_to_paths[fname] if fname in names_to_paths 201 names.append(names_to_paths[fname] if fname in names_to_paths
115 else None) 202 else None)
116 # replace orig. file names with matching links to galaxy datasets 203 # replace orig. file names with matching links to galaxy datasets
117 design['Name'] = names 204 design['Name'] = names
118 205 design['paramGroup'] = groups
206 MQParam._check_validity(design, len(files))
119 return design 207 return design
120 208
121 def add_infiles(self, infiles, interactive): 209 def add_infiles(self, infiles):
122 """Add a list of raw/mzxml files to the mqpar.xml. 210 """Add a list of raw/mzxml files to the mqpar.xml.
123 If experimental design template was specified, 211 If experimental design template was specified,
124 modify other parameters accordingly. 212 modify other parameters accordingly.
125 The files must be specified as absolute paths 213 The files must be specified as absolute paths
126 for maxquant to find them. 214 for maxquant to find them.
127 >>> t1 = MQParam("test", './test-data/template.xml', None) 215 Also add parameter Groups.
128 >>> t1.add_infiles(('test1', ), True) 216 Args:
129 >>> t1.root.find("filePaths")[0].text 217 infiles: a list of infile lists. first dimension denotes the
130 'test1' 218 parameter group.
131 >>> t1.root.find("fractions")[0].text 219 Returns:
132 '32767' 220 None
133 >>> len(t1.root.find("fractions")) 221 """
134 1 222 groups, files = zip(*[(num, f) for num, l in enumerate(infiles) for f in l])
135 >>> t2 = MQParam("test", './test-data/template.xml', \ 223 self._paramGroups = [ParamGroup(self.pg_node) for i in range(len(infiles))]
136 './test-data/exp_design_test.txt') 224 nodenames = ('filePaths', 'experiments', 'fractions',
137 >>> t2.add_infiles(('test-data/QEplus021874.thermo.raw', \ 225 'ptms', 'paramGroupIndices', 'referenceChannel')
138 'test-data/QEplus021876.thermo.raw'), True) 226 design = self._make_exp_design(groups, files)
139 >>> len(t2.root.find("filePaths"))
140 2
141 >>> t2.root.find("filePaths")[1].text
142 'test-data/QEplus021876.thermo.raw'
143 >>> t2.root.find("experiments")[1].text
144 '2'
145 >>> t2.root.find("fractions")[0].text
146 '3'
147 """
148
149 # Create experimental design for interactive mode.
150 # In non-interactive mode only filepaths are modified, but
151 # their order from the original mqpar must be kept.
152 if interactive:
153 index = range(len(infiles))
154 nodenames = ('filePaths', 'experiments', 'fractions',
155 'ptms', 'paramGroupIndices', 'referenceChannel')
156 design = self._make_exp_design(infiles)
157 else:
158 index = [-1] * len(infiles)
159 # kind of a BUG: fails if filename starts with '.'
160 infilenames = [os.path.basename(f).split('.')[0] for f in infiles]
161 i = 0
162 for child in self.root.find('filePaths'):
163 # either windows or posix path
164 win = ntpath.basename(child.text)
165 posix = os.path.basename(child.text)
166 basename = win if len(win) < len(posix) else posix
167 basename_with_sub = re.sub(self.substitution_rx, '_',
168 basename.split('.')[0])
169 # match infiles to their names in mqpar.xml,
170 # ignore files missing in mqpar.xml
171 if basename_with_sub in infilenames:
172 index[i] = infilenames.index(basename_with_sub)
173 i += 1
174 else:
175 raise ValueError("no matching infile found for "
176 + child.text)
177
178 nodenames = ('filePaths', )
179 design = {'Name': infiles}
180
181 # Get parent nodes from document 227 # Get parent nodes from document
182 nodes = dict() 228 nodes = dict()
183 for nodename in nodenames: 229 for nodename in nodenames:
184 node = self.root.find(nodename) 230 node = self._root.find(nodename)
185 if node is None: 231 if node is None:
186 raise ValueError('Element {} not found in parameter file' 232 raise ValueError('Element {} not found in parameter file'
187 .format(nodename)) 233 .format(nodename))
188 nodes[nodename] = node 234 nodes[nodename] = node
189 node.clear() 235 node.clear()
190 node.tag = nodename 236 node.tag = nodename
191
192 # Append sub-elements to nodes (one per file) 237 # Append sub-elements to nodes (one per file)
193 for i in index: 238 for i, name in enumerate(design['Name']):
194 if i > -1 and design['Name'][i]: 239 if name:
195 MQParam._add_child(nodes['filePaths'], 'string', 240 et_add_child(nodes['filePaths'], 'string', name)
196 design['Name'][i]) 241 et_add_child(nodes['experiments'], 'string',
197 if interactive: 242 design['Experiment'][i])
198 MQParam._add_child(nodes['experiments'], 'string', 243 et_add_child(nodes['fractions'], 'short',
199 design['Experiment'][i]) 244 design['Fraction'][i])
200 MQParam._add_child(nodes['fractions'], 'short', 245 et_add_child(nodes['ptms'], 'boolean',
201 design['Fraction'][i]) 246 design['PTM'][i])
202 MQParam._add_child(nodes['ptms'], 'boolean', 247 et_add_child(nodes['paramGroupIndices'], 'int',
203 design['PTM'][i]) 248 design['paramGroup'][i])
204 MQParam._add_child(nodes['paramGroupIndices'], 'int', 0) 249 et_add_child(nodes['referenceChannel'], 'string', '')
205 MQParam._add_child(nodes['referenceChannel'], 'string', '') 250
206 251 def translate(self, infiles):
207 def add_fasta_files(self, files, 252 """Map a list of given infiles to the files specified in the parameter file.
208 identifier=r'>([^\s]*)', 253 Needed for the mqpar upload in galaxy. Removes the path and then tries
209 description=r'>(.*)'): 254 to match the files.
255 Args:
256 infiles: list or tuple of the input
257 Returns:
258 None
259 """
260 # kind of a BUG: fails if filename starts with '.'
261 infilenames = [os.path.basename(f).split('.')[0] for f in infiles]
262 filesNode = self._root.find('filePaths')
263 files_from_mqpar = [e.text for e in filesNode]
264 filesNode.clear()
265 filesNode.tag = 'filePaths'
266 for f in files_from_mqpar:
267 # either windows or posix path
268 win = ntpath.basename(f)
269 posix = os.path.basename(f)
270 basename = win if len(win) < len(posix) else posix
271 basename_with_sub = re.sub(self.substitution_rx, '_',
272 basename.split('.')[0])
273 # match infiles to their names in mqpar.xml,
274 # ignore files missing in mqpar.xml
275 if basename_with_sub in infilenames:
276 i = infilenames.index(basename_with_sub)
277 et_add_child(filesNode, 'string', infiles[i])
278 else:
279 raise ValueError("no matching infile found for " + f)
280
281 def add_fasta_files(self, files, parse_rules={}):
210 """Add fasta file groups. 282 """Add fasta file groups.
211 >>> t = MQParam('test', './test-data/template.xml', None) 283 Args:
212 >>> t.add_fasta_files(('test1', 'test2')) 284 files: (list) of fasta file paths
213 >>> len(t.root.find('fastaFiles')) 285 parseRules: (dict) the parse rules as (tag, text)-pairs
214 2 286 Returns:
215 >>> t.root.find('fastaFiles')[0].find("fastaFilePath").text 287 None
216 'test1' 288 """
217 """ 289 fasta_node = self._root.find('fastaFiles')
218 fasta_node = self.root.find("fastaFiles")
219 fasta_node.clear() 290 fasta_node.clear()
220 fasta_node.tag = "fastaFiles" 291 for f in files:
221 292 fasta_node.append(copy.deepcopy(self.fasta_file_node))
222 for index in range(len(files)): 293 fasta_node[-1].find('fastaFilePath').text = f
223 filepath = '<fastaFilePath>' + files[index] 294 for rule in parse_rules:
224 fasta = self.fasta_template.replace('<fastaFilePath>', filepath) 295 fasta_node[-1].find(rule).text = parse_rules[rule]
225 fasta = fasta.replace('<identifierParseRule>',
226 '<identifierParseRule>' + identifier)
227 fasta = fasta.replace('<descriptionParseRule>',
228 '<descriptionParseRule>' + description)
229 ff_node = self.root.find('.fastaFiles')
230 fastaentry = ET.fromstring(fasta)
231 ff_node.append(fastaentry)
232 296
233 def set_simple_param(self, key, value): 297 def set_simple_param(self, key, value):
234 """Set a simple parameter. 298 """Set a simple parameter.
235 >>> t = MQParam(None, './test-data/template.xml', None) 299 Args:
236 >>> t.set_simple_param('min_unique_pep', 4) 300 key: (string) XML tag of the parameter
237 >>> t.root.find('.minUniquePeptides').text 301 value: the text of the parameter XML node
238 '4' 302 Returns:
239 """ 303 None
240 # map simple params to their node in the xml tree 304 """
241 simple_params = {'missed_cleavages': 305 node = self._root.find(key)
242 '.parameterGroups/parameterGroup/maxMissedCleavages', 306 if node is None:
243 'min_unique_pep': '.minUniquePeptides', 307 raise ValueError('Element {} not found in parameter file'
244 'num_threads': 'numThreads', 308 .format(key))
245 'calc_peak_properties': '.calcPeakProperties', 309 node.text = str(value)
246 'write_mztab': 'writeMzTab', 310
247 'min_peptide_len': 'minPepLen', 311 def _from_yaml(self, conf):
248 'max_peptide_mass': 'maxPeptideMass', 312 """Read a yaml config file.
249 'ibaq': 'ibaq', # lfq global options 313 Args:
250 'ibaq_log_fit': 'ibaqLogFit', 314 conf: (string) path to the yaml conf file
251 'separate_lfq': 'separateLfq', 315 Returns:
252 'lfq_stabilize_large_ratios': 316 None
253 'lfqStabilizeLargeRatios', 317 """
254 'lfq_require_msms': 'lfqRequireMsms', 318 with open(conf) as f:
255 'advanced_site_intensities': 319 conf_dict = yaml.safe_load(f.read())
256 'advancedSiteIntensities', 320 paramGroups = conf_dict.pop('paramGroups')
257 'lfq_mode': # lfq param group options 321 self.add_infiles([pg.pop('files') for pg in paramGroups])
258 '.parameterGroups/parameterGroup/lfqMode', 322 for i, pg in enumerate(paramGroups):
259 'lfq_skip_norm': 323 silac = pg.pop('labelMods', False)
260 '.parameterGroups/parameterGroup/lfqSkipNorm', 324 if silac:
261 'lfq_min_edges_per_node': 325 self[i].set_silac(*silac)
262 '.parameterGroups/parameterGroup/lfqMinEdgesPerNode', 326 isobaricLabels = pg.pop('isobaricLabels', False)
263 'lfq_avg_edges_per_node': 327 if isobaricLabels:
264 '.parameterGroups/parameterGroup/lfqAvEdgesPerNode', 328 for l in isobaricLabels:
265 'lfq_min_ratio_count': 329 self[i].set_isobaric_label(*l)
266 '.parameterGroups/parameterGroup/lfqMinRatioCount'} 330 for el in ['fixedModifications', 'variableModifications', 'enzymes']:
267 331 lst = pg.pop(el, None)
268 if key in simple_params: 332 if lst is not None:
269 node = self.root.find(simple_params[key]) 333 self[i].set_list_param(el, lst)
270 if node is None: 334 for key in pg:
271 raise ValueError('Element {} not found in parameter file' 335 self[i].set_simple_param(key, pg[key])
272 .format(simple_params[key])) 336 fastafiles = conf_dict.pop('fastaFiles', False)
273 node.text = str(value) 337 if fastafiles:
338 self.add_fasta_files(fastafiles, parse_rules=conf_dict.pop('parseRules', {}))
274 else: 339 else:
275 raise ValueError("Parameter not found.") 340 raise Exception('No fasta files provided.')
276 341 for key in conf_dict:
277 def set_silac(self, light_mods, medium_mods, heavy_mods): 342 self.set_simple_param(key, conf_dict[key])
278 """Set label modifications. 343
279 >>> t1 = MQParam('test', './test-data/template.xml', None) 344 def write(self, mqpar_out):
280 >>> t1.set_silac(None, ('test1', 'test2'), None) 345 """Write pretty formatted xml parameter file.
281 >>> t1.root.find('.parameterGroups/parameterGroup/maxLabeledAa').text 346 Compose it from global parameters and parameter Groups.
282 '2' 347 """
283 >>> t1.root.find('.parameterGroups/parameterGroup/multiplicity').text 348 if self._paramGroups:
284 '3' 349 pg_node = self._root.find('parameterGroups')
285 >>> t1.root.find('.parameterGroups/parameterGroup/labelMods')[1].text 350 pg_node.remove(pg_node[0])
286 'test1;test2' 351 for group in self._paramGroups:
287 >>> t1.root.find('.parameterGroups/parameterGroup/labelMods')[2].text 352 pg_node.append(group._root)
288 '' 353 rough_string = ET.tostring(self._root, 'utf-8', short_empty_elements=False)
289 """
290 multiplicity = 3 if medium_mods else 2 if heavy_mods else 1
291 max_label = str(max(len(light_mods) if light_mods else 0,
292 len(medium_mods) if medium_mods else 0,
293 len(heavy_mods) if heavy_mods else 0))
294 multiplicity_node = self.root.find('.parameterGroups/parameterGroup/'
295 + 'multiplicity')
296 multiplicity_node.text = str(multiplicity)
297 max_label_node = self.root.find('.parameterGroups/parameterGroup/'
298 + 'maxLabeledAa')
299 max_label_node.text = max_label
300
301 node = self.root.find('.parameterGroups/parameterGroup/labelMods')
302 node[0].text = ';'.join(light_mods) if light_mods else ''
303 if multiplicity == 3:
304 MQParam._add_child(node, name='string', text=';'.join(medium_mods))
305 if multiplicity > 1:
306 MQParam._add_child(node, name='string',
307 text=';'.join(heavy_mods) if heavy_mods else '')
308
309 def set_list_params(self, key, vals):
310 """Set a list parameter.
311 >>> t = MQParam(None, './test-data/template.xml', None)
312 >>> t.set_list_params('proteases', ('test 1', 'test 2'))
313 >>> len(t.root.find('.parameterGroups/parameterGroup/enzymes'))
314 2
315 >>> t.set_list_params('var_mods', ('Oxidation (M)', ))
316 >>> var_mods = '.parameterGroups/parameterGroup/variableModifications'
317 >>> t.root.find(var_mods)[0].text
318 'Oxidation (M)'
319 """
320
321 params = {'var_mods':
322 '.parameterGroups/parameterGroup/variableModifications',
323 'fixed_mods':
324 '.parameterGroups/parameterGroup/fixedModifications',
325 'proteases':
326 '.parameterGroups/parameterGroup/enzymes'}
327
328 if key in params:
329 node = self.root.find(params[key])
330 if node is None:
331 raise ValueError('Element {} not found in parameter file'
332 .format(params[key]))
333 node.clear()
334 node.tag = params[key].split('/')[-1]
335 for e in vals:
336 MQParam._add_child(node, name='string', text=e)
337 else:
338 raise ValueError("Parameter {} not found.".format(key))
339
340 def write(self):
341 rough_string = ET.tostring(self.root, 'utf-8', short_empty_elements=False)
342 reparsed = minidom.parseString(rough_string) 354 reparsed = minidom.parseString(rough_string)
343 pretty = reparsed.toprettyxml(indent="\t") 355 pretty = reparsed.toprettyxml(indent="\t")
344 even_prettier = re.sub(r"\n\s+\n", r"\n", pretty) 356 even_prettier = re.sub(r"\n\s+\n", r"\n", pretty)
345 with open(self.mqpar_out, 'w') as f: 357 with open(mqpar_out, 'w') as f:
346 print(even_prettier, file=f) 358 print(even_prettier, file=f)