#!/usr/bin/python
# -*- coding: UTF-8
'''
注意：
修改该文件，print打印信息中不能包含中文，否则在不支持中文的操作系统中会出现如下的错误，
导致编译失败或者达不到预期的显示效果。
'ascii' codec can't encode characters in position

2023年12月04日 修改历史
------------------------------------

主要修改以下bug：
1. 当表格通过class属性设置为longtable，但是表头用“-”分割而不是用“=”分割的时候，在sphinx5.3.0以下版本编译失败的问题。
   如果没有替换最新的parsejson.py文件，该bug在sphinx 5.3.0以下版本，可以采用以下方式规避：
   当表格设置为longtable时，表头改用“=”分割，而不是“-”分割，可以规避该问题。
   如果替换了最新的parsejson.py文件，则不存在该问题，无需刻意规避。
2. 修复了当表头为两行，并且合并列单元格放在表格的最前几列的时候，表头中的横线制表符没有画的bug。

优化了以下实现方式：
1. 将sphinx 5.3.0之后版本，为了适配表格改动所做的修改，固定在该文件的ModifyReplacePackage函数中。
   这样只需替换该文件，而无需再修改conf.json文件，即可兼容sphinx5.3.0之前及之后版本，做到全版本兼容。
2. 当有合并单元格的表头时，如果用“widths”参数设置的列宽，则表头中的标题不会自动换行，必须手动换行。
   如果用“tabularcolumns”指令设置的列宽，则默认会自动换行。之前的行为是默认都会自动换行。
   因此当前的实现要想使表头能够自动换行，必须采用tabularcolumns指令设置每一列的列宽。如果用“widths”设置列宽，只能手动换行。
   修改原因：
   因为如果要实现自动换行，每一列的列宽必须以“m{0.1\textwidth}”这种方式实现，之前是把“widths”设置的列宽进行了转换。
   这种转换对于列比较少的表格没什么问题。但是当表格比较大，列比较多时，这种转换就导致表格的显示超出了页面可显示的宽度，
   而且也会导致如何修改“widths”的值，都没什么效果。因此修改了自动换行的规则。
   结论：
   对于列比较多而且表头有合并单元格的表格，建议用“tabularcolumns”设置每一列的列宽，因为该指令相当于latex的原始指令，设置的列宽直接反应每一列的实际宽度。
   对于表头没有合并单元格的表格，建议用“widths”设置列宽比较简单，让sphinx转化实际的列宽，同时对latex和html都生效。

2023年11月15日 修改历史
----------------------------
将路径字符串相加的赋值方式，修改为os.path.join的方式，解决编译文档提示如下告警的问题：
RemovedInSphinx80Warning: Sphinx 8 will drop support for representing paths as strings. Use "pathlib.Path" or "os.fspath" instead.
sphinx 8之后将不再支持字符串表示路径。
使用os.path.join的方式一定要特别注意以下几点：
1.join函数会自动添加“/”符号，因此一定不要再添加多余的“/”，否则可能得到的路径是错误的。
2.join可以链接超过两个的路径，但是最后一个要链接的路径一定不能加“/”符号，否则前面的路径都失效。举例如下：
  path1 = "home"
  path2 = "rst"
  path3 = "/meerkat"
  path = os.path.join(path1,path2,path3)
  则得到的path为“/meerkat”，因此最后一个被链接的路径一定不能加“/”。

'''

from __future__ import print_function

import os
import sys
import json
import re
import codecs
import shutil
import platform
import traceback
import datetime as dt
from subprocess import Popen, PIPE, STDOUT
#import importlib

try:
    import sphinx
    sphinx_version = sphinx.__display_version__[0:3]
except Exception as e:
    sphinx_version = ""

Py_version = sys.version_info
Py_v_info = str(Py_version.major) + '.' + str(Py_version.minor) + '.' + str(Py_version.micro)

#if Py_version >= (3,5,0) and Py_version <(3,7,0):
#    print(False)
#
#if Py_version >= (3,7,0):
#    print(True)
#
#if platform.system().lower() == 'windows':
#    print("windows")
#elif platform.system().lower() == 'linux':
#    print("linux")

filepathlist=os.path.split(os.path.realpath(__file__))
currfilepath=filepathlist[0]
latexfontsize={'tiny','scriptsize','footnotesize','small','normalsize','large','Large','LARGE','huge','Huge'}

def __dictIsHasKey__(dict,key):
    if Py_version >= (3,0,0):
        return dict.__contains__(key)
    else:
        return dict.has_key(key)

def __openconfjsonfile__(filepath = ''):
    jsonfile = filepath
    if filepath == '':
        jsonfile = currfilepath+'/conf.json'

    with codecs.open(jsonfile,"r+",encoding = 'utf-8') as load_f:
        load_dict = json.load(load_f)
        return load_dict

def getignorewarning():
    return __load_dict__["ignorewarnkey"]

def GetOtherIncludePackage():
#    packagearr = __load_dict__['package']
#    for package in packagearr:
#        print(package)
    return __load_dict__['package']

def GetReplacePackage():
    return __load_dict__['replacepackage']

def GetCustomOptions():
    return __load_dict__['customoptions']

def GetIsTocContents():
    return __load_dict__['isfiguretabletoc']

def GetSensitiveword():
    #得到敏感词数组，便于搜索文档中是否有敏感词存在
    return __load_dict__['sensitivewords']

def GetTablesContent():
    return __load_dict__['tables']

def GetTablerowtype():
#    packagearr = __load_dict__['tables']['rowtype']
#    print(packagearr)
    return __load_dict__['tables']['rowtype']

def GetTableheadtype():
#    packagearr = __load_dict__['tables']['headtype']
#    print(packagearr)
    return __load_dict__['tables']['headtype']

def GetTableHeadFontColor():
    return __load_dict__['tables']['headfontcolor']

def GetTableStylesArr():
#    packagearr = __load_dict__['tables']['styles']
#    for package in packagearr:
#        print(package)
    return __load_dict__['tables']['styles']

def GetImageStyleArr():
#    packagearr = __load_dict__['image']['styles']
#    for package in packagearr:
#        print(package)
    return __load_dict__['image']['styles']

#判断是否有忽略告警的类
class clsIgnoreWarn:

    def __init__(self,warnfile):
        self.warnfile = warnfile  #保存告警文件

    #判断该关键字是否在告警中，在告警中则返回true，不在告警中则返回fasle
    def __JudgeWarnKey(self,keystr,warnstr):

        #先判断是否为组合关键字
        if "&&" in keystr:
            #解析组合关键字为字符串列表
            keyls = keystr.split("&&")
            isignore = True  #默认该告警为忽略，如果组合关键字其中一个关键字没匹配上，则该告警不能忽略。
            for i in range(0,len(keyls)):
                if keyls[i].strip().lower() not in warnstr.lower():
                    isignore =False
                    break
            return isignore
        else:
            if keystr.lower() in warnstr.lower():
                return True  #忽略告警在告警字符串里面，则返回true，表示该告警可以忽略
            else:
                return False

    #解析告警文件
    def parsewarnfile(self,ignorekey):
        '''
        #make latex命令产生的告警都会保存在stderr文件中，只要判断该文件中的告警是否在忽略告警里就可以了，不在忽略告警里，则肯定有错误，停止执行
        '''
        if not os.path.exists(self.warnfile):
            return True

        fs = codecs.open(self.warnfile,"r",encoding = 'utf-8')
        fstr = fs.read()   #先将所有内容读取到字符串中，方便正则表达式查找
        fs.close()

        #查找带warning的内容
        pattern = re.compile(r"([\s\S].*)[WARNING:|ERROR:]([\s\S].*)", re.I | re.U | re.M)
        mobjarr = pattern.finditer(fstr)

        errstr = '' #保存不能被忽略的告警
        isNotError = True
        for mobj in mobjarr:
            amarr = mobj.group()

            if amarr == '':
                    continue

            amarr = amarr.strip()
            amarr = amarr.strip('\r')
            amarr = amarr.strip('\n')
            amarr = amarr.strip()  #再去掉首尾空格，避免多余的空格出现

            #判断该告警是否在忽略列表里，不在忽略列表里，保存在errstr字符串中

            if ("WARNING:" not in amarr) and ("ERROR:" not in amarr): #直接忽略
                continue

            isWrite = False  #默认不能忽略
            for igkey in ignorekey:
               isWrite = self.__JudgeWarnKey(igkey,amarr)
               if isWrite:
                    break

            if isWrite is False:
                 #写入stderr文件
                 isNotError = False
                 #fe.writelines(amarr+'\n')
                 errstr += amarr

        if errstr != '':
            #如果有不能被忽略的告警，则将不能忽略的告警重新写入warnfile文件中
            #先删除源文件，避免原文件无法写入
            fw = codecs.open(self.warnfile, "w",encoding = 'utf-8')
            fw.write(errstr)
            fw.close()
        else:
            #如果所有的告警都能忽略则删除之前的告警文件
            #该功能暂时不实现，还是保存原始的告警文件，方便查找。
            #os.remove(self.warnfile)
            pass

        return isNotError

    #该函数暂时未用
    def __parsepdfarg(stdout,stderr,ignorelist,ignorekey):
        '''
        make all-pdf命令产生的所有输出都只会保存在stdout中，因此从stdout中判断是否有告警内容
        '''
        stdoutfilepath = currfilepath+'/'+stdout
        stderrfilepath = currfilepath+'/'+stderr

        if not os.path.exists(stdoutfilepath):
            return True

        fs = codecs.open(stdoutfilepath, "r+",encoding = 'utf-8')
        fstr = fs.read()   #先将所有内容读取到字符串中，方便正则表达式查找
        fs.close
        #查找latexmk的位置，latexmk位置的开始即make all-pdf打印输出的开始
        searchstr = r"latexmk"
        m = re.search(searchstr, fstr, re.I|re.U )
        if m == None:
            return True

        spos = m.span()  #获取位置
        latexcont = fstr[spos[0]:len(fstr)]  #获取到make all-pdf产生的内容

        #查找带warning的内容
        pattern = re.compile(r'([\s\S].*)Warning([\s\S].*)',  re.I|re.U)
        mobjarr = pattern.finditer(latexcont)

        #打开stderr文件，方便后续写
        fe = codecs.open(stderrfilepath, "a+",encoding = 'utf-8')
        isNotError = True
        for mobj in mobjarr:
            amarr = mobj.group()
            if amarr == '':
                    continue
            amarr = amarr.strip()
            amarr = amarr.strip('\r')
            amarr = amarr.strip('\n')
            amarr = amarr.strip()  #再去掉首尾空格，避免多余的空格出现

            #判断该告警是否在忽略列表里，不在忽略列表里，要写入stderr文件
            if amarr.lower() not in [elem.lower() for elem in ignorelist]: #如果错误不在忽略告警里面
                #如果不能整行匹配，再判断该行是否包含需忽略的告警的关键字
                isWrite = False  #默认不能忽略
                for igkey in ignorekey:

                    isWrite = self.__JudgeWarnKey(igkey,amarr)
                    if isWrite:
                        break;

                if isWrite == False:
                    #写入stderr文件
                    isNotError = False
                    fe.writelines(amarr)
        fe.close

        return isNotError

def warn_main(warnfile,app=None):

    global __load_dict__

    if len(__load_dict__) ==0:
        GetConfjsondict(app)

    ignorekey = getignorewarning()
    clswarn = clsIgnoreWarn(warnfile)

    if not clswarn.parsewarnfile(ignorekey):
        #如果存在不可忽略的告警，则返回True，否则返回False
        return True

    return False


class clsTableattr:

    def __init__(self, tables):
        self.rowtype = tables['rowtype']
        if __dictIsHasKey__(tables,'headtype'):
            self.headtype = tables['headtype']
        else:
            self.headtype = ""
        if __dictIsHasKey__(tables,'headfontcolor'):
            self.headfontcolor = tables['headfontcolor']
        else:
            self.headfontcolor = ""
        if __dictIsHasKey__(tables,'styles'):
            self.tablestyles = tables['styles']
        else:
            self.tablestyles = None
        if __dictIsHasKey__(tables,'isname'):
            self.isname = tables['isname']
        else:
            self.isname = False
        if __dictIsHasKey__(tables,'fontsize'):
            self.fontsize = tables['fontsize']
        else:
            self.fontsize = ""

        if __dictIsHasKey__(tables,'ismulticolleftalign'):
            self.ismulticolleftalign = tables['ismulticolleftalign']
        else:
            self.ismulticolleftalign = False

        if __dictIsHasKey__(tables,'ismultirowautowrap'):
            self.ismultirowautowrap = tables['ismultirowautowrap']
        else:
            self.ismultirowautowrap = True

        if __dictIsHasKey__(tables,'ismultirowatupcell'):
            self.ismultirowatupcell = tables['ismultirowatupcell']
        else:
            self.ismultirowatupcell = False

        if __dictIsHasKey__(tables,'ismultirowatlowcell'):
            self.ismultirowatlowcell = tables['ismultirowatlowcell']
        else:
            self.ismultirowatlowcell = False

