Full Code of yanghuan/proton for AI

master e19e26a4c727 cached
20 files
56.7 KB
15.1k tokens
91 symbols
1 requests
Download .txt
Repository: yanghuan/proton
Branch: master
Commit: e19e26a4c727
Files: 20
Total size: 56.7 KB

Directory structure:
gitextract_2lhivfiy/

├── .gitignore
├── LICENSE
├── README.md
├── nested_parser.py
├── proton.py
├── raw/
│   ├── en/
│   │   ├── sample.xlsx
│   │   └── sample2.xlsx
│   └── zh/
│       ├── sample.xlsx
│       └── sample2.xlsx
└── sample/
    ├── README.md
    ├── __export.bat
    ├── __export.py
    ├── complex_nested_obj.xlsx
    ├── hero.xlsx
    ├── mount.xlsx
    ├── text.xlsx
    └── tools/
        ├── CSharpGeneratorForProton/
        │   └── README.md
        └── py37/
            ├── README.md
            └── sxl/
                ├── __init__.py
                └── sxl.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
*.pyc

================================================
FILE: LICENSE
================================================
Copyright 2016 YANG Huan (sy.yanghuan@gmail.com)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.


================================================
FILE: README.md
================================================
[English](https://github.com/yanghuan/proton#proton)   [Chinese](https://github.com/yanghuan/proton#proton-1)  
# proton
Proton 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.

## Features
- 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  
- 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) 
- Can export excel format information for external use, can be used to automatically generate read configuration code

## Generates an auto-read code
Use 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.
- [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).

