[
  {
    "path": ".gitignore",
    "content": "*.pyc"
  },
  {
    "path": "LICENSE",
    "content": "Copyright 2016 YANG Huan (sy.yanghuan@gmail.com)\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "[English](https://github.com/yanghuan/proton#proton)   [Chinese](https://github.com/yanghuan/proton#proton-1)  \n# proton\nProton is a excel export configuration file for the tool, you can export to xml, json, lua format, through external expansion can automatically generate the configuration to read the code, simple and flexible easy to use, indeed powerful.\n\n## Features\n- Writeing in Python,cross-platform, referenced [sxl](https://pypi.org/project/sxl/) only, [full code](https://github.com/yanghuan/proton/blob/master/proton.py), just more than 600 lines  \n- Has a specific rule syntax description excel format information, simple and easy to understand, flexible and powerful, [detailed description](https://github.com/yanghuan/proton/wiki/document_en) \n- Can export excel format information for external use, can be used to automatically generate read configuration code\n\n## Generates an auto-read code\nUse the \"-c\" parameter to generate a json file containing excel format information, each language can be automatically generated to achieve this code to read the tool, [the specific format](https://github.com/yanghuan/proton/wiki/schema_en). Has achieved the C # language tools, other language users, can be realized, welcomed the realization of the code links for the needs of people to use.\n- [CSharpGeneratorForProton](https://github.com/yanghuan/CSharpGeneratorForProton) generates C # code that reads xml, json, protobuf. You can convert xml, json to protobuf's binary format and generate the corresponding read code (using protobuf-net).\n\n## Example\n[sample directory](https://github.com/yanghuan/proton/tree/master/sample) is a well configured under the direct use of the Windows example. Already contains a python3 environment, directly run __export.bat to complete the export. Need to add a new Excel file, modify the __export.py related array.\n\n## Command Line Parameters\n```cmd\nusage python proton.py [-p filelist] [-f outfolder] [-e format]\nArguments \n-p      : input excel files, use , or ; or space to separate \n-f      : out folder\n-e      : format, json or xml or lua     \n\nOptions\n-s      ：sign, controls whether the column is exported, defalut all export\n-t      : suffix, export file suffix\n-r      : the separator of object field, default is ; you can use it to change\n-m      : use the count of multiprocesses to export, default is cpu count\n-c      : a file path, save the excel structure to json, \n          the external program uses this file to automatically generate the read code      \n-h      : print this help message and exit\n-x      : don't append 's' on names\n```\n\n## Documentation\nWiki https://github.com/yanghuan/proton/wiki/document_en  \nFAQ https://github.com/yanghuan/proton/wiki/FAQ_en\n\n## *License*\n[Apache 2.0 license](https://github.com/yanghuan/proton/blob/master/LICENSE).\n\n_____________________\n# proton\nproton是一个将excel导出为配置文件的工具，可以导出为xml、json、lua格式，通过外部扩展可支持自动生成读取配置的代码，简单灵活易于使用，确不失强大。\n## 特点\n- python编写可跨平台使用，仅依赖第三方库[sxl](https://pypi.org/project/sxl/)，[完整代码仅600余行](https://github.com/yanghuan/proton/blob/master/proton.py)。\n- 有特定的规则语法描述excel的格式信息，简洁易懂，灵活强大，[详细说明](https://github.com/yanghuan/proton/wiki/document_zh)。\n- 可导出excel格式信息供外部程序使用，可用来自动生成读取配置的代码。\n\n## 后端程序（生成自动读取的代码）\n使用“-c”参数可生成内含excel格式信息的json文件，各个语言可据此实现自动生成读取代码的工具，[具体格式说明](https://github.com/yanghuan/proton/wiki/schema_zh)。已经实现了C#语言的工具，其他语言使用者，可自行实现，欢迎提供实现的代码链接，以供需要的同学使用。\n\n- [CSharpGeneratorForProton](https://github.com/yanghuan/CSharpGeneratorForProton) 可生成读取xml、json、protobuf的C#代码。 可将xml、json转换为protobuf的二进制格式，并生成对应的读取代码（使用protobuf-net）。\n\n## 实例工程\n[sample目录](https://github.com/yanghuan/proton/tree/master/sample)下是一个配置好了的可在windows下直接使用的实例。已经包含了python3环境，直接运行__export.bat即可完成导出。需要添加新的Excel文件，修改__export.py中相关数组，加入即可。\n\n## 命令行参数\n```cmd\nusage python proton.py [-p filelist] [-f outfolder] [-e format]\nArguments \n-p      : input excel files, use , or ; or space to separate \n-f      : out folder\n-e      : format, json or xml or lua     \n\nOptions\n-s      ：sign, controls whether the column is exported, defalut all export\n-t      : suffix, export file suffix\n-r      : the separator of object field, default is ; you can use it to change\n-m      : use the count of multiprocesses to export, default is cpu count\n-c      : a file path, save the excel structure to json, \n          the external program uses this file to automatically generate the read code      \n-h      : print this help message and exit\n-x      : don't append 's' on names\n```\n\n## 文档\n格式说明 https://github.com/yanghuan/proton/wiki/document_zh  \nFAQ https://github.com/yanghuan/proton/wiki/FAQ_zh\n\n## 交流讨论\n- [常见问题](https://github.com/yanghuan/proton/wiki/FAQ_zh)\n- 邮箱：sy.yanghuan@gmail.com\n- QQ群：715350749\n\n## *许可证*\n[Apache 2.0 license](https://github.com/yanghuan/proton/blob/master/LICENSE).\n"
  },
  {
    "path": "nested_parser.py",
    "content": "#encoding=utf-8\nimport string\n\n_OPEN_TO_CLOSE = {\n  '{': '}',\n  '[': ']',\n  '(': ')',\n}\n\n_CLOSE_SET = set(_OPEN_TO_CLOSE.values())\n\n\ndef split_top_level(text, delimiter, skip_empty = False):\n  if text is None:\n    return []\n  if not delimiter:\n    raise ValueError('delimiter can not be empty')\n\n  values = []\n  stack = []\n  start = 0\n  i = 0\n  n = len(text)\n\n  while i < n:\n    c = text[i]\n\n    if c == '\\\\' and i + 1 < n:\n      i += 2\n      continue\n\n    if c in _OPEN_TO_CLOSE:\n      stack.append(_OPEN_TO_CLOSE[c])\n      i += 1\n      continue\n\n    if c in _CLOSE_SET:\n      if not stack or c != stack[-1]:\n        raise ValueError('%s is not a legal nested expression' % text)\n      stack.pop()\n      i += 1\n      continue\n\n    if not stack and text.startswith(delimiter, i):\n      value = text[start:i]\n      if not skip_empty or value:\n        values.append(value)\n      i += len(delimiter)\n      start = i\n      continue\n\n    i += 1\n\n  if stack:\n    raise ValueError('%s is not a legal nested expression' % text)\n\n  value = text[start:]\n  if not skip_empty or value:\n    values.append(value)\n\n  return values\n\n\ndef unwrap_container(text, begin, end):\n  value = text.strip()\n  if len(value) >= 2 and value[0] == begin and value[-1] == end:\n    return value[1:-1]\n  return value\n\n\ndef split_list_values(value):\n  return split_top_level(unwrap_container(value, '[', ']'), ',', True)\n\n\ndef split_obj_type_fields(type_, separator):\n  return split_top_level(unwrap_container(type_, '{', '}'), separator, True)\n\n\ndef split_obj_values(value, separator):\n  return split_top_level(unwrap_container(value, '{', '}'), separator, True)\n\n\ndef split_field_declaration(text):\n  declaration = text.strip()\n  if not declaration:\n    raise ValueError('field declaration can not be empty')\n\n  values = []\n  stack = []\n  start = None\n  i = 0\n  n = len(declaration)\n\n  while i < n:\n    c = declaration[i]\n\n    if c == '\\\\' and i + 1 < n:\n      if start is None:\n        start = i\n      i += 2\n      continue\n\n    if c in _OPEN_TO_CLOSE:\n      if start is None:\n        start = i\n      stack.append(_OPEN_TO_CLOSE[c])\n      i += 1\n      continue\n\n    if c in _CLOSE_SET:\n      if not stack or c != stack[-1]:\n        raise ValueError('%s is not a legal field declaration' % declaration)\n      stack.pop()\n      i += 1\n      continue\n\n    if c in string.whitespace and not stack:\n      if start is not None:\n        values.append(declaration[start:i])\n        start = None\n      i += 1\n      continue\n\n    if start is None:\n      start = i\n    i += 1\n\n  if stack:\n    raise ValueError('%s is not a legal field declaration' % declaration)\n\n  if start is not None:\n    values.append(declaration[start:])\n\n  if len(values) < 2:\n    raise ValueError('%s is not a legal field declaration' % declaration)\n\n  return (' '.join(values[:-1]), values[-1])"
  },
  {
    "path": "proton.py",
    "content": "#encoding=utf-8\n'''\nCopyright YANG Huan (sy.yanghuan@gmail.com)\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n'''\nimport sys        \n\nif sys.version_info < (3, 0):\n  print('python version need more than 3.x')\n  sys.exit(1)\n    \nimport os\nimport string\nimport collections\nimport codecs\nimport getopt\nimport re\nimport json\nimport traceback\nimport multiprocessing\nimport xml.etree.ElementTree as ElementTree\nimport xml.dom.minidom as minidom\nimport sxl\nimport nested_parser\n\ndef fillvalue(parent, name, value, isschema):\n  if isinstance(parent, list):\n    parent.append(value) \n  else:\n    if isschema and not re.match('^_|[a-zA-Z]\\w*$', name):\n      raise ValueError('%s is a illegal identifier' % name)\n    parent[name] = value\n    \ndef getindex(infos, name):\n  return next((i for i, j in enumerate(infos) if j == name), -1)\n  \ndef getcellvalue(value):\n  return str(value) if value is not None else ''\n\ndef getscemainfo(typename, description):\n  if isinstance(typename, BindType):\n    typename = typename.typename\n  return [typename, description] if description else [typename]\n        \ndef getexportmark(sheetName):\n  p = re.search('\\|[' + string.whitespace + ']*(_|[a-zA-Z]\\w+)', sheetName)\n  return p.group(1) if p else False\n\ndef issignmatch(signarg, sign):\n  if signarg is None:\n    return True\n  return True if [s for s in re.split(r'[/\\\\, :]', sign) if s in signarg] else False\n\ndef isoutofdate(srcfile, tarfile):\n  return not os.path.isfile(tarfile) or os.path.getmtime(srcfile) > os.path.getmtime(tarfile)\n\ndef gerexportfilename(root, format_, folder):\n  filename = root +  '.' + format_\n  return os.path.join(folder, filename)\n\ndef splitspace(s):\n  return nested_parser.split_field_declaration(s)\n  \ndef buildbasexml(parent, name, value, noplural = False):\n  value = str(value)\n  listtag = name if noplural else name + 's'\n  if parent.tag == listtag:\n    element = ElementTree.Element(name)\n    element.text = value\n    parent.append(element)\n  else:\n    parent.set(name, value)\n            \ndef buildlistxml(parent, name, list_, noplural = False):\n  element = ElementTree.Element(name)\n  parent.append(element)\n  itemname = name if noplural else name[:-1]\n  for v in list_:\n    buildxml(element, itemname, v, noplural)    \n\ndef buildobjxml(parent, name, obj, noplural = False):\n  element = ElementTree.Element(name)\n  parent.append(element)\n  \n  for k, v in obj.items():\n    buildxml(element, k, v, noplural)\n        \ndef buildxml(parent, name, value, noplural = False):\n  if isinstance(value, int) or isinstance(value, float) or isinstance(value, str):\n    buildbasexml(parent, name, value, noplural)\n      \n  elif isinstance(value, list):\n    buildlistxml(parent, name, value, noplural)\n      \n  elif isinstance(value, dict):\n    buildobjxml(parent, name, value, noplural)\n            \ndef savexml(record, noplural = False):\n  book = ElementTree.ElementTree()\n  book.append = lambda e: book._setroot(e)\n  buildxml(book, record.root, record.obj, noplural)\n  \n  xmlstr = ElementTree.tostring(book.getroot(), 'utf-8')\n  dom = minidom.parseString(xmlstr)\n  with codecs.open(record.exportfile, 'w', 'utf-8') as f:\n    dom.writexml(f, '', '  ', '\\n', 'utf-8')\n      \n  print('save %s from %s in %s' % (record.exportfile, record.sheet.name, record.path))\n  \ndef newline(count):\n  return '\\n' + '  ' * count\n  \ndef tolua(obj, indent = 1):    \n  if isinstance(obj, int) or isinstance(obj, float) or isinstance(obj, str):\n    yield json.dumps(obj, ensure_ascii = False)\n  else:\n    yield '{'\n    islist = isinstance(obj, list)\n    isfirst = True\n    for i in obj:\n      if isfirst:\n        isfirst = False\n      else:\n        yield ','\n      yield newline(indent)\n      if not islist:\n        k = i\n        i = obj[k]\n        yield k \n        yield ' = '                \n      for part in tolua(i, indent + 1):\n        yield part\n    yield newline(indent - 1)\n    yield '}'\n    \ndef toycl(obj, indent = 0):\n  islist = isinstance(obj, list)\n  for i in obj:\n    yield newline(indent)  \n    if not islist:\n      k = i\n      i = obj[k]\n      yield k \n    if isinstance(i, int) or isinstance(i, float) or isinstance(i, str): \n      if not islist:\n        yield ' = '\n      yield json.dumps(i, ensure_ascii = False)\n    else:\n      if not islist:\n        yield ' '\n      yield '{'\n      for part in toycl(i, indent + 1):\n        yield part\n      yield newline(indent)  \n      yield '}'     \n\nclass BindType:\n  def __init__(self, type_):\n    self.typename = type_\n      \n  def __eq__(self, other):\n    return self.typename == other\n    \nclass Record:\n  def __init__(self, path, sheet, exportfile, root, item, obj, exportmark):\n    self.path = path \n    self.sheet = sheet \n    self.exportfile = exportfile \n    self.root = root \n    self.item = item\n    self.setobj(obj)\n    self.exportmark = exportmark\n\n  def setobj(self, obj):    \n    self.schema = obj[0] if obj else None\n    self.obj = obj[1] if obj else None\n        \nclass Constraint:\n  def __init__(self, mark, filed):\n    self.mark = mark\n    self.field = filed     \n        \nclass Exporter:\n  configsheettitles = ('name', 'value', 'type', 'sign', 'description')\n  spacemaxrowcount = 3\n  \n  def __init__(self, context):\n    self.context = context\n    self.records = []\n    \n  def checkstringescape(self, t, v):\n    return v if not v or not 'string' in t else v.replace('\\\\n', '\\n').replace('\\,', '\\0').replace('\\\\' + self.context.objseparator, '\\a')\n  \n  def stringescape(self, s):\n    return s.replace('\\0', ',').replace('\\a', self.context.objseparator)\n\n  def pluralname(self, name):\n    return name if self.context.noplural else name + 's'\n  \n  def gettype(self, type_):\n    if type_[-2] == '[' and  type_[-1] == ']':\n      return 'list'\n    if type_[0] == '{' and type_[-1] == '}':\n      return 'obj'\n    if type_ in ('int', 'double', 'string', 'bool', 'long', 'float'):\n      return type_\n    \n    p = re.search('(int|string|long)[' + string.whitespace + ']*\\((\\S+)\\.(\\S+)\\)', type_)\n    if p:\n      type_ = BindType(p.group(1))\n      type_.mark = p.group(2)\n      type_.field = p.group(3)\n      return type_\n        \n    raise ValueError('%s is not a legal type' % type_)\n    \n  def buildlistexpress(self, parent, type_, name, value, isschema):\n    basetype = type_[:-2]\n    list_ = []\n    if isschema:\n      self.buildexpress(list_, basetype, name, None, isschema)\n      list_ = getscemainfo(list_[0], value)\n    else:\n      valuelist = nested_parser.split_list_values(value)\n      for v in valuelist:\n        self.buildexpress(list_, basetype, name, v, False, True)\n       \n    fillvalue(parent, self.pluralname(name), list_, isschema)\n      \n  def buildobjexpress(self, parent, type_, name, value, isschema):\n    obj = collections.OrderedDict()\n    fieldnamestypes = nested_parser.split_obj_type_fields(type_, self.context.objseparator)\n    \n    if isschema:\n      for i in range(0, len(fieldnamestypes)):\n        fieldtype, fieldname = splitspace(fieldnamestypes[i])\n        self.buildexpress(obj, fieldtype, fieldname, None, isschema)\n      obj = getscemainfo(obj, value)\n    else:\n      fieldValues = nested_parser.split_obj_values(value, self.context.objseparator)\n      for i in range(0, len(fieldnamestypes)):\n        if i < len(fieldValues):\n          fieldtype, fieldname = splitspace(fieldnamestypes[i])\n          self.buildexpress(obj, fieldtype, fieldname, fieldValues[i], False, True)\n\n    fillvalue(parent, name, obj, isschema)       \n      \n  def buildbasexpress(self, parent, type_, name, value, isschema, inobj):\n    typename = self.gettype(type_) \n    if isschema:\n      value = getscemainfo(typename, value)\n    else:\n      if typename != 'string' and value.isspace():\n        return\n        \n      if typename == 'int' or typename == 'long':\n        value = int(float(value))\n      elif typename == 'double' or typename == 'float':\n        value = float(value)   \n      elif typename == 'string':\n        if value.endswith('.0'):          # may read is like \"123.0\"\n          try:\n            value = str(int(float(value)))\n          except ValueError:\n            value = self.stringescape(str(value))\n        else:            \n          value = self.stringescape(str(value))\n        if inobj and len(value) > 0 and value[0] == '\\n':\n          value = value[1:]\n      elif typename == 'bool':\n        try:\n          value = int(float(value))\n          value = False if value == 0 else True \n        except ValueError:\n          value = value.lower() \n          if value in ('false', 'no', 'off'):\n            value = False\n          elif value in ('true', 'yes', 'on'):\n            value = True\n          else:\n            raise ValueError('%s is a illegal bool value' % value)\n            \n    fillvalue(parent, name, value, isschema)\n\n  def buildexpress(self, parent, type_, name, value, isschema = False, inobj = False):\n    typename = self.gettype(type_)\n    if typename == 'list':\n      self.buildlistexpress(parent, type_, name, value, isschema)\n    elif typename == 'obj':\n      self.buildobjexpress(parent, type_, name, value, isschema)\n    else:\n      self.buildbasexpress(parent, type_, name, value, isschema, inobj)\n      \n  def getrootname(self, exportmark, isitem):\n    root = self.pluralname(exportmark) if isitem else exportmark\n    return root + (self.context.extension or '')\n\n  def export(self, path):\n    self.path = path\n    data = sxl.Workbook(self.path)\n    cout = None\n\n    for sheetname in [i for i in data.sheets if type(i) is str]:\n      self.sheetname = sheetname\n      exportmark = getexportmark(sheetname)\n      if exportmark:\n        sheet = data.sheets[sheetname]\n        coutmark = sheetname.endswith('<<')\n        configtitleinfo = self.getconfigsheetfinfo(sheet)\n        if not configtitleinfo:\n          root = self.getrootname(exportmark, not coutmark)\n          item = exportmark\n        else:\n          root = self.getrootname(exportmark, False)\n          item = None\n          \n        if not cout:  \n          self.checksheetname(self.path, sheetname, root)\n          exportfile = gerexportfilename(root, self.context.format, self.context.folder)\n          \n          if isoutofdate(self.path, exportfile):\n            if item:\n              exportobj = self.exportitemsheet(sheet)\n            else:\n              exportobj = self.exportconfigsheet(sheet, configtitleinfo)\n          \n            if coutmark:\n              if not item:\n                cout = exportobj\n              else:\n                cout = (collections.OrderedDict(), collections.OrderedDict())\n                itemkey = self.pluralname(item)\n                cout[0][itemkey] = [[exportobj[0]]]\n                item = None\n                exportobj = cout\n                obj = exportobj[1]\n                if obj:\n                  cout[1][itemkey] = obj\n                  \n            self.records.append(Record(self.path, sheet, exportfile, root, item, exportobj, exportmark))\n          else:\n            print('%s is not changed' % (self.path))\n            break\n        else:\n          if item:\n            exportobj = self.exportitemsheet(sheet)\n            cout[0][self.pluralname(item)] = [[exportobj[0]]]\n            obj = exportobj[1]\n            if obj:\n              cout[1][self.pluralname(item)] = obj\n          else:\n            exportobj = self.exportconfigsheet(sheet, configtitleinfo)\n            cout[0].update(exportobj[0])   \n            obj = exportobj[1]\n            if obj:\n              cout[1].update(obj)\n              \n    return self.saves()\n\n  def getconfigsheetfinfo(self, sheet):\n    titles = sheet.head(1)[0]\n    \n    nameindex = getindex(titles, self.configsheettitles[0])\n    valueindex = getindex(titles, self.configsheettitles[1])\n    typeindex = getindex(titles, self.configsheettitles[2])\n    signindex = getindex(titles, self.configsheettitles[3])\n    descriptionindex = getindex(titles, self.configsheettitles[4])\n    \n    if nameindex != -1 and valueindex != -1 and typeindex != -1:\n      return (nameindex, valueindex, typeindex, signindex, descriptionindex)\n    else:\n      return None\n      \n  def exportitemsheet(self, sheet):\n    rows = iter(sheet.rows)\n    descriptions = next(rows)\n    types = next(rows)\n    names = next(rows)\n    signs = next(rows)\n    \n    ncols = len(types)\n    titleinfos = []\n    schemaobj = collections.OrderedDict()\n    \n    try:\n      for colindex in range(ncols):\n        type_ = getcellvalue(types[colindex]).strip()\n        name = getcellvalue(names[colindex]).strip()\n        signmatch = issignmatch(self.context.sign, getcellvalue(signs[colindex]).strip())        \n        titleinfos.append((type_, name, signmatch))\n        \n        if self.context.codegenerator:\n          if type_ and name and signmatch:\n            self.buildexpress(schemaobj, type_, name, descriptions[colindex], True)\n            \n    except Exception as e:\n      e.args += ('%s has a title error, %s at %d column in %s' % (sheet.name, (type_, name), colindex + 1, self.path) , '')\n      raise e\n      \n    list_ = []\n    hasexport = next((i for i in titleinfos if i[0] and i[1] and i[2]), False)\n    if hasexport:\n      try:\n        spacerowcount = 0\n        self.rowindex = 3\n        for row in rows:\n          self.rowindex += 1\n          \n          item = collections.OrderedDict()\n          firsttext = getcellvalue(row[0]).strip()\n          if not firsttext:\n            spacerowcount += 1\n            if spacerowcount >= self.spacemaxrowcount:      # if space row is than max count, skil follow rows     \n              break\n          \n          if not firsttext or firsttext[0] == '#':    # current line skip\n            continue\n            \n          skiptokenindex = None\n          if firsttext[0] == '!':\n            nextpos = firsttext.find('!', 1)\n            if nextpos >= 2:\n              signtoken = firsttext[1: nextpos]\n              if issignmatch(self.context.sign, signtoken.strip()):\n                continue\n              else:\n                skiptokenindex = len(signtoken) + 2\n          \n          for self.colindex in range(ncols):\n            signmatch = titleinfos[self.colindex][2]\n            if signmatch:\n              type_ = titleinfos[self.colindex][0]\n              name = titleinfos[self.colindex][1]\n              value = getcellvalue(row[self.colindex])\n              \n              if skiptokenindex and self.colindex == 0:\n                value = value.lstrip()[skiptokenindex:]\n                \n              if type_ and name and value:\n                self.buildexpress(item, type_, name, self.checkstringescape(type_, value))  \n            spacerowcount = 0\n            \n          if item:\n            list_.append(item) \n          \n      except Exception as e:        \n          e.args += ('%s has a error in %d row %d(%s) column in %s' % (sheet.name, self.rowindex + 1, self.colindex + 1, name, self.path) , '')\n          raise e\n    \n    return (schemaobj, list_)\n        \n  def exportconfigsheet(self, sheet, titleindexs):\n    rows = iter(sheet.rows)\n    next(rows)\n  \n    nameindex = titleindexs[0]\n    valueindex = titleindexs[1]\n    typeindex = titleindexs[2]\n    signindex = titleindexs[3]\n    descriptionindex = titleindexs[4]\n    \n    schemaobj = collections.OrderedDict()\n    obj = collections.OrderedDict()\n    \n    try:\n      spacerowcount = 0\n      self.rowindex = 0\n      for row in rows:\n        self.rowindex += 1\n        name = getcellvalue(row[nameindex]).strip()\n        value = getcellvalue(row[valueindex])\n        type_ = getcellvalue(row[typeindex]).strip()\n        description = getcellvalue(row[descriptionindex]).strip()\n        \n        if signindex > 0:\n          sign = getcellvalue(row[signindex]).strip()\n          if not issignmatch(self.context.sign, sign):\n            continue\n          \n        if not name and not value and not type_:\n          spacerowcount += 1\n          if spacerowcount >= self.spacemaxrowcount:\n            break            # if space row is than max count, skil follow rows     \n          continue\n            \n        if name and type_:\n          if(name[0] != '#'):         # current line skip\n            if self.context.codegenerator:\n              self.buildexpress(schemaobj, type_, name, description, True)\n            if value:    \n              self.buildexpress(obj, type_, name, self.checkstringescape(type_, value))\n          spacerowcount = 0    \n               \n    except Exception as e:\n      e.args += ('%s has a error in %d row (%s, %s, %s) in %s' % (sheet.name, self.rowindex + 1, type_, name, value, self.path) , '')\n      raise e\n  \n    return (schemaobj, obj)\n    \n  def saves(self):\n    schemas = []\n    for r in self.records:\n      if r.obj:\n        self.save(r)\n\n        if self.context.codegenerator:        # has code generator\n          schemas.append({ 'path': r.path, 'exportfile' : r.exportfile, 'root' : r.root, 'item' : r.item or r.exportmark, 'schema' : r.schema })\n\n    return schemas\n                \n  def save(self, record):\n    if not record.obj:\n      return\n  \n    if not os.path.isdir(self.context.folder):\n      os.makedirs(self.context.folder)\n        \n    if self.context.format == 'json':\n      jsonstr = json.dumps(record.obj, ensure_ascii = False, indent = 2)\n      with codecs.open(record.exportfile, 'w', 'utf-8') as f:\n        f.write(jsonstr)\n      print('save %s from %s in %s' % (record.exportfile, record.sheet.name, record.path))\n        \n    elif self.context.format == 'xml':\n      if record.item:\n        record.obj = { self.pluralname(record.item) : record.obj }\n      savexml(record, self.context.noplural) \n        \n    elif self.context.format == 'lua':\n      luastr = \"\".join(tolua(record.obj))\n      with codecs.open(record.exportfile, 'w', 'utf-8') as f:\n        f.write('return ')\n        f.write(luastr)\n      print('save %s from %s in %s' % (record.exportfile, record.sheet.name, record.path))\n      \n    elif self.context.format == 'ycl':\n      g = toycl(record.obj)\n      next(g) # skip first newline\n      yclstr = \"\".join(g)\n      with codecs.open(record.exportfile, 'w', 'utf-8') as f:\n        f.write(yclstr)\n      print('save %s from %s in %s' % (record.exportfile, record.sheet.name, record.path))\n  \n  def checksheetname(self, path, sheetname, root):\n    r = next((r for r in self.records if r.root == root), False)\n    if r:\n      raise ValueError('%s in %s is already defined in %s' % (root, path, r.path))\n     \ndef export(context, path):\n  try:\n    return Exporter(context).export(path)\n  except Exception as e:  \n    return traceback.format_exc()\n\ndef exportpack(args):\n  return export(args[0], args[1])\n\ndef exportfiles(context):\n  paths = []\n  for path in re.split(r'[,;|]+', context.path.strip()):\n    if path:\n      if not os.path.isfile(path):\n        raise ValueError('%s is not exists' % path)\n      elif path in paths:\n        raise ValueError('%s is already has' % path)    \n      paths.append(path)\n\n  errors = []\n  schemas = []\n\n  def append(result):\n    if type(result) is str:\n      errors.append(result)\n    else:   \n      schemas.extend(result)\n      \n  if context.multiprocessescount is None or context.multiprocessescount > 1:\n    with multiprocessing.Pool(context.multiprocessescount) as p:\n      for i in p.map(exportpack, [(context, x) for x in paths]):\n        append(i)\n  else:\n    for path in paths:\n      result = export(context, path)\n      append(result)\n\n  if schemas:\n    if context.codegenerator:\n      schemasjson = json.dumps(schemas, ensure_ascii = False, indent = 2)\n      dir = os.path.dirname(context.codegenerator)\n      if dir and not os.path.isdir(dir):\n        os.makedirs(dir)\n      with codecs.open(context.codegenerator, 'w', 'utf-8') as f:\n        f.write(schemasjson)\n\n    exports = []\n    for schema in schemas:\n      exportfile = schema['exportfile']\n      r = next((r for r in exports if r['exportfile'] == exportfile), False)\n      if r:\n        errors.append('%s in %s is already defined in %s' % (schema['root'], schema['path'], r['path']))\n        os.remove(exportfile)\n      else:\n        exports.append(schema)\n\n  if errors:\n    print('\\n\\n'.join(errors))\n    sys.exit(-1)\n\n  print(\"export finsish successful!!!\")\n\nclass Context:\n  '''usage python proton.py [-p filelist] [-f outfolder] [-e format]\n  Arguments\n  -p      : input excel files, use , or ; or space to separate\n  -f      : out folder\n  -e      : format, json or xml or lua or ycl\n\n  Options\n  -s      ：sign, controls whether the column is exported, defalut all export\n  -t      : suffix, export file suffix\n  -r      : the separator of object field, default is ; you can use it to change\n  -m      : use the count of multiprocesses to export, default is cpu count\n  -c      : a file path, save the excel structure to json\n            the external program uses this file to automatically generate the read code\n  -x      : disable auto plural naming (do not append 's')\n  -h      : print this help message and exit\n\n  https://github.com/yanghuan/proton'''\n\nif __name__ == '__main__':\n  print('argv:' , sys.argv)\n  opst, args = getopt.getopt(sys.argv[1:], 'p:f:e:s:t:r:m:c:xh')\n\n  context = Context()\n  context.path = None\n  context.folder = '.'\n  context.format = 'json'\n  context.sign = None\n  context.extension = None\n  context.objseparator = ';'\n  context.codegenerator = None\n  context.multiprocessescount = None\n  context.noplural = False\n\n  for op, v in opst:\n    if op == '-p':\n      context.path = v\n    elif op == '-f':\n      context.folder = v\n    elif op == '-e':\n      context.format = v.lower() \n    elif op == '-s':\n      context.sign = v \n    elif op == '-t':\n      context.extension = v\n    elif op == '-r':\n      context.objseparator = v\n    elif op == '-m':\n      context.multiprocessescount = int(v) if v is not None else None\n    elif op == '-c':\n      context.codegenerator = v    \n    elif op == '-x':\n      context.noplural = True\n    elif op == '-h':\n      print(Context.__doc__)\n      sys.exit()\n      \n  if not context.path:\n    print(Context.__doc__)\n    sys.exit(2)\n\n  exportfiles(context)"
  },
  {
    "path": "sample/README.md",
    "content": "This is a good configuration can be used directly in the Windows instance. Already contains a python3 environment, directly run __export.bat to complete the export. Need to add a new Excel file, modify the __export.py related array.\n\n"
  },
  {
    "path": "sample/__export.bat",
    "content": "tools\\py37\\py37.exe __export.py"
  },
  {
    "path": "sample/__export.py",
    "content": "﻿#encoding=utf-8\n\n# Need to export the public configuration file (client, server needs) 需要导出的公共配置文件(客户端,服务器都需要)\nEXPORT_FILES = [\n\"hero.xlsx\",\n\"mount.xlsx\",\n]\n\n# Additional configuration files that the client needs to export (only the client needs) 客户端额外需要导出的额外配置文件(仅客户端需要)\nEXPORT_CLIENT_ONLY = [\n\"text.xlsx\"\n]\n\n# Server-side need to export the configuration file (only the server needs) 服务器端额外需要导出的配置文件(仅服务器需要)\nEXPORT_SERVER_ONLY = [\n]\n\n# do not modify the following\n\nimport os\nimport platform\nimport traceback\nimport shutil\nimport sys\n\nexportscript = '../proton.py'     \npythonpath = 'tools\\\\py37\\\\py37.exe ' if platform.system() == 'Windows' else 'python '\n\nclass ExportError(Exception):\n  pass\n\ndef export(filelist, format, sign, outfolder, suffix, schema):\n  cmd = r' -p \"' + ','.join(filelist) + '\" -f ' + outfolder + ' -e ' + format + ' -s ' + sign\n  if suffix:\n    cmd += ' -t ' + suffix\n  if schema:\n    cmd += ' -c ' + schema\n  cmd = pythonpath + exportscript + cmd\n  code = os.system(cmd)\n  if code != 0:\n    raise ExportError('export excel fail, please see print')\n\ndef codegenerator(schema, outfolder, namespace, suffix):\n  if os.path.exists(schema):\n    cmd = 'tools\\CSharpGeneratorForProton\\CSharpGeneratorForProton.exe ' + '-n ' + namespace + ' -f ' + outfolder + ' -p ' + schema\n    if suffix:\n      cmd += ' -t ' + suffix \n    code = os.system(cmd)\n    os.remove(schema)      \n    if code != 0:\n      raise ExportError('codegenerator fail, please see print')\n        \ndef exportserver():\n  export(EXPORT_FILES + EXPORT_SERVER_ONLY, 'json', 'server', 'config_server', 'Config', 'schemaserver.json')\n  codegenerator('schemaserver.json', 'config_server/ConfigGenerator/Template', 'Ice.Project.Config', 'Template') \n    \ndef exportclient():\n  export(EXPORT_FILES + EXPORT_CLIENT_ONLY, 'lua', 'client', 'config_client', 'Template', None)\n    \ndef main():\n  try:\n    exportserver()\n    exportclient()\n    print(\"all operation finish successful\")\n    return 0\n  except ExportError as e:\n    print(e)\n    print(\"has error, see logs, please return key to exit\")\n    input()\n    return 1\n  except Exception as e:\n    traceback.print_exc()\n    print(\"has error, see logs, please return key to exit\")\n    input()\n    return 1\n    \nif __name__ == '__main__':\n    sys.exit(main())\n"
  },
  {
    "path": "sample/tools/CSharpGeneratorForProton/README.md",
    "content": "[English](https://github.com/sy-yanghuan/CSharpGeneratorForProton#csharpgeneratorforproton)   [Chinese](https://github.com/sy-yanghuan/CSharpGeneratorForProton#csharpgeneratorforproton-1)  \n# CSharpGeneratorForProton\nCSharpGeneratorForProton generated C # code that reads xml, json, protobuf for [proton] (https://github.com/sy-yanghuan/proton). And xml, json can be converted to protobuf binary format (using protobuf-net).\n## Command Line Parameters\n```cmd\nUsage: CSharpGeneratorForProton [-p schemaFile] [-f output] [-n namespace]\nArguments \n-p              : schema file, Proton output\n-f              : output directory, will put the generated class code\n-n              : namespace of the generated class \n\nOptions\n-t              : suffix, generates the suffix for the class  \n-e              : open convert exportfile to protobuf\n-d              : protobuf binary data output directory, use only when '-e' exists  \n-b              : protobuf binary data file extension, use only when '-e' exists\n-h              : show the help message and exit \n```\n## Generated Code Import\nGenerated C # code is not associated with the specific format, the specific read operation, are assigned to the GeneratorUtility class for processing, so the need to add the corresponding class into the project. The code is under the [Directory GeneratorUtility] (https://github.com/sy-yanghuan/CSharpGeneratorForProton/tree/master/CSharpGeneratorForProton/GeneratorUtility), you can modify the code according to the specific requirements, such as replacing the namespace, replace the read Library and so on.\n- [GeneratorUtility for xml](https://github.com/sy-yanghuan/CSharpGeneratorForProton/blob/master/CSharpGeneratorForProton/CSharpGeneratorForProton/GeneratorUtility/XmlLoader.cs)\n- [GeneratorUtility for json](https://github.com/sy-yanghuan/CSharpGeneratorForProton/blob/master/CSharpGeneratorForProton/CSharpGeneratorForProton/GeneratorUtility/JsonLoader.cs)\n- [GeneratorUtility for protobuf](https://github.com/sy-yanghuan/CSharpGeneratorForProton/blob/master/CSharpGeneratorForProton/CSharpGeneratorForProton/GeneratorUtility/ProtobufLoader.cs)  \n\n## Example\n[Example] (https://github.com/sy-yanghuan/CSharpGeneratorForProton/tree/master/CSharpGeneratorForProton/Example), A project is an instance of a full load configuration that generated by [proton's sample](https://github.com/sy-yanghuan/proton/tree/master/sample).\n\n## *License*\n[Apache 2.0 license](https://github.com/sy-yanghuan/CSharpGeneratorForProton/blob/master/LICENSE).\n\n_____________________\n# CSharpGeneratorForProton\nCSharpGeneratorForProton 是为[proton] (https://github.com/sy-yanghuan/proton)产生读取xml、json、protobuf的C#的代码。其还可将xml、jsond配置文件转换成protobuf二进制格式。\n## 命令行参数\n```cmd\nUsage: CSharpGeneratorForProton [-p schemaFile] [-f output] [-n namespace]\nArguments \n-p              : schema file, Proton output\n-f              : output directory, will put the generated class code\n-n              : namespace of the generated class \n\nOptions\n-t              : suffix, generates the suffix for the class  \n-e              : open convert exportfile to protobuf\n-d              : protobuf binary data output directory, use only when '-e' exists  \n-b              : protobuf binary data file extension, use only when '-e' exists\n-h              : show the help message and exit \n```\n## 导入生成的代码\n生成的C#代码并不与具体格式相关联，具体读取操作，均外派给GeneratorUtility工具类进行处理，所以还需将对应工具类添加入工程。代码均在[目录GeneratorUtility](https://github.com/sy-yanghuan/CSharpGeneratorForProton/tree/master/CSharpGeneratorForProton/CSharpGeneratorForProton/GeneratorUtility)下，可按具体使用要求修改其代码，例如更换命名空间、更换读取库等。\n- [GeneratorUtility for xml](https://github.com/sy-yanghuan/CSharpGeneratorForProton/blob/master/CSharpGeneratorForProton/CSharpGeneratorForProton/GeneratorUtility/XmlLoader.cs)\n- [GeneratorUtility for json](https://github.com/sy-yanghuan/CSharpGeneratorForProton/blob/master/CSharpGeneratorForProton/CSharpGeneratorForProton/GeneratorUtility/JsonLoader.cs)\n- [GeneratorUtility for protobuf](https://github.com/sy-yanghuan/CSharpGeneratorForProton/blob/master/CSharpGeneratorForProton/CSharpGeneratorForProton/GeneratorUtility/ProtobufLoader.cs)  \n\n## 实例工程\n[Example](https://github.com/sy-yanghuan/CSharpGeneratorForProton/tree/master/CSharpGeneratorForProton/Example)工程是一个完整的载入配置的实例，其载入配置是通过[proton的实例](https://github.com/sy-yanghuan/proton/tree/master/sample)导出的。\n\n##*许可证*\n[Apache 2.0 license](https://github.com/sy-yanghuan/CSharpGeneratorForProton/blob/master/LICENSE).\n"
  },
  {
    "path": "sample/tools/py37/README.md",
    "content": "# pyexe.exe\nhttps://github.com/manthey/pyexe\n\n[![Build status](https://ci.appveyor.com/api/projects/status/n18f0997k18x87lw/branch/master?svg=true)](https://ci.appveyor.com/project/manthey/pyexe/branch/master)\n\nHere is a stand-alone version of python that is a single Windows executable.\n\nIt consists of the most recent versions of Python (with builds for 2.7, 3.5,\nand 3.6 each in 32-bit and 64-bit versions), pywin32, psutil, six, pip, \nsetuptools, and includes all packages that can be included without additional \ndlls, excepting tkinter.\n\nSee the appveyor script for build instructions.\n\n## Installing other modules\n\nPython is most useful with additional modules.  The stand-alone executable can use pip to install modules from pypi to the local directory.  For instance:\n\n```bash\npy36-64.exe -m pip install --no-cache-dir --target . --upgrade sympy\n```\n\nUse `-m pip` to run the pip module.  Use `--no-cache-dir` to avoid writing files to the user's data directory.  Use `--target .` to install to the current directory, allowing you to import the modules easily.  Use `--upgrade` to replace existing files, such as the common `bin` directory.  Note that using `--upgrade` will overwrite or discard existing files, which may not be what you want (the `bin` directory will end up with just files for the most recently installed package).\n\n## Differences from installed Python\n\nAlthough the stand-alone Python attempts to have the same features as a normally installed Python, there are some differences.\n\n- If command line options are specified, there may be some differences in `sys.flags`, since it is read-only and cannot be altered after start.\n- `PYTHONHOME` is ignored.  This option doesn't make sense for a stand-alone version.\n- `-V` and `PYTHONVERBOSE` don't print exactly the same information as installed Python, partly because the verbosity is increased after some modules are already imported.\n- `--check-hash-based-pycs` is ignored.  This option cannot be changed after the Python executable starts.\n- `-R` and `PYTHONHASHSEED` are ignored.  These options cannot be changed after the Python executable starts.\n- `PYTHONCASEOK` is not honored on Python 2.7.  It behaves as installed Python for Python 3.x, i.e., `-E` does not ignore it, but `-I` does, see [Python issue 16826](https://bugs.python.org/issue16826) for some discussion.\n- Not all environment variables are handled, such as: `PYTHONIOENCODING`, `PYTHONFAULTHANDLER`, `PYTHONLEGACYWINDOWSFSENCODING`, `PYTHONLEGACYWINDOWSSTDIO`, `PYTHONMALLOC`, `PYTHONCOERCECLOCALE`, `PYTHONDEVMODE`.  Some of these are ignored; some are used and cannot be suppressed with `-E` or `-I`.  Many of these could be handled properly with additional work.\n"
  },
  {
    "path": "sample/tools/py37/sxl/__init__.py",
    "content": "\nfrom .sxl import Workbook, col2num, num2col\n\n__version__ = '0.0.1a10'\n"
  },
  {
    "path": "sample/tools/py37/sxl/sxl.py",
    "content": "\"\"\"\nxl.py - python library to deal with *big* Excel files.\n\"\"\"\n\nfrom abc import ABC\nfrom collections import namedtuple, ChainMap\nfrom contextlib import contextmanager\nimport datetime\nimport io\nfrom itertools import zip_longest\nimport os\nimport re\nimport string\nimport xml.etree.cElementTree as ET\nfrom zipfile import ZipFile\n\n# ISO/IEC 29500:2011 in Part 1, section 18.8.30\nSTANDARD_STYLES = {\n    '0' : 'General',\n    '1' : '0',\n    '2' : '0.00',\n    '3' : '#,##0',\n    '4' : '#,##0.00',\n    '9' : '0%',\n    '10' : '0.00%',\n    '11' : '0.00E+00',\n    '12' : '# ?/?',\n    '13' : '# ??/??',\n    '14' : 'mm-dd-yy',\n    '15' : 'd-mmm-yy',\n    '16' : 'd-mmm',\n    '17' : 'mmm-yy',\n    '18' : 'h:mm AM/PM',\n    '19' : 'h:mm:ss AM/PM',\n    '20' : 'h:mm',\n    '21' : 'h:mm:ss',\n    '22' : 'm/d/yy h:mm',\n    '37' : '#,##0 ;(#,##0)',\n    '38' : '#,##0 ;[Red](#,##0)',\n    '39' : '#,##0.00;(#,##0.00)',\n    '40' : '#,##0.00;[Red](#,##0.00)',\n    '45' : 'mm:ss',\n    '46' : '[h]:mm:ss',\n    '47' : 'mmss.0',\n    '48' : '##0.0E+0',\n    '49' : '@',\n}\n\n\nExcelErrorValue = namedtuple('ExcelErrorValue', 'value')\n\n\nclass ExcelObj(ABC):\n    \"\"\"\n    Abstract base class for other excel objects (workbooks, worksheets, etc.)\n    \"\"\"\n    main_ns = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'\n    rel_ns = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'\n\n    @staticmethod\n    def tag_with_ns(tag, ns):\n        \"Return XML tag with namespace that can be used with ElementTree\"\n        return '{%s}%s' % (ns, tag)\n\n    @staticmethod\n    def col_num_to_letter(n):\n        \"Return column letter for column number ``n``\"\n        string = \"\"\n        while n > 0:\n            n, remainder = divmod(n - 1, 26)\n            string = chr(65 + remainder) + string\n        return string\n\n    @staticmethod\n    def col_letter_to_num(letter):\n        \"Return column number for column letter ``letter``\"\n        assert re.match(r'[A-Z]+', letter)\n        num = 0\n        for char in letter:\n            num = num * 26 + (ord(char.upper()) - ord('A')) + 1\n        return num\n\n\nclass Worksheet(ExcelObj):\n    \"\"\"\n    Excel worksheet\n    \"\"\"\n\n    def __init__(self, workbook, name, number, location=''):\n        self._used_area = None\n        self._row_length = None\n        self._num_rows = None\n        self._num_cols = None\n        self.workbook = self.wb = workbook\n        self.name = name\n        self.number = number\n        self.location = location or 'xl/worksheets/sheet{number}.xml'\n\n    @contextmanager\n    def get_sheet_xml(self):\n        \"Get a pointer to the xml file underlying the current sheet\"\n        with self.workbook.xls.open(self.location) as f:\n            yield io.TextIOWrapper(f, self.workbook.encoding)\n\n    @property\n    def range(self):\n        \"Return data found in range of cells\"\n        return Range(self)\n\n    @property\n    def rows(self):\n        \"Iterator that will yield every row in this sheet between start/end\"\n        return Range(self)\n\n    def _set_dimensions(self):\n        \"Return the 'standard' row length of each row in this worksheet\"\n        if ':' not in self.used_area:\n            self._num_cols = 0\n            self._num_rows = 0\n        else:\n            _, end = self.used_area.split(':')\n            last_col, last_row = re.match(r\"([A-Z]+)([0-9]+)\", end).groups()\n            self._num_cols = self.col_letter_to_num(last_col)\n            self._num_rows = int(last_row)\n\n    def _get_num_cols(self):\n        \"Return the number of standard columns in this worksheet\"\n        if self._num_cols is None:\n            self._set_dimensions()\n        return self._num_cols\n\n    def _set_num_cols(self, n):\n        \"Set the number of columns in the sheet (use with caution!)\"\n        self._num_cols = n\n\n    num_cols = property(_get_num_cols, _set_num_cols)\n\n    @property\n    def num_rows(self):\n        \"Return the total number of rows used in this worksheet\"\n        if self._num_rows is None:\n            self._set_dimensions()\n        return self._num_rows\n\n    @property\n    def used_area(self):\n        \"Return the used area of this sheet\"\n        if self._used_area is not None:\n            return self._used_area\n        dimension_tag = self.tag_with_ns('dimension', self.main_ns)\n        sheet_data_tag = self.tag_with_ns('sheetData', self.main_ns)\n        with self.get_sheet_xml() as sheet:\n            for event, elem in ET.iterparse(sheet, events=('start', 'end')):\n                if event == 'start':\n                    if elem.tag == dimension_tag:\n                        used_area = elem.get('ref')\n                        if used_area != 'A1':\n                            break\n                    if elem.tag == sheet_data_tag:\n                        # unreliable\n                        if list(elem):\n                            num_cols = len(list(elem)[0])\n                            used_area = f'A1:{num2col(num_cols)}{len(elem)}'\n                        break\n                elem.clear()\n            self._used_area = used_area\n        return used_area\n\n    def head(self, num_rows=10):\n        \"Return first 'num_rows' from this worksheet\"\n        return self.rows[:num_rows+1] # 1-based\n\n    def cat(self, tab=1):\n        \"Return/yield all rows from this worksheet\"\n        dat = self.rows[1] # 1 based!\n        XLRec = namedtuple('XLRec', dat[0], rename=True) # pylint: disable=C0103\n        for row in self.rows[1:]:\n            yield XLRec(*row)\n\n\nclass Range(ExcelObj):\n    \"\"\"\n    Excel ranges\n    \"\"\"\n\n    def __init__(self, ws):\n        self.worksheet = self.ws = ws\n        self.start = None\n        self.stop = None\n        self.step = None\n        self.colstart = None\n        self.colstop = None\n        self.colstep = None\n\n    def __len__(self):\n        return self.worksheet.num_rows\n\n    def __iter__(self):\n        with self.ws.get_sheet_xml() as xml_doc:\n            row_tag = self.tag_with_ns('row', self.main_ns)\n            c_tag = self.tag_with_ns('c', self.main_ns)\n            v_tag = self.tag_with_ns('v', self.main_ns)\n            row = []\n            this_row = -1\n            next_row = 1 if self.start is None else self.start\n            # last_row = self.ws.num_rows + 1 if self.stop is None else self.stop\n            last_row = 1_048_576 if self.stop is None else self.stop\n            context = ET.iterparse(xml_doc, events=('start', 'end'))\n            context = iter(context)\n            event, root = next(context)\n            for event, elem in context:\n                if event == 'end':\n                    if elem.tag == row_tag:\n                        this_row = int(elem.get('r'))\n                        if this_row >= last_row:\n                            break\n                        while next_row < this_row:\n                            yield self._row([])\n                            next_row += 1\n                        if this_row == next_row:\n                            yield self._row(row)\n                            next_row += 1\n                        row = []\n                        this_row = -1\n                        root.clear()\n                    elif elem.tag == c_tag:\n                        val = elem.findtext(v_tag)\n                        if not val:\n                            is_elem = elem.find(self.tag_with_ns('is', self.main_ns))\n                            if is_elem:\n                                val = is_elem.findtext(self.tag_with_ns('t', self.main_ns))\n                        if val:\n                            # only append cells with values\n                            cell = ['', '', '', ''] # ref, type, value, style\n                            cell[0] = elem.get('r') # cell ref\n                            cell[1] = elem.get('t') # cell type\n                            if cell[1] == 's': # string\n                                cell[2] = self.ws.workbook.strings[int(val)]\n                            else:\n                                cell[2] = val\n                            cell[3] = elem.get('s') # cell style\n                            row.append(cell)\n\n    def __getitem__(self, rng):\n        if isinstance(rng, slice):\n            if rng.start is not None:\n                self.start = rng.start\n            if rng.stop is not None:\n                self.stop = rng.stop\n            if rng.step is not None:\n                self.step = rng.step\n            matx = [_ for _ in self]\n            self.start = self.stop = self.step = None\n            return matx\n        elif isinstance(rng, str):\n            if ':' in rng:\n                beg, end = rng.split(':')\n            else:\n                beg = end = rng\n            cell_split = lambda cell: re.match(r\"([A-Z]+)([0-9]+)\", cell).groups()\n            first_col, first_row = cell_split(beg)\n            last_col, last_row = cell_split(end)\n            first_col = self.col_letter_to_num(first_col) - 1 # python addressing\n            first_row = int(first_row)\n            last_col = self.col_letter_to_num(last_col)\n            last_row = int(last_row)\n            self.start = first_row\n            self.stop = last_row + 1\n            self.colstart = first_col\n            self.colstop = last_col\n            matx = [_ for _ in self]\n            # reset\n            self.start = self.stop = self.step = None\n            self.colstart = self.colstop = self.colstep = None\n            return matx\n        elif isinstance(rng, int):\n            self.start = rng\n            self.stop = rng + 1\n            matx = [_ for _ in self]\n            self.start = self.stop = self.step = None\n            return matx\n        else:\n            raise NotImplementedError(\"Cannot understand request\")\n\n    def __call__(self, rng):\n        return self.__getitem__(rng)\n\n    def _row(self, row):\n        lst = [None] * self.ws.num_cols\n        col_re = re.compile(r'[A-Z]+')\n        col_pos = 0\n        for cell in row:\n            # apparently, 'r' attribute is optional and some MS products don't\n            # spit it out. So we default to incrementing from last known col\n            # (or 0 if we are at the beginning) when r is not available.\n            if cell[0]:\n                col = cell[0][:col_re.match(cell[0]).end()]\n                col_pos = self.col_letter_to_num(col) - 1\n            else:\n                col_pos += 1\n\n            if col_pos >= len(lst):\n                # dimensions may not be set right in worksheet\n                extend_by = col_pos - len(lst) + 1\n                self.ws.num_cols += extend_by\n                lst += [None for _ in range(extend_by)]\n\n            try:\n                style = self.ws.wb.styles[int(cell[3])]\n            except Exception as e:\n                style = ''\n\n            # convert to python value (if necessary)\n            celltype = cell[1]\n            cellvalue = cell[2]\n            if celltype in ('str', 's', 'inlineStr'):\n                lst[col_pos] = cellvalue\n            elif celltype == 'b':\n                lst[col_pos] = bool(int(cellvalue))\n            elif celltype == 'e':\n                lst[col_pos] = ExcelErrorValue(cellvalue)\n            elif celltype == 'bl':\n                lst[col_pos] = None\n            # Lastly, default to a number\n            else:\n                lst[col_pos] = float(cellvalue)\n        colstart = 0 if self.colstart is None else self.colstart\n        colstop = self.ws.num_cols if self.colstop is None else self.colstop\n        return lst[colstart:colstop]\n\n\nclass Workbook(ExcelObj):\n    \"\"\"\n    Excel workbook\n    \"\"\"\n\n    def __init__(self, file_obj, workbook_path=None, encoding='utf8'):\n        self.xls = ZipFile(file_obj)\n        self.encoding = encoding\n        self._strings = None\n        self._sheets = None\n        self._styles = None\n        self.date_system = self.get_date_system()\n        if workbook_path:\n            self.name = os.path.basename(workbook_path)\n            self.path = workbook_path\n        else:\n            self.name = self.workbook_path = ''\n\n    def get_date_system(self):\n        \"Determine the date system used by the current workbook\"\n        with self.xls.open('xl/workbook.xml') as xml_doc:\n            tree = ET.parse(io.TextIOWrapper(xml_doc, self.encoding))\n            tag = self.tag_with_ns('workbookPr', self.main_ns)\n            tag_element = tree.find(tag)\n            if tag_element and tag_element.get('date1904') == '1':\n                return 1904\n            return 1900\n\n    @property\n    def sheets(self):\n        \"Return list of all sheets in workbook\"\n        if self._sheets is not None:\n            return self._sheets\n        tag = self.tag_with_ns('sheet', self.main_ns)\n        ref_tag = self.tag_with_ns('id', self.rel_ns)\n        sheet_map = {}\n        locs = {} # locations from relationship id to target location\n        with self.xls.open('xl/_rels/workbook.xml.rels') as xml_doc:\n            tree = ET.parse(io.TextIOWrapper(xml_doc, self.encoding))\n            for rshp in tree.iter(self.tag_with_ns('Relationship', 'http://schemas.openxmlformats.org/package/2006/relationships')):\n                id = rshp.get('Id')\n                target = rshp.get('Target')\n                locs[id] = target\n        with self.xls.open('xl/workbook.xml') as xml_doc:\n            tree = ET.parse(io.TextIOWrapper(xml_doc, self.encoding))\n            for sheet in tree.iter(tag):\n                name = sheet.get('name')\n                ref = sheet.get(ref_tag)\n                num = int(sheet.get('sheetId'))\n                sheet = Worksheet(self, name, num, 'xl/' + locs[ref] if not locs[ref].startswith('/') else locs[ref][1:])\n                sheet_map[name] = sheet\n                sheet_map[num] = sheet\n        self._sheets = sheet_map\n        return self._sheets\n\n    @property\n    def strings(self):\n        \"Return list of shared strings within this workbook\"\n        if self._strings is not None:\n            return self._strings\n        # Cannot use t element (which we were doing before). See\n        # http://bit.ly/2J7xAPu for more info on shared strings.\n        tag = self.tag_with_ns('si', self.main_ns)\n        strings = []\n        with self.xls.open('xl/sharedStrings.xml') as xml_doc:\n            tree = ET.parse(io.TextIOWrapper(xml_doc, self.encoding))\n            for elem in tree.iter(tag):\n                strings.append(''.join(_ for _ in elem.itertext()))\n        self._strings = strings\n        return strings\n\n    @property\n    def styles(self):\n        \"Return list of styles used within this workbook\"\n        if self._styles is not None:\n            return self._styles\n        styles = []\n        style_tag = self.tag_with_ns('xf', self.main_ns)\n        numfmt_tag = self.tag_with_ns('numFmt', self.main_ns)\n        with self.xls.open('xl/styles.xml') as xml_doc:\n            tree = ET.parse(io.TextIOWrapper(xml_doc, self.encoding))\n            number_fmts_table = tree.find(self.tag_with_ns('numFmts', self.main_ns))\n            number_fmts = {}\n            if number_fmts_table:\n                for num_fmt in number_fmts_table.iter(numfmt_tag):\n                    number_fmts[num_fmt.get('numFmtId')] = num_fmt.get('formatCode')\n            number_fmts.update(STANDARD_STYLES)\n            style_table = tree.find(self.tag_with_ns('cellXfs', self.main_ns))\n            if style_table:\n                for style in style_table.iter(style_tag):\n                    fmtid = style.get('numFmtId')\n                    if fmtid in number_fmts:\n                        styles.append(number_fmts[fmtid])\n        self._styles = styles\n        return styles\n\n\n    def num_to_date(self, number):\n        \"\"\"\n        Return date of \"number\" based on the date system used in this workbook.\n\n        The date system is either the 1904 system or the 1900 system depending\n        on which date system the spreadsheet is using. See\n        http://bit.ly/2He5HoD for more information on date systems in Excel.\n        \"\"\"\n        if self.date_system == 1900:\n            # Under the 1900 base system, 1 represents 1/1/1900 (so we start\n            # with a base date of 12/31/1899).\n            base = datetime.datetime(1899, 12, 31)\n            # BUT (!), Excel considers 1900 a leap-year which it is not. As\n            # such, it will happily represent 2/29/1900 with the number 60, but\n            # we cannot convert that value to a date so we throw an error.\n            if number == 60:\n                raise ValueError(\"Bad date in Excel file - 2/29/1900 not valid\")\n            # Otherwise, if the value is greater than 60 we need to adjust the\n            # base date to 12/30/1899 to account for this leap year bug.\n            elif number > 60:\n                base = base - datetime.timedelta(days=1)\n        else:\n            # Under the 1904 system, 1 represent 1/2/1904 so we start with a\n            # base date of 1/1/1904.\n            base = datetime.datetime(1904, 1, 1)\n        days = int(number)\n        partial_days = number - days\n        seconds = int(round(partial_days * 86400000.0))\n        seconds, milliseconds = divmod(seconds, 1000)\n        if days < -693594:\n            return days\n        date = base + datetime.timedelta(days, seconds, 0, milliseconds)\n        if days == 0:\n            return date.time()\n        return date\n\n\n# Some helper functions\ndef num2col(num):\n    \"\"\"Convert given column letter to an Excel column number.\"\"\"\n    result = []\n    while num:\n        num, rem = divmod(num-1, 26)\n        result[:0] = string.ascii_uppercase[rem]\n    return ''.join(result)\n\ndef col2num(ltr):\n    num = 0\n    for c in ltr:\n        if c in string.ascii_letters:\n            num = num * 26 + (ord(c.upper()) - ord('A')) + 1\n    return num\n"
  }
]