class clsModifyTex:

    def __init__(self, content,app=None):
        self.content = content
        self.app = app
        self.tablesattrobj = clsTableattr(GetTablesContent())

        #构建模板字符串
        #自动换行模板
        self.autolinetemplat =r'''\renewcommand{\arraystretch}{0.8}
        \begin{tabular}[c]{@{}l@{}}%(autocontent)s\end{tabular}'''

        fontcolor = self.tablesattrobj.headfontcolor
        fontcolor = fontcolor.replace('{}', '{%(content)s}', 1)
        self.commonstr = r'''%(sphinxstyletheadfamily)s\cellcolor''' + \
                         self.tablesattrobj.headtype + "\n{" + fontcolor + "}"

        # multirow模板
        self.multirowstr = r'''\multirow{-%(count)s}{%(coluwidth)s}%(raggedright)s{
        ''' + self.commonstr + "}"

        self.simplemultirow = r'''\multirow{%(count)s}{%(coluwidth)s}{\cellcolor''' + \
                              self.tablesattrobj.headtype + "}"

        # multicolumn模板
        self.multicolumnstr = r'''\multicolumn{%(count)s}{%(flag)s}{%(prefix)s''' + \
                              self.commonstr + r'''%(suffix)s'''

        # 为了普通标题文本看起来和使用了multicolum你的位置一致，对于非multirow和multicolumn单元格的内容，都用multicolumn{1}{|l|}标志
        self.normalstr = r'''\multicolumn{1}{%(flag)s}{
                \begin{varwidth}[c]{%(colwidth)s}
                 ''' + self.commonstr + r'''
\par
\vskip-\baselineskip\vbox{\hbox{\strut}}\end{varwidth}}'''
        self.comcolstr = r'''\multicolumn{%(count)s}{%(flag)s}{''' + self.commonstr +"}"
        # 空的单元格内容,multirow位置变换后，会有空的单元格
        self.emptystr = r"\cellcolor" + self.tablesattrobj.headtype+"{}"

        #删除字符串列表。有时候sphinx生成的文本内容里会带有特殊的latex指令，该指令不支持设置字体颜色，需要删除。删除不影响内容显示。
        self.delstrlst=[r'\begin{quote}',r'\end{quote}']

    def __checktpacknameandparam(self,packlist,packname,*packarg):
        '''
        检查指定的包是否需要增加或者修改参数
        :param packlist: 已经读出的包含的package
        :param packname: 要修改或者添加的包
        :param packarg: 要修改或者添加的包参数，可变参数。不会覆盖已有参数
        '''
        if len(packarg) == 0:
            #如果没有参数则直接添加该包
            loadpackagestr = "\\\\usepackage{" + packname + "}"
            if loadpackagestr not in packlist:
                packlist.append(loadpackagestr)
            return

        params = ",".join(packarg)
        loadpackagestr = "\\\\usepackage[" + params + "]{" + packname + "}"
        #print(loadpackagestr)
        packstr = ",".join(packlist)
        #print(packstr)
        searchstr = r'\\\\usepackage(\[[\S, ][^\\]+\])?\{'+packname+'\}'
        match = re.search(searchstr,packstr)
        if match is None:
            #如果根本没有加载packname包，则加载该包
            packlist.append(loadpackagestr)
            return
        latexpack = match.group()
        #print(latexpack)
        if match.group(1) is None:
            #如果该包没有携带参数，则重新加载该包
            #得到老包的位置，尽量原位置插入，避免因为位置问题导致编译失败
            index = packlist.index(latexpack)
            del packlist[index]
            #再重新加载新的包
            packlist.insert(index,loadpackagestr)
            return
        #如果携带了参数，则更新该参数
        #为了兼容后续新添加参数，并防止覆盖已有参数，将每个参数进行比较添加
        paramstr = match.group(1)
        searchstr = '\[([\S, ]+)?\]'
        match = re.search(searchstr,paramstr)
        if match.group(1).strip() == params:
            #print('-------------enter-----------')
            #为了提高运行效率，两个参数相等直接返回，不再走下面的循环
            return
        params = match.group(1)
        for param in packarg:
            if param not in params:
                params += "," + param
        #print(params)
        loadpackagestr = "\\\\usepackage[" + params+"]{" + packname +"}"
        #先删除旧包，原位置插入，避免因位置错误导致的编译失败。
        index = packlist.index(latexpack)
        del packlist[index]
        packlist.insert(index,loadpackagestr)

    #加入其它包
    def AddPackageToTex(self):
        #得到需要包的数组
        packarr = GetOtherIncludePackage()
        if len(packarr)==0:
            return  False

        self.__checktpacknameandparam(packarr,"multirow")
        self.__checktpacknameandparam(packarr,"tcolorbox","most","skins","breakable")

        #如果数组有内容，就需要将包添加到latex文件的导言区
        #搜索\usepackage{sphinx}，将包加在它的前面，用正则表达式搜索它的位置
        #采用正则表达式替换的方式，替换搜索到的字符串，因此需要先构建字符串
        #python认为\u后面为unicode字符，因此需要多加一个转义字符\，python才认为是整个的字符串
        #searchstr = r'\\usepackage\[dontkeepoldnames\]{sphinx}'
        searchstr = r'\\usepackage(\[\S*\]*)?{sphinx}'
        matchstr = re.search(searchstr,self.content)

        replacestr=""
        for package in packarr:
            replacestr  += package+'\n'

        if Py_version >= (3,7,0):
            replacestr += "\\" + matchstr.group(0)
        else:
            replacestr += matchstr.group(0)


        self.content = re.sub(searchstr, replacestr, self.content, 1, re.M | re.I|re.U)

        return True

    #加入自定义选项,包放在了sphinx包的前面，因此选项放在sphinx包的后面
    def AddCustormOptionsToTex(self):
        #得到需要包的数组
        packarr = GetCustomOptions()
        if len(packarr)==0:
            return  False

        #如果数组有内容，就需要将包添加到latex文件的导言区
        #搜索\usepackage{sphinx}，将自定义参数放在它的后面，用正则表达式搜索它的位置
        #采用正则表达式替换的方式，替换搜索到的字符串，因此需要先构建字符串
        #python认为\u后面为unicode字符，因此需要多加一个转义字符\，python才认为是整个的字符串
        searchstr = r'\\usepackage(\[\S*\]*)?{sphinx}'
        matchstr = re.search(searchstr,self.content)

        replacestr=""
        for package in packarr:
             replacestr  += package+'\n'

        if Py_version >= (3,7,0):
            replacestr = "\\" + matchstr.group(0)+'\n'+replacestr
        else:
            replacestr = matchstr.group(0)+'\n'+replacestr

        self.content = re.sub(searchstr, replacestr, self.content, 1, re.M | re.I|re.U)
        return True

    #增加figure和table toc到tex
    def AddOtherTocToTex(self):
        #得到需要包的数组
        packarr = GetIsTocContents()
        if len(packarr)==0:
            return  False

        replacestr = ""
        if packarr['isfigurestoc']:
           figlst = packarr['figurestoc']
           for figstr in figlst:
               replacestr += figstr + '\n'

        if packarr['istablestoc']:
           figlst = packarr['tablestoc']
           for figstr in figlst:
               replacestr += figstr + '\n'
        if replacestr == "":
           return

        #如果数组有内容，就需要将包添加到latex文件的导言区
        #搜索\usepackage{sphinx}，将包加在它的前面，用正则表达式搜索它的位置
        #采用正则表达式替换的方式，替换搜索到的字符串，因此需要先构建字符串
        #python认为\u后面为unicode字符，因此需要多加一个转义字符\，python才认为是整个的字符串
        searchstr = r'\\sphinxtableofcontents'
        matchstr = re.search(searchstr,self.content)

        if Py_version >= (3,7,0):
            replacestr = "\\" + matchstr.group(0) + '\n' + replacestr
        else:
            replacestr = matchstr.group(0) + '\n' + replacestr

        self.content = re.sub(searchstr, replacestr, self.content, 1, re.M | re.I|re.U)
        return True

    def __IsRequireReplace(self, value):
        """
        根据sphinx版本判断是否需要替换,有两种写法：
        1. {sphinx:5.3.0}:这种写法是当前sphinx版本大于等于5.3.0版本时，才执行替换。
        2. {5.3.0:sphinx}:这种写法是当前sphinx版本小于5.3.0时，才进行替换
        :return: 返回True需要替换，和需要替换的实际value;或者返回fasle，不需要替换
        """
        #根据正则表达式得到需要替换的sphinx的最小版本号
        if len(sphinx_version)==0:
            return True,value
        #按大于等于版本号进行匹配查找
        searchstr = r"\{sphinx:([0-9\.]+)\}([\s\S]+)"
        match = re.match(searchstr,value,re.I | re.U)
        if match is not None:
            #print(match.group(1),match.group(2))
            try:
                require_version = match.group(1)[:3]
                if sphinx_version >= require_version:
                    return True, match.group(2)
                else:
                    return False, ""
            except Exception as e:
                print(e)
                return True,value

        else:
            #按小于版本号进行匹配查找
            searchstr = r"\{([0-9\.]+):sphinx\}([\s\S]+)"
            match = re.match(searchstr, value, re.I | re.U)
            if match is not None:
                try:
                    require_version = match.group(1)[:3]
                    if sphinx_version < require_version:
                        return True, match.group(2)
                    else:
                        return False, ""
                except Exception as e:
                    print(e)
                    return True, value
            else:
                return True,value


    def ModifyReplacePackage(self):
        #得到字典值
        #得到需要替换的包，用正则表达式替换
        redict = GetReplacePackage()
        if len(redict) ==0:
           return

        #为了兼容sphinx5.3.0版本，固定添加以下配置
        keys = redict.keys()
        if "\\\\sphinxtoprule" not in keys:
            redict["\\\\sphinxtoprule"] = "\\\\hline"
        if "\\\\sphinxmidrule" not in keys:
            redict["\\\\sphinxmidrule"] = "\\\\hline"
        if "\\\\sphinxbottomrule" not in keys:
            redict["\\\\sphinxbottomrule"] = "\\\\hline"
        if "\\\\sphinxhline" not in keys:
            redict["\\\\sphinxhline"] = "\\\\hline"
        if "\\\\sphinxhyphen" not in keys:
            redict["\\\\sphinxhyphen"] = "{sphinx:5.3.0}\\\\PYGZhy"

        #返回字典中所有键值的列表
        keylst = list(redict)
        for key in keylst:
            if key == 'comment' :
                continue
            keyvalue = redict[key]   #得到键对应的值
            result,keyvalue = self.__IsRequireReplace(keyvalue)
            if result :
                #对键值进行替换
                self.content = re.sub(key, keyvalue, self.content, 0, re.I|re.U)
        return

    def __GetReplacedContent(self,afterstr,replacelst):
        """
        对需要替换的内容进行替换
        :param afterstr: 需要替换的字符串
        :return:
        """
        if len(afterstr) ==0 or len(replacelst)==0:
            return afterstr
        for replacelist in replacelst:
            if len(replacelist) !=2:
                continue
            replacedstr = replacelist[0]
            replacestr = replacelist[1]
            if len(replacedstr) ==0 or len(replacestr)==0:
                continue
            afterstr = re.sub(replacedstr, replacestr, afterstr, 0, re.U | re.M)

        return afterstr

    def __ParseStyleForSpecialFunction(self,isshaded,apidict,linecontent):

        if isshaded:
            envstartstr = "\n\\begin{shaded}\n"  # 环境起始字符串
            envendstr = "\n\\end{shaded}\n"  # 环境结束字符串
        else:
            envstartstr = ""  # 环境起始字符串
            envendstr = ""  # 环境结束字符串

        afterstr = linecontent

        if apidict is None or len(apidict)==0:
            return envstartstr,envendstr,afterstr

        if not __dictIsHasKey__(apidict, "isenter") or \
                (__dictIsHasKey__(apidict, "isenter") and \
                 apidict['isenter'] is True):
            afterstr = self.__strreplacepos(afterstr,r"pysiglinewithargsret",r",",r",\\")
        if __dictIsHasKey__(apidict, "istcolorbox") and \
                apidict['istcolorbox'] is True:
            envstartstr, envendstr = self.__GettcolorboxForFunction(apidict)
        elif __dictIsHasKey__(apidict, "fontsize") and \
                apidict['fontsize'] in latexfontsize:
            envstartstr += "\\" + apidict['fontsize'] + "\n"

        #检查是否有替换，有替换的话，对内容进行替换
        if __dictIsHasKey__(apidict, "replace"):
            replacelst = apidict['replace']
            if len(replacelst) > 0:
                afterstr = self.__GetReplacedContent(afterstr,replacelst)

        return envstartstr,envendstr,afterstr

    def __ModifyFunctionBkColorByPython(self,isshaded,apilst,newcontent,linecontent,curpos,prepos):
        #根据python生成的格式为类和函数声明添加灰底，并按逗号换行

        '''
        根据正则表达式获得以\pysiglinewithargsret开头，以“{}”结尾的中间字符串
        对中间字符串添加灰底和换行
        '''
        if isshaded:
            envstartstr = "\n\\begin{shaded}\n"  # 环境起始字符串
            envendstr = "\n\\end{shaded}\n"  # 环境结束字符串
        else:
            envstartstr = ""  # 环境起始字符串
            envendstr = ""  # 环境结束字符串

        startmultiline = '\\pysigstartmultiline\n'
        stopmultiline = '\n\\pysigstopmultiline'
        #searchstr = r'(?=\\pysiglinewithargsret).*(?<={})'
        searchstr=r'(?=\\pysiglinewithargsret).*'
        match = re.search(searchstr, linecontent, re.I|re.U)
        if match is not None:
           #重新组合linecontent
            #pos = match.span()
            isapi, apidict = self.__IsSpecialFunction(apilst, linecontent)
            if  isapi is True and apidict is not None:
                envstartstr,envendstr,afterstr = self.__ParseStyleForSpecialFunction(isshaded,apidict,match.group())
                newstr = '\n' + linecontent[:match.start()] + envstartstr + startmultiline + afterstr+ stopmultiline + envendstr+ linecontent[match.end():len(linecontent)]
            else:
                newstr = '\n' + linecontent[:match.start()] + envstartstr+ startmultiline + match.group().replace(r",",r",\\") + stopmultiline + envendstr + linecontent[match.end():len(linecontent)]
            #计算替换前的内容
            if len(prepos)==0:
                newcontent = self.content[:curpos[0]-1] + newstr
            else:
                newcontent += self.content[prepos[1]:curpos[0]-1] + newstr

        return newcontent

    def __strreplacepos(self,srcstr,posstr,oldstr,newstr):

        # 从指定字符串开始对行内字符串进行替换
        '''
        srcstr：原始字符串
        posstr：从该字符串开始进行替换
        oldstr：被替换字符串
        newstr：替换字符串
        '''
        #查找字符串的起始位置
        pos = srcstr.find(posstr)
        if pos == -1:
            return ""  #如果查找的字符串没有找到，则返回空。

        #根据找到的位置进行字符串拆分
        startstr = srcstr[0:pos]
        beforestr = srcstr[pos:len(srcstr)]
        #对字符串进行替换
        afterstr = beforestr.replace(oldstr,newstr)
        return startstr+afterstr

    def __GetleftrightValueForSpecialapi(self,str):
        """
        得到conf.json specialapi配置中left和right的值
        字符串格式为“[-][数字][单位]”，根据这个格式查找值和单位
        :param str:
        :return:
        """
        if len(str) ==0:
            return 0,""

        searchstr = r'[-]{0,1}([0-9]{1,2})([A-Za-z]{1,2})'
        match = re.search(searchstr, str, re.I)
        if match is not None:
            value = int(match.group(1))
            unit = match.group(2)
            return value,unit
        else:
            return 0,""

    def __GettcolorboxForFunction(self,apidict):
        #判断有没有设置页面左右边距,的左右边距
        #必须通过\geometry指令设置，否则按默认处理

        leftvalue = 0
        leftunit = "mm"
        left = "0mm"

        rightvalue = 0
        rightunit = "mm"
        right="0mm"

        isleftset = False
        isrightset = False

        if __dictIsHasKey__(apidict, "left") and \
            len(apidict['left']) > 0:
            leftvalue,leftunit=self.__GetleftrightValueForSpecialapi(apidict['left'])
            if len(leftunit) > 0:
                left = str(leftvalue)+leftunit
                isleftset = True

        if isleftset is not True:
            leftvalue = 9 #默认值
            leftunit="mm"
            left = str(leftvalue)+leftunit

            searchstr = r'\\geometry{[\s\S]*left=([0-9]{1,2})([A-Za-z]{1,2})[\s\S]*}'
            match = re.search(searchstr, self.content, re.I | re.M)
            if match is not None:
                left = match.group(1) + match.group(2)
                leftvalue = int(match.group(1))
                leftunit = match.group(2)

            #print(left)

        if __dictIsHasKey__(apidict,"right") and \
            len(apidict['right']) >0 :
            rightvalue, rightunit = self.__GetleftrightValueForSpecialapi(apidict['right'])
            if len(rightunit) > 0:
                right = str(rightvalue) + rightunit
                isrightset = True

        if isrightset is not True:
            rightvalue = leftvalue - 1  # 为了使显示效果更好看
            right = str(rightvalue)+leftunit

            searchstr = r'\\geometry{[\s\S]*right=([0-9]{1,2})([A-Za-z]{1,2})[\s\S]*}'
            match =re.search(searchstr,self.content,re.I|re.M)
            if match is not None:
                rightvalue = int(match.group(1)) -2
                rightunit = match.group(2)
                right = str(rightvalue) + rightunit
            #print(right)

        fontsize = ""
        if __dictIsHasKey__(apidict, "fontsize") and \
                apidict['fontsize'] in latexfontsize:
            fontsize = "fontupper=\\" + apidict['fontsize']

        envstartstr = "\n\\begin{tcolorbox}[arc=0mm,boxrule=-1mm,left skip=-" \
                       + str(leftvalue+1) + leftunit +",left="+left +",right=-" +right \
                       + ",colback=shadecolor," + fontsize +"]\n"
        envendstr = "\n\\end{tcolorbox}\n"
        #print("=====================")
        #print(envstartstr)
        #print(envendstr)
        #print("=====================")
        return envstartstr,envendstr


    def __IsSpecialFunction(self,apilst,linecontent):
        '''
        判断是否为特殊api
        :param apilst:
        :param linecontent:
        :return:
        '''
        if len(apilst)==0:
            return False,None

        for apidict in apilst:
            if __dictIsHasKey__(apidict,"latexapiname"):
                apistr=apidict['latexapiname'] #有可能以逗号分割的多个函数名称
                apiarr=apistr.split(",")   #以逗号分割函数名称列表
                for api in apiarr:
                    if api in linecontent:
                        return True,apidict
        return False,None

    def __RemoveLastSpace(self,headstr):
        """
        删除最后的空格，避免函数含义和关键字间有大的空行
        :param headstr:
        :return:
        """
        headlist = headstr.split('\n')
        index = 0
        for i in range(len(headlist)-1,0,-1):
            str = headlist[i].strip('\r')
            if len(str) == 0:
                index = i
                break
        if index > 0:
            headlist.pop(index)
            return '\n'.join(headlist)
        else:
            return headstr

    def __ModifyParamPos(self,paramcontent):
        """
        调整参数位置在最前面，为了适配breathe 4.24.1之后版本
        :param paramcontent:
        :return:
        """
        #查找第一个\begin{description}\n\sphinxlineitem{}字符串
        searchstr = r"(\\begin\{quote\})?(\r\n|\n)?\\begin\{description\}(\r\n|\n)?\\sphinxlineitem\{(\sphinxstylestrong\{)?(?P<paramkey>[\s\S].+)?(\})?\}(\r\n|\n)?[\s\S]+?(\\end\{description\}){1}(\r\n|\n)?(\\end\{quote\})?"
        match = re.search(searchstr,paramcontent,re.I|re.M|re.U)
        if match is not None:

            if "\\begin{quote}" in match.group():
                #删掉缩进
                otherstr = match.group()[len('\\begin{quote}'):len(match.group())-len('\\end{quote}')]
            else:
                otherstr = match.group()

            paramkey = match.group('paramkey')
            headstr = paramcontent[0:match.start()]
            headstr = self.__RemoveLastSpace(headstr)
            if "Parameters" in paramkey:
                #说明参数本身在第一个位置，不做处理
                return headstr + '\n' + otherstr + paramcontent[match.end():len(paramcontent)]

            #如果参数不在第一个位置，查找参数的位置并将第一个位置记录下来
            searchstr = r"(\\begin\{quote\})?(\r\n|\n)?\\begin\{description\}(\r\n|\n)?\\sphinxlineitem\{(\sphinxstylestrong\{)?(?P<paramkey>Parameters)?(\})?\}(\r\n|\n)?[\s\S]+?(\\end\{description\}){1}(\r\n|\n)?(\\end\{quote\})?"
            matchparam = re.search(searchstr,paramcontent,re.I|re.M|re.U)
            if matchparam is None:
                return headstr + '\n' + otherstr + paramcontent[match.end():len(paramcontent)]

            if "\\begin{quote}" in matchparam.group():
                #删掉缩进
                paramstr = matchparam.group()[len('\\begin{quote}'):len(matchparam.group())-len('\\end{quote}')]
            else:
                paramstr = matchparam.group()

            #重新组合字符串,将参数放在第一位
            newparamcontent = headstr + '\n' +paramstr + '\n' +otherstr \
                              + paramcontent[match.end():matchparam.start()] \
                              + paramcontent[matchparam.end():len(paramcontent)]

            return newparamcontent
        else:
            return paramcontent

    def ModifyFunctionBackColor(self):
        '''
        该函数用来修改函数的背景色，并根据参数换行
        * c/c++ sphinx生成的函数都被以下三行包围：
          \pysigstartmultiline
          \pysiglinewithargsret{xxxxx}
          \pysigstopmultiline
          因此利用正则表达式查找这三行所在的位置进行替换。
          注意：
          要实现该功能，latex必须包含framed和color包，同时定义以下颜色：
          \definecolor{shadecolor}{RGB}{220,220,220}
          如果shadecolor颜色未定义，则该函数执行失败。
        * python生成的latex文件中函数或者类的声明，有\pysiglinewithargsret指令，但是没有pysigstartmultiline指令。
          因此需要在\pysiglinewithargsret指令句子的结束，前后先添加pysigstartmultiline和pysigstopmultiline。再添加\begin{shaded}和\end{shaded}
          一句的结束根据"{}"来判断，遇到"{}"即为句末。
        '''
        #判断是否有特殊API需要处理
        apilst = []
        isshaded = True
        if __dictIsHasKey__(__load_dict__,"specialapi"):
            if __dictIsHasKey__(__load_dict__["specialapi"],"styles"):
                apilst=list(__load_dict__["specialapi"]["styles"])
            if __dictIsHasKey__(__load_dict__["specialapi"],"isshaded"):
                isshaded = __load_dict__["specialapi"]["isshaded"]

        pythontype = False
        newcontent = ""
        prepos=[]     #定义需要替换的字符串列表，该列表中的每一个元素都包含上面提到的三行。
        #searchstr = r'^(?=.*\\pysiglinewithargsret).*$'
        #该正则表达式可以把pysiglinewithargsret或者带template的识别出来。有template的会被pysigline标签标识
        #该修改也兼容sphinx 5.3.0及以后版本。否则5.3.0版本之后将无法使用。
        #searchstr=r'(\\pysigline[\s\S].*){0,1}[\n]{0,1}(\\pysiglinewithargsret[\s\S].*)[\n]'
        searchstr=r'(\\pysigstartsignatures){0,1}[\r\n|\n]{0,1}(\\pysigstartmultiline){0,1}(\r\n|\n){0,1}((\\pysigline[\s\S].*){0,1}[\n]{0,1}(\\pysiglinewithargsret[\s\S].*))[\n](\\pysigstopmultiline){0,1}[\r\n|\n]{0,1}(\\pysigstopsignatures){0,1}'
        m = re.finditer(searchstr, self.content, re.M|re.I|re.U)
        for match in m:

            linecontent = match.group()
            #判断是否添加了pysigstartmultiline和pysigstopmultiline，没有添加的话需要添加才能添加灰色背景
            #multilen=len(r'\pysigstartmultiline')
            #startmultistr = self.content[match.start()-multilen-1:match.start()]
            #如果上一行不是\pysigstartmultiline则需要按python风格修改，添加该标志
            #print(startmultistr.strip())
            if match.group(2) is None:
                #print('is python')
                # 计算替换前的内容
                newcontent = self.\
                    __ModifyFunctionBkColorByPython(isshaded,apilst,newcontent,linecontent,match.span(),prepos)
                prepos = match.span()
                pythontype = True
            else:
                #tablestr = match.groups()
                #计算替换前的内容
                if len(prepos)==0:
                    newcontent = self.content[0:match.start()]
                else:
                    # 得到参数信息内容，方便后面调整位置，此时调整的参数为上一个函数的参数位置
                    paramcontent = self.content[prepos[1]:match.start()]
                    # breathe 4.24.1之后版本将parameters放在了最后面，
                    # 为了适配breathe 4.24.1之后版本对格式的调整
                    # 检查参数是否在后面，是的话，需要将参数放到最前面
                    # breathe 4.24.1之后版本将doxygen的标签都放在\begin{description}\n\sphinxlineitem{}标志对里面
                    # 因此对该标志对进行搜索
                    newparamcontent = self.__ModifyParamPos(paramcontent)
                    newcontent += newparamcontent


                #当有template的时候，pysiglinewithargsret不一定在行首，因此需要从pysiglinewithargsret开始替换逗号，加强制换行符
                if isshaded:
                    envstartstr = "\n\\begin{shaded}\n"  # 环境起始字符串
                    envendstr = "\n\\end{shaded}\n"  # 环境结束字符串
                else:
                    envstartstr = ""  # 环境起始字符串
                    envendstr = ""  # 环境结束字符串

                isapi,apidict = self.__IsSpecialFunction(apilst,linecontent)
                if  isapi is True and apidict is not None:
                    envstartstr,envendstr,afterstr=self.__ParseStyleForSpecialFunction(isshaded,apidict,linecontent)
                    newstr = envstartstr + afterstr + envendstr
                else:
                    afterstr=self.__strreplacepos(linecontent,r"pysiglinewithargsret",r",",r",\\")
                    #得到替换后的字符串
                    newstr = envstartstr + afterstr + envendstr
                #得到替换后的内容
                newcontent += newstr
                prepos = match.span()
                pythontype = False

        if len(prepos) > 0:
            paramcontent = self.content[prepos[1]:len(self.content)]
            newparamcontent = self.__ModifyParamPos(paramcontent)
            self.content = newcontent + newparamcontent
            #self.content = newcontent + self.content[prepos[1]:len(self.content)]
        #print('===============================================')

    def __GetTableCaption(self,tablecontent):
        '''
        得到表格的caption，方便输出打印
        :param tablecontent: 表格内容
        :return: 返回表格的caption
        '''
        tablecaption = ""
        searchstr = r'(\\sphinxcaption|\\caption)\{(?P<caption>[\s\S]*?)\}(?=\\label|\\\\)'
        matchcaption = re.search(searchstr, tablecontent, re.M | re.I | re.U)
        if matchcaption is not None:
            tablecaption = matchcaption.group('caption') #得到caption的值
            #长表格caption后面会添加\struct的后缀，去掉
            tablecaption = tablecaption.replace(r"\strut","")

        return tablecaption

    def ModifyTablesAttributes(self):
        #修改表格属性
        newcontent = self.content
        searchstr = r'(\\begin{savenotes})([\s\S]*?)(\\sphinxattablestart|\\sphinxatlongtablestart)([\s\S]*?)(\\sphinxattableend|\\sphinxatlongtableend)([\s\S]*?)(\\end{savenotes})'
        matchobj = re.finditer(searchstr, self.content, re.M|re.I|re.U)
        #outputstr = "\033[34mModifying Table:\033[0m"
        #print(outputstr, end="")
        icount = 0
        for match in matchobj:
            try:
                icount = icount + 1
                tablestr = match.groups()
                tablecontent = tablestr[0]+tablestr[1]+tablestr[2]+tablestr[3]+tablestr[4]
                columnwidthlst,newtablecontent = self.__GetColumnParameters(tablecontent)
                #得到表格caption
                tablecaption=self.__GetTableCaption(tablecontent)
                #print(tablecaption)
                #outputtable="\033[34m[" + tablecaption +"];\033[0m"
                #print(outputtable, end="")
                #为了兼容纯英文linux系统，在打印信息中不能包含中文，因此按自动计数累加表示表格，
                #避免表格标题中有中文时导致修改表格失败。
                prstr = "\033[34m[Modifying table"+str(icount)+":... ...];\033[0m"
                print(prstr, end="\r")
                if self.tablesattrobj.tablestyles is None:
                    #先修改表格的通用属性
                    newtableattr,islongtable = self.__StartModifyTableAttr(newtablecontent,columnwidthlst, False)
                    newcontent = newcontent.replace(tablecontent, newtableattr)
                else:
                    caption_dict = self.__CreateTableCaptionDict(self.tablesattrobj.tablestyles)
                    if len(caption_dict ) > 0 :
                        newtableattr = self.__ModifySingleTableattr(newtablecontent, columnwidthlst, caption_dict ) #tablestr也是3个内容的数组，因为正则表达式被分为了3组，只取中间分组的内容。
                        #重新组成新的字符串
                        newcontent = newcontent.replace(tablecontent, newtableattr)

            except Exception as e:
                print(e)
                traceback.print_exc()

        print("\r\n")
        self.content = newcontent

    #替换表格中字体大小
    def __ModifyTableFontSize(self,singletablecontent,fontsize):

        searchstr = r'(\\begin{savenotes})([\s\S]*?)(\\sphinxattablestart|\\sphinxatlongtablestart)'
        m = re.search(searchstr,singletablecontent, re.M|re.I|re.U)
        tablestr = m.groups()
        #替换表格中的字体
        #同时也要使表格的caption字体大小保持一致，修改表格的caption需要添加如下指令：\renewcommand{\tablename}{\scriptsize{表} }
        captionfont = "\n\\renewcommand{\\tablename}{\\" + fontsize + "{Table} }\n"
        if doc_language=='zh_CN':
            captionfont="\n\\renewcommand{\\tablename}{\\" + fontsize + "{表} }\n"

        tablefontsize = '\\'+fontsize + captionfont

        return tablestr[0]+tablefontsize+tablestr[2]+singletablecontent[m.end():len(singletablecontent)]

    def __CreateTableCaptionDict(self, tablestylesarr):
        #根据caption生成表格字典，key=caption，value=属性数组
        cap_dict = {}

        for tablestyle_dict in tablestylesarr:
            captionarr = tablestyle_dict['caption']
            #该caption可能是一个逗号分隔的字符串数组，因此需要做拆分
            captionlist = captionarr.split(",")
            for caption in captionlist:
                cap_dict[caption] = tablestyle_dict  #以caption为key重新生成字典，便于查找
        return cap_dict

    #查找表格caption是否被匹配
    def __JudgeIsCaption(self,caption_dict,tablecaption):

        captionlst=caption_dict.keys() #得到json配置的所有caption
        for caption in captionlst:
            tablestyle_dict = caption_dict[caption]
            if __dictIsHasKey__(tablestyle_dict, 'ispartial') and tablestyle_dict['ispartial']==True:
                if caption in tablecaption:
                    return True,tablestyle_dict
            else:
                if caption == tablecaption:
                   return True,tablestyle_dict
        return False,None

    def  __ModifySingleTableattr(self, singletablecontent, columnwidthlst, caption_dict):
        #修改单个表格属性
        #从单个表格里用正则表达式找caption
        #定义正则表达式,查找caption内容
        tablecaption = ''
        new_singletablecontent = singletablecontent
        if self.tablesattrobj.isname:
            searchstr = r'.*\\label.*?:(?P<caption>[\s\S].*)}}.*'
            matchcaption = re.search(searchstr, singletablecontent, re.M | re.I | re.U)
            if matchcaption is not None:
                tablecaption = matchcaption.group('caption')  # 得到caption的值
            else:
                tablecaption = ''
        else:
            tablecaption = self.__GetTableCaption(singletablecontent)

        #stylefontsize = False
        iscaption = False
        iscaption,tablestyle_dict=self.__JudgeIsCaption(caption_dict,tablecaption)
        if iscaption:

            #if __dictIsHasKey__(tablestyle_dict,'fontsize') and (tablestyle_dict['fontsize'] in latexfontsize):
            #    stylefontsize = True
            #    tablestyle_dict['isLongTable'] = True  #只有长表格的\caption指令，设置的字体才能生效，所以如果要设置表格字体，需要强制设置为长表格。
            jsonmultirow = []  #保存需要设置自动换行的合并单元格的列号。
            if __dictIsHasKey__(tablestyle_dict, 'multirowcolumn'):
                jsonmultirow = tablestyle_dict['multirowcolumn']

            if __dictIsHasKey__(tablestyle_dict, 'isLongTable') is not True:
                tablestyle_dict['isLongTable'] = False

            if __dictIsHasKey__(tablestyle_dict, 'isCusHead') is not True:
                tablestyle_dict['isCusHead'] = False

            #修改表格通用属性
            new_singletablecontent,islongtable= self.__StartModifyTableAttr(new_singletablecontent,
                                                                columnwidthlst,
                                                                 tablestyle_dict['isLongTable'],
                                                                 tablestyle_dict['isCusHead'],
                                                                 jsonmultirow,
                                                                 tablestyle_dict)
            #设置该表格定制字体
            if __dictIsHasKey__(tablestyle_dict,'fontsize') and (tablestyle_dict['fontsize'] in latexfontsize):
                new_singletablecontent=self.__ModifyTableFontSize(new_singletablecontent,tablestyle_dict['fontsize'])

            #渲染竖型表格的第一列
            if __dictIsHasKey__(tablestyle_dict,'isVertical') and tablestyle_dict['isVertical']==True:
                if islongtable:
                    # 长表格自定义sphinxcolwidth的第2个参数默认为5，否则无法实现自动换行
                    #self.normalstr = self.normalstr % {
                    #    'flag': '%(flag)s',
                    #    'colwidth': '5',
                    #    'sphinxstyletheadfamily': '%(sphinxstyletheadfamily)s',
                    #    'content': '%(content)s'
                    #}
                    new_singletablecontent = self.__ModifyVerticalLongTable(new_singletablecontent,tablestyle_dict,columnwidthlst)
                else:
                    # 普通表格自定义sphinxcolwidth的第2个参数默认为3，否则无法实现自动换行
                    #self.normalstr = self.normalstr % {
                    #    'flag': '%(flag)s',
                    #    'colwidth': '3',
                    #    'sphinxstyletheadfamily': '%(sphinxstyletheadfamily)s',
                    #    'content': '%(content)s'
                    #}
                    new_singletablecontent = self.__ModifyTableByLine(new_singletablecontent,True,False,tablestyle_dict,columnwidthlst)

            if __dictIsHasKey__(tablestyle_dict,'isnewpage') and tablestyle_dict['isnewpage']==True:
                new_singletablecontent = '\\newpage\n' + new_singletablecontent

            #最后修改表格的行高
            if __dictIsHasKey__(tablestyle_dict, 'ht_rowno') and tablestyle_dict['ht_rowno'] is not None:
                new_singletablecontent = self.__ModifyTableRowHeight(islongtable,new_singletablecontent,tablestyle_dict['ht_rowno'])
        else:
            #修改表格的通用属性
            new_singletablecontent,islongtable = self.__StartModifyTableAttr(singletablecontent, columnwidthlst, False)
        if new_singletablecontent == '':
           new_singletablecontent = singletablecontent

        return new_singletablecontent

    def __NormaltableRowHeight(self,rowno,rowheightinfo,singletablecontent):
        #自定义长表格，第一行以“\hline”开头，因此从第一个“\hline”开头进行替换。
        searchstr = r'\\hline'
        match = re.search(searchstr,singletablecontent,re.I | re.U)
        if match is None:
            return singletablecontent

        #latex表格属性内容
        tablehead = singletablecontent[0:match.start()]
        #表格内容
        tablecontent = singletablecontent[match.start():len(singletablecontent)]
        tablecontent = self.__ModifyNormalRowHeight(rowno,rowheightinfo,0,tablecontent)

        return tablehead + tablecontent

    def __ModifyNormalRowHeight(self, rowno, rowheightinfo, startline, tablecontent):
        # 修改除表格外其它行的行高，并返回修改后的内容
        searchstr = r'\\\\'
        pattern = re.compile(searchstr, re.I | re.U)
        matchiter = pattern.finditer(tablecontent)
        i = startline
        for m in matchiter:
            i += 1
            if rowno == i:
                # 开始设置该行的行高
                # 获取起始和结束位置
                posstart = m.start()
                posend = m.end()
                tablecontent = tablecontent[0:posstart] + rowheightinfo + tablecontent[posend:len(tablecontent)]
                break

        return tablecontent

    def __JudgeRowlistRule(self,rowlist):
        #判断行号和行高信息是否符合规则

        if len(rowlist)!=2:
            return False
        rowno = rowlist[0]     #得到行号
        rowhtstr=rowlist[1]    #得到行高信息

        #判断是否为异常信息
        #设置的最大行高不能超过10，因为10的行高太高，正常不需要这么高的行高。而且sphinx会根据换行自动调整行高，该方法只用户行高的微调。
        if rowno <= 0 or len(rowhtstr) == 0 or not rowhtstr.isdigit() or (int(rowhtstr) > 99):
            return False
        return True

    def __ModifyLongTableRowHeight(self,rowno,rowheightinfo,singletablecontent):

        # 判断是自定义长表格还是sphinx生成的长表格。
        # sphinx自动生成的长表格有两个表头，第一个表头以“\endfirsthead”结尾。
        # 自定义的长表格没有“\endfirsthead”标志
        searchstr = r'\\endfirsthead'
        matchobj = re.search(searchstr, singletablecontent, re.I | re.U)
        if matchobj is None:
            # 可能是自定义长表格，自定义长表格没有endfirsthead和endhead需要单独设置
            singletablecontent = self.__NormaltableRowHeight(rowno,rowheightinfo,singletablecontent)
            return singletablecontent

        if rowno==1: #因为长表格有两个表头，因此两个表头都要设置，避免换页后表头格式不一致。
            #修改长表格的表头
            firsthead = singletablecontent[0:matchobj.end()]
            rpos = firsthead.rfind(r"\\")
            firsthead = firsthead[0:rpos] + rowheightinfo + firsthead[rpos+len(r"\\"):len(firsthead)]

            #查找第2个表头，第2个表头以“\endhead”结尾
            searchstr = r'\\endhead'
            matchobj2 = re.search(searchstr, singletablecontent, re.I | re.U)
            endhead = singletablecontent[matchobj.end():matchobj2.end()]
            rpos = endhead.rfind(r"\\")

            endhead = endhead[0:rpos]+rowheightinfo+ endhead[rpos+len(r"\\"):len(firsthead)]

            singletablecontent = firsthead+endhead+singletablecontent[matchobj2.end():len(singletablecontent)]

        if rowno > 1:
            #长表格先将表头去除掉，否则表头会包含很多“\\”，但不代表行。
            #长表格的表头以“\endlastfoot”结尾
            searchstr = r'\\endlastfoot'
            matchobj = re.search(searchstr, singletablecontent, re.I | re.U)
            tablehead = singletablecontent[0:matchobj.end()]
            othercontent = singletablecontent[matchobj.end():len(singletablecontent)]

            #修改表格内容，因为长表格有两个表头，因此表头后的行号起始行从1开始。
            othercontent = self.__ModifyNormalRowHeight(rowno,rowheightinfo,1,othercontent)

        singletablecontent = tablehead + othercontent
        return singletablecontent

    def __ModifyTableRowHeight(self,islongtable,singletablecontent, rowlistarry):

        #解析行信息，包含行号和行高信息
        for rowlist in rowlistarry:

            if not self.__JudgeRowlistRule(rowlist):
                continue
            rowno = rowlist[0]
            rowheightinfo = r"\\[" + rowlist[1] +r"ex]"   #要替换的行高信息
            #根据正则表达式，查找换行符"\\",每一个“\\"代表表格的一行，需要在“\\”的末尾添加行高数据：“\\[rowhtstr+ex]”
            if islongtable:
                singletablecontent = self.__ModifyLongTableRowHeight(rowno, rowheightinfo,singletablecontent)
            else:
                singletablecontent = self.__NormaltableRowHeight(rowno,rowheightinfo,singletablecontent)

        return singletablecontent

    def __StartModifyTableAttr(self, singletablecontent, columnwidthlst, islongtable, isCusHead=True,jsonmultirow=None,tablestyle_dict=None):
        #修改表格的通用属性
        searchstr = r'(\\begin{tabular}|\\begin{tabulary})(\[[a-z]\]|{\\linewidth}\[[a-z]\])([\s\S].*)'
        #为了添加表格的通用属性，先对字符串做分割
        #python会把正则表达式中的分组自动分割，因此上面的正则表达式会自动分割为三个字符串
        #加上头尾字符串总共分为5个字符串数组。要修改第1维字符串为\\being{longtable},
        #第2维字符串直接删除，第3维字符串不变
        splittable = re.split(searchstr, singletablecontent,0, re.M | re.I|re.U )
        if splittable is None or len(splittable) < 5:
            #再修改长表格属性
            searchstr = r'\\begin{longtable}([\s\S].*)'
            #为了添加表格的通用属性，先对字符串做分割
            #python会把正则表达式中的分组自动分割，因此上面的正则表达式会自动分割为三个字符串
            #加上头尾字符串总共分为5个字符串数组。要修改第1维字符串为\\being{longtable},第2维字符串直接删除，
            #第3维字符串不变
            splittable = re.split(searchstr, singletablecontent,0, re.M | re.I|re.U )
            if len(splittable) < 3:
                #至少是3维的数组，否则不是预想的内容，不做处理
                return singletablecontent
            if isCusHead:
                newtable4 = self.__ModifyLongTableHead(splittable[2],
                                                       columnwidthlst,
                                                       self.tablesattrobj.headtype,
                                                       jsonmultirow,
                                                       tablestyle_dict)
                # begin{longtable}必须再加上，因为Python并不认为它是正则表达式，因此不再分组里面第0个分组为空。
                singletablecontent = splittable[0]+r'\begin{longtable}'+splittable[1]+newtable4
            if self.tablesattrobj.fontsize !="":
                #设置表格字体
                singletablecontent=self.__ModifyTableFontSize(singletablecontent,self.tablesattrobj.fontsize)
            return singletablecontent,True

        #拆分后splittable应该为5个字符串的数组，拆分后便于添加通用属性
        if self.tablesattrobj.rowtype != '':
            splittable[0] += self.tablesattrobj.rowtype + '\n'

        if isCusHead is True:
            #修改表头字体颜色为白色加粗
            newtable4 = self.__ModifyTableHead(splittable[4],
                                               columnwidthlst,
                                               self.tablesattrobj.headtype,
                                               jsonmultirow,
                                               tablestyle_dict)
        else:
            newtable4 = splittable[4]

        singletablecontent = splittable[0]+splittable[1]+splittable[2]+splittable[3]+newtable4
        #根据配置设置表格为长表格，现在基本通过将sphinx的class属性设置为longtable实现，不再通过手动设置完成。
        if islongtable is True:
            singletablecontent = self.__ModifyTableLongHeadAndTail(singletablecontent)

        if self.tablesattrobj.fontsize !="":
            #设置表格字体
            singletablecontent=self.__ModifyTableFontSize(singletablecontent,self.tablesattrobj.fontsize)

        return singletablecontent,False

    def __ModifyTableLongHeadAndTail(self,singletablecontent):
        #长表格的头尾都要替换，因此单独封装成一个函数，否则长表格的表格序号将加2
        #替换第一行为长表格
        searchstr = r'(\\begin{savenotes}[\S]*?\\sphinxattablestart)'
        splittable = re.search(searchstr, singletablecontent,re.M | re.I|re.U )
        firstend =splittable.end()
        #替换为长表格
        tablefirstline = re.sub(r'\\sphinxattablestart',r'\n\\sphinxatlongtablestart\n', splittable.group(0), 1,re.M|re.I|re.U)
        if r'\sphinxthistablewithglobalstyle' in singletablecontent:
            tablefirstline += '\\sphinxthistablewithglobalstyle\n'
        if r'\sphinxthistablewithvlinesstyle' in singletablecontent:
            tablefirstline += '\\sphinxthistablewithvlinesstyle\n'
        #替换第2行为长表格
        searchstr = r'(\\begin{tabular}|\\begin{tabulary})(\[[a-z]\]|{\\linewidth}\[[a-z]\])([\s\S].*)'
        splittable = re.search(searchstr, singletablecontent,re.I|re.U )
        #记录表头的位置
        headstartpos= splittable.start()
        headlastpos = splittable.end()
        tablesecondline = re.sub(r'\\begin{tabular}|\\begin{tabulary}',r'\\begin{longtable}', splittable.group(0), 1,re.I|re.U)
        tablesecondline = re.sub(r'\{\\linewidth\}',r'', tablesecondline, 1,re.I|re.U)

        #查找caption
        searchstr = r'\\sphinxcaption([\s\S].*)'
        splittable = re.search(searchstr, singletablecontent, re.I|re.U)
        if splittable is not None:
            longcaption = re.sub(r'\\sphinxcaption',r'\\caption', splittable.group(0), 1,re.I|re.U)
            #添加长表格专用指令
            longcaption += r"\\*[\sphinxlongtablecapskipadjust]"

            #构建长表个的表头部分
            longhead = tablefirstline + tablesecondline + '\n' + r'\sphinxthelongtablecaptionisattop' + '\n'+ longcaption+'\n'
        else:
            #如果没有caption需要把中间的内容加上，否则会丢失label导致连接失效
            longhead = tablefirstline + singletablecontent[firstend:headstartpos]+tablesecondline + '\n'

        #替换表尾
        newtablecontent = singletablecontent[headlastpos:len(singletablecontent)]
        index = newtablecontent.rfind('\\end{tabulary}')
        if index > 0:
            endtable = newtablecontent[index:len(newtablecontent)]
            othercontent = newtablecontent[0:index]
            endtable = re.sub(r'(\\end{tabular}|\\end{tabulary})', r'\\end{longtable}', endtable, 1,re.M | re.I | re.U)
            endtable = re.sub(r'\\par', r'', endtable, 1, re.M | re.I | re.U)
            endtable = re.sub(r'\\sphinxattableend', r'\\sphinxatlongtableend', endtable, 1, re.M | re.I | re.U)
            newtablecontent = othercontent + endtable
        else:
            index = newtablecontent.rfind('\\end{tabular}')
            if index >0:
                endtable = newtablecontent[index:len(newtablecontent)]
                othercontent = newtablecontent[0:index]
                endtable = re.sub(r'(\\end{tabular}|\\end{tabulary})', r'\\end{longtable}', endtable, 1,re.M | re.I | re.U)
                endtable = re.sub(r'\\par', r'', endtable, 1, re.M | re.I | re.U)
                endtable = re.sub(r'\\sphinxattableend', r'\\sphinxatlongtableend', endtable, 1, re.M | re.I | re.U)
                newtablecontent = othercontent + endtable

        singletablecontent = longhead + newtablecontent
        return singletablecontent

    #为表格增加h属性对齐方式，避免表格浮动，让表格在当前位置显示
    def __AddHAttrForTable(self,content):

        newcontent=""
        searchstr = r'(\\begin{tabular}|\\begin{tabulary}|\\begin{longtable})({\\linewidth})?(\[(?P<attr>[a-z]{1,4})\])?([\s\S].*)'
        m = re.search(searchstr, content, re.M|re.I|re.U )
        attrcontent = m.group('attr')
        posarr = m.span()
        if m.group(3) == '':   #group(3)是[htp]的组合，如果没有表格属性则添加上表格属性
            newcontent = content[0:posarr[0]]+m.group(1)+m.group(2)+r'[h]'+m.group(5)+content[posarr[1]:len(content)]
        else:
            replacestr = m.group(4)  #需要被替换的表格属性
            #判断该表格是否有P属性，如果有p属性需要删掉
            replacestr.replace('p','')
            #给该表格添加h属性，避免表格浮动，让表格在当前位置显示
            replacestr = 'h'+replacestr
            newcontent = content[0:posarr[0]]+m.group(1)+m.group(2)+'['+replacestr+']'+m.group(5)+content[posarr[1]:len(content)]

        return newcontent
    def __GetColumnParaByWidths(self,content):
        # 可能用的widths参数设置的列宽，再根据widths参数解析列宽
        columnwidthlst = []
        # 如果列宽不等于100，为了使表格不超过页面显示范围，以100为基数重新调整各列的列宽
        newcolumnwidth = "|"
        isadjustcol = False
        newcontent = content
        try:
            searchstr = r'(?<=\|)\\X\{(?P<count1>[0-9]*)\}\{(?P<count2>[0-9]*)\}(?=\|)'
            pattern = re.compile(searchstr, re.I | re.U)
            match = pattern.finditer(content)
            if match is None:
                return [],content
            for m in match:
                count1 = int(m.group('count1'))
                count2 = int(m.group('count2'))

                tempcount = round(count1/count2,2)
                colwidth = "%(float)s\\textwidth"
                colwidth = colwidth % {
                    'float': str(tempcount)
                }
                columnwidthlst.append(colwidth)
                if count2 != 100:
                    isadjustcol = True
                    #重新调整count1和count2的值，并加入列表
                    percolwidth = r"\X{" + str(int(tempcount * 100)) + "}{100}|"
                    newcolumnwidth = newcolumnwidth + percolwidth

            if isadjustcol is True:
                searchstr = r"(?<=\{)\|\\X[\s\S]*?\|(?=\})"
                match = re.search(searchstr,content,re.I | re.U)
                if match is not None:
                    newcontent = content.replace(match.group(),newcolumnwidth,1)

        except Exception as e:
            print(e)
        finally:
            return columnwidthlst,newcontent

    def __GetColumnParameters(self,content):
        '''
        得到通过tablecolumns参数设置的列宽，multirow的自动换行，必须有列宽参数，否则无法实现自动换行。
        而且不能通过widths参数设置每列的列宽，否则生成的列宽无法用于multirow，依然无法实现multirow的自动换行
        :param content:
        :return:
        '''
        # 默认ismultirowautowrap为自动换行
        # 如果表格用tabluarcolumns指令设置的列宽，因为每列的宽度有具体的值，则默认自动换行
        # 如果表格用widths指令设置的列宽，因为每列的宽度不具体，则默认不自动换行，需要手动换行
        self.tablesattrobj.ismultirowautowrap = True  #默认ismultirowautowrap为自动换行
        newcontent = content
        columnwidthlst=[] #保存每一列宽度的列表
        #根据表格内容，得到每一列宽度参数，方便multirow的时候自动换行
        #m垂直居中，p垂直靠上，b垂直靠下
        searchstr= r'(?<=\|)[\s\S]*?[m|p|b]\{(?P<width>[\s\S]*?)\}[\s\S]*?(?=\|)'
        pattern = re.compile(searchstr,re.I | re.U)
        match = pattern.finditer(content)
        if match is None:
            #columnwidthlst,newcontent = self.__GetColumnParaByWidths(content)
            self.tablesattrobj.ismultirowautowrap = False #则默认需要手动换行
            return [],newcontent

        for m in match:
            columnwidthlst.append(m.group('width'))
        if len(columnwidthlst) ==0:
            #columnwidthlst,newcontent= self.__GetColumnParaByWidths(content)
            self.tablesattrobj.ismultirowautowrap = False #则默认需要手动换行
            return [],newcontent
        return columnwidthlst,newcontent

    def __FindTableHeadForFamily(self,content,isLongTable):
        '''
        根据sphinxstyletheadfamily标志查找表格的表头，有sphinxstyletheadfamily标志的表头，源文件必须以“======”分割。
        具有合并单元格的表头，源文件必须以“===”分割，而且表头最多支持两行，否则合并单元格的表头不支持渲染。
        :param content: 表格内容
        :return: headcontent 表格内容
        '''
        #查找表头的起始位置，以第一个\hline开始。
        if isLongTable is True:
            searchstr =r"(\\hline[\s\S]*)(?=\\endfirsthead)"
            matchobj = re.search(searchstr, content, re.I|re.M|re.U)
            tablehead = matchobj.group()
        else:
            searchstr = r'\\hline'
            matchobj = re.search(searchstr,content,re.I)
            if matchobj is None:
                return ""
            startpos = matchobj.start() #表头的起始位置
            familypos = content.rfind(r'\sphinxstyletheadfamily') #查找最后一个sphinxstyletheadfamily出现的位置
            if familypos ==-1:
                return ""

            #从familypos开始，查找到的第一个“\\”，则为表头的行结束符号。
            endpos = content.find(r"\hline",familypos)
            # 得到表头内容，表头内容从起始\hline开始，到\hline结束，这种查找方式，兼容表头为两行的情况。
            tablehead = content[startpos:endpos+len(r"\hline")]

        return tablehead

    def __FindAndFlagPos(self,tablehead,andpos):
        if andpos != -1:
            # 查找&前面有没有转义字符，有转义字符，则&不为单元格分割符，需要再继续查找
            preflag = tablehead[andpos-1]
            if preflag == '\\':
                #如果是\&说明这个&不是单元格的分隔符,则需要继续查找
                newpos=tablehead.find("&",andpos+1)
                andpos = self.__FindAndFlagPos(tablehead,newpos)
                return andpos
            else:
                return andpos
        return andpos

    def __FindCellContentForHead(self,tablehead,startpos):
        #查找每个单元格的内容
        if startpos == -1:
            return 0,-1,None

        # 查找&和\\的位置，&为单元格的分割标志,\\为行结束标志，一个单元格的分割标志可能是&，也可能是\\
        cellcontent = []
        andpos = tablehead.find("&", startpos)
        # 得到实际的分隔符位置，避免&内容里面的符号，而不是单元格分隔符
        andpos = self.__FindAndFlagPos(tablehead, andpos)
        linepos = tablehead.find(r"\\", startpos)

        if andpos ==-1 and linepos ==-1:
            return 0,-1,None

        newpos = -1
        lineflag = 0  # 是否启动新行的标志。0说明在当前行；1说明遇到了\\，需要启动新的一行
        if andpos == -1 or linepos < andpos:
            # 该单元格的分割符为\\
            cellcontent.append(tablehead[startpos:linepos])
            cellcontent.append(r"\\")
            newpos = linepos + len(r"\\")
            lineflag = 1
        else:
            cellcontent.append(tablehead[startpos:andpos])
            cellcontent.append(r"&")
            newpos = andpos + len(r"&")
        return lineflag,newpos,cellcontent
    '''
    #暂时注释，不在使用该函数，使用上面函数的写法
    def __delNotMustStrFromContent(self, content):
        content = content.strip().strip("\n").strip("\r")
        for delstr in self.delstrlst:
            if delstr in content:
                content = content.replace(delstr,"")

        content = content.strip().strip("\n").strip("\r")
        print('content=%s' % content)
        strlst = content.split('\n')
        newcontent = ""
        for i in strlst:
            if i != "":
                i.strip("\r")
            if i != strlst[-1] and i != "":
                i = i + r"\\ "
            newcontent = newcontent + i
        content = self.autolinetemplat % {
            'autocontent': newcontent
        }
        return content
    '''

    def __delNotMustrowStrFromContent(self, content, ismanualwraplst=None):
        """
        该函数用于multirow的手动换行，为了更好的显示效果，multicolumn的手动换行请参见__delNotMustStrFromContent
        :param content:
        :param ismanualwraplst: list类型，只有一个元素。是否有手动换行，为了回传，并兼容之前的调用，设计了list类型。
        :return:
        """
        # 实现自动换行
        # 去掉首尾换行和空白字符
        content = content.strip().strip("\n").strip("\r")
        # 删除单元格内容里非必须的成分
        for delstr in self.delstrlst:
            if delstr in content:
                content = content.replace(delstr, "")
        # 正则匹配既是加了“r”，也要用转义字符，因为r只是去掉了python的转义，
        # 但正则本身也需要一层转义
        content = content.strip().strip("\n").strip("\r")
        searchstr = r"\\sphinxAtStartPar"
        match = re.search(searchstr, content, re.I | re.M)
        if match is not None:
            # 说明在字符串的起始位置匹配成功
            content = content[match.end():len(content)]
        # 再去掉首尾空格和换行
        content = content.strip().strip("\n").strip("\r")
        if r"\sphinxAtStartPar" in content:
            # 字符串替换比较，如果用了r则无需再加一次转义字符
            # 替换所有的回车换行符，否则autolinetemplat中间不能有回车换行，否则将导致错误
            content = content.replace("\n", "").replace("\r", "")
            content = content.replace(r"\sphinxAtStartPar", r"\newline ")
            if ismanualwraplst is not None:
                ismanualwraplst[0] = True
        else:
            strlst = content.split('\n')
            newcontent = ""
            for i in range(0, len(strlst)):
                str = strlst[i].strip("\r")
                if str != "":
                    strlst[i] = str
                else:
                    if i > 0 and i < (len(strlst) - 1) and strlst[i - 1] != r"\newline ":
                        strlst[i] = r"\newline "

            content = "".join(strlst)
            if r"\newline" in content and ismanualwraplst is not None:
                ismanualwraplst[0] = True

        return content

    def __delNotMustStrFromContent(self,content,ismanualwraplst=None):
        """
        该函数用于multicolumn的手动换行，为了更好的显示效果，multirow的手动换行请参见__delNotMustrowStrFromContent
        :param content:
        :param ismanualwraplst: list类型，只有一个元素。是否有手动换行，为了回传，并兼容之前的调用，设计了list类型。
        :return:
        """
        #实现自动换行
        #去掉首尾换行和空白字符
        content = content.strip().strip("\n").strip("\r")
        #删除单元格内容里非必须的成分
        for delstr in self.delstrlst:
            if delstr in content:
                content = content.replace(delstr,"")
        # 正则匹配既是加了“r”，也要用转义字符，因为r只是去掉了python的转义，
        # 但正则本身也需要一层转义
        content = content.strip().strip("\n").strip("\r")
        searchstr = r"\\sphinxAtStartPar"
        match = re.search(searchstr,content,re.I|re.M)
        if match is not None:
            #说明在字符串的起始位置匹配成功
            content = content[match.end():len(content)]
        # 再去掉首尾空格和换行
        content = content.strip().strip("\n").strip("\r")
        if r"\sphinxAtStartPar" in content:
            #字符串替换比较，如果用了r则无需再加一次转义字符
            #替换所有的回车换行符，否则autolinetemplat中间不能有回车换行，否则将导致错误
            content=content.replace("\n","").replace("\r","")
            content = content.replace(r"\sphinxAtStartPar", r"\\ ")
            content = self.autolinetemplat % {
                'autocontent': content
            }
            if ismanualwraplst is not None:
                ismanualwraplst[0] = True
        else:
            strlst = content.split('\n')
            newcontent = ""
            for i in range(0,len(strlst)):
                str = strlst[i].strip("\r")
                if str != "":
                    strlst[i]=str
                else:
                    if i>0 and i< (len(strlst)-1) and strlst[i-1] != r"\\ ":
                        strlst[i]=r"\\ "

            newcontent = "".join(strlst)
            if r"\\ " in newcontent:
                content = self.autolinetemplat % {
                    'autocontent': newcontent
                }
                if ismanualwraplst is not None:
                    ismanualwraplst[0] = True
            else:
                content = newcontent

        return content

    def __ModifySphinxMultirow(self,cellstr,contentdict,columnwidthlst,jsonmultirow,lineindex,colindex,multirowindex,tablestyle_dict,isvertical=False):
        '''
        修改multirow内容
        取出关键内容合并单元格的数量和单元格内容
        :param isvertical:如果是渲染第一列，multirow加\raggedright参数为了对齐
        渲染行，无需加该参数，显示效果没明显差别。
        '''
        #searchstr=r"(?<=\\sphinxmultirow){(?P<count>[0-9]*)}{(?P<flag>[0-9]*)}([\s\S]*?)\\sphinxstyletheadfamily(?P<content>[\s\S]*?)\\par([\s\S]*?)(?=\}\%)"
        searchstr=r"(?<=\\sphinxmultirow){(?P<count>[1-9]*)}{(?P<flag>[0-9]*)}{([\s\S]*?){\\sphinxcolwidth{[0-9]*}{[0-9]*}}[\s]*(\\sphinxstyletheadfamily)?(?P<content>[\s\S]*?)\\par([\s\S]*?)(?=\}\%)"
        match = re.finditer(searchstr, cellstr, re.M | re.I | re.U)
        count = ""
        flag = ""
        content = ""
        for m in match:
            if count == "":
                count = m.group('count')
            if flag == "":
                flag = m.group('flag')
            if content == "":
                content = m.group('content')

        #如果是自动换行，设置列宽
        coluwidth = "*"
        if len(columnwidthlst) > 0:
            if tablestyle_dict is not None and \
                __dictIsHasKey__(tablestyle_dict,"ismultirowautowrap"):
                if tablestyle_dict['ismultirowautowrap'] is True:
                    coluwidth = columnwidthlst[colindex-1]
            else:
                if self.tablesattrobj.ismultirowautowrap is True:
                    coluwidth = columnwidthlst[colindex-1]

        #if (jsonmultirow is not None) and \
        #    (multirowindex>=0 and len(jsonmultirow)>multirowindex) and \
        #    (len(columnwidthlst)>jsonmultirow[multirowindex]-1):
        #    #得到要设置自动换行multirow的列号
        #    coluwidth = columnwidthlst[jsonmultirow[multirowindex]-1]
        #    #print("coluwidth = %s" % coluwidth)
        #else:
        #    coluwidth = "*"

        #if r"\sphinxstyletheadfamily" in cellstr:
        #实测，multirow要支持渲染背景色，必须带上sphinxstyletheadfamily，否则会编译失败
        sphinxstyletheadfamily = r"\sphinxstyletheadfamily"
        raggedright = ""
        if (isvertical is True) or \
                (tablestyle_dict is not None and \
                 __dictIsHasKey__(tablestyle_dict, "ismultirowatupcell") and \
                 tablestyle_dict['ismultirowatupcell'] is True) or \
                self.tablesattrobj.ismultirowatupcell is True:
            #raggedright = r"{\raggedright}"
            # 得到单元格内容
            cellstr = self.commonstr % {
                'sphinxstyletheadfamily': sphinxstyletheadfamily,
                'content': self.__delNotMustrowStrFromContent(content)
            }
            multistr = self.multirowstr % {
                'count': count,
                'coluwidth': coluwidth,
                'raggedright': raggedright,
                'sphinxstyletheadfamily': sphinxstyletheadfamily,
                'content': ""
            }
            #将flag对应的内容加到字典里，以方便后续交换
            contentdict[flag] = str(int(count) + lineindex-1) + "," + multistr #为了支持超过两行的合并单元格，将count数量也带出去。第一个","之前的就是count数量

            #返回空的字符串
            return cellstr
        else:
            ismanualwraplst = [False]
            if (tablestyle_dict is not None and \
                    __dictIsHasKey__(tablestyle_dict, "ismultirowatlowcell") and \
                    tablestyle_dict['ismultirowatlowcell'] is True ) or \
                    self.tablesattrobj.ismultirowatlowcell is True:

                simplemultirow = self.simplemultirow % {
                    'count': count,
                    'coluwidth': coluwidth
                }

                cellstr = self.commonstr % {
                    'sphinxstyletheadfamily': sphinxstyletheadfamily,
                    'content': self.__delNotMustrowStrFromContent(content, ismanualwraplst)
                }

                contentdict[flag] = str(int(count) + lineindex - 1) + "," + cellstr

                return simplemultirow

            multistr = self.multirowstr % {
                'count': count,
                'coluwidth': coluwidth,
                'raggedright': raggedright,
                'sphinxstyletheadfamily': sphinxstyletheadfamily,
                'content': self.__delNotMustStrFromContent(content)
            }
            # 将flag对应的内容加到字典里，以方便后续交换
            contentdict[flag] = str(int(count) + lineindex - 1) + "," + multistr  # 为了支持超过两行的合并单元格，将count数量也带出去。第一个","之前的就是count数量

            # 返回空的字符串
            return self.emptystr



    def __ModifyMulticolumn(self,cellstr,tablestyle_dict):
        '''
        :param cellstr:
        :return:
        '''
        searchstr = r"(?<=\\multicolumn){(?P<count>[1-9]*)}\{(?P<flag>[\s\S]*?)\}\{%(?P<prefix>[\s\S]*?)\\sphinxstyletheadfamily(?P<content>[\s\S]*?)\\par(?P<suffix>[\s\S]*)"
        match = re.finditer(searchstr, cellstr, re.M | re.I | re.U)
        count = ""
        content = ""
        flag=""
        prefix=""
        suffix=""
        for m in match:
            if count == "":
                count = m.group('count')
            if flag == "":
                flag = m.group('flag')
            if content == "":
                content = m.group('content')
            if prefix == "":
                prefix = m.group('prefix')
                #如果有\begin{varwidth}[t],替换为\begin{varwidth}[c]
                #[t]会使得当自动换行的时候，单元格上面会有很大的空白
                #print('===========================================')
                #print(prefix)
                searchstr = r"\\begin{varwidth}\[t\]"
                replstr = r"\\begin{varwidth}[c]"
                prefix = re.sub(searchstr,replstr,prefix,1,re.I|re.U|re.M)
                #print(prefix)
                #print('===========================================')
            if suffix == "":
                suffix = '\n\\par' + m.group('suffix')  #因为正则表达式把\par去掉了，再加上

        #行合并单元格，改成居中显示
        newflag=re.sub(r"[a-z]",r"c",flag,1,re.I)
        #newflag = flag
        if (tablestyle_dict is not None and \
            __dictIsHasKey__(tablestyle_dict,'ismulticolleftalign') and \
                tablestyle_dict['ismulticolleftalign'] is True) or \
                self.tablesattrobj.ismulticolleftalign is True:
                #multistr = self.comcolstr % {
                #    'count': count,
                #    'flag': flag,
                #    'sphinxstyletheadfamily': "\\sphinxstyletheadfamily",
                #    'content': self.__delNotMustStrFromContent(content)
                #}
                multistr = self.multicolumnstr % {
                    'count':count,
                    'flag': flag,
                    'prefix':prefix,
                    'sphinxstyletheadfamily': "\\sphinxstyletheadfamily",
                    'content':self.__delNotMustStrFromContent(content),
                    'suffix':suffix
                }
        else:
                multistr = self.multicolumnstr % {
                    'count':count,
                    'flag': newflag,
                    'prefix':prefix,
                    'sphinxstyletheadfamily': "\\sphinxstyletheadfamily",
                    'content':self.__delNotMustStrFromContent(content),
                    'suffix':suffix
                }

        #multistr = self.comcolstr % {
        #    'count': count,
        #    'flag': flag,
        #    'sphinxstyletheadfamily': "\\sphinxstyletheadfamily",
        #    'content': self.__delNotMustStrFromContent(content)
        #}
        return multistr

    def __ModifyCellContentForMergeCell(self,cellcontent,
                                        tablestyle_dict,islongtable,
                                        contentdict,columnwidthlst,jsonmultirow,
                                        colindex,lineindex,multirowindex,ismulticol = False):
        '''
        修改单元格内容
        :param cellcontent:
        :param ismulticol: 当前行是否有列合并单元格。
        如果有列合并单元格，则独立的单元格需要用multcolumn命令，否则无法对齐。
        :return:
        '''
        multirowflag = False #是否是multirow单元格
        cellstr = cellcontent[0] #取得单元格内容
        #print('===========================')
        #print(cellstr)

        #查找有没有\cline指令，如果有这个指令，为了划线清晰在前面再多加一个。
        searchstr= r"\\cline\{[\s\S]*?\}"
        match = re.search(searchstr,cellstr,re.I | re.M)
        if match is not None:
            cline = match.group()
            #去掉开头的换行符，否则影响后面的正则表达式,导致正则表达式查找失败
            cellstr = cellstr.lstrip()
            cellstr = cline + cellstr

        sphinxstyletheadfamily=""
        if r'\sphinxmultirow' in cellstr:
            cellcontent[0] = self.__ModifySphinxMultirow(cellstr,
                                                         contentdict,
                                                         columnwidthlst,
                                                         jsonmultirow,
                                                         lineindex,
                                                         colindex,
                                                         multirowindex,
                                                         tablestyle_dict)
            multirowflag = True
        elif r'\multicolumn' in cellstr:
            cellcontent[0] = self.__ModifyMulticolumn(cellstr,tablestyle_dict)
        else:
            if colindex == 1:
                if len(columnwidthlst)>0 and colindex > 0:
                    flag = "|m{" + columnwidthlst[colindex - 1] + "}|"
                else:
                    flag = "|l|"
            else:
                if len(columnwidthlst) > 0 and colindex > 0:
                    flag = "m{" + columnwidthlst[colindex - 1] + "}|"
                else:
                    flag = "l|"

            #查找\sphinxAtStartPar位置
            precell = ""
            pos = cellstr.find(r'\sphinxstyletheadfamily')
            #print("cellstr = %s"  % cellstr)
            #print("pos=%d" % pos)
            if pos > 0:
                #说明前面还有内容，需要把前面的内容保存下来
                precell = cellstr[0:pos] + "\n"
            if pos != -1:
                cellstr = cellstr[pos+len(r'\sphinxstyletheadfamily'):len(cellstr)]
                sphinxstyletheadfamily = "\\sphinxstyletheadfamily"

            ismanualwraplst=[False]
            self.__delNotMustStrFromContent(cellstr,ismanualwraplst)
            if ismanualwraplst[0] is True:
                if colindex == 1:
                    flag = "|l|"
                else:
                    flag = "l|"
                colwidth = ""
                if (len(columnwidthlst) > 0) and (colindex > 0):
                    colwidth = columnwidthlst[colindex-1]

                fullcontentstr = self.normalstr % {
                   'flag': flag,
                   'colwidth': colwidth,
                   'sphinxstyletheadfamily': sphinxstyletheadfamily,
                   'content': self.__delNotMustStrFromContent(cellstr)
                }
            else:
                fullcontentstr = self.comcolstr % {
                    'count': "1",
                    'flag': flag,
                    'sphinxstyletheadfamily': sphinxstyletheadfamily,
                    'content': self.__delNotMustStrFromContent(cellstr)
                }
            #if ismulticol is True:
            #必须用multicolumn命令，否则当单元格后面还有列的时候，刷新会出现问题
            #fullcontentstr = self.comcolstr % {
            #   'count': "1",
            #   'flag': flag,
            #   'sphinxstyletheadfamily': sphinxstyletheadfamily,
            #   'content': self.__delNotMustStrFromContent(cellstr)
            #}
            #else:
            #    fullcontentstr = self.commonstr % {
            #        'sphinxstyletheadfamily': "\\sphinxstyletheadfamily",
            #        'content': self.__delNotMustStrFromContent(contentstr)
            #    }
            cellcontent[0] =precell + fullcontentstr
        #print('===========================')
        return multirowflag

    def __FindMergeRowCountAndContent(self,multirowstr):
        #找出合并行的数量和合并单元格的内容
        count = 0
        newmultistr = multirowstr

        try:
            searchstr = "([1-9]*?)(?=,)"
            match = re.match(searchstr,newmultistr,re.I)
            if match is not None:
                count = int(match.group(0))
                startpos = match.end()
                newmultistr = multirowstr[startpos+1:len(multirowstr)]
        finally:
            return count,newmultistr

    def __AdjustMultirowContent(self,cellcontent,contentdict,lineindex,colindex,columnwidthlst):
        '''
        调整multirow单元格到对应的下面的单元格
        '''
        #查找flag数量
        cellstr = cellcontent[0]
        #查找有没有\cline指令，如果有这个指令，为了划线清晰在前面再多加一个。
        searchstr= r"\\cline\{[\s\S]*?\}"
        match = re.search(searchstr,cellstr,re.I | re.M)
        if match is not None:
            cline = match.group()
            #去掉开头的换行符，否则影响后面的正则表达式,导致正则表达式查找失败
            cellstr = cellstr.lstrip()
            cellstr = cline + cellstr

        searchstr = r"\\sphinxtablestrut\{(?P<flag>[0-9]*)\}"
        matchobj = re.search(searchstr, cellstr, re.I | re.M | re.U)
        flag = matchobj.group('flag')
        #得到修改后的multirow内容
        if __dictIsHasKey__(contentdict,flag):
            multirowstr = contentdict[flag]
            #替换实际列号
            replstr = r'\\sphinxtablestrut{' + str(colindex)+'}'
            cellstr = re.sub(searchstr,replstr,cellstr,1,re.I|re.U|re.M)
            #找出合并行的最大数量
            count,multirowstr = self.__FindMergeRowCountAndContent(multirowstr)
            if lineindex == count: #是最后一行了再合并，否则会重复
                cellcontent[0] = cellstr+"\n"+multirowstr
            else:
                cellcontent[0] = cellstr+'\n' + r"\cellcolor" + self.tablesattrobj.headtype + "{}"

        return cellcontent

    def __GetFactColIndex(self,cellcontent):
        '''
        得到真实的列索引。当有multicolumn的时候，列索引需要加合并单元格的数量。
        返回合并单元格的数量，没合并单元格返回1
        :para cellcontent: 单元格内容字符串
        '''
        if len(cellcontent) ==0:
            return 0
        searchstr = r"\\multicolumn{(?P<count>[1-9]+)}"
        match = re.search(searchstr,cellcontent,re.I|re.U)
        if match is None:
            return 1 #不是合并单元格，默认加1列
        factcount = 1
        try:
            factcount = int(match.group('count'))
        except Exception as e:
            print('------------------')
            print(e)
            # traceback.print_stack()
            traceback.print_exc()
            print('------------------')

        return factcount
    def __ModifyTableHeadForFamilyList(self,tablehead,isLongtable,columnwidthlst,jsonmultirow,tablestyle_dict):
        '''
        用列表的方式逐个拆解每个单元格内容，列表格式为[['cell content','分割符号&或者\\'],[],[]]
        :param tablehead:
        :return:
        '''
        #内容的起始位置
        startpos = tablehead.find(r'\\hline') + len(r'\\hline')

        contentdict ={} #创建一个字典，用来保存mlutirow交换内容
        #得到每个单元格的内容
        colindex = 0  #是否第一个单元格的索引
        lineindex = 1  #行索引，为了支持多行合并，默认起始在第一行
        # multirowindex：第几个multirow单元格的索引号，用于作为conf.json中multirowcolumn字段的索引值，
        # 取出当前multirow单元格真实所在列
        #得到该列的列宽，填写到multirow指令里面，实现multirow指令中的内容自动换行
        multirowindex = 0
        lineflag = 0
        newtablehead = "\\hline\n"
        while startpos !=-1:
            multirowflag = False  # 是否是multirow处理，是的话__ModifyCellContentForMergeCell返回true，默认返回false
            lineflag,startpos,cellcontent = self.__FindCellContentForHead(tablehead,startpos)
            if cellcontent is not None:
                factcolcount = self.__GetFactColIndex(cellcontent[0])
                colindex = colindex+factcolcount #当前列号
                if r"\sphinxtablestrut" in cellcontent[0]:
                    # 需要将multirow的内容copy到该单元格
                    self.__AdjustMultirowContent(cellcontent, contentdict,lineindex,colindex,columnwidthlst)
                else:

                    multirowflag = self.__ModifyCellContentForMergeCell(cellcontent,
                                                                        tablestyle_dict,
                                                                        isLongtable,
                                                                        contentdict,
                                                                        columnwidthlst,
                                                                        jsonmultirow,
                                                                        colindex,
                                                                        lineindex,
                                                                        multirowindex)
                newtablehead = newtablehead+cellcontent[0] + "\n" + cellcontent[1]+ "\n"

            if multirowflag is True:
                multirowindex = multirowindex+1
            lineindex = lineindex + lineflag  # 计算新的行数
            if lineflag:
                # 如果开启了新行，则列索引从0开始计算
                colindex = 0

        #组成新的表头内容
        newtablehead =newtablehead + "\\hline\n"
        return newtablehead
    def __modifyTableHeadForMinusSign(self,content,isLongTable,headtype,tablestyle_dict):
        """
        如果表头不是用“=”分割的，而是用“-”分割，调用该函数修改表头
        """
        # 先找出第一行
        if isLongTable:
            searchstr = r'\\endlastfoot[\s]+(\\sphinxtableatstartofbodyhook)?(?P<content>[\s\S]*?)\\hline'
        else:
            searchstr = r'\\hline(?P<content>[\s\S]*?)\\hline'
        m = re.search(searchstr, content, re.M | re.I | re.U)
        headcontent = m.group('content')  # 匹配到的第一个即为表头内容
        posarr = m.span('content')  # 保存起始位置和结束位置，便于组成新的内容

        if 'multicolumn' in headcontent:
            return content

        if r'\sphinxstyletheadfamily' in headcontent:
            pattern = re.compile(r'(?<=\\sphinxstyletheadfamily)(?P<value>[\s\S]*?)(?=(\\unskip|&)|\\\\)',
                                 re.M | re.I | re.U)
            aftercontent = headcontent
            # pattern = re.compile(r'(?<=\\sphinxstylethead{\\sphinxstyletheadfamily)([\s\S]*?)(?=\\unskip}\\relax &)', re.M | re.I|re.U)
        else:
            aftercontent = headcontent.replace(r'\\', '&', 1)
            pattern = re.compile(r'(?P<value>[\s\S]*?)(&\s{1})', re.M | re.I | re.U)
            # pattern = re.compile(r'[\s\S]*?&|\\unskip', re.M | re.I|re.U)

        mobjarr = pattern.finditer(aftercontent)
        headlist = []
        preposlist = []
        fontcolor = ""
        for mobj in mobjarr:
            amarr = mobj.group('value')
            curposlist = [mobj.start(), mobj.start() + len(amarr)]

            # 用表头内容数组替换
            if self.tablesattrobj.headfontcolor != "":
                fontcolor = self.tablesattrobj.headfontcolor
            # 先去掉首尾空格，避免首尾有空格无法去掉回车换行符
            amarr = amarr.strip()
            amarr = amarr.strip('\r')
            amarr = amarr.strip('\n')
            amarr = amarr.strip()  # 再去掉首尾空格，避免多余的空格出现
            if amarr == '':
                continue
            amarr = self.__delNotMustStrFromContent(amarr)
            fontcolor = fontcolor.replace('{}', '{' + amarr + '}', 1)
            # 当表头没有合并单元格时，不能用cellcolor，否则表头不对齐
            # fontcolor = self.commonstr % {
            #    'sphinxstyletheadfamily':'',
            #    'content': self.__delNotMustStrFromContent(amarr)
            # }
            if len(preposlist) > 0:
                headlist.append(headcontent[preposlist[1]:curposlist[0]])
            else:
                headlist.append(headcontent[0:curposlist[0]])
            headlist.append(fontcolor)
            preposlist = curposlist

        headlist.append(headcontent[preposlist[1]:len(headcontent)])  # 把最后一个字符串加上
        headcontent = ''
        for prelist in headlist:
            headcontent = headcontent + prelist + '\n'
        newcontent = content[0:posarr[0]] + r'\rowcolor' + headtype + '\n' + headcontent + content[
                                                                                           posarr[1]:len(content)]
        return newcontent
    #修改sphinx自动生成的长表格表头
    def __ModifyLongTableHead(self,content,columnwidthlst,headtype,jsonmultirow,tablestyle_dict):

        tablehead = self.__FindTableHeadForFamily(content,True)
        if tablehead != "" and (r"\sphinxmultirow" in tablehead or r"\multicolumn" in tablehead):
            #长表格自定义sphinxcolwidth的第2个参数默认为5，否则无法实现自动换行
            #self.normalstr= self.normalstr % {
            #    'flag': '%(flag)s',
            #    'colwidth':'5',
            #    'sphinxstyletheadfamily':'%(sphinxstyletheadfamily)s',
            #    'content':'%(content)s'
            #}
            newtablehead = self.__ModifyTableHeadForFamilyList(tablehead,
                                                               True,
                                                               columnwidthlst,
                                                               jsonmultirow,
                                                               tablestyle_dict)
            return content.replace(tablehead,newtablehead,2)

        #先找出第一行
        if tablehead.strip() == r"\hline":
            #进入该分支，说明表格设置为了长表格，但是表头没有用“=”分割，用“-”做的分割。
            return self.__modifyTableHeadForMinusSign(content,True,headtype,tablestyle_dict)

        searchstr = r'\\hline(?P<content>[\s\S]*?)\\hline'
        pattern = re.compile(searchstr,re.M | re.I|re.U)
        matchiter = pattern.finditer(content)
        posarr = []
        i = 0
        for m in matchiter:

            if i > 1:
                break

            posarr.append([])
            posarr[i] = m.span() #保存起始位置和结束位置，便于组成新的内容

            if i ==0:
                newcontent = content[0:posarr[i][0]]
            else:
                newcontent = newcontent+content[posarr[i-1][1]:posarr[i][0]]

            newcontent += r'\hline\rowcolor'+headtype
            headcontent = m.group(1) #匹配到的第一个即为表头内容

            if 'multicolumn' in headcontent:
                return content

            headlist = []

            if r'\sphinxstyletheadfamily' in headcontent:
                pattern = re.compile(r'(?<=\\sphinxstyletheadfamily)(?P<value>[\s\S]*?)(?=(\\unskip|&)|\\\\)', re.M | re.I|re.U)
                aftercontent = headcontent
                mobjarr = pattern.finditer(aftercontent)

                preposlist = []
                for mobj in mobjarr:
                    amarr = mobj.group('value')
                    curposlist = mobj.span()

                    #用表头内容数组替换
                    fontcolor = self.tablesattrobj.headfontcolor
                    #先去掉首尾空格，避免首尾有空格无法去掉回车换行符
                    amarr = amarr.strip()
                    amarr = amarr.strip('\r')
                    amarr = amarr.strip('\n')
                    amarr = amarr.strip()  #再去掉首尾空格，避免多余的空格出现
                    if amarr == '':
                        continue
                    amarr = self.__delNotMustStrFromContent(amarr)
                    fontcolor = fontcolor.replace('{}','{'+ amarr+'}',1)
                    # 当表头没有合并单元格时，不能用cellcolor，否则表头不对齐
                    #fontcolor = self.commonstr % {
                    #    'sphinxstyletheadfamily': '',
                    #    'content': self.__delNotMustStrFromContent(amarr)
                    #}
                    if len(preposlist) > 0:
                        headlist.append(headcontent[preposlist[1]:curposlist[0]])
                    else:
                        headlist.append(headcontent[0:curposlist[0]])
                    headlist.append(fontcolor)
                    preposlist = curposlist
                headlist.append(headcontent[preposlist[1]:len(headcontent)])  #把最后一个字符串加上
                headcontent = ''
                for prelist in headlist:
                    headcontent = headcontent + prelist + '\n'
                newcontent += headcontent+r'\hline'
            i +=1
        newcontent += content[posarr[i-1][1]:len(content)]
        return newcontent

    def __ModifyTableHead(self, content,columnwidthlst, headtype,jsonmultirow,tablestyle_dict):

        tablehead = self.__FindTableHeadForFamily(content,False)
        if tablehead != "" and (r"\sphinxmultirow" in tablehead or r"\multicolumn" in tablehead):
            #为了实现自动换行，普通表格自定义sphinxcolwidth的第2个参数默认为3，否则无法实现自动换行
            #self.normalstr= self.normalstr % {
            #    'flag': '%(flag)s',
            #    'colwidth':'3',
            #    'sphinxstyletheadfamily':'%(sphinxstyletheadfamily)s',
            #    'content':'%(content)s'
            #}
            # 得到每一列的列宽供multirow自动换行使用
            newtablehead = self.__ModifyTableHeadForFamilyList(tablehead,
                                                               False,
                                                               columnwidthlst,
                                                               jsonmultirow,
                                                               tablestyle_dict)
            return content.replace(tablehead,newtablehead,2)

        return self.__modifyTableHeadForMinusSign(content,False,headtype,tablestyle_dict)
        """
        #先找出第一行
        searchstr = r'\\hline(?P<content>[\s\S]*?)\\hline'
        m = re.search(searchstr, content, re.M|re.I|re.U )
        headcontent = m.group(1) #匹配到的第一个即为表头内容
        posarr = m.span(1)  #保存起始位置和结束位置，便于组成新的内容

        if 'multicolumn' in headcontent:
            return content

        if r'\sphinxstyletheadfamily' in headcontent:
            pattern = re.compile(r'(?<=\\sphinxstyletheadfamily)(?P<value>[\s\S]*?)(?=(\\unskip|&)|\\\\)', re.M | re.I|re.U)
            aftercontent = headcontent
            #pattern = re.compile(r'(?<=\\sphinxstylethead{\\sphinxstyletheadfamily)([\s\S]*?)(?=\\unskip}\\relax &)', re.M | re.I|re.U)
        else:
            aftercontent = headcontent.replace(r'\\','&',1)
            pattern = re.compile(r'(?P<value>[\s\S]*?)(&\s{1})', re.M | re.I|re.U)
            #pattern = re.compile(r'[\s\S]*?&|\\unskip', re.M | re.I|re.U)

        mobjarr = pattern.finditer(aftercontent)
        headlist = []
        preposlist = []
        fontcolor=""
        for mobj in mobjarr:
            amarr = mobj.group('value')
            curposlist = [mobj.start(),mobj.start()+len(amarr)]

            #用表头内容数组替换
            if self.tablesattrobj.headfontcolor !="":
                fontcolor = self.tablesattrobj.headfontcolor
            #先去掉首尾空格，避免首尾有空格无法去掉回车换行符
            amarr = amarr.strip()
            amarr = amarr.strip('\r')
            amarr = amarr.strip('\n')
            amarr = amarr.strip()  #再去掉首尾空格，避免多余的空格出现
            if amarr == '':
                continue
            amarr = self.__delNotMustStrFromContent(amarr)
            fontcolor = fontcolor.replace('{}','{'+ amarr+'}',1)
            #当表头没有合并单元格时，不能用cellcolor，否则表头不对齐
            #fontcolor = self.commonstr % {
            #    'sphinxstyletheadfamily':'',
            #    'content': self.__delNotMustStrFromContent(amarr)
            #}
            if len(preposlist) > 0:
                headlist.append(headcontent[preposlist[1]:curposlist[0]])
            else:
                headlist.append(headcontent[0:curposlist[0]])
            headlist.append(fontcolor)
            preposlist = curposlist

        headlist.append(headcontent[preposlist[1]:len(headcontent)])  #把最后一个字符串加上
        headcontent = ''
        for prelist in headlist:
            headcontent = headcontent + prelist + '\n'
        newcontent = content[0:posarr[0]]+r'\rowcolor'+headtype+'\n'+headcontent+content[posarr[1]:len(content)]
        return newcontent
        """

    def __ModifySecondTableHeadForLongTable(self,headcontent):
        #如果表头没有渲染过，对于只渲染第一列的长表格，分页的表格不能有重复的表头内容，需要将其删除
        startpos = headcontent.find(r"\hline")
        newhead = headcontent[0:startpos] + "\\hline\n\\endhead" #因为前面删除了行结束符，最后再添加上
        return newhead

    def __GetTableRowCount(self,tablecontent):
        '''
        得到传进来的表格行数量
        统计原则：latex以“\\”+回车换行为表格的一行，因此以"\\CRLF"或者“\\LF”作为一行，
        正则表达式统计"\\CRLF"或者“\\LF”的数量作为行的数量。
        :param tablecontent: 表格内容，必须去除了latex表格的前面参数部分，否则得到的第一行也包含其内容
        :return: 返回行的数量和每一行内容的列表，如果找不到的话，返回0和None。
        '''
        #以下正则表达式还可以解析出表格的每一行内容
        #在末尾补充一个换行符，避免如果最后一行以“\\”结尾，漏掉最后一行。
        tablecontent = tablecontent+'\n'
        searchstr = "[\s\S]*?(?<count>\\\\[\r|\r\n|\n|\n\r])[\s\S]*?"
        pattern = re.compile(searchstr,re.I | re.M |re.U)
        linelst =  pattern.findall(tablecontent)
        if linelst is not None:
            return len(linelst),linelst

        return 0,None


    def __ModifyVerticalLongTable(self, singletablecontent,tablestyle_dict,columnwidthlst):
        #修改长表格的第一列
        #查找长表格的表头，如果是通过sphinx指令设置的长表格，表头分为三个部分，
        #第一个表头以第一个\hline开始，以\endfirsthead结束
        #第二个表头，以\endfirsthead后续的内容开始，\endhead结束
        #下页继续的说明，以\endhead后续内容开始，以\endlastfoot结束，该部分内容不做处理
        #以\endlastfoot后续内容的第一部分即第一列的内容，需要添加背景色和字体颜色。后续的\hline按正常处理即可
        #先将所有表头内容找出来
        isCusHead = tablestyle_dict['isCusHead']
        headstartpos = singletablecontent.find(r"\hline")
        headendpos = singletablecontent.find(r"\endfirsthead")
        fcontent = singletablecontent[0:headstartpos] #字符串起始内容
        fhead = singletablecontent[headstartpos:headendpos+len(r"\endfirsthead")] #第一个表头内容
        #修改第一个表头内容
        if not isCusHead: #如果表头已经渲染过了，则不再对第一个单元格再渲染一边。
            fhead = self.__ModifyTableByLine(fhead,True,True,tablestyle_dict,columnwidthlst)

        #获得第2个表头的原始内容
        headstartpos = singletablecontent.find(r"\endhead")
        shead = singletablecontent[headendpos+len(r"\endfirsthead"):headstartpos+len(r"\endhead")] #第2个表头的内容
        #修改第2个表头内容
        if not isCusHead: #如果表头已经渲染过了，则不再对第一个单元格再渲染一遍。
            shead = self.__ModifySecondTableHeadForLongTable(shead)
        #获得第3部分内容
        headendpos = singletablecontent.find(r"\endlastfoot")
        thead = singletablecontent[headstartpos+len(r"\endhead"):headendpos+len(r"\endlastfoot")] #第3部分表头内容
        othercontent = singletablecontent[headendpos+len(r"\endlastfoot"):len(singletablecontent)]  #其它表格内容

        #因为第3部分后续紧跟的内容即为下一行的内容，为了统一处理，在前面固定添加行元素，再修改完成后，再将其删除
        othercontent = "\\hline\n" + othercontent
        #修改表格内容部分
        othercontent = self.__ModifyTableByLine(othercontent,True,True,tablestyle_dict,columnwidthlst)
        #修改完成后，去掉固定添加的内容
        othercontent = othercontent[len("\\hline\n"):len(othercontent)]
        return fcontent + fhead + shead + thead + othercontent

    def __RenderTableHeadByLine(self, linecontent, lineindex, isLongtable, columnwidthlst, jsonmultirow, tablestyle_dict, contentdict):
        '''
        该函数暂时未用
        用列表的方式逐个拆解每个单元格内容，列表格式为[['cell content','分割符号&或者\\'],[],[]]
        :return:
        '''
        # 内容的起始位置
        searchstr = r"(\\hline|\\cline|\\sphinxcline)"
        match = re.match(searchstr,linecontent)
        if match is None:
            return linecontent
        if match.group() == r"\hline":
            startpos = match.end()
            newtablehead = "\\hline\n"
        else:
            startpos = 0
            newtablehead = ""

        # 得到每个单元格的内容
        colindex = 0  # 是否第一个单元格的索引
        lineindex = 1  # 行索引，为了支持多行合并，默认起始在第一行
        # multirowindex：第几个multirow单元格的索引号，用于作为conf.json中multirowcolumn字段的索引值，
        # 取出当前multirow单元格真实所在列
        # 得到该列的列宽，填写到multirow指令里面，实现multirow指令中的内容自动换行
        multirowindex = 0
        lineflag = 0

        while startpos != -1:
            multirowflag = False  # 是否是multirow处理，是的话__ModifyCellContentForMergeCell返回true，默认返回false
            lineflag, startpos, cellcontent = self.__FindCellContentForHead(linecontent, startpos)
            if cellcontent is not None:
                factcolcount = self.__GetFactColIndex(cellcontent[0])
                colindex = colindex + factcolcount  # 当前列号
                if r"\sphinxtablestrut" in cellcontent[0]:
                    # 需要将multirow的内容copy到该单元格
                    self.__AdjustMultirowContent(cellcontent, contentdict, lineindex,colindex,columnwidthlst)
                else:

                    multirowflag = self.__ModifyCellContentForMergeCell(cellcontent,
                                                                        tablestyle_dict,
                                                                        isLongtable,
                                                                        contentdict,
                                                                        columnwidthlst,
                                                                        jsonmultirow,
                                                                        colindex,
                                                                        lineindex,
                                                                        multirowindex)
                newtablehead = newtablehead + cellcontent[0] + "\n" + cellcontent[1] + "\n"

            if multirowflag is True:
                multirowindex = multirowindex + 1
            lineindex = lineindex + lineflag  # 计算新的行数
            if lineflag:
                # 如果开启了新行，则列索引从0开始计算
                colindex = 0

        # 组成新的表头内容
        #newtablehead = newtablehead + "\\hline\n"
        return newtablehead

    def __ModifyLineContent(self,linecontent,isvertical,islongtable,lineindex,columnwidthlst,colno,maxcolno,tablestyle_dict,rowdict):
        '''
        修改行内容，当colno为-1时，则修改整行内容，否则根据colno的值修改单元格
        :param linecontent: 该行的内容，以行结束标志“\\”结束。
        :param columnwidthlst: 保存的每一列的列宽内容，根据tablecolumns参数解析出来
                               如果实现自动换行，必须用tablecolumns这个参数，不能用widths参数
        :param colno: 要修改的列号，如果为-1，则修改整行内容。否则根据colno的值修改单元格。
                      如果该行有合并列单元格，则colno为合并单元格后的列号。
        :param maxcolno: 将合并单元格拆分后，得到的最大的列号，该列号为了取得列宽，使multirow内容自动换行。
        :param rowdict: 传出参数，如果是multirow的合并单元格，则返回单元格的内容
        :return: 返回修改后的行的内容。
        '''
        if isvertical is False:
            #修改行，暂时不支持修改任意行，表头的渲染由其他函数完成
            #按表头内容渲染

            jsonmultirow = []  # 保存需要设置自动换行的合并单元格的列号。
            if __dictIsHasKey__(tablestyle_dict, 'multirowcolumn'):
                jsonmultirow = tablestyle_dict['multirowcolumn']
            newlinecontent = self.__RenderTableHeadByLine(linecontent,
                                                          lineindex,
                                                          islongtable,
                                                          columnwidthlst,
                                                          jsonmultirow,
                                                          tablestyle_dict,
                                                          rowdict)
            return newlinecontent

        #下面的内容修改列，即只修改行的第一个单元格
        linecontentstr=linecontent
        #startpos = linecontent.find('\\hline')
        #lineprefix = ""
        #if startpos != -1:
        #    linecontentstr = linecontent[startpos + len("\\hline"):len(linecontent)]
        #    lineprefix = linecontent[0:startpos+len("\\hline")]
        #得到行内容和前缀
        lineprefix = ""
        searchstr=r"\\hline|\\cline{\S+?}|\\sphinxcline{\S+?}\\sphinxfixclines{\d+?}"
        match = re.search(searchstr,linecontent,re.I|re.U|re.M)
        if match is not None:
            linecontentstr = linecontent[match.end():len(linecontent)]
            lineprefix = linecontent[0:match.start()+ len(match.group())]

        othercontent = ""
        newlinecontent = ""

        #暂时仅支持修改第一列
        lineflag,nextpos,cellcontent = self.__FindCellContentForHead(linecontentstr,0)
        if (cellcontent is None) or (r"\cellcolor" in cellcontent[0]):
            #说明该单元格已经修改过了，直接返回
            return linecontent

        othercontent = linecontentstr[nextpos:len(linecontentstr)]

        if r"\sphinxtablestrut" in cellcontent[0]:
            # 需要将multirow的内容copy到该单元格
            self.__AdjustMultirowContent(cellcontent, rowdict, lineindex,maxcolno,columnwidthlst)
        else:
            jsonmultirow =[1]
            cellstr = cellcontent[0]  # 取得单元格内容

            if r'\sphinxmultirow' in cellstr:
                cellcontent[0] = self.__ModifySphinxMultirow(cellstr,
                                                             rowdict,
                                                             columnwidthlst,
                                                             jsonmultirow,
                                                             lineindex,
                                                             maxcolno,
                                                             0,
                                                             tablestyle_dict,
                                                             isvertical)
            else:
                coluwidth = "*"
                if len(columnwidthlst) > 0:
                    coluwidth = columnwidthlst[0]
                # 查找\sphinxAtStartPar位置
                pos = cellstr.find(r'\sphinxstyletheadfamily')
                if pos != -1:
                    contentstr = cellstr[pos + len(r'\sphinxstyletheadfamily'):len(cellstr)]

                    fullcontentstr = self.commonstr % {
                           'sphinxstyletheadfamily': "\\sphinxstyletheadfamily",
                           'content': self.__delNotMustStrFromContent(contentstr)
                    }
                    #fullcontentstr = self.multirowstr % {
                    #    'count': 1,
                    #    'coluwidth': coluwidth,
                    #    'sphinxstyletheadfamily': r"\sphinxstyletheadfamily",
                    #    'content': self.__delNotMustStrFromContent(contentstr)
                    #}
                    #fullcontentstr = self.normalstr % {
                    #    'flag': '|l|',
                    #    'sphinxstyletheadfamily': "\\sphinxstyletheadfamily",
                    #    'content': self.__delNotMustStrFromContent(contentstr)
                    #}

                    cellcontent[0] = fullcontentstr
                else:
                    fullcontentstr = self.commonstr % {
                        'sphinxstyletheadfamily': "\\sphinxstyletheadfamily",
                        'content': self.__delNotMustStrFromContent(cellstr)
                    }
                    #fullcontentstr = self.multirowstr % {
                    #    'count': 1,
                    #    'coluwidth': coluwidth,
                    #    'sphinxstyletheadfamily': r"\sphinxstyletheadfamily",
                    #    'content': self.__delNotMustStrFromContent(cellstr)
                    #}
                    #fullcontentstr = self.normalstr % {
                    #    'flag': '|l|',
                    #    'sphinxstyletheadfamily': "\\sphinxstyletheadfamily",
                    #    'content': self.__delNotMustStrFromContent(cellstr)
                    #}

                    cellcontent[0] = fullcontentstr

        newlinecontent = lineprefix +"\n" + cellcontent[0] + "\n" + cellcontent[1] + "\n" + othercontent
        return newlinecontent


    def __ModifyTableByLine(self,tablecontent,isvertical,islongtable,tablestyle_dict,columnwidthlst):
        '''
        按行渲染每一个单元格。根据正则表达式解析出每一行的内容，
        然后再解析这一行中每一个单元格的内容，根据条件判断是否需要渲染该单元格
        :param tablecontent:
        表格内容，对于长表格去除表格头的表格，对于普通表格，整个表格内容
        :param isvertical:
        是否修改列还是修改行
        :return: 返回修改后的内容
        '''
        #找出每一行的内容

        searchstr = r'(\\hline|\\cline{\S+}|\\sphinxcline{\S+})(?P<content>[\s\S]*?)(?=\\hline|\\cline{\S+}|\\sphinxcline{\S+})'
        pattern = re.compile(searchstr,re.M | re.I|re.U)
        # 得到每一行的内容，以\hline或者\cline开头，以\\结尾
        linelst = pattern.findall(tablecontent)
        if len(linelst) ==0:
            return tablecontent

        tableprefix = ""  #得到latex表格的前缀参数部分，除表格内容外的部分
        tablepostfix = "" #得到latex表格的后缀参数部分，出表格内容外的部分
        prepos = tablecontent.find(''.join(linelst[0]))
        if prepos > 0:
            tableprefix = tablecontent[0:prepos]
        lastline = ''.join(linelst[-1])
        postpos = tablecontent.find(lastline)
        if (postpos+len(lastline)) < len(tablecontent):
            tablepostfix = tablecontent[postpos+len(lastline):len(tablecontent)]

        newtablecontent = ""
        rowdict = {} #保存合并单元格的字典，用于multirow
        for lineindex in range(0,len(linelst)):
            line =''.join(linelst[lineindex])
            newline= self.__ModifyLineContent(line,isvertical,islongtable,lineindex+1,columnwidthlst,1,1,tablestyle_dict,rowdict)
            newtablecontent = newtablecontent + newline

        newtablecontent = tableprefix + newtablecontent+tablepostfix
        return newtablecontent

    #渲染树型表格的第一列
    def __ModifyVerticalTable(self, singletablecontent, tablestyle_dict, columnwidthlst):

        #找出每一行的内容
        searchstr = r'(\\hline|\\cline{\S+}|\\sphinxcline{\S+})(?P<content>[\s\S]*?)(?=\\hline|\\cline{\S+}|\\sphinxcline{\S+})'
        pattern = re.compile(searchstr,re.M | re.I|re.U)
        matchiter = pattern.finditer(singletablecontent)
        posarr=[]  #保存位置，便于组合
        i = 0
        for m in matchiter:

            posarr.append([])
            posarr[i] = m.span()

            if i ==0:
                newcontent = singletablecontent[0:posarr[i][0]]+m.group(1)
            else:
                newcontent = newcontent+singletablecontent[posarr[i-1][1]:posarr[i][0]]+m.group(1)

            cellcontent = m.group('content')
            #匹配到的第一个即为表头内容
            #将第一个单元格内容渲染成蓝底白字
            firstcellcontent = self.__ModifyFirstColumnType(cellcontent)
            newcontent += firstcellcontent
            i+=1
        newcontent += singletablecontent[posarr[i-1][1]:len(singletablecontent)]
        return newcontent

    #渲染第一个单元格内容
    def __ModifyFirstColumnType(self,cellcontent):

        new_cellcontent = ""

        if r'\sphinxstyletheadfamily' in cellcontent:

            searchstr = r'(?<=\\sphinxstyletheadfamily)(?P<value>[\s\S]*?)(?=(\\unskip|&)|\\\\)'

            aftercontent = cellcontent.strip()
            aftercontent = aftercontent.strip('\r')
            aftercontent = aftercontent.strip('\n')
            aftercontent = aftercontent.strip()
            mobj = re.search(searchstr, aftercontent, re.M|re.I|re.U )  #匹配到的第一个既是需要修改的内容

            #修改字体颜色
            amarr = mobj.group('value')
            posarr = mobj.span()
            new_cellcontent = aftercontent[0:posarr[0]]+'\n'+r'\cellcolor'+self.tablesattrobj.headtype
            #用表头内容数组替换
            fontcolor = self.tablesattrobj.headfontcolor
            #先去掉首尾空格，避免首尾有空格无法去掉回车换行符
            amarr = amarr.strip()
            amarr = amarr.strip('\r')
            amarr = amarr.strip('\n')
            amarr = amarr.strip()  #再去掉首尾空格，避免多余的空格出现
            #if amarr == '':
            #    continue
            if (r'\textbf' or r'\textcolor') in amarr:
                return cellcontent
            fontcolor = fontcolor.replace('{}','{'+ amarr+'}',1)
            new_cellcontent +=r'{'+fontcolor + '}\n' + aftercontent[posarr[1]:len(aftercontent)]
        else:
            aftercontent = cellcontent.replace(r'\\','&',1)
            #去掉首尾空格和换行符
            aftercontent = aftercontent.strip()
            aftercontent = aftercontent.strip('\r')
            aftercontent = aftercontent.strip('\n')
            aftercontent = aftercontent.strip()

            tmplist = re.split(r'&',aftercontent)

            preposlist = 0
            #只对第一个做修改
            onelist = tmplist[0]
            #先去掉首尾空格，避免首尾有空格无法去掉回车换行符
            onelist = onelist.strip()
            onelist = onelist.strip('\r')
            onelist = onelist.strip('\n')
            onelist = onelist.strip()  #再去掉首尾空格，避免多余的空格出现
            #if onelist == '':
            #        continue
            if (r'\textbf' or r'\textcolor') in onelist:
                return cellcontent
            new_cellcontent = self.__ModifyFirstColumnContentPart(onelist)

            for i in range(1,len(tmplist)):
                if len(tmplist[i])>0:
                    new_cellcontent += '&' +tmplist[i]
            new_cellcontent+=r'\\' #将最后被替换掉的\\再加上

        return new_cellcontent + '\n'

    def __ModifyFirstColumnContentPart(self,firstcontent):
        #查找firtcontent的内容部分，因为如果存在合并单元格，第一列的内容起始部分不一定是内容，可能是latex的其它标签。
        #sphinx生成的latex表格都以“\sphinxAtStartPar”标识开始
        startpos = firstcontent.find(r"\sphinxAtStartPar")
        firstpart = ""
        if startpos==-1:
            contentpart = firstcontent
        else:
            firstpart = firstcontent[0:startpos]
            contentpart=firstcontent[startpos:len(firstcontent)]
        fontcolor = self.tablesattrobj.headfontcolor
        # 先去掉首尾空格，避免首尾有空格无法去掉回车换行符
        new_cellcontent = '\n'+r'\cellcolor'+self.tablesattrobj.headtype+r'{'+fontcolor.replace('{}','{'+ contentpart+'}',1)+r'}'+'\n'
        return firstpart+new_cellcontent

    def __ModifyReplaceContent(self):
        '''
        修改replace内容。为了删除sphinx在替换时多加的空格，在替换时，每个替换内容前后都加了"@@"。
        对于前后都加了“@@”的内容，删除前后的"@@"，同时删除前后的空格。
        :return:
        '''
        searchstr = r"@@(?P<content>[\s\S]+?)@@"
        pattern = re.compile(searchstr)
        match = pattern.search(self.content)
        if match is None:
            return None
        str = match.group()
        replace = match.group('content')

        prepos = match.start()
        endpos = match.end()
        replaceed = str
        if prepos > 1:
            #判断前一个字符是否为空格
            prechar = self.content[prepos-1]
            if prechar==" ":
                replaceed = " "+replaceed

        endchar = self.content[endpos]
        if endchar == " ":
            replaceed = replaceed + " "

        #进行替换
        self.content = self.content.replace(replaceed,replace)

        return self.content


    def ModifyReplaceContent(self):

        while True:
            '''
            可能会有多个需要替换，直到找不到为止
            '''
            result = self.__ModifyReplaceContent()
            if result is None:
                break