## Example
[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.

## Command Line Parameters
```cmd
usage python proton.py [-p filelist] [-f outfolder] [-e format]
Arguments 
-p      : input excel files, use , or ; or space to separate 
-f      : out folder
-e      : format, json or xml or lua     

Options
-s      :sign, controls whether the column is exported, defalut all export
-t      : suffix, export file suffix
-r      : the separator of object field, default is ; you can use it to change
-m      : use the count of multiprocesses to export, default is cpu count
-c      : a file path, save the excel structure to json, 
          the external program uses this file to automatically generate the read code      
-h      : print this help message and exit
-x      : don't append 's' on names
```

## Documentation
Wiki https://github.com/yanghuan/proton/wiki/document_en  
FAQ https://github.com/yanghuan/proton/wiki/FAQ_en

## *License*
[Apache 2.0 license](https://github.com/yanghuan/proton/blob/master/LICENSE).

_____________________
# proton
proton是一个将excel导出为配置文件的工具,可以导出为xml、json、lua格式,通过外部扩展可支持自动生成读取配置的代码,简单灵活易于使用,确不失强大。
## 特点
- python编写可跨平台使用,仅依赖第三方库[sxl](https://pypi.org/project/sxl/),[完整代码仅600余行](https://github.com/yanghuan/proton/blob/master/proton.py)。
- 有特定的规则语法描述excel的格式信息,简洁易懂,灵活强大,[详细说明](https://github.com/yanghuan/proton/wiki/document_zh)。
- 可导出excel格式信息供外部程序使用,可用来自动生成读取配置的代码。

## 后端程序(生成自动读取的代码)
使用“-c”参数可生成内含excel格式信息的json文件,各个语言可据此实现自动生成读取代码的工具,[具体格式说明](https://github.com/yanghuan/proton/wiki/schema_zh)。已经实现了C#语言的工具,其他语言使用者,可自行实现,欢迎提供实现的代码链接,以供需要的同学使用。

- [CSharpGeneratorForProton](https://github.com/yanghuan/CSharpGeneratorForProton) 可生成读取xml、json、protobuf的C#代码。 可将xml、json转换为protobuf的二进制格式,并生成对应的读取代码(使用protobuf-net)。

## 实例工程
[sample目录](https://github.com/yanghuan/proton/tree/master/sample)下是一个配置好了的可在windows下直接使用的实例。已经包含了python3环境,直接运行__export.bat即可完成导出。需要添加新的Excel文件,修改__export.py中相关数组,加入即可。

## 命令行参数
```cmd
usage python proton.py [-p filelist] [-f outfolder] [-e format]
Arguments 
-p      : input excel files, use , or ; or space to separate 
-f      : out folder
-e      : format, json or xml or lua     

Options
-s      :sign, controls whether the column is exported, defalut all export
-t      : suffix, export file suffix
-r      : the separator of object field, default is ; you can use it to change
-m      : use the count of multiprocesses to export, default is cpu count
-c      : a file path, save the excel structure to json, 
          the external program uses this file to automatically generate the read code      
-h      : print this help message and exit
-x      : don't append 's' on names
```

## 文档
格式说明 https://github.com/yanghuan/proton/wiki/document_zh  
FAQ https://github.com/yanghuan/proton/wiki/FAQ_zh

## 交流讨论
- [常见问题](https://github.com/yanghuan/proton/wiki/FAQ_zh)
- 邮箱:sy.yanghuan@gmail.com
- QQ群:715350749

## *许可证*
[Apache 2.0 license](https://github.com/yanghuan/proton/blob/master/LICENSE).


================================================
FILE: nested_parser.py
================================================
#encoding=utf-8
import string

_OPEN_TO_CLOSE = {
  '{': '}',
  '[': ']',
  '(': ')',
}

_CLOSE_SET = set(_OPEN_TO_CLOSE.values())


def split_top_level(text, delimiter, skip_empty = False):
  if text is None:
    return []
  if not delimiter:
    raise ValueError('delimiter can not be empty')

  values = []
  stack = []
  start = 0
  i = 0
  n = len(text)

  while i < n:
    c = text[i]

    if c == '\\' and i + 1 < n:
      i += 2
      continue

    if c in _OPEN_TO_CLOSE:
      stack.append(_OPEN_TO_CLOSE[c])
      i += 1
      continue

    if c in _CLOSE_SET:
      if not stack or c != stack[-1]:
        raise ValueError('%s is not a legal nested expression' % text)
      stack.pop()
      i += 1
      continue

    if not stack and text.startswith(delimiter, i):
      value = text[start:i]
      if not skip_empty or value:
        values.append(value)
      i += len(delimiter)
      start = i
      continue

    i += 1

  if stack:
    raise ValueError('%s is not a legal nested expression' % text)

  value = text[start:]
  if not skip_empty or value:
    values.append(value)

  return values


def unwrap_container(text, begin, end):
  value = text.strip()
  if len(value) >= 2 and value[0] == begin and value[-1] == end:
    return value[1:-1]
  return value


def split_list_values(value):
  return split_top_level(unwrap_container(value, '[', ']'), ',', True)


def split_obj_type_fields(type_, separator):
  return split_top_level(unwrap_container(type_, '{', '}'), separator, True)


def split_obj_values(value, separator):
  return split_top_level(unwrap_container(value, '{', '}'), separator, True)


def split_field_declaration(text):
  declaration = text.strip()
  if not declaration:
    raise ValueError('field declaration can not be empty')

  values = []
  stack = []
  start = None
  i = 0
  n = len(declaration)

  while i < n:
    c = declaration[i]

    if c == '\\' and i + 1 < n:
      if start is None:
        start = i
      i += 2
      continue

    if c in _OPEN_TO_CLOSE:
      if start is None:
        start = i
      stack.append(_OPEN_TO_CLOSE[c])
      i += 1
      continue

    if c in _CLOSE_SET:
      if not stack or c != stack[-1]:
        raise ValueError('%s is not a legal field declaration' % declaration)
      stack.pop()
      i += 1
      continue

    if c in string.whitespace and not stack:
      if start is not None:
        values.append(declaration[start:i])
        start = None
      i += 1
      continue

    if start is None:
      start = i
    i += 1

  if stack:
    raise ValueError('%s is not a legal field declaration' % declaration)

  if start is not None:
    values.append(declaration[start:])

  if len(values) < 2:
    raise ValueError('%s is not a legal field declaration' % declaration)

  return (' '.join(values[:-1]), values[-1])

================================================
FILE: proton.py
================================================
#encoding=utf-8
'''
Copyright YANG Huan (sy.yanghuan@gmail.com)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
'''
import sys        

if sys.version_info < (3, 0):
  print('python version need more than 3.x')
  sys.exit(1)
    
import os
import string
import collections
import codecs
import getopt
import re
import json
import traceback
import multiprocessing
import xml.etree.ElementTree as ElementTree
import xml.dom.minidom as minidom
import sxl
import nested_parser

def fillvalue(parent, name, value, isschema):
  if isinstance(parent, list):
    parent.append(value) 
  else:
    if isschema and not re.match('^_|[a-zA-Z]\w*$', name):
      raise ValueError('%s is a illegal identifier' % name)
    parent[name] = value
    
def getindex(infos, name):
  return next((i for i, j in enumerate(infos) if j == name), -1)
  
def getcellvalue(value):
  return str(value) if value is not None else ''

def getscemainfo(typename, description):
  if isinstance(typename, BindType):
    typename = typename.typename
  return [typename, description] if description else [typename]
        
def getexportmark(sheetName):
  p = re.search('\|[' + string.whitespace + ']*(_|[a-zA-Z]\w+)', sheetName)
  return p.group(1) if p else False

def issignmatch(signarg, sign):
  if signarg is None:
    return True
  return True if [s for s in re.split(r'[/\\, :]', sign) if s in signarg] else False

def isoutofdate(srcfile, tarfile):
  return not os.path.isfile(tarfile) or os.path.getmtime(srcfile) > os.path.getmtime(tarfile)

def gerexportfilename(root, format_, folder):
  filename = root +  '.' + format_
  return os.path.join(folder, filename)

def splitspace(s):
  return nested_parser.split_field_declaration(s)
  
def buildbasexml(parent, name, value, noplural = False):
  value = str(value)
  listtag = name if noplural else name + 's'
  if parent.tag == listtag:
    element = ElementTree.Element(name)
    element.text = value
    parent.append(element)
  else:
    parent.set(name, value)
            
def buildlistxml(parent, name, list_, noplural = False):
  element = ElementTree.Element(name)
  parent.append(element)
  itemname = name if noplural else name[:-1]
  for v in list_:
    buildxml(element, itemname, v, noplural)    

def buildobjxml(parent, name, obj, noplural = False):
  element = ElementTree.Element(name)
  parent.append(element)
  
  for k, v in obj.items():
    buildxml(element, k, v, noplural)
        
def buildxml(parent, name, value, noplural = False):
  if isinstance(value, int) or isinstance(value, float) or isinstance(value, str):
    buildbasexml(parent, name, value, noplural)
      
  elif isinstance(value, list):
    buildlistxml(parent, name, value, noplural)
      
  elif isinstance(value, dict):
    buildobjxml(parent, name, value, noplural)
            
def savexml(record, noplural = False):
  book = ElementTree.ElementTree()
  book.append = lambda e: book._setroot(e)
  buildxml(book, record.root, record.obj, noplural)
  
  xmlstr = ElementTree.tostring(book.getroot(), 'utf-8')
  dom = minidom.parseString(xmlstr)
  with codecs.open(record.exportfile, 'w', 'utf-8') as f:
    dom.writexml(f, '', '  ', '\n', 'utf-8')
      
  print('save %s from %s in %s' % (record.exportfile, record.sheet.name, record.path))
  
def newline(count):
  return '\n' + '  ' * count
  
def tolua(obj, indent = 1):    
  if isinstance(obj, int) or isinstance(obj, float) or isinstance(obj, str):
    yield json.dumps(obj, ensure_ascii = False)
  else:
    yield '{'
    islist = isinstance(obj, list)
    isfirst = True
    for i in obj:
      if isfirst:
        isfirst = False
      else:
        yield ','
      yield newline(indent)
      if not islist:
        k = i
        i = obj[k]
        yield k 
        yield ' = '                
      for part in tolua(i, indent + 1):
        yield part
    yield newline(indent - 1)
    yield '}'
    
def toycl(obj, indent = 0):
  islist = isinstance(obj, list)
  for i in obj:
    yield newline(indent)  
    if not islist:
      k = i
      i = obj[k]
      yield k 
    if isinstance(i, int) or isinstance(i, float) or isinstance(i, str): 
      if not islist:
        yield ' = '
      yield json.dumps(i, ensure_ascii = False)
    else:
      if not islist:
        yield ' '
      yield '{'
      for part in toycl(i, indent + 1):
        yield part
      yield newline(indent)  
      yield '}'     

class BindType:
  def __init__(self, type_):
    self.typename = type_
      
  def __eq__(self, other):
    return self.typename == other
    
class Record:
  def __init__(self, path, sheet, exportfile, root, item, obj, exportmark):
    self.path = path 
    self.sheet = sheet 
    self.exportfile = exportfile 
    self.root = root 
    self.item = item
    self.setobj(obj)
    self.exportmark = exportmark

  def setobj(self, obj):    
    self.schema = obj[0] if obj else None
    self.obj = obj[1] if obj else None
        
class Constraint:
  def __init__(self, mark, filed):
    self.mark = mark
    self.field = filed     
        
class Exporter:
  configsheettitles = ('name', 'value', 'type', 'sign', 'description')
  spacemaxrowcount = 3
  
  def __init__(self, context):
    self.context = context
    self.records = []
    
  def checkstringescape(self, t, v):
    return v if not v or not 'string' in t else v.replace('\\n', '\n').replace('\,', '\0').replace('\\' + self.context.objseparator, '\a')
  
  def stringescape(self, s):
    return s.replace('\0', ',').replace('\a', self.context.objseparator)

  def pluralname(self, name):
    return name if self.context.noplural else name + 's'
  
  def gettype(self, type_):
    if type_[-2] == '[' and  type_[-1] == ']':
      return 'list'
    if type_[0] == '{' and type_[-1] == '}':
      return 'obj'
    if type_ in ('int', 'double', 'string', 'bool', 'long', 'float'):
      return type_
    
    p = re.search('(int|string|long)[' + string.whitespace + ']*\((\S+)\.(\S+)\)', type_)
    if p:
      type_ = BindType(p.group(1))
      type_.mark = p.group(2)
      type_.field = p.group(3)
      return type_
        
    raise ValueError('%s is not a legal type' % type_)
    
  def buildlistexpress(self, parent, type_, name, value, isschema):
    basetype = type_[:-2]
    list_ = []
    if isschema:
      self.buildexpress(list_, basetype, name, None, isschema)
      list_ = getscemainfo(list_[0], value)
    else:
      valuelist = nested_parser.split_list_values(value)
      for v in valuelist:
        self.buildexpress(list_, basetype, name, v, False, True)
       
    fillvalue(parent, self.pluralname(name), list_, isschema)
      
  def buildobjexpress(self, parent, type_, name, value, isschema):
    obj = collections.OrderedDict()
    fieldnamestypes = nested_parser.split_obj_type_fields(type_, self.context.objseparator)
    
    if isschema:
      for i in range(0, len(fieldnamestypes)):
        fieldtype, fieldname = splitspace(fieldnamestypes[i])
        self.buildexpress(obj, fieldtype, fieldname, None, isschema)
      obj = getscemainfo(obj, value)
    else:
      fieldValues = nested_parser.split_obj_values(value, self.context.objseparator)
      for i in range(0, len(fieldnamestypes)):
        if i < len(fieldValues):
          fieldtype, fieldname = splitspace(fieldnamestypes[i])
          self.buildexpress(obj, fieldtype, fieldname, fieldValues[i], False, True)

    fillvalue(parent, name, obj, isschema)       
      
  def buildbasexpress(self, parent, type_, name, value, isschema, inobj):
    typename = self.gettype(type_) 
    if isschema:
      value = getscemainfo(typename, value)
    else:
      if typename != 'string' and value.isspace():
        return
        
      if typename == 'int' or typename == 'long':
        value = int(float(value))
      elif typename == 'double' or typename == 'float':
        value = float(value)   
      elif typename == 'string':
        if value.endswith('.0'):          # may read is like "123.0"
          try:
            value = str(int(float(value)))
          except ValueError:
            value = self.stringescape(str(value))
        else:            
          value = self.stringescape(str(value))
        if inobj and len(value) > 0 and value[0] == '\n':
          value = value[1:]
      elif typename == 'bool':
        try:
          value = int(float(value))
          value = False if value == 0 else True 
        except ValueError:
          value = value.lower() 
          if value in ('false', 'no', 'off'):
            value = False
          elif value in ('true', 'yes', 'on'):
            value = True
          else:
            raise ValueError('%s is a illegal bool value' % value)
            
    fillvalue(parent, name, value, isschema)

  def buildexpress(self, parent, type_, name, value, isschema = False, inobj = False):
    typename = self.gettype(type_)
    if typename == 'list':
      self.buildlistexpress(parent, type_, name, value, isschema)
    elif typename == 'obj':
      self.buildobjexpress(parent, type_, name, value, isschema)
    else:
      self.buildbasexpress(parent, type_, name, value, isschema, inobj)
      
  def getrootname(self, exportmark, isitem):
    root = self.pluralname(exportmark) if isitem else exportmark
    return root + (self.context.extension or '')

  def export(self, path):
    self.path = path
    data = sxl.Workbook(self.path)
    cout = None

    for sheetname in [i for i in data.sheets if type(i) is str]:
      self.sheetname = sheetname
      exportmark = getexportmark(sheetname)
      if exportmark:
        sheet = data.sheets[sheetname]
        coutmark = sheetname.endswith('<<')
        configtitleinfo = self.getconfigsheetfinfo(sheet)
        if not configtitleinfo:
          root = self.getrootname(exportmark, not coutmark)
          item = exportmark
        else:
          root = self.getrootname(exportmark, False)
          item = None
          
        if not cout:  
          self.checksheetname(self.path, sheetname, root)
          exportfile = gerexportfilename(root, self.context.format, self.context.folder)
          
          if isoutofdate(self.path, exportfile):
            if item:
              exportobj = self.exportitemsheet(sheet)
            else:
              exportobj = self.exportconfigsheet(sheet, configtitleinfo)
          
            if coutmark:
              if not item:
                cout = exportobj
              else:
                cout = (collections.OrderedDict(), collections.OrderedDict())
                itemkey = self.pluralname(item)
                cout[0][itemkey] = [[exportobj[0]]]
                item = None
                exportobj = cout
                obj = exportobj[1]
                if obj:
                  cout[1][itemkey] = obj
                  
            self.records.append(Record(self.path, sheet, exportfile, root, item, exportobj, exportmark))
          else:
            print('%s is not changed' % (self.path))
            break
        else:
          if item:
            exportobj = self.exportitemsheet(sheet)
            cout[0][self.pluralname(item)] = [[exportobj[0]]]
            obj = exportobj[1]
            if obj:
              cout[1][self.pluralname(item)] = obj
          else:
            exportobj = self.exportconfigsheet(sheet, configtitleinfo)
            cout[0].update(exportobj[0])   
            obj = exportobj[1]
            if obj:
              cout[1].update(obj)
              
    return self.saves()

  def getconfigsheetfinfo(self, sheet):
    titles = sheet.head(1)[0]
    
    nameindex = getindex(titles, self.configsheettitles[0])
    valueindex = getindex(titles, self.configsheettitles[1])
    typeindex = getindex(titles, self.configsheettitles[2])
    signindex = getindex(titles, self.configsheettitles[3])
    descriptionindex = getindex(titles, self.configsheettitles[4])
    
    if nameindex != -1 and valueindex != -1 and typeindex != -1:
      return (nameindex, valueindex, typeindex, signindex, descriptionindex)
    else:
      return None
      
  def exportitemsheet(self, sheet):
    rows = iter(sheet.rows)
    descriptions = next(rows)
    types = next(rows)
    names = next(rows)
    signs = next(rows)
    
    ncols = len(types)
    titleinfos = []
    schemaobj = collections.OrderedDict()
    
    try:
      for colindex in range(ncols):
        type_ = getcellvalue(types[colindex]).strip()
        name = getcellvalue(names[colindex]).strip()
        signmatch = issignmatch(self.context.sign, getcellvalue(signs[colindex]).strip())        
        titleinfos.append((type_, name, signmatch))
        
        if self.context.codegenerator:
          if type_ and name and signmatch:
            self.buildexpress(schemaobj, type_, name, descriptions[colindex], True)
            
    except Exception as e:
      e.args += ('%s has a title error, %s at %d column in %s' % (sheet.name, (type_, name), colindex + 1, self.path) , '')
      raise e
      
    list_ = []
    hasexport = next((i for i in titleinfos if i[0] and i[1] and i[2]), False)
    if hasexport:
      try:
        spacerowcount = 0
        self.rowindex = 3
        for row in rows:
          self.rowindex += 1
          
          item = collections.OrderedDict()
          firsttext = getcellvalue(row[0]).strip()
          if not firsttext:
            spacerowcount += 1
            if spacerowcount >= self.spacemaxrowcount:      # if space row is than max count, skil follow rows     
              break
          
          if not firsttext or firsttext[0] == '#':    # current line skip
            continue
            
          skiptokenindex = None
          if firsttext[0] == '!':
            nextpos = firsttext.find('!', 1)
            if nextpos >= 2:
              signtoken = firsttext[1: nextpos]
              if issignmatch(self.context.sign, signtoken.strip()):
                continue
              else:
                skiptokenindex = len(signtoken) + 2
          
          for self.colindex in range(ncols):
            signmatch = titleinfos[self.colindex][2]
            if signmatch:
              type_ = titleinfos[self.colindex][0]
              name = titleinfos[self.colindex][1]
              value = getcellvalue(row[self.colindex])
              
              if skiptokenindex and self.colindex == 0:
                value = value.lstrip()[skiptokenindex:]
                
              if type_ and name and value:
                self.buildexpress(item, type_, name, self.checkstringescape(type_, value))  
            spacerowcount = 0
            
          if item:
            list_.append(item) 
          
      except Exception as e:        
          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) , '')
          raise e
    
    return (schemaobj, list_)
        
  def exportconfigsheet(self, sheet, titleindexs):
    rows = iter(sheet.rows)
    next(rows)
  
    nameindex = titleindexs[0]
    valueindex = titleindexs[1]
    typeindex = titleindexs[2]
    signindex = titleindexs[3]
    descriptionindex = titleindexs[4]
    
    schemaobj = collections.OrderedDict()
    obj = collections.OrderedDict()
    
    try:
      spacerowcount = 0
      self.rowindex = 0
      for row in rows:
        self.rowindex += 1
        name = getcellvalue(row[nameindex]).strip()
        value = getcellvalue(row[valueindex])
        type_ = getcellvalue(row[typeindex]).strip()
        description = getcellvalue(row[descriptionindex]).strip()
        
        if signindex > 0:
          sign = getcellvalue(row[signindex]).strip()
          if not issignmatch(self.context.sign, sign):
            continue
          
        if not name and not value and not type_:
          spacerowcount += 1
          if spacerowcount >= self.spacemaxrowcount:
            break            # if space row is than max count, skil follow rows     
          continue
            
        if name and type_:
          if(name[0] != '#'):         # current line skip
            if self.context.codegenerator:
              self.buildexpress(schemaobj, type_, name, description, True)
            if value:    
              self.buildexpress(obj, type_, name, self.checkstringescape(type_, value))
          spacerowcount = 0    
               
    except Exception as e:
      e.args += ('%s has a error in %d row (%s, %s, %s) in %s' % (sheet.name, self.rowindex + 1, type_, name, value, self.path) , '')
      raise e
  
    return (schemaobj, obj)
    
  def saves(self):
    schemas = []
    for r in self.records:
      if r.obj:
        self.save(r)

        if self.context.codegenerator:        # has code generator
          schemas.append({ 'path': r.path, 'exportfile' : r.exportfile, 'root' : r.root, 'item' : r.item or r.exportmark, 'schema' : r.schema })

    return schemas
                
  def save(self, record):
    if not record.obj:
      return
  
    if not os.path.isdir(self.context.folder):
      os.makedirs(self.context.folder)
        
    if self.context.format == 'json':
      jsonstr = json.dumps(record.obj, ensure_ascii = False, indent = 2)
      with codecs.open(record.exportfile, 'w', 'utf-8') as f:
        f.write(jsonstr)
      print('save %s from %s in %s' % (record.exportfile, record.sheet.name, record.path))
        
    elif self.context.format == 'xml':
      if record.item:
        record.obj = { self.pluralname(record.item) : record.obj }
      savexml(record, self.context.noplural) 
        
    elif self.context.format == 'lua':
      luastr = "".join(tolua(record.obj))
      with codecs.open(record.exportfile, 'w', 'utf-8') as f:
        f.write('return ')
        f.write(luastr)
      print('save %s from %s in %s' % (record.exportfile, record.sheet.name, record.path))
      
    elif self.context.format == 'ycl':
      g = toycl(record.obj)
      next(g) # skip first newline
      yclstr = "".join(g)
      with codecs.open(record.exportfile, 'w', 'utf-8') as f:
        f.write(yclstr)
      print('save %s from %s in %s' % (record.exportfile, record.sheet.name, record.path))
  
  def checksheetname(self, path, sheetname, root):
    r = next((r for r in self.records if r.root == root), False)
    if r:
      raise ValueError('%s in %s is already defined in %s' % (root, path, r.path))
     
def export(context, path):
  try:
    return Exporter(context).export(path)
  except Exception as e:  
    return traceback.format_exc()

def exportpack(args):
  return export(args[0], args[1])

def exportfiles(context):
  paths = []
  for path in re.split(r'[,;|]+', context.path.strip()):
    if path:
      if not os.path.isfile(path):
        raise ValueError('%s is not exists' % path)
      elif path in paths:
        raise ValueError('%s is already has' % path)    
      paths.append(path)

  errors = []
  schemas = []

  def append(result):
    if type(result) is str:
      errors.append(result)
    else:   
      schemas.extend(result)
      
  if context.multiprocessescount is None or context.multiprocessescount > 1:
    with multiprocessing.Pool(context.multiprocessescount) as p:
      for i in p.map(exportpack, [(context, x) for x in paths]):
        append(i)
  else:
    for path in paths:
      result = export(context, path)
      append(result)

  if schemas:
    if context.codegenerator:
      schemasjson = json.dumps(schemas, ensure_ascii = False, indent = 2)
      dir = os.path.dirname(context.codegenerator)
      if dir and not os.path.isdir(dir):
        os.makedirs(dir)
      with codecs.open(context.codegenerator, 'w', 'utf-8') as f:
        f.write(schemasjson)

    exports = []
    for schema in schemas:
      exportfile = schema['exportfile']
      r = next((r for r in exports if r['exportfile'] == exportfile), False)
      if r:
        errors.append('%s in %s is already defined in %s' % (schema['root'], schema['path'], r['path']))
        os.remove(exportfile)
      else:
        exports.append(schema)

  if errors:
    print('\n\n'.join(errors))
    sys.exit(-1)

  print("export finsish successful!!!")

class Context:
  '''usage python proton.py [-p filelist] [-f outfolder] [-e format]
  Arguments
  -p      : input excel files, use , or ; or space to separate
  -f      : out folder
  -e      : format, json or xml or lua or ycl

  Options
  -s      :sign, controls whether the column is exported, defalut all export
  -t      : suffix, export file suffix
  -r      : the separator of object field, default is ; you can use it to change
  -m      : use the count of multiprocesses to export, default is cpu count
  -c      : a file path, save the excel structure to json
            the external program uses this file to automatically generate the read code
  -x      : disable auto plural naming (do not append 's')
  -h      : print this help message and exit

  https://github.com/yanghuan/proton'''

if __name__ == '__main__':
  print('argv:' , sys.argv)
  opst, args = getopt.getopt(sys.argv[1:], 'p:f:e:s:t:r:m:c:xh')

  context = Context()
  context.path = None
  context.folder = '.'
  context.format = 'json'
  context.sign = None
  context.extension = None
  context.objseparator = ';'
  context.codegenerator = None
  context.multiprocessescount = None
  context.noplural = False

  for op, v in opst:
    if op == '-p':
      context.path = v
    elif op == '-f':
      context.folder = v
    elif op == '-e':
      context.format = v.lower() 
    elif op == '-s':
      context.sign = v 
    elif op == '-t':
      context.extension = v
    elif op == '-r':
      context.objseparator = v
    elif op == '-m':
      context.multiprocessescount = int(v) if v is not None else None
    elif op == '-c':
      context.codegenerator = v    
    elif op == '-x':
      context.noplural = True
    elif op == '-h':
      print(Context.__doc__)
      sys.exit()
      
  if not context.path:
    print(Context.__doc__)
    sys.exit(2)

  exportfiles(context)

================================================
FILE: sample/README.md
================================================
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.



================================================
FILE: sample/__export.bat
================================================
tools\py37\py37.exe __export.py

================================================
FILE: sample/__export.py
================================================
#encoding=utf-8

# Need to export the public configuration file (client, server needs) 需要导出的公共配置文件(客户端,服务器都需要)
EXPORT_FILES = [
"hero.xlsx",
"mount.xlsx",
]

# Additional configuration files that the client needs to export (only the client needs) 客户端额外需要导出的额外配置文件(仅客户端需要)
EXPORT_CLIENT_ONLY = [
"text.xlsx"
]

# Server-side need to export the configuration file (only the server needs) 服务器端额外需要导出的配置文件(仅服务器需要)
EXPORT_SERVER_ONLY = [
]

# do not modify the following

import os
import platform
import traceback
import shutil
import sys

exportscript = '../proton.py'     
pythonpath = 'tools\\py37\\py37.exe ' if platform.system() == 'Windows' else 'python '

class ExportError(Exception):
  pass

def export(filelist, format, sign, outfolder, suffix, schema):
  cmd = r' -p "' + ','.join(filelist) + '" -f ' + outfolder + ' -e ' + format + ' -s ' + sign
  if suffix:
    cmd += ' -t ' + suffix
  if schema:
    cmd += ' -c ' + schema
  cmd = pythonpath + exportscript + cmd
  code = os.system(cmd)
  if code != 0:
    raise ExportError('export excel fail, please see print')

def codegenerator(schema, outfolder, namespace, suffix):
  if os.path.exists(schema):
    cmd = 'tools\CSharpGeneratorForProton\CSharpGeneratorForProton.exe ' + '-n ' + namespace + ' -f ' + outfolder + ' -p ' + schema
    if suffix:
      cmd += ' -t ' + suffix 
    code = os.system(cmd)
    os.remove(schema)      
    if code != 0:
      raise ExportError('codegenerator fail, please see print')
        
def exportserver():
  export(EXPORT_FILES + EXPORT_SERVER_ONLY, 'json', 'server', 'config_server', 'Config', 'schemaserver.json')
  codegenerator('schemaserver.json', 'config_server/ConfigGenerator/Template', 'Ice.Project.Config', 'Template') 
    
def exportclient():
  export(EXPORT_FILES + EXPORT_CLIENT_ONLY, 'lua', 'client', 'config_client', 'Template', None)
    
def main():
  try:
    exportserver()
    exportclient()
    print("all operation finish successful")
    return 0
  except ExportError as e:
    print(e)
    print("has error, see logs, please return key to exit")
    input()
    return 1
  except Exception as e:
    traceback.print_exc()
    print("has error, see logs, please return key to exit")
    input()
    return 1
    
if __name__ == '__main__':
    sys.exit(main())


================================================
FILE: sample/tools/CSharpGeneratorForProton/README.md
================================================
[English](https://github.com/sy-yanghuan/CSharpGeneratorForProton#csharpgeneratorforproton)   [Chinese](https://github.com/sy-yanghuan/CSharpGeneratorForProton#csharpgeneratorforproton-1)  
# CSharpGeneratorForProton
CSharpGeneratorForProton 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).
## Command Line Parameters
```cmd
Usage: CSharpGeneratorForProton [-p schemaFile] [-f output] [-n namespace]
Arguments 
-p              : schema file, Proton output
-f              : output directory, will put the generated class code
-n              : namespace of the generated class 

Options
-t              : suffix, generates the suffix for the class  
-e              : open convert exportfile to protobuf
-d              : protobuf binary data output directory, use only when '-e' exists  
-b              : protobuf binary data file extension, use only when '-e' exists
-h              : show the help message and exit 
```
## Generated Code Import
Generated 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.
- [GeneratorUtility for xml](https://github.com/sy-yanghuan/CSharpGeneratorForProton/blob/master/CSharpGeneratorForProton/CSharpGeneratorForProton/GeneratorUtility/XmlLoader.cs)
- [GeneratorUtility for json](https://github.com/sy-yanghuan/CSharpGeneratorForProton/blob/master/CSharpGeneratorForProton/CSharpGeneratorForProton/GeneratorUtility/JsonLoader.cs)
- [GeneratorUtility for protobuf](https://github.com/sy-yanghuan/CSharpGeneratorForProton/blob/master/CSharpGeneratorForProton/CSharpGeneratorForProton/GeneratorUtility/ProtobufLoader.cs)  

## Example
[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).

## *License*
[Apache 2.0 license](https://github.com/sy-yanghuan/CSharpGeneratorForProton/blob/master/LICENSE).

_____________________
# CSharpGeneratorForProton
CSharpGeneratorForProton 是为[proton] (https://github.com/sy-yanghuan/proton)产生读取xml、json、protobuf的C#的代码。其还可将xml、jsond配置文件转换成protobuf二进制格式。
## 命令行参数
```cmd
Usage: CSharpGeneratorForProton [-p schemaFile] [-f output] [-n namespace]
Arguments 
-p              : schema file, Proton output
-f              : output directory, will put the generated class code
-n              : namespace of the generated class 

Options
-t              : suffix, generates the suffix for the class  
-e              : open convert exportfile to protobuf
-d              : protobuf binary data output directory, use only when '-e' exists  
-b              : protobuf binary data file extension, use only when '-e' exists
-h              : show the help message and exit 
```
## 导入生成的代码
生成的C#代码并不与具体格式相关联,具体读取操作,均外派给GeneratorUtility工具类进行处理,所以还需将对应工具类添加入工程。代码均在[目录GeneratorUtility](https://github.com/sy-yanghuan/CSharpGeneratorForProton/tree/master/CSharpGeneratorForProton/CSharpGeneratorForProton/GeneratorUtility)下,可按具体使用要求修改其代码,例如更换命名空间、更换读取库等。
- [GeneratorUtility for xml](https://github.com/sy-yanghuan/CSharpGeneratorForProton/blob/master/CSharpGeneratorForProton/CSharpGeneratorForProton/GeneratorUtility/XmlLoader.cs)
- [GeneratorUtility for json](https://github.com/sy-yanghuan/CSharpGeneratorForProton/blob/master/CSharpGeneratorForProton/CSharpGeneratorForProton/GeneratorUtility/JsonLoader.cs)
- [GeneratorUtility for protobuf](https://github.com/sy-yanghuan/CSharpGeneratorForProton/blob/master/CSharpGeneratorForProton/CSharpGeneratorForProton/GeneratorUtility/ProtobufLoader.cs)  

## 实例工程
[Example](https://github.com/sy-yanghuan/CSharpGeneratorForProton/tree/master/CSharpGeneratorForProton/Example)工程是一个完整的载入配置的实例,其载入配置是通过[proton的实例](https://github.com/sy-yanghuan/proton/tree/master/sample)导出的。

##*许可证*
[Apache 2.0 license](https://github.com/sy-yanghuan/CSharpGeneratorForProton/blob/master/LICENSE).


================================================
FILE: sample/tools/py37/README.md
================================================
# pyexe.exe
https://github.com/manthey/pyexe

[![Build status](https://ci.appveyor.com/api/projects/status/n18f0997k18x87lw/branch/master?svg=true)](https://ci.appveyor.com/project/manthey/pyexe/branch/master)

Here is a stand-alone version of python that is a single Windows executable.

It consists of the most recent versions of Python (with builds for 2.7, 3.5,
and 3.6 each in 32-bit and 64-bit versions), pywin32, psutil, six, pip, 
setuptools, and includes all packages that can be included without additional 
dlls, excepting tkinter.

See the appveyor script for build instructions.

## Installing other modules

Python is most useful with additional modules.  The stand-alone executable can use pip to install modules from pypi to the local directory.  For instance:

```bash
py36-64.exe -m pip install --no-cache-dir --target . --upgrade sympy
```

Use `-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).

## Differences from installed Python

Although the stand-alone Python attempts to have the same features as a normally installed Python, there are some differences.

- 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.
- `PYTHONHOME` is ignored.  This option doesn't make sense for a stand-alone version.
- `-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.
- `--check-hash-based-pycs` is ignored.  This option cannot be changed after the Python executable starts.
- `-R` and `PYTHONHASHSEED` are ignored.  These options cannot be changed after the Python executable starts.
- `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.
- 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.


================================================
FILE: sample/tools/py37/sxl/__init__.py
================================================

from .sxl import Workbook, col2num, num2col

__version__ = '0.0.1a10'


================================================
FILE: sample/tools/py37/sxl/sxl.py
================================================
"""
xl.py - python library to deal with *big* Excel files.
"""

from abc import ABC
from collections import namedtuple, ChainMap
from contextlib import contextmanager
import datetime
import io
from itertools import zip_longest
import os
import re
import string
import xml.etree.cElementTree as ET
from zipfile import ZipFile

# ISO/IEC 29500:2011 in Part 1, section 18.8.30
STANDARD_STYLES = {
    '0' : 'General',
    '1' : '0',
    '2' : '0.00',
    '3' : '#,##0',
    '4' : '#,##0.00',
    '9' : '0%',
    '10' : '0.00%',
    '11' : '0.00E+00',
    '12' : '# ?/?',
    '13' : '# ??/??',
    '14' : 'mm-dd-yy',
    '15' : 'd-mmm-yy',
    '16' : 'd-mmm',
    '17' : 'mmm-yy',
    '18' : 'h:mm AM/PM',
    '19' : 'h:mm:ss AM/PM',
    '20' : 'h:mm',
    '21' : 'h:mm:ss',
    '22' : 'm/d/yy h:mm',
    '37' : '#,##0 ;(#,##0)',
    '38' : '#,##0 ;[Red](#,##0)',
    '39' : '#,##0.00;(#,##0.00)',
    '40' : '#,##0.00;[Red](#,##0.00)',
    '45' : 'mm:ss',
    '46' : '[h]:mm:ss',
    '47' : 'mmss.0',
    '48' : '##0.0E+0',
    '49' : '@',
}


ExcelErrorValue = namedtuple('ExcelErrorValue', 'value')


class ExcelObj(ABC):
    """
    Abstract base class for other excel objects (workbooks, worksheets, etc.)
    """
    main_ns = 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'
    rel_ns = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'

    @staticmethod
    def tag_with_ns(tag, ns):
        "Return XML tag with namespace that can be used with ElementTree"
        return '{%s}%s' % (ns, tag)

    @staticmethod
    def col_num_to_letter(n):
        "Return column letter for column number ``n``"
        string = ""
        while n > 0:
            n, remainder = divmod(n - 1, 26)
            string = chr(65 + remainder) + string
        return string

    @staticmethod
    def col_letter_to_num(letter):
        "Return column number for column letter ``letter``"
        assert re.match(r'[A-Z]+', letter)
        num = 0
        for char in letter:
            num = num * 26 + (ord(char.upper()) - ord('A')) + 1
        return num


class Worksheet(ExcelObj):
    """
    Excel worksheet
    """

    def __init__(self, workbook, name, number, location=''):
        self._used_area = None
        self._row_length = None
        self._num_rows = None
        self._num_cols = None
        self.workbook = self.wb = workbook
        self.name = name
        self.number = number
        self.location = location or 'xl/worksheets/sheet{number}.xml'

    @contextmanager
    def get_sheet_xml(self):
        "Get a pointer to the xml file underlying the current sheet"
        with self.workbook.xls.open(self.location) as f:
            yield io.TextIOWrapper(f, self.workbook.encoding)

    @property
    def range(self):
        "Return data found in range of cells"
        return Range(self)

    @property
    def rows(self):
        "Iterator that will yield every row in this sheet between start/end"
        return Range(self)

    def _set_dimensions(self):
        "Return the 'standard' row length of each row in this worksheet"
        if ':' not in self.used_area:
            self._num_cols = 0
            self._num_rows = 0
        else:
            _, end = self.used_area.split(':')
            last_col, last_row = re.match(r"([A-Z]+)([0-9]+)", end).groups()
            self._num_cols = self.col_letter_to_num(last_col)
            self._num_rows = int(last_row)

    def _get_num_cols(self):
        "Return the number of standard columns in this worksheet"
        if self._num_cols is None:
            self._set_dimensions()
        return self._num_cols

    def _set_num_cols(self, n):
        "Set the number of columns in the sheet (use with caution!)"
        self._num_cols = n

    num_cols = property(_get_num_cols, _set_num_cols)

    @property
    def num_rows(self):
        "Return the total number of rows used in this worksheet"
        if self._num_rows is None:
            self._set_dimensions()
        return self._num_rows

    @property
    def used_area(self):
        "Return the used area of this sheet"
        if self._used_area is not None:
            return self._used_area
        dimension_tag = self.tag_with_ns('dimension', self.main_ns)
        sheet_data_tag = self.tag_with_ns('sheetData', self.main_ns)
        with self.get_sheet_xml() as sheet:
            for event, elem in ET.iterparse(sheet, events=('start', 'end')):
                if event == 'start':
                    if elem.tag == dimension_tag:
                        used_area = elem.get('ref')
                        if used_area != 'A1':
                            break
                    if elem.tag == sheet_data_tag:
                        # unreliable
                        if list(elem):
                            num_cols = len(list(elem)[0])
                            used_area = f'A1:{num2col(num_cols)}{len(elem)}'
                        break
                elem.clear()
            self._used_area = used_area
        return used_area

    def head(self, num_rows=10):
        "Return first 'num_rows' from this worksheet"
        return self.rows[:num_rows+1] # 1-based

    def cat(self, tab=1):
        "Return/yield all rows from this worksheet"
        dat = self.rows[1] # 1 based!
        XLRec = namedtuple('XLRec', dat[0], rename=True) # pylint: disable=C0103
        for row in self.rows[1:]:
            yield XLRec(*row)


class Range(ExcelObj):
    """
    Excel ranges
    """

    def __init__(self, ws):
        self.worksheet = self.ws = ws
        self.start = None
        self.stop = None
        self.step = None
        self.colstart = None
        self.colstop = None
        self.colstep = None

    def __len__(self):
        return self.worksheet.num_rows

    def __iter__(self):
        with self.ws.get_sheet_xml() as xml_doc:
            row_tag = self.tag_with_ns('row', self.main_ns)
            c_tag = self.tag_with_ns('c', self.main_ns)
            v_tag = self.tag_with_ns('v', self.main_ns)
            row = []
            this_row = -1
            next_row = 1 if self.start is None else self.start
            # last_row = self.ws.num_rows + 1 if self.stop is None else self.stop
            last_row = 1_048_576 if self.stop is None else self.stop
            context = ET.iterparse(xml_doc, events=('start', 'end'))
            context = iter(context)
            event, root = next(context)
            for event, elem in context:
                if event == 'end':
                    if elem.tag == row_tag:
                        this_row = int(elem.get('r'))
                        if this_row >= last_row:
                            break
                        while next_row < this_row:
                            yield self._row([])
                            next_row += 1
                        if this_row == next_row:
                            yield self._row(row)
                            next_row += 1
                        row = []
                        this_row = -1
                        root.clear()
                    elif elem.tag == c_tag:
                        val = elem.findtext(v_tag)
                        if not val:
                            is_elem = elem.find(self.tag_with_ns('is', self.main_ns))
                            if is_elem:
                                val = is_elem.findtext(self.tag_with_ns('t', self.main_ns))
                        if val:
                            # only append cells with values
                            cell = ['', '', '', ''] # ref, type, value, style
                            cell[0] = elem.get('r') # cell ref
                            cell[1] = elem.get('t') # cell type
                            if cell[1] == 's': # string
                                cell[2] = self.ws.workbook.strings[int(val)]
                            else:
                                cell[2] = val
                            cell[3] = elem.get('s') # cell style
                            row.append(cell)

    def __getitem__(self, rng):
        if isinstance(rng, slice):
            if rng.start is not None:
                self.start = rng.start
            if rng.stop is not None:
                self.stop = rng.stop
            if rng.step is not None:
                self.step = rng.step
            matx = [_ for _ in self]
            self.start = self.stop = self.step = None
            return matx
        elif isinstance(rng, str):
            if ':' in rng:
                beg, end = rng.split(':')
            else:
                beg = end = rng
            cell_split = lambda cell: re.match(r"([A-Z]+)([0-9]+)", cell).groups()
            first_col, first_row = cell_split(beg)
            last_col, last_row = cell_split(end)
            first_col = self.col_letter_to_num(first_col) - 1 # python addressing
            first_row = int(first_row)
            last_col = self.col_letter_to_num(last_col)
            last_row = int(last_row)
            self.start = first_row
            self.stop = last_row + 1
            self.colstart = first_col
            self.colstop = last_col
            matx = [_ for _ in self]
            # reset
            self.start = self.stop = self.step = None
            self.colstart = self.colstop = self.colstep = None
            return matx
        elif isinstance(rng, int):
            self.start = rng
            self.stop = rng + 1
            matx = [_ for _ in self]
            self.start = self.stop = self.step = None
            return matx
        else:
            raise NotImplementedError("Cannot understand request")

    def __call__(self, rng):
        return self.__getitem__(rng)

    def _row(self, row):
        lst = [None] * self.ws.num_cols
        col_re = re.compile(r'[A-Z]+')
        col_pos = 0
        for cell in row:
            # apparently, 'r' attribute is optional and some MS products don't
            # spit it out. So we default to incrementing from last known col
            # (or 0 if we are at the beginning) when r is not available.
            if cell[0]:
                col = cell[0][:col_re.match(cell[0]).end()]
                col_pos = self.col_letter_to_num(col) - 1
            else:
                col_pos += 1

            if col_pos >= len(lst):
                # dimensions may not be set right in worksheet
                extend_by = col_pos - len(lst) + 1
                self.ws.num_cols += extend_by
                lst += [None for _ in range(extend_by)]

            try:
                style = self.ws.wb.styles[int(cell[3])]
            except Exception as e:
                style = ''

            # convert to python value (if necessary)
            celltype = cell[1]
            cellvalue = cell[2]
            if celltype in ('str', 's', 'inlineStr'):
                lst[col_pos] = cellvalue
            elif celltype == 'b':
                lst[col_pos] = bool(int(cellvalue))
            elif celltype == 'e':
                lst[col_pos] = ExcelErrorValue(cellvalue)
            elif celltype == 'bl':
                lst[col_pos] = None
            # Lastly, default to a number
            else:
                lst[col_pos] = float(cellvalue)
        colstart = 0 if self.colstart is None else self.colstart
        colstop = self.ws.num_cols if self.colstop is None else self.colstop
        return lst[colstart:colstop]


class Workbook(ExcelObj):
    """
    Excel workbook
    """

    def __init__(self, file_obj, workbook_path=None, encoding='utf8'):
        self.xls = ZipFile(file_obj)
        self.encoding = encoding
        self._strings = None
        self._sheets = None
        self._styles = None
        self.date_system = self.get_date_system()
        if workbook_path:
            self.name = os.path.basename(workbook_path)
            self.path = workbook_path
        else:
            self.name = self.workbook_path = ''

    def get_date_system(self):
        "Determine the date system used by the current workbook"
        with self.xls.open('xl/workbook.xml') as xml_doc:
            tree = ET.parse(io.TextIOWrapper(xml_doc, self.encoding))
            tag = self.tag_with_ns('workbookPr', self.main_ns)
            tag_element = tree.find(tag)
            if tag_element and tag_element.get('date1904') == '1':
                return 1904
            return 1900

    @property
    def sheets(self):
        "Return list of all sheets in workbook"
        if self._sheets is not None:
            return self._sheets
        tag = self.tag_with_ns('sheet', self.main_ns)
        ref_tag = self.tag_with_ns('id', self.rel_ns)
        sheet_map = {}
        locs = {} # locations from relationship id to target location
        with self.xls.open('xl/_rels/workbook.xml.rels') as xml_doc:
            tree = ET.parse(io.TextIOWrapper(xml_doc, self.encoding))
            for rshp in tree.iter(self.tag_with_ns('Relationship', 'http://schemas.openxmlformats.org/package/2006/relationships')):
                id = rshp.get('Id')
                target = rshp.get('Target')
                locs[id] = target
        with self.xls.open('xl/workbook.xml') as xml_doc:
            tree = ET.parse(io.TextIOWrapper(xml_doc, self.encoding))
            for sheet in tree.iter(tag):
                name = sheet.get('name')
                ref = sheet.get(ref_tag)
                num = int(sheet.get('sheetId'))
                sheet = Worksheet(self, name, num, 'xl/' + locs[ref] if not locs[ref].startswith('/') else locs[ref][1:])
                sheet_map[name] = sheet
                sheet_map[num] = sheet
        self._sheets = sheet_map
        return self._sheets

    @property
    def strings(self):
        "Return list of shared strings within this workbook"
        if self._strings is not None:
            return self._strings
        # Cannot use t element (which we were doing before). See
        # http://bit.ly/2J7xAPu for more info on shared strings.
        tag = self.tag_with_ns('si', self.main_ns)
        strings = []
        with self.xls.open('xl/sharedStrings.xml') as xml_doc:
            tree = ET.parse(io.TextIOWrapper(xml_doc, self.encoding))
            for elem in tree.iter(tag):
                strings.append(''.join(_ for _ in elem.itertext()))
        self._strings = strings
        return strings

    @property
    def styles(self):
        "Return list of styles used within this workbook"
        if self._styles is not None:
            return self._styles
        styles = []
        style_tag = self.tag_with_ns('xf', self.main_ns)
        numfmt_tag = self.tag_with_ns('numFmt', self.main_ns)
        with self.xls.open('xl/styles.xml') as xml_doc:
            tree = ET.parse(io.TextIOWrapper(xml_doc, self.encoding))
            number_fmts_table = tree.find(self.tag_with_ns('numFmts', self.main_ns))
            number_fmts = {}
            if number_fmts_table:
                for num_fmt in number_fmts_table.iter(numfmt_tag):
                    number_fmts[num_fmt.get('numFmtId')] = num_fmt.get('formatCode')
            number_fmts.update(STANDARD_STYLES)
            style_table = tree.find(self.tag_with_ns('cellXfs', self.main_ns))
            if style_table:
                for style in style_table.iter(style_tag):
                    fmtid = style.get('numFmtId')
                    if fmtid in number_fmts:
                        styles.append(number_fmts[fmtid])
        self._styles = styles
        return styles


    def num_to_date(self, number):
        """
        Return date of "number" based on the date system used in this workbook.

        The date system is either the 1904 system or the 1900 system depending
        on which date system the spreadsheet is using. See
        http://bit.ly/2He5HoD for more information on date systems in Excel.
        """
        if self.date_system == 1900:
            # Under the 1900 base system, 1 represents 1/1/1900 (so we start
            # with a base date of 12/31/1899).
            base = datetime.datetime(1899, 12, 31)
            # BUT (!), Excel considers 1900 a leap-year which it is not. As
            # such, it will happily represent 2/29/1900 with the number 60, but
            # we cannot convert that value to a date so we throw an error.
            if number == 60:
                raise ValueError("Bad date in Excel file - 2/29/1900 not valid")
            # Otherwise, if the value is greater than 60 we need to adjust the
            # base date to 12/30/1899 to account for this leap year bug.
            elif number > 60:
                base = base - datetime.timedelta(days=1)
        else:
            # Under the 1904 system, 1 represent 1/2/1904 so we start with a
            # base date of 1/1/1904.
            base = datetime.datetime(1904, 1, 1)
        days = int(number)
        partial_days = number - days
        seconds = int(round(partial_days * 86400000.0))
        seconds, milliseconds = divmod(seconds, 1000)
        if days < -693594:
            return days
        date = base + datetime.timedelta(days, seconds, 0, milliseconds)
        if days == 0:
            return date.time()
        return date


# Some helper functions
def num2col(num):
    """Convert given column letter to an Excel column number."""
    result = []
    while num:
        num, rem = divmod(num-1, 26)
        result[:0] = string.ascii_uppercase[rem]
    return ''.join(result)

def col2num(ltr):
    num = 0
    for c in ltr:
        if c in string.ascii_letters:
            num = num * 26 + (ord(c.upper()) - ord('A')) + 1
    return num
Download .txt
gitextract_2lhivfiy/

├── .gitignore
├── LICENSE
├── README.md
├── nested_parser.py
├── proton.py
├── raw/
│   ├── en/
│   │   ├── sample.xlsx
│   │   └── sample2.xlsx
│   └── zh/
│       ├── sample.xlsx
│       └── sample2.xlsx
└── sample/
    ├── README.md
    ├── __export.bat
    ├── __export.py
    ├── complex_nested_obj.xlsx
    ├── hero.xlsx
    ├── mount.xlsx
    ├── text.xlsx
    └── tools/
        ├── CSharpGeneratorForProton/
        │   └── README.md
        └── py37/
            ├── README.md
            └── sxl/
                ├── __init__.py
                └── sxl.py
Download .txt
SYMBOL INDEX (91 symbols across 4 files)

FILE: nested_parser.py
  function split_top_level (line 13) | def split_top_level(text, delimiter, skip_empty = False):
  function unwrap_container (line 64) | def unwrap_container(text, begin, end):
  function split_list_values (line 71) | def split_list_values(value):
  function split_obj_type_fields (line 75) | def split_obj_type_fields(type_, separator):
  function split_obj_values (line 79) | def split_obj_values(value, separator):
  function split_field_declaration (line 83) | def split_field_declaration(text):

FILE: proton.py
  function fillvalue (line 37) | def fillvalue(parent, name, value, isschema):
  function getindex (line 45) | def getindex(infos, name):
  function getcellvalue (line 48) | def getcellvalue(value):
  function getscemainfo (line 51) | def getscemainfo(typename, description):
  function getexportmark (line 56) | def getexportmark(sheetName):
  function issignmatch (line 60) | def issignmatch(signarg, sign):
  function isoutofdate (line 65) | def isoutofdate(srcfile, tarfile):
  function gerexportfilename (line 68) | def gerexportfilename(root, format_, folder):
  function splitspace (line 72) | def splitspace(s):
  function buildbasexml (line 75) | def buildbasexml(parent, name, value, noplural = False):
  function buildlistxml (line 85) | def buildlistxml(parent, name, list_, noplural = False):
  function buildobjxml (line 92) | def buildobjxml(parent, name, obj, noplural = False):
  function buildxml (line 99) | def buildxml(parent, name, value, noplural = False):
  function savexml (line 109) | def savexml(record, noplural = False):
  function newline (line 121) | def newline(count):
  function tolua (line 124) | def tolua(obj, indent = 1):
  function toycl (line 147) | def toycl(obj, indent = 0):
  class BindType (line 168) | class BindType:
    method __init__ (line 169) | def __init__(self, type_):
    method __eq__ (line 172) | def __eq__(self, other):
  class Record (line 175) | class Record:
    method __init__ (line 176) | def __init__(self, path, sheet, exportfile, root, item, obj, exportmark):
    method setobj (line 185) | def setobj(self, obj):
  class Constraint (line 189) | class Constraint:
    method __init__ (line 190) | def __init__(self, mark, filed):
  class Exporter (line 194) | class Exporter:
    method __init__ (line 198) | def __init__(self, context):
    method checkstringescape (line 202) | def checkstringescape(self, t, v):
    method stringescape (line 205) | def stringescape(self, s):
    method pluralname (line 208) | def pluralname(self, name):
    method gettype (line 211) | def gettype(self, type_):
    method buildlistexpress (line 228) | def buildlistexpress(self, parent, type_, name, value, isschema):
    method buildobjexpress (line 241) | def buildobjexpress(self, parent, type_, name, value, isschema):
    method buildbasexpress (line 259) | def buildbasexpress(self, parent, type_, name, value, isschema, inobj):
    method buildexpress (line 296) | def buildexpress(self, parent, type_, name, value, isschema = False, i...
    method getrootname (line 305) | def getrootname(self, exportmark, isitem):
    method export (line 309) | def export(self, path):
    method getconfigsheetfinfo (line 371) | def getconfigsheetfinfo(self, sheet):
    method exportitemsheet (line 385) | def exportitemsheet(self, sheet):
    method exportconfigsheet (line 463) | def exportconfigsheet(self, sheet, titleindexs):
    method saves (line 511) | def saves(self):
    method save (line 522) | def save(self, record):
    method checksheetname (line 555) | def checksheetname(self, path, sheetname, root):
  function export (line 560) | def export(context, path):
  function exportpack (line 566) | def exportpack(args):
  function exportfiles (line 569) | def exportfiles(context):
  class Context (line 622) | class Context:

FILE: sample/__export.py
  class ExportError (line 29) | class ExportError(Exception):
  function export (line 32) | def export(filelist, format, sign, outfolder, suffix, schema):
  function codegenerator (line 43) | def codegenerator(schema, outfolder, namespace, suffix):
  function exportserver (line 53) | def exportserver():
  function exportclient (line 57) | def exportclient():
  function main (line 60) | def main():

FILE: sample/tools/py37/sxl/sxl.py
  class ExcelObj (line 53) | class ExcelObj(ABC):
    method tag_with_ns (line 61) | def tag_with_ns(tag, ns):
    method col_num_to_letter (line 66) | def col_num_to_letter(n):
    method col_letter_to_num (line 75) | def col_letter_to_num(letter):
  class Worksheet (line 84) | class Worksheet(ExcelObj):
    method __init__ (line 89) | def __init__(self, workbook, name, number, location=''):
    method get_sheet_xml (line 100) | def get_sheet_xml(self):
    method range (line 106) | def range(self):
    method rows (line 111) | def rows(self):
    method _set_dimensions (line 115) | def _set_dimensions(self):
    method _get_num_cols (line 126) | def _get_num_cols(self):
    method _set_num_cols (line 132) | def _set_num_cols(self, n):
    method num_rows (line 139) | def num_rows(self):
    method used_area (line 146) | def used_area(self):
    method head (line 169) | def head(self, num_rows=10):
    method cat (line 173) | def cat(self, tab=1):
  class Range (line 181) | class Range(ExcelObj):
    method __init__ (line 186) | def __init__(self, ws):
    method __len__ (line 195) | def __len__(self):
    method __iter__ (line 198) | def __iter__(self):
    method __getitem__ (line 244) | def __getitem__(self, rng):
    method __call__ (line 285) | def __call__(self, rng):
    method _row (line 288) | def _row(self, row):
  class Workbook (line 332) | class Workbook(ExcelObj):
    method __init__ (line 337) | def __init__(self, file_obj, workbook_path=None, encoding='utf8'):
    method get_date_system (line 350) | def get_date_system(self):
    method sheets (line 361) | def sheets(self):
    method strings (line 388) | def strings(self):
    method styles (line 404) | def styles(self):
    method num_to_date (line 429) | def num_to_date(self, number):
  function num2col (line 467) | def num2col(num):
  function col2num (line 475) | def col2num(ltr):
Condensed preview — 20 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (62K chars).
[
  {
    "path": ".gitignore",
    "chars": 5,
    "preview": "*.pyc"
  },
  {
    "path": "LICENSE",
    "chars": 574,
    "preview": "Copyright 2016 YANG Huan (sy.yanghuan@gmail.com)\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou ma"
  },
  {
    "path": "README.md",
    "chars": 4778,
    "preview": "[English](https://github.com/yanghuan/proton#proton)   [Chinese](https://github.com/yanghuan/proton#proton-1)  \n# proton"
  },
  {
    "path": "nested_parser.py",
    "chars": 2827,
    "preview": "#encoding=utf-8\nimport string\n\n_OPEN_TO_CLOSE = {\n  '{': '}',\n  '[': ']',\n  '(': ')',\n}\n\n_CLOSE_SET = set(_OPEN_TO_CLOSE"
  },
  {
    "path": "proton.py",
    "chars": 22449,
    "preview": "#encoding=utf-8\n'''\nCopyright YANG Huan (sy.yanghuan@gmail.com)\n\nLicensed under the Apache License, Version 2.0 (the \"Li"
  },
  {
    "path": "sample/README.md",
    "chars": 234,
    "preview": "This is a good configuration can be used directly in the Windows instance. Already contains a python3 environment, direc"
  },
  {
    "path": "sample/__export.bat",
    "chars": 31,
    "preview": "tools\\py37\\py37.exe __export.py"
  },
  {
    "path": "sample/__export.py",
    "chars": 2284,
    "preview": "#encoding=utf-8\n\n# Need to export the public configuration file (client, server needs) 需要导出的公共配置文件(客户端,服务器都需要)\nEXPORT_F"
  },
  {
    "path": "sample/tools/CSharpGeneratorForProton/README.md",
    "chars": 4478,
    "preview": "[English](https://github.com/sy-yanghuan/CSharpGeneratorForProton#csharpgeneratorforproton)   [Chinese](https://github.c"
  },
  {
    "path": "sample/tools/py37/README.md",
    "chars": 2713,
    "preview": "# pyexe.exe\nhttps://github.com/manthey/pyexe\n\n[![Build status](https://ci.appveyor.com/api/projects/status/n18f0997k18x8"
  },
  {
    "path": "sample/tools/py37/sxl/__init__.py",
    "chars": 71,
    "preview": "\nfrom .sxl import Workbook, col2num, num2col\n\n__version__ = '0.0.1a10'\n"
  },
  {
    "path": "sample/tools/py37/sxl/sxl.py",
    "chars": 17615,
    "preview": "\"\"\"\nxl.py - python library to deal with *big* Excel files.\n\"\"\"\n\nfrom abc import ABC\nfrom collections import namedtuple, "
  }
]

// ... and 8 more files (download for full content)

About this extraction

This page contains the full source code of the yanghuan/proton GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 20 files (56.7 KB), approximately 15.1k tokens, and a symbol index with 91 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!