chromium/tools/translation/helper/grd_helper.py

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

import os
import sys
import tempfile

here = os.path.dirname(os.path.realpath(__file__))
repo_root = os.path.normpath(os.path.join(here, '..', '..', '..'))

try:
  old_sys_path = sys.path
  sys.path = sys.path + [os.path.join(repo_root, 'tools', 'grit')]
  import grit.grd_reader
  import grit.node.message
  import grit.util
finally:
  sys.path = old_sys_path

TAGS_TO_IGNORE = (
    # <include> tags mostly point to resource files that don't contain UI
    # strings.
    'include',
    # <structure> tags point to image files.
    'structure',
    # <part> tags point to .grdp files. Don't load included part files when
    # loading a .grd or .grdp file. A grd file's contents can refer to deleted
    # grdp files (e.g. if a grdp file was renamed). Trying to load it would
    # fail. It's also unnecessary to load <part> files because grdp files are
    # handled separately in GetGrdpMessagesFromString. Grdp files are also
    # expected to not contain any <part> tags.
    'part')


def GetGrdMessages(grd_path_or_string, dir_path):
  """Load the grd file and return a dict of message ids to messages.

  Ignores non-translateable messages."""
  doc = grit.grd_reader.Parse(grd_path_or_string,
                              dir_path,
                              stop_after=None,
                              first_ids_file=None,
                              debug=False,
                              defines={'_chromium': 1},
                              tags_to_ignore=set(TAGS_TO_IGNORE),
                              skip_validation_checks=True)
  return {
      msg.attrs['name']: msg
      for msg in doc.GetChildrenOfType(grit.node.message.MessageNode)
      if msg.IsTranslateable() and not msg.IsAccessibilityWithNoUI()
  }


def GetGrdpMessagesFromString(grdp_string):
  """Parses the contents of the grdp file given in grdp_string.

    grd_reader can't parse grdp files directly. Instead, this replaces grd
    specific tags in the input string with grdp specific tags, writes the output
    string in a temporary file and loads the grd from the temporary file.

    This code previously created a temporary directory (using grit.util), wrote
    a temporary grd file pointing to another temporary grdp file that contained
    the input string. This was not testable since os.path.exists is overridden
    in tests and grit.util uses mkdirs which in turn calls os.path.exists. This
    new code works because it doesn't need to check the existence of a path.

    It's expected that a grdp file will not refer to another grdp file via a
    <part> tag. Currently, none of the grdp files in the repository does that.
     """
  replaced_string = grdp_string.replace(
      '<grit-part>',
      """<grit base_dir="." latest_public_release="1" current_release="1">
            <release seq="1">
              <messages fallback_to_english="true">
        """)
  replaced_string = replaced_string.replace(
      '</grit-part>', """</messages>
          </release>
        </grit>""")
  with tempfile.NamedTemporaryFile(delete=False) as f:
    f.write(replaced_string.encode('utf-8'))
    f.close()
    messages = GetGrdMessages(f.name, os.path.dirname(f.name))
    os.remove(f.name)
    return messages