class clsSensitiveWords:

    def __init__(self,entext):
        self.passkey="Hello123"
        self.entext = entext
        self.jsonwords = GetSensitiveword()

    def GetFullSensitiveWords(self):
        senwords = "|".join(self.jsonwords)
        detext = ""
        if self.entext !="":
            command="echo '" + self.entext +"'| openssl aes-128-cbc -d -base64 -pbkdf2 -k " + self.passkey
            process = Popen(command, stdout=PIPE, stderr=STDOUT, shell=True)
            with process.stdout:
                for line in iter(process.stdout.readline, b''):
                    detext = detext + line.decode().strip()
            senwords = senwords + '|' + detext

        return senwords

class clsCheckDocCopyrightYear:
    '''
    检查文档是否符合规范。
    2022年07月12日更新
    -------------------
    增加对当前年份的检查，如果年份不符合要求，自动修改conf.py和copyright.rst文档中的年份。
    '''
    def __init__(self,app,copyrightfile):
        self.app=app
        self.confdir = app.confdir
        self.language = app.config.language
        self.confright = app.config.copyright
        if (app.config.latex_elements is not None) and __dictIsHasKey__(app.config.latex_elements,'preamble'):
            self.latex_preamble = app.config.latex_elements['preamble']
        else:
            self.latex_preamble = ""

        if len(copyrightfile) > 0:
            self.copyrightfile = copyrightfile
        elif hasattr(app.config,"copyfile_path") and len(app.config.copyfile_path)>0:
            self.copyrightfile = app.config.copyfile_path
        else:
            self.copyrightfile = ""

    def __FindCopyrightFile(self):
        '''
        #如果copyrightfile为空，则查找copyrightfile文件
        '''
        if self.copyrightfile =="":

            copyrightpre = "copyright/copyright"
            filepath = os.path.join(self.app.srcdir,copyrightpre + ".rst")

            if os.path.isfile(filepath):
                return filepath

            if self.language=="zh_CN":
                filepath = os.path.join(self.app.srcdir,copyrightpre + "_zh.rst")
            else:
                filepath = os.path.join(self.app.srcdir,copyrightpre +"_en.rst")

            if os.path.isfile(filepath):
                return filepath
            else:
                return ""
        else:
            filepath = os.path.join(self.confdir , self.copyrightfile)

            #转为绝对路径
            filepath = os.path.abspath(filepath)
            if os.path.isfile(filepath):
                return filepath
            else:
                return ""

    def __IsModifyfootYear(self,year):
        '''
        查找lfoot设置版权的语句年份是否需要修改
        :return: True - 需要修改年份
                 False - 不需要修改年份
        '''

        ismodify = False
        searchstr = r'\\lfoot{[ ]*Copyright [\s\S]*?}'
        pattern = re.compile(searchstr,re.I|re.U)
        matchiter = pattern.finditer(self.latex_preamble)
        if matchiter is None:
            return ismodify

        for m in matchiter:
            lfoot = m.group()
            if year in lfoot:
                #如果当前年份已在脚注中，则认为年份已修改，则不再修改。
                break
            else:
                # 如果当前年份不在脚注中，则认为年份还未修改，需要修改。
                ismodify = True
                break
        return ismodify

    def __ModifyYearofConfforDetail(self,year,ispremble=False):
        '''
        修改conf.py中的年份
        :param ispremble: 是否只修改ispremble中脚注的年份。
                          True-只修改脚注的年份；False-同时修改copyright和脚注的年份。
        '''
        #打开文件
        conffile = self.confdir +"/conf.py"
        fo = codecs.open(conffile, "r+", encoding='utf-8')
        textcontent = fo.read()
        fo.close()

        if ispremble is False:
            #修改copyright的年份
            searchstr = r"[0-9]{4}"
            matchobj = re.search(searchstr,self.confright)
            if matchobj is not None:
                copyright = re.sub(searchstr,year,self.confright)
                textcontent=textcontent.replace(self.confright,copyright,1)
                #为了看到最终效果，同时修改已经读取的变量，因为调用该函数前，conf.py的配置已经被sphinx读取
                self.app.config.copyright=copyright

        #开始替换脚注中的年份
        #得到脚注中带有年份的声明
        searchstr = r'\\lfoot{[ ]*Copyright [\s\S]*?}'
        pattern = re.compile(searchstr, re.I | re.U)
        matchiter = pattern.finditer(self.latex_preamble)
        if matchiter is not None:
            for m in matchiter:
                lfoot = m.group()
                searchstr= r"[0-9]{4}"
                matchobj = re.search(searchstr,lfoot)
                if matchobj is not None:
                    newlfoot = re.sub(searchstr,year,lfoot)
                    textcontent=textcontent.replace(lfoot,newlfoot)
                    #两个脚注相同，因此只替换一次即可
                    # 为了看到最终效果，同时修改已经读取的变量，因为调用该函数前，conf.py的配置已经被sphinx读取
                    self.app.config.latex_elements['preamble'] = self.latex_preamble.replace(lfoot,newlfoot)
                    break
        #将替换后的内容重新写入文件
        fw = codecs.open(conffile, "w+", encoding='utf-8')
        fw.write(textcontent)
        fw.close()

    def __ModifyYearofCopyright(self,year):

        #得到copyright绝对文件名
        filepath = self.__FindCopyrightFile()
        if filepath == "":
            return

        fo = codecs.open(filepath, "r+", encoding='utf-8')
        textcontent = fo.read()
        fo.close()
        #新的版权年份
        newyear = '© ' + year
        #if newyear in textcontent:
        #    return
        isupdate = False
        #避免替换错误，循环找到最后一个进行替换
        searchstr = "©( )*([0-9]{4})"
        pattern = re.compile(searchstr, re.I | re.U)
        matchiter = pattern.finditer(textcontent)
        for m in matchiter:
            oldyear = m.group()
            if oldyear == newyear:
                continue
            textcontent = textcontent.replace(oldyear,newyear,1)
            isupdate = True

        if isupdate:
            fw = codecs.open(filepath, "w+", encoding='utf-8')
            fw.write(textcontent)
            fw.close()
        #pattern = re.compile(searchstr, re.I | re.U)
        #matchlst = pattern.findall(textcontent)
        #if len(matchlst)==0:
        #    #如果没有找到四数字的年份，直接返回
        #    return

        #i=0
        #content=""
        #matchiter = pattern.finditer(textcontent)
        #for m in matchiter:
        #    if (i+1)==len(matchlst):
        #        #对最后一个年份进行替换
        #        content = textcontent[0:m.start()] + year + textcontent[m.end():len(textcontent)]
        #        break
        #    else:
        #        i = i+1

        #fw = codecs.open(filepath, "w+", encoding='utf-8')
        #fw.write(content)
        #fw.close

    def __ModifyYearofConf(self,year):
        #开始修改conf.py中的年份
        if (year not in self.confright) and (self.confright != ""):
            #修改conf.py中的年份
            self.__ModifyYearofConfforDetail(year)
            return
        #开始修改conf.py中脚注的年份
        if (self.latex_preamble!="") and (self.__IsModifyfootYear(year) is True):
            #需要修改年份，走到这一步conf.py的copyright年份肯定修改过的，因此只修改foot中的年份即可
            self.__ModifyYearofConfforDetail(year,True)
            return

    def CheckYearIsRight(self):
        '''
        检查年份是否正确
        '''
        #得到系统当前年份
        today = dt.date.today()
        year = today.strftime('%Y')
        #开始检查conf.py中的年份
        self.__ModifyYearofConf(year)
        #开始检查copyright文件中的年份
        self.__ModifyYearofCopyright(year)

    def __AddAdSymbolForReplaceZh(self,content):
        '''
        解析是否需要替换，需要的话就进行替换。
        替换的条件：1.被替换字符串全部为中文，2。被替换字符的首尾有一个不为英文
        :param content: 要替换的内容
        :return: 已替换或者未替换的字符串
        '''
        #用正则表达式解析出要替换的内容
        searchstr = r"[\s\S]+ replace:: (?P<replace>[\s\S]+)"
        match = re.search(searchstr,content)
        if match is None:
            return content
        replacestr = match.group('replace').strip('\r')
        #取得字符串的首和尾字符
        #prochr = ord(replacestr[0])
        #print(prochr)
        #epichr = ord(replacestr[-1])
        #print(epichr)
        #if ((64 < prochr and prochr<91) or \
        #    (96 < prochr and prochr<123)) and \
        #    ((64 < epichr and epichr < 91) or \
        #     (96 < epichr and epichr < 123)):
        #    #首尾都为英文则无需替换空格，直接返回
        #    return content
        newreplacestr = "@@"+replacestr+"@@"
        return content.replace(replacestr,newreplacestr)

    def __ParseReplaceList(self,contentlst):
        '''
        为中文替换词前后添加@@符号
        :param contentlst: 需要替换的list内容
        :return: 新生成列表，如果无需替换，返回None。
        '''
        if contentlst is None or len(contentlst)==0:
            return None

        for i in range(0,len(contentlst)):
            content = contentlst[i]
            if content=="":
                continue
            #进行替换
            newcontent = self.__AddAdSymbolForReplaceZh(content)
            contentlst[i] = newcontent

        return contentlst

    def CheckReplaceContent(self):
        '''
        该函数用来判断conf.py文件中是否包含rst_epilog或者rst_prolog的配置
        如果有的话，修改这两个参数中的配置在需替换的名词前后都加@@符号，以方便在latex文件中删除前后的空格。
        该修改为了解决sphinx对中文的替换前后都加空格的bug。
        :return:
        '''
        epilogstr = self.app.config.rst_epilog
        prologstr = self.app.config.rst_prolog

        if self.app.config.language != "zh_CN":
            '''
            如果不是中文文档，则直接返回。
            '''
            return

        if prologstr is not None:
            # 为rst_prolog的中文替换前后添加@@符号
            prologlst = prologstr.split('\n')
            newlst = self.__ParseReplaceList(prologlst)
            if newlst is not None:
                self.app.config.rst_prolog = '\n'.join(newlst)

        if epilogstr is not None:
            #为rst_epilog的中文替换前后添加@@符号
            epiloglst = epilogstr.split('\n')
            newlst = self.__ParseReplaceList(epiloglst)
            if newlst is not None:
                self.app.config.rst_epilog = '\n'.join(newlst)



