Robot Framework
xmlbuilder.py
Go to the documentation of this file.
1 # Copyright 2008-2015 Nokia Networks
2 # Copyright 2016- Robot Framework Foundation
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 import os.path
17 
18 from robot.errors import DataError
19 from robot.running import ArgInfo
20 from robot.utils import ET, ETSource
21 
22 from .datatypes import EnumMember, TypedDictItem, TypeDoc
23 from .model import LibraryDoc, KeywordDoc
24 
25 
27 
28  def build(self, path):
29  spec = self._parse_spec_parse_spec(path)
30  libdoc = LibraryDoc(name=spec.get('name'),
31  type=spec.get('type').upper(),
32  version=spec.find('version').text or '',
33  doc=spec.find('doc').text or '',
34  scope=spec.get('scope'),
35  doc_format=spec.get('format') or 'ROBOT',
36  source=spec.get('source'),
37  lineno=int(spec.get('lineno')) or -1)
38  libdoc.inits = self._create_keywords_create_keywords(spec, 'inits/init', libdoc.source)
39  libdoc.keywords = self._create_keywords_create_keywords(spec, 'keywords/kw', libdoc.source)
40  # RF >= 5 have 'typedocs', RF >= 4 have 'datatypes', older/custom may have neither.
41  if spec.find('typedocs') is not None:
42  libdoc.type_docs = self._parse_type_docs_parse_type_docs(spec)
43  else:
44  libdoc.type_docs = self._parse_data_types_parse_data_types(spec)
45  return libdoc
46 
47  def _parse_spec(self, path):
48  if not os.path.isfile(path):
49  raise DataError(f"Spec file '{path}' does not exist.")
50  with ETSource(path) as source:
51  root = ET.parse(source).getroot()
52  if root.tag != 'keywordspec':
53  raise DataError(f"Invalid spec file '{path}'.")
54  version = root.get('specversion')
55  if version not in ('3', '4'):
56  raise DataError(f"Invalid spec file version '{version}'. "
57  f"Supported versions are 3 and 4.")
58  return root
59 
60  def _create_keywords(self, spec, path, lib_source):
61  return [self._create_keyword_create_keyword(elem, lib_source) for elem in spec.findall(path)]
62 
63  def _create_keyword(self, elem, lib_source):
64  kw = KeywordDoc(name=elem.get('name', ''),
65  doc=elem.find('doc').text or '',
66  shortdoc=elem.find('shortdoc').text or '',
67  tags=[t.text for t in elem.findall('tags/tag')],
68  private=elem.get('private', 'false') == 'true',
69  deprecated=elem.get('deprecated', 'false') == 'true',
70  source=elem.get('source') or lib_source,
71  lineno=int(elem.get('lineno', -1)))
72  self._create_arguments_create_arguments(elem, kw)
73  return kw
74 
75  def _create_arguments(self, elem, kw: KeywordDoc):
76  spec = kw.args
77  setters = {
78  ArgInfo.POSITIONAL_ONLY: spec.positional_only.append,
79  ArgInfo.POSITIONAL_ONLY_MARKER: lambda value: None,
80  ArgInfo.POSITIONAL_OR_NAMED: spec.positional_or_named.append,
81  ArgInfo.VAR_POSITIONAL: lambda value: setattr(spec, 'var_positional', value),
82  ArgInfo.NAMED_ONLY_MARKER: lambda value: None,
83  ArgInfo.NAMED_ONLY: spec.named_only.append,
84  ArgInfo.VAR_NAMED: lambda value: setattr(spec, 'var_named', value),
85  }
86  for arg in elem.findall('arguments/arg'):
87  name_elem = arg.find('name')
88  if name_elem is None:
89  continue
90  name = name_elem.text
91  setters[arg.get('kind')](name)
92  default_elem = arg.find('default')
93  if default_elem is not None:
94  spec.defaults[name] = default_elem.text or ''
95  if not spec.types:
96  spec.types = {}
97  types = []
98  type_docs = {}
99  for typ in arg.findall('type'):
100  types.append(typ.text)
101  if typ.get('typedoc'):
102  type_docs[typ.text] = typ.get('typedoc')
103  spec.types[name] = tuple(types)
104  kw.type_docs[name] = type_docs
105 
106  def _parse_type_docs(self, spec):
107  for elem in spec.findall('typedocs/type'):
108  doc = TypeDoc(elem.get('type'), elem.get('name'), elem.find('doc').text,
109  [e.text for e in elem.findall('accepts/type')],
110  [e.text for e in elem.findall('usages/usage')])
111  if doc.type == TypeDoc.ENUM:
112  doc.members = self._parse_members_parse_members(elem)
113  if doc.type == TypeDoc.TYPED_DICT:
114  doc.items = self._parse_items_parse_items(elem)
115  yield doc
116 
117  def _parse_members(self, elem):
118  return [EnumMember(member.get('name'), member.get('value'))
119  for member in elem.findall('members/member')]
120 
121  def _parse_items(self, elem):
122  def get_required(item):
123  required = item.get('required', None)
124  return None if required is None else required == 'true'
125  return [TypedDictItem(item.get('key'), item.get('type'), get_required(item))
126  for item in elem.findall('items/item')]
127 
128  # Code below used for parsing legacy 'datatypes'.
129 
130  def _parse_data_types(self, spec):
131  for elem in spec.findall('datatypes/enums/enum'):
132  yield self._create_enum_doc_create_enum_doc(elem)
133  for elem in spec.findall('datatypes/typeddicts/typeddict'):
134  yield self._create_typed_dict_doc_create_typed_dict_doc(elem)
135 
136  def _create_enum_doc(self, elem):
137  return TypeDoc(TypeDoc.ENUM, elem.get('name'), elem.find('doc').text,
138  members=self._parse_members_parse_members(elem))
139 
140  def _create_typed_dict_doc(self, elem):
141  return TypeDoc(TypeDoc.TYPED_DICT, elem.get('name'), elem.find('doc').text,
142  items=self._parse_items_parse_items(elem))
Documentation for a single keyword or an initializer.
Definition: model.py:156
Documentation for a library, a resource file or a suite file.
Definition: model.py:30
def _create_arguments(self, elem, KeywordDoc kw)
Definition: xmlbuilder.py:75
def _create_keyword(self, elem, lib_source)
Definition: xmlbuilder.py:63
def _create_keywords(self, spec, path, lib_source)
Definition: xmlbuilder.py:60