Package node_manager_fkie :: Package editor :: Module xml_highlighter
[frames] | no frames]

Source Code for Module node_manager_fkie.editor.xml_highlighter

  1  # Software License Agreement (BSD License) 
  2  # 
  3  # Copyright (c) 2012, Fraunhofer FKIE/US, Alexander Tiderko 
  4  # based on code of Timo Roehling 
  5  # All rights reserved. 
  6  # 
  7  # Redistribution and use in source and binary forms, with or without 
  8  # modification, are permitted provided that the following conditions 
  9  # are met: 
 10  # 
 11  #  * Redistributions of source code must retain the above copyright 
 12  #    notice, this list of conditions and the following disclaimer. 
 13  #  * Redistributions in binary form must reproduce the above 
 14  #    copyright notice, this list of conditions and the following 
 15  #    disclaimer in the documentation and/or other materials provided 
 16  #    with the distribution. 
 17  #  * Neither the name of Fraunhofer nor the names of its 
 18  #    contributors may be used to endorse or promote products derived 
 19  #    from this software without specific prior written permission. 
 20  # 
 21  # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 22  # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 23  # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 24  # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 25  # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 26  # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 27  # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
 28  # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 29  # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
 30  # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 
 31  # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 32  # POSSIBILITY OF SUCH DAMAGE. 
 33   
 34  from python_qt_binding.QtCore import QRegExp, Qt 
 35  from python_qt_binding.QtGui import QColor, QFont, QSyntaxHighlighter, QTextCharFormat 
 36   
 37   