class clsCheckHtmlRule:
    '''
    检查html配置是否符合规范
    '''
    def __init__(self,app):
        self.app=app
        #错误描述模板
        #配置参数错误
        self.configparam_error="Parameter %(param)s configuration error!"
        self.correctvalue="The correct value is %(value)s!"
        self.config_error=self.configparam_error + self.correctvalue+"\n"
        #工程名称配置不一致
        self.inconsistent_error="The project names configured by conf.py and %(rootdoc)s.rst are inconsistent!\n"
        #缺少custom.css文件
        self.csslack_error="custom.css file not found!\n"
        self.config_detail = "For detailed configuration, see wiki: pageId=46137795\n"

    def __CheckCommonParamForHtml(self, theme):
        '''
        检查相关配置是否正确
        :param theme:为主题文件，如果和配置的不一致返回错误。默认为sphinx_rtd_theme。
        '''
        error = ""
        if self.app.config.html_theme != theme:
            error = self.config_error % {
                'param': 'html_theme',
                'value': theme
            }
        if self.app.config.html_copy_source is True:
            error = error + self.config_error % {
                'param': 'html_copy_source',
                'value': 'False'
            }
        # 判断配置的css文件是否存在
        if len(self.app.config.html_css_files) == 0:
            error = error + self.configparam_error % {
                'param': 'html_css_files',
            } + \
            "Please configure a css file, such as custom.css."
            "The css file needs to be found under the path specified by html_static_path!"
        if len(self.app.config.html_static_path) == 0:
            error = error + self.configparam_error % {
                'param': 'html_static_path',
            } + \
            "Please configure the path of the file specified by html_css_files."
        return error

    def CheckProjectNameConsistent(self):
        '''
        检查conf.py中project配置工程名称和主索引文件index.rst中的标题是否一致
        两者不一致返回错误。
        该接口必须在builder-inited事件之后调用，否则得不到index.rst的标题，无法比较。
        '''
        #为了兼容性获取config对象的属性，没有的属性不做比较
        cfgattrlst=dir(self.app.config)
        #主索引文件名
        if "master_doc" in cfgattrlst:
            indexname = self.app.config.master_doc
        elif "root_doc" in cfgattrlst:
            indexname = self.app.config.root_doc
        else:
            return ""

        #得到主索引文件的标题
        indextitle = self.app.env.titles[indexname][0]
        #得到配置的工程名称
        projectname = self.app.config.project
        if indextitle != projectname:
            error = self.inconsistent_error % {
                'rootdoc': indexname
            }
            return error
        else:
            return ""

    def CheckCommConfigHtmlRule(self, theme):
        '''
        检查生成html前参数配置是否符合规范
        '''
        # 开始检查参数配置
        error = self.__CheckCommonParamForHtml(theme)
        return error

