We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
With change in #2588, xmlquery sometimes doesn't give you an error when it should
This is a minor issue, so I don't feel like it needs to be fixed for the CESM2 release, but I feel like it would eventually be good to fix this to work more robustly:

With the fix that @jedwards4b put in in #2588 , a new issue appears: If you xmlquery a non-existent variable with flag `--file`, you no longer get an error: e.g.:

```
$ ./xmlquery STOP_FOO --file env_run.xml
setting case file to env_run.xml
$ echo $?
0
```

(This still works fine - giving an error as expected - if you don't include the --file option.)

Since specifying --file in this single-variable usage of xmlquery seems uncommon, this doesn't feel critical to fix immediately.

From a quick look through the logic, I'm wondering if a better solution to #2580 would be to revert the logic on these lines to the pre-PR #2588 logic:

https://github.com/ESMCI/cime/blob/6c4b1524cd0cdab8257dc98ea6c7e6024a1ac9d9/scripts/Tools/xmlquery#L255-L258

and instead adding an option to `case.get_record_fields`, as called on this line:

https://github.com/ESMCI/cime/blob/6c4b1524cd0cdab8257dc98ea6c7e6024a1ac9d9/scripts/Tools/xmlquery#L286

to pass in an optional file; if specified, it would only query that file, rather than querying all files.

My understanding of the current --listall logic when --file is given is that it gets variables from all files, then builds up a result list containing just those variables in the given file; with the above suggestion, the subsetting on file would happen up-front. I'm not sure if this would work right, though.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `scripts/lib/CIME/XML/env_base.py`
Content:
```
1 """
2 Base class for env files.  This class inherits from EntryID.py
3 """
4 from CIME.XML.standard_module_setup import *
5 from CIME.XML.entry_id import EntryID
6 from CIME.XML.headers import Headers
7 from CIME.utils import convert_to_type
8 logger = logging.getLogger(__name__)
9 
10 class EnvBase(EntryID):
11 
12     def __init__(self, case_root, infile, schema=None):
13         if case_root is None:
14             case_root = os.getcwd()
15 
16         if os.path.isabs(infile):
17             fullpath = infile
18         else:
19             fullpath = os.path.join(case_root, infile)
20 
21         EntryID.__init__(self, fullpath, schema=schema, read_only=False)
22 
23         self._id_map = None
24         self._group_map = None
25 
26         if not os.path.isfile(fullpath):
27             headerobj = Headers()
28             headernode = headerobj.get_header_node(os.path.basename(fullpath))
29             self.add_child(headernode)
30         else:
31             self._setup_cache()
32 
33     def _setup_cache(self):
34         self._id_map = {}    # map id directly to nodes
35         self._group_map = {} # map group name to entry id dict
36 
37         group_elems = self.get_children("group")
38         for group_elem in group_elems:
39             group_name = self.get(group_elem, "id")
40             expect(group_name not in self._group_map, "Repeat group '{}'".format(group_name))
41             group_map = {}
42             self._group_map[group_name] = group_map
43             entry_elems = self.get_children("entry", root=group_elem)
44             for entry_elem in entry_elems:
45                 entry_id = self.get(entry_elem, "id")
46                 expect(entry_id not in group_map, "Repeat entry '{}' in group '{}'".format(entry_id, group_name))
47                 group_map[entry_id] = entry_elem
48                 if entry_id in self._id_map:
49                     self._id_map[entry_id].append(entry_elem)
50                 else:
51                     self._id_map[entry_id] = [entry_elem]
52 
53         self.lock()
54 
55     def change_file(self, newfile, copy=False):
56         self.unlock()
57         EntryID.change_file(self, newfile, copy=copy)
58         self._setup_cache()
59 
60     def get_children(self, name=None, attributes=None, root=None):
61         if self.locked and name == "entry" and attributes is not None and attributes.keys() == ["id"]:
62             entry_id = attributes["id"]
63             if root is None or self.name(root) == "file":
64                 if entry_id in self._id_map:
65                     return self._id_map[entry_id]
66                 else:
67                     return []
68             else:
69                 expect(self.name(root) == "group", "Unexpected elem '{}' for {}, attrs {}".format(self.name(root), self.filename, self.attrib(root)))
70                 group_id = self.get(root, "id")
71                 if group_id in self._group_map and entry_id in self._group_map[group_id]:
72                     return [self._group_map[group_id][entry_id]]
73                 else:
74                     return []
75 
76         else:
77             # Non-compliant look up
78             return EntryID.get_children(self, name=name, attributes=attributes, root=root)
79 
80     def scan_children(self, nodename, attributes=None, root=None):
81         if self.locked and nodename == "entry" and attributes is not None and attributes.keys() == ["id"]:
82             return EnvBase.get_children(self, name=nodename, attributes=attributes, root=root)
83         else:
84             return EntryID.scan_children(self, nodename, attributes=attributes, root=root)
85 
86     def set_components(self, components):
87         if hasattr(self, '_components'):
88             # pylint: disable=attribute-defined-outside-init
89             self._components = components
90 
91     def check_if_comp_var(self, vid, attribute=None, node=None):
92         comp = None
93         if node is None:
94             nodes = self.scan_children("entry", {"id" : vid})
95             if len(nodes):
96                 node = nodes[0]
97 
98         if node:
99             valnodes = self.scan_children("value", attributes={"compclass":None}, root=node)
100             if len(valnodes) == 0:
101                 logger.debug("vid {} is not a compvar".format(vid))
102                 return vid, None, False
103             else:
104                 logger.debug("vid {} is a compvar".format(vid))
105                 if attribute is not None:
106                     comp = attribute["compclass"]
107                 return vid, comp, True
108         else:
109             if hasattr(self, "_components"):
110                 new_vid = None
111                 for comp in self._components:
112                     if vid.endswith('_'+comp):
113                         new_vid = vid.replace('_'+comp, '', 1)
114                     elif vid.startswith(comp+'_'):
115                         new_vid = vid.replace(comp+'_', '', 1)
116                     elif '_' + comp + '_' in vid:
117                         new_vid = vid.replace(comp+'_','', 1)
118                     if new_vid is not None:
119                         break
120                 if new_vid is not None:
121                     logger.debug("vid {} is a compvar with comp {}".format(vid, comp))
122                     return new_vid, comp, True
123 
124         return vid, None, False
125 
126     def get_value(self, vid, attribute=None, resolved=True, subgroup=None):
127         """
128         Get a value for entry with id attribute vid.
129         or from the values field if the attribute argument is provided
130         and matches
131         """
132         value = None
133         vid, comp, iscompvar = self.check_if_comp_var(vid, attribute)
134         logger.debug("vid {} comp {} iscompvar {}".format(vid, comp, iscompvar))
135         if iscompvar:
136             if comp is None:
137                 if subgroup is not None:
138                     comp = subgroup
139                 else:
140                     logger.debug("Not enough info to get value for {}".format(vid))
141                     return value
142             if attribute is None:
143                 attribute = {"compclass" : comp}
144             else:
145                 attribute["compclass"] = comp
146             node = self.scan_optional_child("entry", {"id":vid})
147             if node is not None:
148                 type_str = self._get_type_info(node)
149                 values = self.get_optional_child("values", root=node)
150                 node = values if values is not None else node
151                 val = self.get_element_text("value", attribute, root=node)
152                 if val is not None:
153                     if val.startswith("$"):
154                         value = val
155                     else:
156                         value = convert_to_type(val,type_str, vid)
157                 return value
158 
159         return EntryID.get_value(self, vid, attribute=attribute, resolved=resolved, subgroup=subgroup)
160 
161     def set_value(self, vid, value, subgroup=None, ignore_type=False):
162         """
163         Set the value of an entry-id field to value
164         Returns the value or None if not found
165         subgroup is ignored in the general routine and applied in specific methods
166         """
167         vid, comp, iscompvar = self.check_if_comp_var(vid, None)
168         val = None
169         root = self.root if subgroup is None else self.get_optional_child("group", {"id":subgroup})
170         node = self.scan_optional_child("entry", {"id":vid}, root=root)
171         if node is not None:
172             if iscompvar and comp is None:
173                 # pylint: disable=no-member
174                 for comp in self._components:
175                     val = self._set_value(node, value, vid, subgroup, ignore_type, compclass=comp)
176             else:
177                 val = self._set_value(node, value, vid, subgroup, ignore_type, compclass=comp)
178         return val
179 
180     # pylint: disable=arguments-differ
181     def _set_value(self, node, value, vid=None, subgroup=None, ignore_type=False, compclass=None):
182         if vid is None:
183             vid = self.get(node, "id")
184         vid, _, iscompvar = self.check_if_comp_var(vid, node=node)
185 
186         if iscompvar:
187             expect(compclass is not None, "compclass must be specified if is comp var")
188             attribute = {"compclass":compclass}
189             str_value = self.get_valid_value_string(node, value, vid, ignore_type)
190             values = self.get_optional_child("values", root=node)
191             node = values if values is not None else node
192             val = self.set_element_text("value", str_value, attribute, root=node)
193         else:
194             val = EntryID._set_value(self, node, value, vid, subgroup, ignore_type)
195         return val
196 
197     def get_nodes_by_id(self, varid):
198         varid, _, _ = self.check_if_comp_var(varid, None)
199         return EntryID.get_nodes_by_id(self, varid)
200 
201     def cleanupnode(self, node):
202         """
203         Remove the <group>, <file>, <values> and <value> childnodes from node
204         """
205         fnode = self.get_child("file", root=node)
206         self.remove_child(fnode, node)
207         gnode = self.get_child("group", root=node)
208         self.remove_child(gnode, node)
209         dnode = self.get_optional_child("default_value", root=node)
210         if dnode is not None:
211             self.remove_child(dnode, node)
212 
213         vnode = self.get_optional_child("values", root=node)
214         if vnode is not None:
215             componentatt = self.get_children("value", attributes={"component":"ATM"}, root=vnode)
216             # backward compatibility (compclasses and component were mixed
217             # now we seperated into component and compclass)
218             if len(componentatt) > 0:
219                 for ccnode in self.get_children("value", attributes={"component":None}, root=vnode):
220                     val = self.get(ccnode, "component")
221                     self.pop(ccnode, "component")
222                     self.set(ccnode, "compclass", val)
223 
224             compclassatt = self.get_children("value", attributes={"compclass":None}, root=vnode)
225             if len(compclassatt) == 0:
226                 self.remove_child(vnode, root=node)
227 
228         return node
229
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/scripts/lib/CIME/XML/env_base.py b/scripts/lib/CIME/XML/env_base.py
--- a/scripts/lib/CIME/XML/env_base.py
+++ b/scripts/lib/CIME/XML/env_base.py
@@ -106,7 +106,7 @@
                     comp = attribute["compclass"]
                 return vid, comp, True
         else:
-            if hasattr(self, "_components"):
+            if hasattr(self, "_components") and self._components:
                 new_vid = None
                 for comp in self._components:
                     if vid.endswith('_'+comp):

{"golden_diff": "diff --git a/scripts/lib/CIME/XML/env_base.py b/scripts/lib/CIME/XML/env_base.py\n--- a/scripts/lib/CIME/XML/env_base.py\n+++ b/scripts/lib/CIME/XML/env_base.py\n@@ -106,7 +106,7 @@\n                     comp = attribute[\"compclass\"]\n                 return vid, comp, True\n         else:\n-            if hasattr(self, \"_components\"):\n+            if hasattr(self, \"_components\") and self._components:\n                 new_vid = None\n                 for comp in self._components:\n                     if vid.endswith('_'+comp):\n", "issue": "With change in #2588, xmlquery sometimes doesn't give you an error when it should\nThis is a minor issue, so I don't feel like it needs to be fixed for the CESM2 release, but I feel like it would eventually be good to fix this to work more robustly:\r\n\r\nWith the fix that @jedwards4b put in in #2588 , a new issue appears: If you xmlquery a non-existent variable with flag `--file`, you no longer get an error: e.g.:\r\n\r\n```\r\n$ ./xmlquery STOP_FOO --file env_run.xml\r\nsetting case file to env_run.xml\r\n$ echo $?\r\n0\r\n```\r\n\r\n(This still works fine - giving an error as expected - if you don't include the --file option.)\r\n\r\nSince specifying --file in this single-variable usage of xmlquery seems uncommon, this doesn't feel critical to fix immediately.\r\n\r\nFrom a quick look through the logic, I'm wondering if a better solution to #2580 would be to revert the logic on these lines to the pre-PR #2588 logic:\r\n\r\nhttps://github.com/ESMCI/cime/blob/6c4b1524cd0cdab8257dc98ea6c7e6024a1ac9d9/scripts/Tools/xmlquery#L255-L258\r\n\r\nand instead adding an option to `case.get_record_fields`, as called on this line:\r\n\r\nhttps://github.com/ESMCI/cime/blob/6c4b1524cd0cdab8257dc98ea6c7e6024a1ac9d9/scripts/Tools/xmlquery#L286\r\n\r\nto pass in an optional file; if specified, it would only query that file, rather than querying all files.\r\n\r\nMy understanding of the current --listall logic when --file is given is that it gets variables from all files, then builds up a result list containing just those variables in the given file; with the above suggestion, the subsetting on file would happen up-front. I'm not sure if this would work right, though.\n", "before_files": [{"content": "\"\"\"\nBase class for env files.  This class inherits from EntryID.py\n\"\"\"\nfrom CIME.XML.standard_module_setup import *\nfrom CIME.XML.entry_id import EntryID\nfrom CIME.XML.headers import Headers\nfrom CIME.utils import convert_to_type\nlogger = logging.getLogger(__name__)\n\nclass EnvBase(EntryID):\n\n    def __init__(self, case_root, infile, schema=None):\n        if case_root is None:\n            case_root = os.getcwd()\n\n        if os.path.isabs(infile):\n            fullpath = infile\n        else:\n            fullpath = os.path.join(case_root, infile)\n\n        EntryID.__init__(self, fullpath, schema=schema, read_only=False)\n\n        self._id_map = None\n        self._group_map = None\n\n        if not os.path.isfile(fullpath):\n            headerobj = Headers()\n            headernode = headerobj.get_header_node(os.path.basename(fullpath))\n            self.add_child(headernode)\n        else:\n            self._setup_cache()\n\n    def _setup_cache(self):\n        self._id_map = {}    # map id directly to nodes\n        self._group_map = {} # map group name to entry id dict\n\n        group_elems = self.get_children(\"group\")\n        for group_elem in group_elems:\n            group_name = self.get(group_elem, \"id\")\n            expect(group_name not in self._group_map, \"Repeat group '{}'\".format(group_name))\n            group_map = {}\n            self._group_map[group_name] = group_map\n            entry_elems = self.get_children(\"entry\", root=group_elem)\n            for entry_elem in entry_elems:\n                entry_id = self.get(entry_elem, \"id\")\n                expect(entry_id not in group_map, \"Repeat entry '{}' in group '{}'\".format(entry_id, group_name))\n                group_map[entry_id] = entry_elem\n                if entry_id in self._id_map:\n                    self._id_map[entry_id].append(entry_elem)\n                else:\n                    self._id_map[entry_id] = [entry_elem]\n\n        self.lock()\n\n    def change_file(self, newfile, copy=False):\n        self.unlock()\n        EntryID.change_file(self, newfile, copy=copy)\n        self._setup_cache()\n\n    def get_children(self, name=None, attributes=None, root=None):\n        if self.locked and name == \"entry\" and attributes is not None and attributes.keys() == [\"id\"]:\n            entry_id = attributes[\"id\"]\n            if root is None or self.name(root) == \"file\":\n                if entry_id in self._id_map:\n                    return self._id_map[entry_id]\n                else:\n                    return []\n            else:\n                expect(self.name(root) == \"group\", \"Unexpected elem '{}' for {}, attrs {}\".format(self.name(root), self.filename, self.attrib(root)))\n                group_id = self.get(root, \"id\")\n                if group_id in self._group_map and entry_id in self._group_map[group_id]:\n                    return [self._group_map[group_id][entry_id]]\n                else:\n                    return []\n\n        else:\n            # Non-compliant look up\n            return EntryID.get_children(self, name=name, attributes=attributes, root=root)\n\n    def scan_children(self, nodename, attributes=None, root=None):\n        if self.locked and nodename == \"entry\" and attributes is not None and attributes.keys() == [\"id\"]:\n            return EnvBase.get_children(self, name=nodename, attributes=attributes, root=root)\n        else:\n            return EntryID.scan_children(self, nodename, attributes=attributes, root=root)\n\n    def set_components(self, components):\n        if hasattr(self, '_components'):\n            # pylint: disable=attribute-defined-outside-init\n            self._components = components\n\n    def check_if_comp_var(self, vid, attribute=None, node=None):\n        comp = None\n        if node is None:\n            nodes = self.scan_children(\"entry\", {\"id\" : vid})\n            if len(nodes):\n                node = nodes[0]\n\n        if node:\n            valnodes = self.scan_children(\"value\", attributes={\"compclass\":None}, root=node)\n            if len(valnodes) == 0:\n                logger.debug(\"vid {} is not a compvar\".format(vid))\n                return vid, None, False\n            else:\n                logger.debug(\"vid {} is a compvar\".format(vid))\n                if attribute is not None:\n                    comp = attribute[\"compclass\"]\n                return vid, comp, True\n        else:\n            if hasattr(self, \"_components\"):\n                new_vid = None\n                for comp in self._components:\n                    if vid.endswith('_'+comp):\n                        new_vid = vid.replace('_'+comp, '', 1)\n                    elif vid.startswith(comp+'_'):\n                        new_vid = vid.replace(comp+'_', '', 1)\n                    elif '_' + comp + '_' in vid:\n                        new_vid = vid.replace(comp+'_','', 1)\n                    if new_vid is not None:\n                        break\n                if new_vid is not None:\n                    logger.debug(\"vid {} is a compvar with comp {}\".format(vid, comp))\n                    return new_vid, comp, True\n\n        return vid, None, False\n\n    def get_value(self, vid, attribute=None, resolved=True, subgroup=None):\n        \"\"\"\n        Get a value for entry with id attribute vid.\n        or from the values field if the attribute argument is provided\n        and matches\n        \"\"\"\n        value = None\n        vid, comp, iscompvar = self.check_if_comp_var(vid, attribute)\n        logger.debug(\"vid {} comp {} iscompvar {}\".format(vid, comp, iscompvar))\n        if iscompvar:\n            if comp is None:\n                if subgroup is not None:\n                    comp = subgroup\n                else:\n                    logger.debug(\"Not enough info to get value for {}\".format(vid))\n                    return value\n            if attribute is None:\n                attribute = {\"compclass\" : comp}\n            else:\n                attribute[\"compclass\"] = comp\n            node = self.scan_optional_child(\"entry\", {\"id\":vid})\n            if node is not None:\n                type_str = self._get_type_info(node)\n                values = self.get_optional_child(\"values\", root=node)\n                node = values if values is not None else node\n                val = self.get_element_text(\"value\", attribute, root=node)\n                if val is not None:\n                    if val.startswith(\"$\"):\n                        value = val\n                    else:\n                        value = convert_to_type(val,type_str, vid)\n                return value\n\n        return EntryID.get_value(self, vid, attribute=attribute, resolved=resolved, subgroup=subgroup)\n\n    def set_value(self, vid, value, subgroup=None, ignore_type=False):\n        \"\"\"\n        Set the value of an entry-id field to value\n        Returns the value or None if not found\n        subgroup is ignored in the general routine and applied in specific methods\n        \"\"\"\n        vid, comp, iscompvar = self.check_if_comp_var(vid, None)\n        val = None\n        root = self.root if subgroup is None else self.get_optional_child(\"group\", {\"id\":subgroup})\n        node = self.scan_optional_child(\"entry\", {\"id\":vid}, root=root)\n        if node is not None:\n            if iscompvar and comp is None:\n                # pylint: disable=no-member\n                for comp in self._components:\n                    val = self._set_value(node, value, vid, subgroup, ignore_type, compclass=comp)\n            else:\n                val = self._set_value(node, value, vid, subgroup, ignore_type, compclass=comp)\n        return val\n\n    # pylint: disable=arguments-differ\n    def _set_value(self, node, value, vid=None, subgroup=None, ignore_type=False, compclass=None):\n        if vid is None:\n            vid = self.get(node, \"id\")\n        vid, _, iscompvar = self.check_if_comp_var(vid, node=node)\n\n        if iscompvar:\n            expect(compclass is not None, \"compclass must be specified if is comp var\")\n            attribute = {\"compclass\":compclass}\n            str_value = self.get_valid_value_string(node, value, vid, ignore_type)\n            values = self.get_optional_child(\"values\", root=node)\n            node = values if values is not None else node\n            val = self.set_element_text(\"value\", str_value, attribute, root=node)\n        else:\n            val = EntryID._set_value(self, node, value, vid, subgroup, ignore_type)\n        return val\n\n    def get_nodes_by_id(self, varid):\n        varid, _, _ = self.check_if_comp_var(varid, None)\n        return EntryID.get_nodes_by_id(self, varid)\n\n    def cleanupnode(self, node):\n        \"\"\"\n        Remove the <group>, <file>, <values> and <value> childnodes from node\n        \"\"\"\n        fnode = self.get_child(\"file\", root=node)\n        self.remove_child(fnode, node)\n        gnode = self.get_child(\"group\", root=node)\n        self.remove_child(gnode, node)\n        dnode = self.get_optional_child(\"default_value\", root=node)\n        if dnode is not None:\n            self.remove_child(dnode, node)\n\n        vnode = self.get_optional_child(\"values\", root=node)\n        if vnode is not None:\n            componentatt = self.get_children(\"value\", attributes={\"component\":\"ATM\"}, root=vnode)\n            # backward compatibility (compclasses and component were mixed\n            # now we seperated into component and compclass)\n            if len(componentatt) > 0:\n                for ccnode in self.get_children(\"value\", attributes={\"component\":None}, root=vnode):\n                    val = self.get(ccnode, \"component\")\n                    self.pop(ccnode, \"component\")\n                    self.set(ccnode, \"compclass\", val)\n\n            compclassatt = self.get_children(\"value\", attributes={\"compclass\":None}, root=vnode)\n            if len(compclassatt) == 0:\n                self.remove_child(vnode, root=node)\n\n        return node\n", "path": "scripts/lib/CIME/XML/env_base.py"}], "after_files": [{"content": "\"\"\"\nBase class for env files.  This class inherits from EntryID.py\n\"\"\"\nfrom CIME.XML.standard_module_setup import *\nfrom CIME.XML.entry_id import EntryID\nfrom CIME.XML.headers import Headers\nfrom CIME.utils import convert_to_type\nlogger = logging.getLogger(__name__)\n\nclass EnvBase(EntryID):\n\n    def __init__(self, case_root, infile, schema=None):\n        if case_root is None:\n            case_root = os.getcwd()\n\n        if os.path.isabs(infile):\n            fullpath = infile\n        else:\n            fullpath = os.path.join(case_root, infile)\n\n        EntryID.__init__(self, fullpath, schema=schema, read_only=False)\n\n        self._id_map = None\n        self._group_map = None\n\n        if not os.path.isfile(fullpath):\n            headerobj = Headers()\n            headernode = headerobj.get_header_node(os.path.basename(fullpath))\n            self.add_child(headernode)\n        else:\n            self._setup_cache()\n\n    def _setup_cache(self):\n        self._id_map = {}    # map id directly to nodes\n        self._group_map = {} # map group name to entry id dict\n\n        group_elems = self.get_children(\"group\")\n        for group_elem in group_elems:\n            group_name = self.get(group_elem, \"id\")\n            expect(group_name not in self._group_map, \"Repeat group '{}'\".format(group_name))\n            group_map = {}\n            self._group_map[group_name] = group_map\n            entry_elems = self.get_children(\"entry\", root=group_elem)\n            for entry_elem in entry_elems:\n                entry_id = self.get(entry_elem, \"id\")\n                expect(entry_id not in group_map, \"Repeat entry '{}' in group '{}'\".format(entry_id, group_name))\n                group_map[entry_id] = entry_elem\n                if entry_id in self._id_map:\n                    self._id_map[entry_id].append(entry_elem)\n                else:\n                    self._id_map[entry_id] = [entry_elem]\n\n        self.lock()\n\n    def change_file(self, newfile, copy=False):\n        self.unlock()\n        EntryID.change_file(self, newfile, copy=copy)\n        self._setup_cache()\n\n    def get_children(self, name=None, attributes=None, root=None):\n        if self.locked and name == \"entry\" and attributes is not None and attributes.keys() == [\"id\"]:\n            entry_id = attributes[\"id\"]\n            if root is None or self.name(root) == \"file\":\n                if entry_id in self._id_map:\n                    return self._id_map[entry_id]\n                else:\n                    return []\n            else:\n                expect(self.name(root) == \"group\", \"Unexpected elem '{}' for {}, attrs {}\".format(self.name(root), self.filename, self.attrib(root)))\n                group_id = self.get(root, \"id\")\n                if group_id in self._group_map and entry_id in self._group_map[group_id]:\n                    return [self._group_map[group_id][entry_id]]\n                else:\n                    return []\n\n        else:\n            # Non-compliant look up\n            return EntryID.get_children(self, name=name, attributes=attributes, root=root)\n\n    def scan_children(self, nodename, attributes=None, root=None):\n        if self.locked and nodename == \"entry\" and attributes is not None and attributes.keys() == [\"id\"]:\n            return EnvBase.get_children(self, name=nodename, attributes=attributes, root=root)\n        else:\n            return EntryID.scan_children(self, nodename, attributes=attributes, root=root)\n\n    def set_components(self, components):\n        if hasattr(self, '_components'):\n            # pylint: disable=attribute-defined-outside-init\n            self._components = components\n\n    def check_if_comp_var(self, vid, attribute=None, node=None):\n        comp = None\n        if node is None:\n            nodes = self.scan_children(\"entry\", {\"id\" : vid})\n            if len(nodes):\n                node = nodes[0]\n\n        if node:\n            valnodes = self.scan_children(\"value\", attributes={\"compclass\":None}, root=node)\n            if len(valnodes) == 0:\n                logger.debug(\"vid {} is not a compvar\".format(vid))\n                return vid, None, False\n            else:\n                logger.debug(\"vid {} is a compvar\".format(vid))\n                if attribute is not None:\n                    comp = attribute[\"compclass\"]\n                return vid, comp, True\n        else:\n            if hasattr(self, \"_components\") and self._components:\n                new_vid = None\n                for comp in self._components:\n                    if vid.endswith('_'+comp):\n                        new_vid = vid.replace('_'+comp, '', 1)\n                    elif vid.startswith(comp+'_'):\n                        new_vid = vid.replace(comp+'_', '', 1)\n                    elif '_' + comp + '_' in vid:\n                        new_vid = vid.replace(comp+'_','', 1)\n                    if new_vid is not None:\n                        break\n                if new_vid is not None:\n                    logger.debug(\"vid {} is a compvar with comp {}\".format(vid, comp))\n                    return new_vid, comp, True\n\n        return vid, None, False\n\n    def get_value(self, vid, attribute=None, resolved=True, subgroup=None):\n        \"\"\"\n        Get a value for entry with id attribute vid.\n        or from the values field if the attribute argument is provided\n        and matches\n        \"\"\"\n        value = None\n        vid, comp, iscompvar = self.check_if_comp_var(vid, attribute)\n        logger.debug(\"vid {} comp {} iscompvar {}\".format(vid, comp, iscompvar))\n        if iscompvar:\n            if comp is None:\n                if subgroup is not None:\n                    comp = subgroup\n                else:\n                    logger.debug(\"Not enough info to get value for {}\".format(vid))\n                    return value\n            if attribute is None:\n                attribute = {\"compclass\" : comp}\n            else:\n                attribute[\"compclass\"] = comp\n            node = self.scan_optional_child(\"entry\", {\"id\":vid})\n            if node is not None:\n                type_str = self._get_type_info(node)\n                values = self.get_optional_child(\"values\", root=node)\n                node = values if values is not None else node\n                val = self.get_element_text(\"value\", attribute, root=node)\n                if val is not None:\n                    if val.startswith(\"$\"):\n                        value = val\n                    else:\n                        value = convert_to_type(val,type_str, vid)\n                return value\n\n        return EntryID.get_value(self, vid, attribute=attribute, resolved=resolved, subgroup=subgroup)\n\n    def set_value(self, vid, value, subgroup=None, ignore_type=False):\n        \"\"\"\n        Set the value of an entry-id field to value\n        Returns the value or None if not found\n        subgroup is ignored in the general routine and applied in specific methods\n        \"\"\"\n        vid, comp, iscompvar = self.check_if_comp_var(vid, None)\n        val = None\n        root = self.root if subgroup is None else self.get_optional_child(\"group\", {\"id\":subgroup})\n        node = self.scan_optional_child(\"entry\", {\"id\":vid}, root=root)\n        if node is not None:\n            if iscompvar and comp is None:\n                # pylint: disable=no-member\n                for comp in self._components:\n                    val = self._set_value(node, value, vid, subgroup, ignore_type, compclass=comp)\n            else:\n                val = self._set_value(node, value, vid, subgroup, ignore_type, compclass=comp)\n        return val\n\n    # pylint: disable=arguments-differ\n    def _set_value(self, node, value, vid=None, subgroup=None, ignore_type=False, compclass=None):\n        if vid is None:\n            vid = self.get(node, \"id\")\n        vid, _, iscompvar = self.check_if_comp_var(vid, node=node)\n\n        if iscompvar:\n            expect(compclass is not None, \"compclass must be specified if is comp var\")\n            attribute = {\"compclass\":compclass}\n            str_value = self.get_valid_value_string(node, value, vid, ignore_type)\n            values = self.get_optional_child(\"values\", root=node)\n            node = values if values is not None else node\n            val = self.set_element_text(\"value\", str_value, attribute, root=node)\n        else:\n            val = EntryID._set_value(self, node, value, vid, subgroup, ignore_type)\n        return val\n\n    def get_nodes_by_id(self, varid):\n        varid, _, _ = self.check_if_comp_var(varid, None)\n        return EntryID.get_nodes_by_id(self, varid)\n\n    def cleanupnode(self, node):\n        \"\"\"\n        Remove the <group>, <file>, <values> and <value> childnodes from node\n        \"\"\"\n        fnode = self.get_child(\"file\", root=node)\n        self.remove_child(fnode, node)\n        gnode = self.get_child(\"group\", root=node)\n        self.remove_child(gnode, node)\n        dnode = self.get_optional_child(\"default_value\", root=node)\n        if dnode is not None:\n            self.remove_child(dnode, node)\n\n        vnode = self.get_optional_child(\"values\", root=node)\n        if vnode is not None:\n            componentatt = self.get_children(\"value\", attributes={\"component\":\"ATM\"}, root=vnode)\n            # backward compatibility (compclasses and component were mixed\n            # now we seperated into component and compclass)\n            if len(componentatt) > 0:\n                for ccnode in self.get_children(\"value\", attributes={\"component\":None}, root=vnode):\n                    val = self.get(ccnode, \"component\")\n                    self.pop(ccnode, \"component\")\n                    self.set(ccnode, \"compclass\", val)\n\n            compclassatt = self.get_children(\"value\", attributes={\"compclass\":None}, root=vnode)\n            if len(compclassatt) == 0:\n                self.remove_child(vnode, root=node)\n\n        return node\n", "path": "scripts/lib/CIME/XML/env_base.py"}]}