38 -class XmlHighlighter(QSyntaxHighlighter):
39 ''' 40 Enabled the syntax highlightning for the ROS launch files. 41 ''' 42 43 LAUNCH_LAUNCH_CHILDS = ['group', 'node', 'test', 'env', 'remap', 'rosparam', 'param', 'machine', 'include', 'arg'] 44 LAUNCH_LAUNCH_ATTR = {'deprecated=': '"message"'} 45 LAUNCH_GROUP_CHILDS = ['node', 'test', 'env', 'remap', 'rosparam', 'param', 'machine', 'include', 'arg'] 46 LAUNCH_GROUP_ATTR = {'ns=': '"foo"', 47 'clear_params=': '"true|false"' 48 } 49 LAUNCH_MACHINE_CHILDS = ['env'] 50 LAUNCH_MACHINE_ATTR = {'name=': '"machine-name"', 51 'address=': '"blah.willowgarage.com"', 52 'env-loader=': '"/opt/ros/fuerte/env.sh"', 53 'default=': '"true|false|never"', 54 'user=': '"username"', 55 'password=': '"passwhat"', 56 'timeout=': '"10.0"' 57 } 58 LAUNCH_NODE_CHILDS = ['env', 'remap', 'rosparam', 'param'] 59 LAUNCH_NODE_ATTR = {'pkg=': '"mypackage"', 60 'type=': '"nodetype"', 61 'name=': '"nodename"', 62 'args=': '"arg1"', 63 'machine=': '"machine-name"', 64 'respawn=': '"true"', 65 'required=': '"true"', 66 'ns=': '"foo"', 67 'clear_params=': '"true|false"', 68 'output=': '"log|screen"', 69 'cwd=': '"ROS_HOME|node"', 70 'launch-prefix=': '"prefix arguments"' 71 } 72 LAUNCH_INCLUDE_CHILDS = ['env', 'arg'] 73 LAUNCH_INCLUDE_ATTR = {'file=': '"$(find pkg-name)/path/filename.xml"', 74 'ns=': '"foo"', 75 'clear_params=': '"true|false"', 76 'pass_all_args=': '"true|false"' 77 } 78 79 LAUNCH_REMAP_ATTR = {'from=': '"originalname"', 80 'to=': '"newname"' 81 } 82 LAUNCH_ENV_ATTR = {'name=': '"name"', 83 'value=': '"value"' 84 } 85 LAUNCH_PARAM_ATTR = {'name=': '"namespace/name"', 86 'value=': '"value"', 87 'type=': '"str|int|double|bool"', 88 'textfile=': '"$(find pkg-name)/path/file.txt"', 89 'binfile=': '"$(find pkg-name)/path/file"', 90 'command=': '"$(find pkg-name)/exe \'$(find pkg-name)/arg.txt\'"' 91 } 92 93 LAUNCH_ROSPARAM_ATTR = {'command=': '"load|dump|delete"', 94 'file=': '"$(find pkg-name)/path/foo.yaml"', 95 'param=': '"name"', 96 'ns=': '"foo"', 97 'subst_value=': '"true|false"' 98 } 99 LAUNCH_ARG_ATTR = {'name=': '"name"', 100 'value=': '"bar"', 101 'default=': '"defbar"' 102 } 103 LAUNCH_TEST_CHILDS = ['env', 'remap', 'rosparam', 'param'] 104 LAUNCH_TEST_ATTR = {'pkg=': '"mypackage"', 105 'type=': '"nodetype"', 106 'name=': '"nodename"', 107 'test-name=': '"test_name"', 108 'args=': '"arg1"', 109 'ns=': '"foo"', 110 'clear_params=': '"true|false"', 111 'retry=': '"0"', 112 'cwd=': '"ROS_HOME|node"', 113 'launch-prefix=': '"prefix arguments"', 114 'time-limit=': '"60.0"' 115 } 116 117 LAUNCH_CHILDS = {'launch': LAUNCH_LAUNCH_CHILDS, 118 'group': LAUNCH_GROUP_CHILDS, 119 'machine': LAUNCH_MACHINE_CHILDS, 120 'node': LAUNCH_NODE_CHILDS, 121 'include': LAUNCH_INCLUDE_CHILDS, 122 'remap': [], 123 'env': [], 124 'param': [], 125 'rosparam': [], 126 'arg': [], 127 'test': LAUNCH_TEST_CHILDS 128 } 129 130 LAUNCH_ATT_GLOBAL = {'if=': '""', 'unless=': '""'} 131 LAUNCH_LAUNCH_ATTR.update(LAUNCH_ATT_GLOBAL) 132 LAUNCH_GROUP_ATTR.update(LAUNCH_ATT_GLOBAL) 133 LAUNCH_MACHINE_ATTR.update(LAUNCH_ATT_GLOBAL) 134 LAUNCH_NODE_ATTR.update(LAUNCH_ATT_GLOBAL) 135 LAUNCH_INCLUDE_ATTR.update(LAUNCH_ATT_GLOBAL) 136 LAUNCH_REMAP_ATTR.update(LAUNCH_ATT_GLOBAL) 137 LAUNCH_ENV_ATTR.update(LAUNCH_ATT_GLOBAL) 138 LAUNCH_PARAM_ATTR.update(LAUNCH_ATT_GLOBAL) 139 LAUNCH_ROSPARAM_ATTR.update(LAUNCH_ATT_GLOBAL) 140 LAUNCH_ARG_ATTR.update(LAUNCH_ATT_GLOBAL) 141 LAUNCH_TEST_ATTR.update(LAUNCH_ATT_GLOBAL) 142 143 LAUNCH_ATTR = {'launch': LAUNCH_LAUNCH_ATTR, 144 'group': LAUNCH_GROUP_ATTR, 145 'machine': LAUNCH_MACHINE_ATTR, 146 'node': LAUNCH_NODE_ATTR, 147 'include': LAUNCH_INCLUDE_ATTR, 148 'remap': LAUNCH_REMAP_ATTR, 149 'env': LAUNCH_ENV_ATTR, 150 'param': LAUNCH_PARAM_ATTR, 151 'rosparam': LAUNCH_ROSPARAM_ATTR, 152 'arg': LAUNCH_ARG_ATTR, 153 'test': LAUNCH_TEST_ATTR, 154 } 155 156 STATE_COMMENT = 2 157 STATE_STRING = 4 158
159 - def __init__(self, parent=None):
160 QSyntaxHighlighter.__init__(self, parent) 161 self.rules = [] 162 self.comment_start = QRegExp("<!--") 163 self.comment_end = QRegExp("-->") 164 self.comment_format = self._create_format(Qt.darkGray, 'italic') 165 # self.mark_background = QBrush(QColor(251, 247, 222)) 166 # create patterns for braces 167 self.rules.append((self._create_regexp("</?|/?>"), self._create_format(QColor(24, 24, 24)))) 168 # create patterns for TAG 169 tag_list = '|'.join(["\\b%s\\b" % t for t in self.LAUNCH_CHILDS.keys()]) 170 self.rules.append((self._create_regexp(tag_list), self._create_format(Qt.darkRed))) 171 # create patterns for ATTRIBUTES 172 attr_list = '|'.join(set(["\\b%s" % attr for v in self.LAUNCH_ATTR.values() for attr in v.keys()])) 173 self.rules.append((self._create_regexp(attr_list), self._create_format(QColor(0, 100, 0)))) # darkGreen 174 # create patterns for substitutions 175 self.rule_arg = (self._create_regexp("\\$\\(.*\\)"), self._create_format(QColor(77, 0, 38))) 176 # create patterns for DOCTYPE 177 self.rules.append((self._create_regexp("<!DOCTYPE.*>"), self._create_format(Qt.lightGray))) 178 self.rules.append((self._create_regexp("<\\?xml.*\\?>"), self._create_format(Qt.lightGray))) 179 # create patterns for yaml parameter inside 180 self.rules.append((self._create_regexp("^\s*[_.\w]*\s*:"), self._create_format(Qt.darkBlue))) 181 # create patterns for yaml oneline strings inside 182 self.rules.append((self._create_regexp("'.*'"), self._create_format(Qt.blue))) 183 # create pattern for list signes 184 self.rules.append((self._create_regexp("^\s*-"), self._create_format(Qt.darkRed, 'bold'))) 185 # create pattern for digits 186 self.rules.append((self._create_regexp("\\d+"), self._create_format(QColor(127, 64, 127)))) 187 # create patterns for strings 188 self.string_pattern = QRegExp("\"") 189 self.string_format = self._create_format(Qt.blue) 190 # part to select an XML block 191 self._tag_hl_range = [] # list with puples (start, length) 192 self._tag_hl_last = set() # set with blocks of last highlighted tags
193
194 - def _create_regexp(self, pattern=''):
195 _regexp = QRegExp() 196 _regexp.setMinimal(True) 197 _regexp.setPattern(pattern) 198 return _regexp
199
200 - def _create_format(self, color, style=''):
201 _format = QTextCharFormat() 202 _format.setForeground(color) 203 if 'bold' in style: 204 _format.setFontWeight(QFont.Bold) 205 else: 206 _format.setFontWeight(QFont.Normal) 207 if 'italic' in style: 208 _format.setFontItalic(True) 209 return _format
210
211 - def highlightBlock(self, text):
212 for pattern, form in self.rules: 213 index = pattern.indexIn(text) 214 while index >= 0: 215 length = pattern.matchedLength() 216 frmt = form 217 if self._in_hl_range(index, self._tag_hl_range): 218 frmt = QTextCharFormat(form) 219 if not self._end_tag_found: 220 frmt.setForeground(Qt.red) 221 frmt.setFontWeight(QFont.Bold) 222 self.setFormat(index, length, frmt) 223 index = pattern.indexIn(text, index + length) 224 self._tag_hl_range = [] 225 self.setCurrentBlockState(0) 226 # detection for comments 227 self._comments_idx = [] 228 idx_start_cmt = 0 229 comment_length = 0 230 if self.previousBlockState() == -1 or not self.previousBlockState() & self.STATE_COMMENT: 231 idx_start_cmt = self.comment_start.indexIn(text) 232 while idx_start_cmt >= 0: 233 idx_end = self.comment_end.indexIn(text, idx_start_cmt) 234 comment_length = 0 235 if idx_end == -1: 236 self.setCurrentBlockState(self.STATE_COMMENT) 237 comment_length = len(text) - idx_start_cmt 238 else: 239 comment_length = idx_end - idx_start_cmt + self.comment_end.matchedLength() 240 self._comments_idx.append((idx_start_cmt, comment_length)) 241 self.setFormat(idx_start_cmt, comment_length, self.comment_format) 242 idx_start_cmt = self.comment_start.indexIn(text, idx_start_cmt + comment_length) 243 # format string and detection for multiline string 244 idx_start = self.string_pattern.indexIn(text) 245 if self.previousBlockState() != -1 and self.previousBlockState() & self.STATE_STRING: 246 strlen = idx_start + self.string_pattern.matchedLength() 247 if idx_start == -1: 248 strlen = len(text) 249 self.setCurrentBlockState(self.currentBlockState() + self.STATE_STRING) 250 self.setFormat(0, strlen, self.string_format) 251 idx_start = self.string_pattern.indexIn(text, strlen) 252 idx_search = idx_start + 1 253 while idx_start >= 0: 254 # skip the strings which are in the comments 255 if not self._in_hl_range(idx_search, self._comments_idx): 256 idx_end = self.string_pattern.indexIn(text, idx_search) 257 strlen = 0 258 if not self._in_hl_range(idx_end, self._comments_idx): 259 if idx_end == -1: 260 self.setCurrentBlockState(self.currentBlockState() + self.STATE_STRING) 261 strlen = len(text) - idx_start 262 else: 263 strlen = idx_end - idx_start + self.string_pattern.matchedLength() 264 idx_search = idx_start + strlen 265 self.setFormat(idx_start, strlen, self.string_format) 266 idx_start = self.string_pattern.indexIn(text, idx_search) 267 idx_search = idx_start + 1 268 else: 269 idx_search = idx_end + 1 270 else: 271 idx_start = self.string_pattern.indexIn(text, idx_search) 272 idx_search = idx_start + 1 273 # mark arguments 274 index = self.rule_arg[0].indexIn(text) 275 while index >= 0: 276 if not self._in_hl_range(index, self._comments_idx): 277 length = self.rule_arg[0].matchedLength() 278 self.setFormat(index, length, self.rule_arg[1]) 279 index = self.rule_arg[0].indexIn(text, index + length)
280
281 - def mark_block(self, block, position):
282 text = block.text() 283 word, idx_word = self._get_current_word(text, position) 284 for hlblock in self._tag_hl_last: 285 self.rehighlightBlock(hlblock) 286 self._tag_hl_last.clear() 287 self._tag_hl_range = [(idx_word, len(word))] 288 next_block = block 289 open_braces = 0 290 closed_braces = 0 291 idx_search = idx_word 292 rindex = -1 293 loop = 0 294 tag_len = 0 295 if self._isclosetag(word): 296 # we are at the close tag: search for the open tag 297 opentag = '<%s' % self._get_tag(word) 298 tag_len = len(opentag) 299 while rindex == -1 and next_block.isValid(): 300 rindex = text.rfind(opentag, 0, idx_search) 301 obr, cbr = self._get_braces_count(text[rindex if rindex != -1 else 0:idx_search]) 302 open_braces += obr 303 closed_braces += cbr 304 loop += 1 305 if loop > 50000: 306 rindex = -1 307 break 308 if rindex == -1: 309 next_block = next_block.previous() 310 text = next_block.text() 311 idx_search = len(text) 312 elif open_braces <= closed_braces: 313 idx_search = rindex 314 rindex = -1 315 elif self._isopentag(word): 316 # we are at the open tag: search for the close tag 317 closetag = QRegExp("</%s>|/>" % self._get_tag(word)) 318 closetag.setMinimal(True) 319 while rindex == -1 and next_block.isValid(): 320 rindex = closetag.indexIn(text, idx_search) 321 max_search_idx = rindex + closetag.matchedLength() if rindex != -1 else len(text) 322 obr, cbr = self._get_braces_count(text[idx_search:max_search_idx]) 323 open_braces += obr 324 closed_braces += cbr 325 loop += 1 326 if loop > 50000: 327 rindex = -1 328 break 329 if rindex == -1: 330 next_block = next_block.next() 331 text = next_block.text() 332 idx_search = 0 333 elif open_braces > closed_braces: 334 idx_search = rindex + closetag.matchedLength() 335 rindex = -1 336 tag_len = closetag.matchedLength() 337 else: 338 self._tag_hl_range = [] 339 self._end_tag_found = rindex != -1 340 if self._tag_hl_range and block != next_block: 341 self.rehighlightBlock(block) 342 self._tag_hl_last.add(block) 343 if rindex != -1: 344 self._tag_hl_range.append((rindex, tag_len)) 345 self.rehighlightBlock(next_block) 346 self._tag_hl_last.add(next_block)
347
348 - def _get_braces_count(self, text):
349 closed_short = text.count('/>') 350 closed_long = text.count('</') 351 cmnt_long = text.count('<!') 352 openbr = text.count('<') - closed_long - cmnt_long 353 return openbr, closed_short + closed_long
354
355 - def _isopentag(self, word):
356 return word.startswith('<') and '/' not in word
357
358 - def _isclosetag(self, word):
359 return '/>' == word or word.startswith('</')
360
361 - def _get_tag(self, word):
362 return word.strip('</>')
363
364 - def _get_current_word(self, text, position):
365 word = '' 366 idx_start = position 367 for i in reversed(range(0, position)): 368 if text[i] in [' ', '\n', '=', '"']: 369 break 370 else: 371 word = "%s%s" % (text[i], word) 372 idx_start = i 373 for i in range(position, len(text)): 374 if text[i] in [' ', '\n', '=', '"']: 375 break 376 else: 377 word += text[i] 378 return word, idx_start
379
380 - def _in_hl_range(self, value, ranges):
381 for (start, length) in ranges: 382 if value >= start and value <= start + length: 383 return True 384 return False
385
386 - def get_tag_of_current_block(self, block, position):
387 text = block.text() 388 next_block = block 389 idx_search = position 390 rindex = -1 391 loop = 0 392 # we are at the close tag: search for the open tag 393 opentag = '<' 394 while rindex == -1 and next_block.isValid(): 395 rindex = text.rfind(opentag, 0, idx_search) 396 loop += 1 397 if loop > 100: 398 rindex = -1 399 break 400 if rindex == -1: 401 next_block = next_block.previous() 402 text = next_block.text() 403 idx_search = len(text) 404 tag = '' 405 if rindex != -1: 406 for i in range(rindex + 1, len(text)): 407 if text[i] in [' ', '\n', '=', '"', '>']: 408 break 409 else: 410 tag += text[i] 411 return tag
412