def CheckProjectNameIsConsistent(app):
    '''
    检查conf.py中配置的工程名称与主索引文件的标题是否一致
    '''
    checkDocobj = clsCheckHtmlRule(app)
    return checkDocobj.CheckProjectNameConsistent()

def CheckHtmlConfigIsCorrect(app,theme='sphinx_rtd_theme'):
    '''
    检查html配置参数是否正确
    '''
    checkDocobj = clsCheckHtmlRule(app)
    return checkDocobj.CheckCommConfigHtmlRule(theme)

def CheckCurrentYearAndModify(app,copyrightfile=""):
    '''
    检查conf.py和copyright中的年份是否为当前年份，如果不是当前年份则进行修改。
    Conf.py主要检查latex_elements和copyright中的年份是否为当前年份
    copyright主要检查copyright.rst文件最后一句的年份是否为当前系统年份。
    :param app:
    :param copyrightfile: 相对于conf.py的相对路径。
    '''
    checkDocobj = clsCheckDocCopyrightYear(app,copyrightfile)
    #检查年份是否需要修改
    checkDocobj.CheckYearIsRight()
    #if (app.builder is not None) and \
    #    (app.builder.name=='latex' or app.builder.name=='latexpdf'):
    #    checkDocobj.CheckReplaceContent()

