Skip to content

Commit b15f4ed

Browse files
pelsonbjlittle
authored andcommitted
Prevent creation of invalid CF variable names. (SciTools#3009)
Prevent creation of invalid CF variable names.
1 parent a3a9a19 commit b15f4ed

File tree

3 files changed

+54
-1
lines changed

3 files changed

+54
-1
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
* All var names being written to NetCDF are now CF compliant. Non alpha-numeric characters are replaced with '_', and must always have a leading letter.
2+
Ref: https://github.com/SciTools/iris/pull/2930

lib/iris/fileformats/netcdf.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,6 +1339,27 @@ def _get_dim_names(self, cube):
13391339
dimension_names.append(dim_name)
13401340
return dimension_names
13411341

1342+
@staticmethod
1343+
def cf_valid_var_name(var_name):
1344+
"""
1345+
Return a valid CF var_name given a potentially invalid name.
1346+
1347+
Args:
1348+
1349+
* var_name (str):
1350+
The var_name to normalise
1351+
1352+
Returns:
1353+
A var_name suitable for passing through for variable creation.
1354+
1355+
"""
1356+
# Replace invalid charaters with an underscore ("_").
1357+
var_name = re.sub(r'[^a-zA-Z0-9]', "_", var_name)
1358+
# Ensure the variable name starts with a letter.
1359+
if re.match(r'^[^a-zA-Z]', var_name):
1360+
var_name = 'var_{}'.format(var_name)
1361+
return var_name
1362+
13421363
@staticmethod
13431364
def _cf_coord_identity(coord):
13441365
"""
@@ -1448,6 +1469,7 @@ def _get_cube_variable_name(self, cube):
14481469
# Convert to lower case and replace whitespace by underscores.
14491470
cf_name = '_'.join(cube.name().lower().split())
14501471

1472+
cf_name = self.cf_valid_var_name(cf_name)
14511473
return cf_name
14521474

14531475
def _get_coord_variable_name(self, cube, coord):
@@ -1480,6 +1502,8 @@ def _get_coord_variable_name(self, cube, coord):
14801502
name = 'unknown_scalar'
14811503
# Convert to lower case and replace whitespace by underscores.
14821504
cf_name = '_'.join(name.lower().split())
1505+
1506+
cf_name = self.cf_valid_var_name(cf_name)
14831507
return cf_name
14841508

14851509
def _create_cf_cell_measure_variable(self, cube, dimension_names,

lib/iris/tests/unit/fileformats/netcdf/test_Saver.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# (C) British Crown Copyright 2013 - 2017, Met Office
1+
# (C) British Crown Copyright 2013 - 2018, Met Office
22
#
33
# This file is part of Iris.
44
#
@@ -472,6 +472,33 @@ def test_masked_byte_fill_value_passed(self):
472472
pass
473473

474474

475+
class Test_cf_valid_var_name(tests.IrisTest):
476+
def test_no_replacement(self):
477+
self.assertEqual(Saver.cf_valid_var_name('valid_Nam3'),
478+
'valid_Nam3')
479+
480+
def test_special_chars(self):
481+
self.assertEqual(Saver.cf_valid_var_name('inv?alid'),
482+
'inv_alid')
483+
484+
def test_leading_underscore(self):
485+
self.assertEqual(Saver.cf_valid_var_name('_invalid'),
486+
'var__invalid')
487+
488+
def test_leading_number(self):
489+
self.assertEqual(Saver.cf_valid_var_name('2invalid'),
490+
'var_2invalid')
491+
492+
def test_leading_invalid(self):
493+
self.assertEqual(Saver.cf_valid_var_name('?invalid'),
494+
'var__invalid')
495+
496+
def test_no_hyphen(self):
497+
# CF explicitly prohibits hyphen, even though it is fine in NetCDF.
498+
self.assertEqual(Saver.cf_valid_var_name('valid-netcdf'),
499+
'valid_netcdf')
500+
501+
475502
class _Common__check_attribute_compliance(object):
476503
def setUp(self):
477504
self.container = mock.Mock(name='container', attributes={})

0 commit comments

Comments
 (0)