chromium/chrome/browser/resources/chromeos/accessibility/tools/fix_grd.py

#!/usr/bin/env python

# Copyright 2020 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
'''Enforces message text style.

Expects one argument to be the path of the .grd(p).

Sample usage:

./fix_grd.py some_messages.grd
git diff
# Audit changes (e.g. formatting, quotes, etc).
'''

import path_helpers
import optparse
import os
import re
import sys
import xml.etree.ElementTree as ET

def Die(message):
  '''Prints an error message and exit the program.'''
  sys.stderr.write(message + '\n')
  sys.exit(1)

def Process(xml_file):
  xml = ET.parse(xml_file)
  root = xml.getroot()
  messages = root.findall('message')
  modified = False
  removed_so_far = set()
  for message in messages:
    modified |= MaybeRemoveTrailingPeriods(message)
    modified |= MaybeRemoveUnusedMessage(root, message, removed_so_far)

  if modified:
    xml.write(xml_file, encoding='UTF-8')

def MaybeRemoveTrailingPeriods(message):
  modified = False
  # Re-write messages containing a period at the end (excluding whitespace)
  # and no other periods. This excludes phrases that have multiple sentences
  # which should retain periods.
  text = message.text.rstrip()
  if text.endswith('.') and text.find('.') == text.rfind('.'):
    modified = True
    message.text = message.text.replace('.', '')
  return modified

def MaybeRemoveUnusedMessage(root, message, removed_so_far):
  found = False

  # Always strip IDS_ and lowercase the message id.
  base_message_id = re.sub('^ids_', '', message.get('name').lower())

  # Get the unprefixed message id. This is used by various extensions like
  # ChromeVox and STS.
  message_id = re.sub(
    '^(chromevox_|select_to_speak_|switch_access_|enhanced_network_tts_)',
    '', base_message_id)

  # This message is needed by the extension system.
  if message_id == 'locale':
    return False

  # Explicitly skip these messages in ChromeVox since they get programmatically
  # constructed. If the non _brl counterpart was removed though, also remove it.
  if message_id.endswith('_brl'):
    if message_id[:-4] in removed_so_far:
      sys.stdout.write('Removing ' + message_id + '\n')
      root.remove(message)
      return True
    else:
      return False

  # Explicitly skip messages referencing ARIA; these strings should be used by
  # ChromeVox.
  if message.get('desc').find('ARIA') != -1:
    return False

  # Explicitly skip messages starting with tag_.
  if message_id.startswith('tag_'):
    return False

  for dir_name, subdir_list, file_list in os.walk(
      path_helpers.AccessibilityPath()):
    for fname in file_list:
      if not fname.endswith('.js') and not fname.endswith('.html'):
        continue
      with open(os.path.join(dir_name, fname), 'r') as f:
        for line in f:
          index = line.find(base_message_id)
          if index == -1:
            index = line.find(message_id)

          # Eliminate partial matches (e.g. for bar, foo_bar).
          if index > 0 and line[index - 1] == '_':
            continue

          if index != -1:
            found = True
            break

  if not found:
    sys.stdout.write('Removing ' + message_id + '\n')
    removed_so_far.add(message_id)
    root.remove(message)

  return not found

if __name__ == '__main__':
  options, args = optparse.OptionParser().parse_args()
  if len(args) != 1:
    Die('Expected one .grd file')
  Process(args[0])