def __returnCfgDate(app):
    '''
    返回conf.py中配置的today的日期。如果today为空，则返回当前日期。
    '''
    if app.config.today != "":
        return app.config.today
    today = dt.date.today()
    if app.config.language =="zh_CN":
        return today.strftime('%Y年%m月%d日')
    else:
        return today.strftime('%b %d, %Y')

def CheckUpdateHistory(app,docname,source):
    '''
    检查是否需要添加更新历史，在source_read_handler这个事件里调用。
    为了提高效率，不写在类里，减少创建和销毁对象的消耗。
    :param app:
    :param docname:
    :param source: 源文件内容
    '''
    #判断文档中是否包含更新历史
    # 默认为英文
    title = "Update History"
    update="Date:"
    changes="Changes:"
    language = app.config.language
    if language == "zh_CN":
        title = "更新历史"
        update="更新时间："
        changes="更新内容："
    index = source.find(title)
    if index==-1:
        return source

    #说明该文档中包含更新历史，判断是否为更新历史标题
    precontent = source[0:index]
    leftcontent = source[index:len(source)]
    #按行读取剩余内容，理论上第2行为标题的下标，如果不是，则认为这个更新历史不是标题，直接忽略。
    linelst = leftcontent.splitlines(True)
    flagline = linelst[1]
    search = r"([\x21-\x2f|\x3a-\x40|\x5b-\x60|\x7b-\x7d])\1+(?=[\r|\n|\r\n])"
    matchobj=re.match(search,flagline)
    if matchobj is None:
        return source
    #说明是更新历史章节，再比较版本号是否一致
    search = r"\* \*\*V(?P<version>[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2})\*\*"
    matchobj=re.search(search,leftcontent)
    if matchobj is None:
        return source
    version=matchobj.group('version')
    if version==app.config.version:
        return source
    #如果版本不一致，自动添加新的版本内容
    dateformat = __returnCfgDate(app)
    updatehistory="\n* **V%(version)s**\n\n" \
                  "  **%(date)s** %(dateformat)s\n\n" \
                  "  **%(changes)s**\n\n" \
                  "  - 无内容更新。\n"

    upcontent= updatehistory % {
        'version':app.config.version,
        'date':update,
        'dateformat':dateformat,
        'changes':changes
        }
    #拆分列表
    titlestr =''.join(linelst[0:2])
    otherstr =''.join(linelst[2:len(linelst)+1])
    newcontent = precontent + titlestr + upcontent + otherstr
    #将新内容写入文档
    filepath = app.project.doc2path(docname)
    fw = codecs.open(filepath, "w+", encoding='utf-8')
    fw.write(newcontent)
    fw.close()
    return newcontent


def __exe_command_forprint(command):
    """
    执行 shell 命令并实时打印输出
    :param command: shell 命令
    :return: process, exitcode
    """
    process = Popen(command, stdout=PIPE, stderr=STDOUT, shell=True)
    with process.stdout:
        for line in iter(process.stdout.readline, b''):
            print(line.decode().strip())
    exitcode = process.wait()
    return process, exitcode

def CheckSensitiveWordsForLinux(app,docname,entext):
    '''
    该接口对外提供，可在conf.py的source_read_handler处理函数中调用。对sphinx工程中的文件检查敏感词。

    **注意：**

    因为检查敏感词使用的linux命令，如果敏感词中包含中文，则在不支持中文的linux系统下不能使用该接口，
    否则无法完成敏感词。

    :param app: sphinx环境变量，可以获取输出目录、文件后缀等相关信息。
    :param docname: 需检查的文件路径。该路径是基于源目录的相对路径，而且不包含文件扩展名，
                    因此在检查时需要补充上扩展名，并转为绝对路径。
    :param entext: 已加密的密文，如果为空，则只对conf.json中保存的敏感词做检查。
                   如何加密参考GetFullSensitiveWords的实现，加解密保持一致。
    :return: 无返回值。
    '''
    #windows系统请使用工具组开发的sensitive-filter.exe工具
    try:
        if platform.system().lower() == 'windows':
            return

        filepath = app.project.doc2path(docname,True)

        #print("Checking file:["+docname+"]")
        sensitivewordsobj = clsSensitiveWords(entext)
        sensitivewords = sensitivewordsobj.GetFullSensitiveWords()
        #print(sensitivewords)
        cmd = "egrep -rni --color=always '"+  sensitivewords + "' " + filepath
        __exe_command_forprint(cmd)
    except Exception as e:
        print("Sensitive words check failed, the system may not support Chinese. "
             "The detailed error information is as follows:")
        print(e)
        return

# 打开Makefile文件查找source和build文件夹
def OpenMakefile():
    global source_dir
    global build_dir
    source_dir = ''
    build_dir = ''
    try:
        if platform.system().lower() == 'windows':
            with open('make.bat',"r") as f:
                fstr = f.read()

            #用正则表达式查找source和build文件夹具体路径
            searchstr = r"set *SOURCEDIR *= *(\S+)"
            m = re.search(searchstr, fstr, re.M|re.I|re.U )
            source_dir = m.group(1) #匹配到的第一个即为source所在目录

            searchstr = r"set *BUILDDIR *= *(\S+)"
            m = re.search(searchstr, fstr, re.M|re.I|re.U )
            build_dir = m.group(1) #匹配到的第一个即为build所在目录
        else:
            with open('Makefile',"r") as f:
                fstr = f.read()

            #用正则表达式查找source和build文件夹具体路径
            searchstr = r"SOURCEDIR *= *(\S+)"
            m = re.search(searchstr, fstr, re.M|re.I|re.U )
            source_dir = m.group(1) #匹配到的第一个即为源所在目录

            searchstr = r"BUILDDIR *= *(\S+)"
            m = re.search(searchstr, fstr, re.M|re.I|re.U )
            build_dir = m.group(1) #匹配到的第一个即为源所在目录

    except Exception as e:
        print(e)
        return

def GetLatex_documents():
    global source_dir
    if source_dir == '':
        return
    #得到配置文件conf.py的路径
    if source_dir == '.':
        confdir = './conf.py'
    else:
        confdir = './' + source_dir +'/conf.py'

    conffile = os.path.abspath(confdir)
     #打开conf.py文件
    with codecs.open(conffile,"r+",encoding='utf-8') as f:
            fstr = f.read()
    list = []
    versioninfo = GetVersionInfo(fstr)
    fileprefile = GetFilePreInfo(fstr)
    if versioninfo=="" or fileprefile=="":
    #根据正则表达式，找出latex_documents内容
        searchstr = r"latex_documents *= *\[([\s\S]*?)\]"
        m = re.search(searchstr, fstr, re.M|re.I|re.U )
        latex_documents = m.group(1) #匹配到的第一个即为源所在目录
        #拆分二维数组，兼容多个情况
        list = latex_documents.split(")")
        for i in range(len(list)):
            if IsComment(list[i]):
                list[i]= list[i].split(",")
        list.pop()
    else:
        #创建2维列表
        for i in range(1):
            list.append([])
            for j in range(2):
                list[i].append("")
        list[0][0] = "comment"  #为了兼容文件解析内容，进行补位。
        list[0][1] = '"' + fileprefile + versioninfo + '.tex"'  #为了兼容解析过程，添加双引号。
    return list

#得到版本信息
def GetVersionInfo(fstr):

    if releasever != '':
       return releasever

    versioninfo = ""
    searchstr = r"version *= *(u*)'(.*?)'"
    m = re.search(searchstr, fstr, re.I)
    if m is not None:
        versioninfo = m.group(2) #匹配到的第一个即为源所在目录
    print("version = " + versioninfo)
    return versioninfo

#得到文件前缀信息
def GetFilePreInfo(fstr):
    filepre = ""
    searchstr = r"curfnpre *= *(u*)'(.*?)'"
    m = re.search(searchstr, fstr, re.M|re.I|re.U )
    if m is not None:
        filepre = m.group(2) #匹配到的第一个即为源所在目录
    return filepre

#判断是否为注释行
def IsComment(instr):

    if instr.strip() is None:
        return False

    rule = re.compile('^#.*$')
    if rule.match(instr.strip()) is None:
        return True
    else:
        return False

#根据正则表达式取单引号和双引号中的内容
def getquomarkcontent(strarr):
    #根据正则表达式，找出双引号和单引号中的内容
    searchstr = r"[\"|'](.*?)[\"|']"
    m = re.search(searchstr, strarr, re.M|re.I|re.U )
    if m is None:
        return None
    return m.group(1).strip() #匹配到的第一个即为源所在目录

def GetConfjsondict(app):
    '''
    得到配置文件conf.json的字典
    :param app:
    :return:
    '''
    global __load_dict__


    if app is not None:
        #先判断conf.json路径在配置文件中是否配置
        if hasattr(app.config, "confjson_path") and len(app.config.confjson_path) > 0:
            confpath = os.path.abspath(app.confdir + "/" + app.config.confjson_path)
        else:
            confpath = os.path.join(app.confdir ,"conf.json")

        if os.path.exists(confpath):
            __load_dict__ = __openconfjsonfile__(confpath)
        else:
            __load_dict__ = __openconfjsonfile__()
    else:
        __load_dict__ = __openconfjsonfile__()


def Modifylatex_main(build_dir,latexdocumentslst,app=None,language='zh_CN'):

    global __load_dict__
    global doc_language
    doc_language=language

    if len(__load_dict__) == 0:
        GetConfjsondict(app)


    doclen = len(latexdocumentslst)
    for i in range(0,doclen):
        #得到latex路径
        latexpath = build_dir
        desbkpaperfile= os.path.join(latexpath,'chapterbkpaper.pdf')
        #copy 背景图到latex路径，背景图必须与该文件在同一个目录下
        if (app is not None) and hasattr(app.config,'chapterbkpaper_image') and (app.config.chapterbkpaper_image!=""):
            #转为绝对路径进行判断，兼容conf.py和parsejson.py不在同一目录的情况
            bkpaperimage = os.path.abspath(os.path.join(app.confdir,app.config.chapterbkpaper_image))
            if os.path.isfile (bkpaperimage):
                shutil.copy(bkpaperimage,desbkpaperfile)
        elif os.path.exists('./chapterbkpaper.pdf'):
            shutil.copy('./chapterbkpaper.pdf', latexpath)
        else:
            #取配置文件所在目录下的chapterbkpaper.pdf,将其copy到latex文件夹
            bkpaperimage = os.path.abspath(os.path.join(app.confdir,"chapterbkpaper.pdf"))
            if os.path.isfile(bkpaperimage):
                shutil.copy(bkpaperimage, latexpath)

        #得到相对路径
        filename = latexdocumentslst[i][1]
        if  filename is None:
            continue
        texfilepath = os.path.join(latexpath,filename)
        #相对路径转绝对路径
        texfile = os.path.abspath(texfilepath)
        if not os.path.exists(texfile):
            continue
        fo = codecs.open(texfile, "r+",encoding = 'utf-8')
        texcontent = fo.read()
        fo.close()

        #得到修改tex文件的对象
        ModTexobj = clsModifyTex(texcontent)
        ModTexobj.AddPackageToTex()
        ModTexobj.AddOtherTocToTex()
        ModTexobj.AddCustormOptionsToTex()
        ModTexobj.ModifyReplacePackage()
        ModTexobj.ModifyFunctionBackColor()
        ModTexobj.ModifyTablesAttributes()
        #if (app is not None) and \
        #    (app.builder.name=='latex' or app.builder.name=='latexpdf'):
        #    ModTexobj.ModifyReplaceContent()

        fw = codecs.open(texfile, "w+",encoding = 'utf-8')
        fw.write(ModTexobj.content)
        fw.close()

def parseargv():
    global source_dir
    global latex_dir
    if len(sys.argv) <= 1:
        return
    for i in range(1,len(sys.argv)):
        param = sys.argv[i]
        if param=='-c':  #解析conf.py所在目录
            source_dir = sys.argv[i+1] #保存相对路径
            #print("argv source=" + source_dir)
            i = i+2
        elif param=='-l': #保存输出的latex目录
            latex_dir = sys.argv[i+1] #保存相对路径
            i = i+2
            #print("latex_dir = "+latex_dir)

source_dir = '' #保存源文件所在目录
build_dir = ''  #保存生成文件所在目录
releasever = ''
latex_dir = ''
doc_language =''
__load_dict__ = {}

if __name__ == '__main__':

    parseargv()  #解析系统参数
    OpenMakefile()

    latex_documents = GetLatex_documents() #保存latex文档所在数组
    __load_dict__ = __openconfjsonfile__()

    latexdir=""
    if latex_dir == '':
        latexdir = '/latex/'
    else:
        latexdir = '/' + latex_dir + '/'

    doclen = len(latex_documents)
    for i in range(0,doclen):
        #得到latex路径
        latexpath = './' + build_dir + latexdir
        #copy 背景图到latex路径，背景图必须与该文件在同一个目录下
        if os.path.exists('./chapterbkpaper.pdf'):
            shutil.copy('./chapterbkpaper.pdf',latexpath)
        #得到相对路径
        if getquomarkcontent(latex_documents[i][1]) is None:
            continue
        texfilepath = latexpath + getquomarkcontent(latex_documents[i][1])
        #相对路径转绝对路径
        texfile = os.path.abspath(texfilepath)
        if not os.path.exists(texfile):
            continue
        fo = codecs.open(texfile, "r+",encoding = 'utf-8')
        texcontent = fo.read()
        fo.close()

        #得到修改tex文件的对象
        ModTexobj = clsModifyTex(texcontent)
        ModTexobj.AddPackageToTex()
        ModTexobj.AddOtherTocToTex()
        ModTexobj.AddCustormOptionsToTex()
        ModTexobj.ModifyFunctionBackColor()
        ModTexobj.ModifyTablesAttributes()
        ModTexobj.ModifyReplacePackage()

        fw = codecs.open(texfile, "w+",encoding = 'utf-8')
        fw.write(ModTexobj.content)
        fw.close()
