当前位置: 首页 > article >正文

LaTeXChecker:使用 Python 实现以主 TEX 文件作为输入的 LaTeX 检查和统计工具

使用 Python 实现以主 TEX 文件作为输入的 LaTeX 检查和统计工具,适用于包括但不限于一稿多模板的复杂排版方式,工具以只读模式运行。

Github 链接:https://github.com/BatchClayderman/LaTeXChecker

import os
from sys import argv, executable, exit
from re import findall
from time import sleep
PLATFORM = __import__("platform").system().upper()
os.chdir(os.path.abspath(os.path.dirname(__file__)))
EXIT_SUCCESS = 0
EXIT_FAILURE = 1
CLEAR_SCREEN_COMMAND = ("CLS" if PLATFORM == "WINDOWS" else "clear") if __import__("sys").stdin.isatty() else None
STARTUP_COMMAND_FORMAT = "START \"\" \"{0}\" \"{1}\" \"{2}\"" if PLATFORM == "WINDOWS" else "\"{0}\" \"{1}\" \"{2}\"&"


class DebugLevel:
	defaultCharacter = "?"
	defaultName = "*"
	defaultSymbol = "[?]"
	defaultValue = 0
	def __init__(self:object, d:dict) -> object:
		self.character = d["character"] if "character" in d else DebugLevel.defaultCharacter
		self.name = d["name"] if "name" in d else DebugLevel.defaultName
		self.symbol = d["symbol"] if "symbol" in d else DebugLevel.defaultSymbol
		self.value = d["value"] if "value" in d else DebugLevel.defaultValue
	def __eq__(self:object, other:object) -> bool:
		if isinstance(other, DebugLevel):
			return self.value == other.value
		elif isinstance(other, (int, float)):
			return self.value == other
		else:
			return False
	def __ne__(self:object, other:object) -> bool:
		if isinstance(other, DebugLevel):
			return self.value != other.value
		elif isinstance(other, (int, float)):
			return self.value != other
		else:
			return True
	def __lt__(self:object, other:object) -> bool:
		if isinstance(other, DebugLevel):
			return self.value < other.value
		elif isinstance(other, (int, float)):
			return self.value < other
		else:
			raise TypeError("TypeError: '<' not supported between instances of '{0}' and '{1}'".format(type(self), type(other)))
	def __le__(self:object, other:object) -> bool:
		if isinstance(other, DebugLevel):
			return self.value <= other.value
		elif isinstance(other, (int, float)):
			return self.value <= other
		else:
			raise TypeError("TypeError: '<=' not supported between instances of '{0}' and '{1}'".format(type(self), type(other)))
	def __gt__(self:object, other:object) -> bool:
		if isinstance(other, DebugLevel):
			return self.value > other.value
		elif isinstance(other, (int, float)):
			return self.value > other
		else:
			raise TypeError("TypeError: '>' not supported between instances of '{0}' and '{1}'".format(type(self), type(other)))
	def __ge__(self:object, other:object) -> bool:
		if isinstance(other, DebugLevel):
			return self.value >= other.value
		elif isinstance(other, (int, float)):
			return self.value >= other
		else:
			raise TypeError("TypeError: '>=' not supported between instances of '{0}' and '{1}'".format(type(self), type(other)))
	def __bool__(self:object) -> bool:
		return bool(self.value)
	def __int__(self:object) -> int:
		return self.value
	def __str__(self:object) -> str:
		return str(self.symbol)
Prompt = DebugLevel({"character":"P", "name":"Prompt", "symbol":"[P]", "value":100})
Fatal = DebugLevel({"character":"F", "name":"Fatal", "symbol":"[F]", "value":60})
Critical = DebugLevel({"character":"C", "name":"Critical", "symbol":"[C]", "value":50})
Error = DebugLevel({"character":"E", "name":"Error", "symbol":"[E]", "value":40})
Warning = DebugLevel({"character":"W", "name":"Warning", "symbol":"[W]", "value":30})
Info = DebugLevel({"character":"I", "name":"Info", "symbol":"[I]", "value":20})
Debug = DebugLevel({"character":"D", "name":"Debug", "symbol":"[D]", "value":10})

class PointerNode:
	def __init__(self:object, filePath:str, parentPointerNode:object = None) -> object:
		self.__filePath = os.path.abspath(str(filePath).replace("\"", "")) # must be an absolute path since __eq__ needs to use it
		self.__lineIdx = 0 # the initialization value
		self.__charIdx = -1 # the initialization value
		self.__parentPointerNode = parentPointerNode if isinstance(parentPointerNode, PointerNode) else None
		self.__children = None # initialization flag
		self.__lines = None # initialization flag
	def __getTxt(self:object, filePath:str, index:int = 0) -> str: # get .txt content
		coding = ("utf-8", "gbk", "utf-16") # codings
		if 0 <= index < len(coding): # in the range
			try:
				with open(filePath, "r", encoding = coding[index]) as f:
					content = f.read()
				return content[1:] if content.startswith("\ufeff") else content # if utf-8 with BOM, remove BOM
			except (UnicodeError, UnicodeDecodeError):
				return self.__getTxt(filePath, index + 1) # recursion
			except:
				return None
		else:
			return None # out of range
	def initialize(self:object) -> bool:
		content = self.__getTxt(self.__filePath)
		if content is None:
			self.__lines = None # avoid re-initialization
			self.__children = None # avoid re-initialization
			return False
		elif content:
			self.__lines = content.splitlines()
			self.__children = []
			return True
		else:
			self.__lines = [""]
			self.__children = []
			return True
	def isInitialized(self:object) -> bool:
		return isinstance(self.__lines, list) and isinstance(self.__children, list)
	def hasNextChar(self:object) -> bool: # only just the status of the current pointer node
		return 0 <= self.__lineIdx < len(self.__lines) and 0 <= self.__charIdx + 1 < len(self.__lines[self.__lineIdx]) if self.isInitialized() else None
	def nextChar(self:object) -> bool:
		bRet = self.hasNextChar() # judge if it is initialized and if there is a following character
		if bRet:
			self.__charIdx += 1 # increse the character count
		return bRet
	def hasNextLine(self:object) -> bool: # only just the status of the current pointer node
		return self.__lineIdx + 1 < len(self.__lines) if self.isInitialized() else None
	def nextLine(self:object) -> bool:
		bRet = self.hasNextLine() # judge if it is initialized and if there is a following line
		if bRet:
			self.__lineIdx += 1 # increase the line count
			self.__charIdx = -1 # reset the char index
		return bRet
	def isEOF(self:object) -> bool:
		return self.__lineIdx + 1 == len(self.__lines) and self.__charIdx >= 0 and self.__charIdx + 1 == len(self.__lines[self.__lineIdx]) if self.isInitialized() else None
	def getCurrentChar(self:object) -> str:
		return self.__lines[self.__lineIdx][self.__charIdx] if self.isInitialized() and self.__lineIdx < len(self.__lines) and 0 <= self.__charIdx < len(self.__lines[self.__lineIdx]) else None
	def getNextChar(self:object) -> str:
		return self.__lines[self.__lineIdx][self.__charIdx + 1] if self.hasNextChar() else None
	def getCurrentLine(self:object) -> str:
		return self.__lines[self.__lineIdx] if self.isInitialized() and self.__lineIdx < len(self.__lines) else None
	def getRemainingChars(self:object) -> str:
		return self.__lines[self.__lineIdx][self.__charIdx + 1:] if self.isInitialized() and self.__lineIdx < len(self.__lines) else None
	def addChildPointerNode(self:object, pointerNode:object) -> bool:
		if isinstance(pointerNode, PointerNode) and pointerNode.isInitialized():
			self.__children.append(pointerNode)
			return True
		else:
			return False
	def getFilePath(self:object) -> str:
		return self.__filePath if self.isInitialized() else None
	def getCurrentLocation(self:object) -> tuple:
		return (self.__filePath, self.__lineIdx, self.__charIdx)
	def getChildren(self:object, isReversed:bool = False) -> list:
		return (self.__children[::-1] if isReversed else self.__children[::]) if self.isInitialized() else None
	def __eq__(self:object, obj:str|object) -> bool:
		if PLATFORM == "WINDOWS":
			return isinstance(obj, PointerNode) and self.__filePath.lower() == obj.__filePath.lower() or isinstance(obj, str) and self.__filePath.lower() == obj.lower()
		else:
			return isinstance(obj, PointerNode) and self.__filePath == obj.__filePath or isinstance(obj, str) and self.__filePath == obj

class Pointer:
	def __init__(self:object, rootFilePath:str) -> object:
		absRootFilePath = os.path.abspath(str(rootFilePath).replace("\"", ""))
		self.__pointerNodeStack = [] # stack (stack[0] is the root) # initialization flag
		self.__pointerNodeStack.append(PointerNode(absRootFilePath)) # write separately to avoid the absence of this attribute caused by exceptions in PointerNode
		self.__currentPointerNode = None # initialization flag
		self.__baseFolderPath = os.path.split(absRootFilePath)[0]
		self.__lastError = "Currently, there are no errors. "
	def initialize(self:object) -> bool:
		if self.__pointerNodeStack and self.__pointerNodeStack[0].initialize():
			self.__currentPointerNode = self.__pointerNodeStack[0]
			return True
		else:
			self.__pointerNodeStack = [self.__pointerNodeStack[0]] if self.__pointerNodeStack else [] # avoid re-initialization
			self.__currentPointerNode = None # avoid re-initialization
			return False
	def isInitialized(self:object) -> bool:
		return bool(self.__pointerNodeStack) and isinstance(self.__currentPointerNode, PointerNode)
	def hasNextChar(self:object, fileSwitch:bool = True) -> bool: # in the current line including "1\input{2}3"
		if self.isInitialized():
			if self.__currentPointerNode.hasNextChar():
				return True
			elif isinstance(fileSwitch, bool) and fileSwitch:
				for i in range(len(self.__pointerNodeStack) - 1, -1, -1): # check the parents constantly
					if self.__pointerNodeStack[i].hasNextChar():
						return True
					elif not self.__pointerNodeStack[i].isEOF(): # there is a following line but there is not a following character
						return False
			return False # no following characters are followed / all the opened files report EOF
		else:
			return None # not initialized
	def nextChar(self:object, fileSwitch:bool = True) -> bool:
		bRet = self.hasNextChar(fileSwitch = fileSwitch) # judge if it is initialized and if there is a following character in the current line
		if bRet:
			while len(self.__pointerNodeStack) > 1: # no need to consider the switch again; keep the main file in the stack
				if self.__pointerNodeStack[-1].nextChar():
					return True
				elif not self.__pointerNodeStack[-1].isEOF(): # there is a following line but there is not a following character
					return False
				self.__pointerNodeStack.pop()
				self.__currentPointerNode = self.__pointerNodeStack[-1] # move the pointer
			if self.__pointerNodeStack[0].nextChar(): # all the opened non-main files report EOF
				return True
			else:
				return False # the main file
		else:
			return bRet
	def hasNextLine(self:object, fileSwitch:bool = True) -> bool:
		if self.isInitialized():
			if self.__currentPointerNode.hasNextLine():
				return True
			else:
				for i in range(len(self.__pointerNodeStack) - 1, -1, -1): # check the parents constantly
					if self.__pointerNodeStack[i].hasNextLine():
						return True
				return False # all the opened files report EOF
		else:
			return None # not initialized
	def nextLine(self:object, fileSwitch:bool = True) -> bool:
		bRet = self.hasNextLine() # judge if it is initialized and if there is a following line
		if bRet:
			while len(self.__pointerNodeStack) > 1: # keep the main file in the stack
				if self.__pointerNodeStack[-1].nextLine():
					return True
				self.__pointerNodeStack.pop()
				self.__currentPointerNode = self.__pointerNodeStack[-1] # move the pointer
			if self.__pointerNodeStack[0].nextLine(): # all the opened non-main files report the end of lines
				return True
			else:
				return False # the main file
		else:
			return bRet
	def getCurrentChar(self:object) -> str:
		return self.__currentPointerNode.getCurrentChar() if self.isInitialized() else None
	def getNextChar(self:object, fileSwitch:bool = True) -> str:
		bRet = self.hasNextChar(fileSwitch = fileSwitch) # judge if it is initialized and if there is a following character in the current line
		if bRet:
			for i in range(len(self.__pointerNodeStack) - 1, -1, -1): # check the parents constantly
					if self.__pointerNodeStack[i].hasNextChar():
						return self.__pointerNodeStack[i].getNextChar()
			return None # all the opened files report EOF
		else:
			return bRet
	def getCurrentLine(self:object) -> str:
		return self.__currentPointerNode.getCurrentLine() if self.isInitialized() else None
	def getRemainingCharactersInTheCurrentLineOfTheCurrentFile(self:object) -> str:
		return self.__currentPointerNode.getRemainingChars() if self.isInitialized() else None
	def getCurrentLocation(self:object) -> tuple:
		if self.isInitialized():
			tp = self.__currentPointerNode.getCurrentLocation()
			return (os.path.relpath(str(tp[0]), self.__baseFolderPath), tp[1], tp[2]) if isinstance(tp, tuple) else None
		else:
			return None
	def getCurrentLocationDescription(self:object) -> str:
		tp = self.getCurrentLocation()
		return ("Char {0}, Line {1}, File \"{2}\"".format(tp[2], tp[1] + 1, tp[0]) if tp[2] >= 0 else "Line {1}, File \"{2}\"".format(tp[2], tp[1] + 1, tp[0])) if isinstance(tp, tuple) else None
	def addPointerNode(self:object, filePath:str, canCallAgain:bool = True) -> bool:
		if not self.isInitialized():
			self.__lastError = "The instance of ``Pointer`` has not been initialized. "
			return False
		elif not isinstance(filePath, str):
			self.__lastError = "The passed file path is not a string. "
			return False
		elif not isinstance(canCallAgain, bool):
			self.__lastError = "The flag for calling the pointer node adding method function is unclear. "
			return False
		strippedFilePath = filePath.replace("\"", "").strip()
		absFilePath = os.path.abspath(strippedFilePath if os.path.isabs(strippedFilePath) else os.path.join(self.__baseFolderPath, strippedFilePath))
		if absFilePath in self.__pointerNodeStack:
			self.__lastError = "The file \"{0}\" has been in the stack for resolving. Please check and make sure that there are no recursive calls. ".format(absFilePath).replace("\\", "\\\\")
			return False
		pointerNode = PointerNode(absFilePath, parentPointerNode = self.__currentPointerNode)
		if pointerNode.initialize() and self.__currentPointerNode.addChildPointerNode(pointerNode):
			self.__currentPointerNode = pointerNode
			self.__pointerNodeStack.append(pointerNode)
			self.__lastError = "Currently, there are no errors. "
			return True
		elif canCallAgain and not strippedFilePath.endswith(".bib") and not strippedFilePath.endswith(".tex"): # call again for the ``.tex`` extension added
			return self.addPointerNode(strippedFilePath + ".tex", False)
		else:
			self.__lastError = "Failed to initialize the call to \"{0}\". ".format(absFilePath).replace("\\", "\\\\")
			return False
	def getLastError(self:object) -> str:
		return self.__lastError
	def getTree(self:object, indentationSymbol:str = "\t", indentationCount:int = 0) -> str:
		if self.isInitialized() and isinstance(indentationSymbol, str) and "\r" not in indentationSymbol and "\n" not in indentationSymbol and isinstance(indentationCount, int) and indentationCount >= 0:
			stack = [(self.__pointerNodeStack[0], 0)]
			res = []
			while stack:
				node, level = stack.pop()
				if node.isInitialized():
					res.append("{0}{1}".format(str(indentationSymbol) * level, os.path.relpath(node.getFilePath(), self.__baseFolderPath)))
					stack.extend([(n, level + 1) for n in node.getChildren(isReversed = True)])
			return "\n".join(res)
		else:
			return None

class StructureNode:
	def __init__(self:object, header:str = "", parent:object = None) -> object:
		self.__header = header if isinstance(header, str) else ""
		self.__footer = ""
		self.__type = None # initialization flag
		self.__descriptor = None
		self.__children = None # initialization flag
		self.__media = None # initialization flag
		self.__parent = parent if isinstance(parent, StructureNode) else None
	def initialize(self:object) -> bool:
		if self.__header:
			if "Root" == self.__header:
				self.__type = "Root"
				self.__descriptor = None
			elif self.__header.startswith("\\begin{") and self.__header.endswith("}"):
				self.__type = "Environment"
				self.__descriptor = self.__header[7:-1] # cannot compile "\\begin{ equation }" in LaTeX
			elif self.__header.startswith("\\documentclass"):
				self.__type = "DocumentClass"
				self.__descriptor = None
			elif (																						\
				(																					\
					self.__header.startswith("\\section{") or self.__header.startswith("\\section*{")					\
					or self.__header.startswith("\\subsection{") or self.__header.startswith("\\subsection*{")			\
					or self.__header.startswith("\\subsubsection{") or self.__header.startswith("\\subsubsection*{")		\
				)																					\
				and self.__header.endswith("}")															\
			):
				self.__type = "S" + self.__header[2:self.__header.index("{")]
				self.__descriptor = self.__header[self.__header.index("{") + 1:-1].strip()
			elif self.__header in ("$", "$$", "\\(", "\\["):
				self.__type = "Equation"
				self.__descriptor = self.__header
			else:
				self.__type = None # avoid re-initialization
				self.__children = None # avoid re-initialization
				return False
			self.__children = []
			self.__media = {}
			return True
		else:
			self.__type = None # avoid re-initialization
			self.__children = None # avoid re-initialization
			self.__media = None # avoid re-initialization
			return False
	def isInitialized(self:object) -> bool:
		return isinstance(self.__type, str) and isinstance(self.__children, list) and isinstance(self.__media, dict)
	def addChildStructureNode(self:object, structureNode:object) -> bool:
		if self.isInitialized() and isinstance(structureNode, StructureNode):
			self.__children.append(structureNode)
			return True
		else:
			return None
	def isFooterAccepted(self:object, footer:str) -> bool:
		if self.isInitialized() and isinstance(footer, str):
			if "Root" == footer or footer.startswith("\\documentclass"):
				return self.__type in ("DocumentClass", "Section", "Section*", "Subsection", "Subsection*", "Subsubsection", "Subsubsection*")
			elif "\\begin{thebibliography}" == footer:
				return self.__type in ("Section", "Section*", "Subsection", "Subsection*", "Subsubsection", "Subsubsection*")
			elif "\\end{document}" == footer:
				return self.__type in ("Section", "Section*", "Subsection", "Subsection*", "Subsubsection", "Subsubsection*") or "Environment" == self.__type and "document" == self.__descriptor
			elif footer.startswith("\\end{") and footer.endswith("}"):
				return (
					"Environment" == self.__type and footer[5:-1] == self.__descriptor
					or "document" == footer[5:-1] and self.__type in ("Section", "Section*", "Subsection", "Subsection*", "Subsubsection", "Subsubsection*")
				)
			elif (footer.startswith("\\section{") or footer.startswith("\\section*{") or footer.startswith("\\subsection{") or footer.startswith("\\subsection*{") or footer.startswith("\\subsubsection") or footer.startswith("\\subsubsection*")) and footer.endswith("}"):
				if self.__type in ("Section", "Section*"):
					return footer.startswith("\\section{") or footer.startswith("\\section*{") # only "\\section" and "\\section*" are allowed
				elif self.__type in ("Subsection", "Subsection*"):
					return footer.startswith("\\section{") or footer.startswith("\\section*{") or footer.startswith("\\subsection{") or footer.startswith("\\subsection*{") # >=
				elif self.__type in ("Subsubsection", "Subsubsection*"):
					return True # all the three are accepted
				else:
					return False
			elif footer in ("$", "$$"):
				return "Equation" == self.__type and footer == self.__descriptor
			elif "\\)" == footer:
				return "Equation" == self.__type and "\\(" == self.__descriptor
			elif "\\]" == footer:
				return "Equation" == self.__type and "\\[" == self.__descriptor
			else: # Root etc. 
				return False
		else:
			return None
	def setFooter(self:object, footer:str) -> bool:
		bRet = self.isFooterAccepted(footer)
		if bRet and self.__header not in ("DocumentClass", "Section", "Section*", "Subsection", "Subsubsection"): # the "documentclass" and section-like structures do not require footnotes
			self.__footer = footer
		return bRet
	def addPlainText(self:object, strings:str = "") -> bool:
		if self.isInitialized() and isinstance(strings, str):
			if self.__children and isinstance(self.__children[-1], str): # the last node is a string
				self.__children[-1] += strings
			else: # create a new string
				self.__children.append(strings)
		else:
			return None
	def addMedia(self:object, mediumType:str) -> bool:
		if self.isInitialized() and isinstance(mediumType, str):
			self.__media.setdefault(mediumType, 0)
			self.__media[mediumType] += 1
			return True
		else:
			return None
	def getType(self:object) -> str:
		return self.__type # return None if it is None
	def getDescriptor(self:object) -> str|None:
		return self.__descriptor # return None if it is None
	def getMedia(self:object, mediumType:str|tuple|list|None = None) -> int|tuple:
		if self.isInitialized():
			if mediumType is None:
				return tuple(self.__media.items())
			elif isinstance(mediumType, str):
				return self.__media[mediumType] if mediumType in self.__media else 0
			elif isinstance(mediumType, tuple):
				return tuple((self.__media[m] if m in self.__media else 0) for m in mediumType if isinstance(m, str))
			elif isinstance(mediumType, list):
				return [(self.__media[m] if m in self.__media else 0) for m in mediumType if isinstance(m, str)]
			else:
				return None
		else:
			return None
	def getChildren(self:object, isReversed:bool = False) -> list:
		return (self.__children[::-1] if isReversed else self.__children[::]) if self.isInitialized() else None
	def getParent(self:object) -> object:
		return self.__parent
	def __str__(self:object) -> str:
		return "{0}".format(self.__type) if self.__descriptor is None else "{0}({1})".format(self.__type, self.__descriptor)

class Structure:
	def __init__(self:object) -> object:
		self.__rootStructureNode = None # initialization flag
		self.__currentStructureNode = None # initialization flag
	def initialize(self:object) -> bool:
		self.__rootStructureNode = StructureNode("Root")
		if self.__rootStructureNode.initialize():
			self.__currentStructureNode = self.__rootStructureNode
			return True
		else:
			self.__rootStructureNode = None # avoid re-initialization
			self.__currentStructureNode = None # avoid re-initialization
			return False
	def isInitialized(self:object) -> bool:
		return isinstance(self.__rootStructureNode, StructureNode) and isinstance(self.__currentStructureNode, StructureNode)
	def addPlainText(self:object, strings:str = "") -> bool:
		return self.__currentStructureNode.addPlainText(strings) if self.isInitialized() else None
	def addMedia(self:object, mediumType:str) -> bool:
		if isinstance(mediumType, str):
			bRet = True
			pStructureNode = self.__currentStructureNode
			while pStructureNode != self.__rootStructureNode:
				bRet = pStructureNode.addMedia(mediumType) and bRet
				pStructureNode = pStructureNode.getParent()
			bRet = pStructureNode.addMedia(mediumType) and bRet # the root
			return bRet
		else:
			return False
	def addStructureNode(self:object, header:str) -> bool:
		if self.isInitialized() and isinstance(header, str):
			while (																									\
				(																									\
					(																								\
						header.startswith("\\documentclass") or header.startswith("\\section{") or header.startswith("\\section*{")			\
						or header.startswith("\\subsection{") or header.startswith("\\subsection*{")									\
						or header.startswith("\\subsubsection{") or header.startswith("\\subsubsection*{")							\
					)																								\
					and header.endswith("}") or "\\begin{thebibliography}" == header											\
				) and self.__currentStructureNode.isFooterAccepted(header)													\
			): # go back to the parent node if the footer is accepted
				self.__currentStructureNode = self.__currentStructureNode.getParent()
			structureNode = StructureNode(header = header, parent = self.__currentStructureNode)
			if structureNode.initialize() and self.__currentStructureNode.addChildStructureNode(structureNode):
				self.__currentStructureNode = structureNode
				return True
			else:
				return False
		else:
			return None
	def canLeaveCurrentStructureNode(self:object, footer:str) -> bool:
		if self.isInitialized() and isinstance(footer, str):
			return self.__currentStructureNode != self.__rootStructureNode and self.__currentStructureNode.isFooterAccepted(footer)
		else:
			return None
	def leaveCurrentStructureNode(self:object, footer:str = "", leavingQueue:list = []) -> bool:
		if isinstance(leavingQueue, list):
			leavingQueue.clear()
		else:
			return None
		bRet = self.canLeaveCurrentStructureNode(footer)
		if bRet:
			if footer == "\\end{document}":
				while self.canLeaveCurrentStructureNode(footer):
					self.__currentStructureNode.setFooter(footer)
					leavingQueue.append(str(self.__currentStructureNode))
					self.__currentStructureNode = self.__currentStructureNode.getParent()
			else:
				self.__currentStructureNode.setFooter(footer)
				leavingQueue.append(str(self.__currentStructureNode))
				self.__currentStructureNode = self.__currentStructureNode.getParent()
		return bRet
	def getCurrentStructureNodeDescription(self:object) -> str:
		return str(self.__currentStructureNode) if self.isInitialized() else None
	def endStructure(self:object) -> bool:
		while self.canLeaveCurrentStructureNode("Root"):
			self.leaveCurrentStructureNode("Root")
		return self.__currentStructureNode == self.__rootStructureNode
	def getMedia(self:object, mediumType:str|tuple|list|None = None, indentationSymbol:str = "\t", indentationCount:int = 0) -> str:
		if self.isInitialized() and isinstance(mediumType, (str, tuple, list, None)) and isinstance(indentationSymbol, str) and "\r" not in indentationSymbol and "\n" not in indentationSymbol and isinstance(indentationCount, int) and indentationCount >= 0:
			stack = [(self.__rootStructureNode, indentationCount)]
			res = []
			while stack:
				node, level = stack.pop()
				if isinstance(node, StructureNode) and node.isInitialized():
					r = node.getMedia(mediumType)
					if isinstance(r, int) and r >= 1: # pruning
						res.append("{0}{1} -> {2}".format(indentationSymbol * level, node, r))
						stack.extend([(n, level + 1) for n in node.getChildren(isReversed = True)])
			return "\n".join(res)
		else:
			return None
	def getTree(self:object, mode:str = "A", indentationSymbol:str = "\t", indentationCount:int = 0) -> str:
		if self.isInitialized() and isinstance(mode, str) and mode in ("A", "B", "D") and isinstance(indentationSymbol, str) and "\r" not in indentationSymbol and "\n" not in indentationSymbol and isinstance(indentationCount, int) and indentationCount >= 0:
			stack = [(self.__rootStructureNode, indentationCount)]
			res = []
			if "D" == mode:
				while stack:
					node, level = stack.pop()
					if isinstance(node, str): # also shows the text
						res.append("{0}Text({1})".format(indentationSymbol * level, len(node)))
					elif node.isInitialized():
						res.append("{0}{1}".format(indentationSymbol * level, node))
						stack.extend([(n, level + 1) for n in node.getChildren(isReversed = True)])
			elif "B" == mode:
				while stack:
					node, level = stack.pop() # only shows some necessary items
					if isinstance(node, StructureNode) and node.isInitialized() and node.getType() in ("Root", "DocumentClass", "Environment", "Section", "Section*", "Subsection", "Subsection*", "Subsubsection", "Subsubsection*"):
						res.append("{0}{1}".format(indentationSymbol * level, node))
						stack.extend([(n, level + 1) for n in node.getChildren(isReversed = True)])
			else:
				while stack:
					node, level = stack.pop()
					if isinstance(node, StructureNode) and node.isInitialized():
						nodeType = node.getType()
						if nodeType in ("Root", "DocumentClass", "Section", "Section*", "Subsection", "Subsection*", "Subsubsection", "Subsubsection*"):
							res.append("{0}{1}".format(indentationSymbol * level, nodeType)) # only shows the type
							stack.extend([(n, level + 1) for n in node.getChildren(isReversed = True)])
						elif "Environment" == nodeType:
							res.append("{0}{1}".format(indentationSymbol * level, node.getDescriptor())) # only shows the descriptor
							stack.extend([(n, level + 1) for n in node.getChildren(isReversed = True)])
			return "\n".join(res)
		else:
			return None

class Checker:
	def __init__(self:object, mainTexPath:str = None, debugLevel:DebugLevel|int = Debug) -> object:
		self.__mainTexPath = os.path.abspath(mainTexPath.replace("\"", "")) if isinstance(mainTexPath, str) else None # transfer to the absolute path
		self.__pointer = None
		self.__structure = None
		self.__definitions = {}
		self.__labels = {}
		self.__citations = {}
		if isinstance(debugLevel, DebugLevel):
			self.__debugLevel = debugLevel
		else:
			try:
				self.__debugLevel = DebugLevel({"value":int(debugLevel)})
			except:
				self.__debugLevel = Debug
				self.__print("The debug level specified is invalid. It is defaulted to {0} ({1}). ".format(self.__debugLevel.name, self.__debugLevel.value), Warning)
		self.__flag = False
	def __print(self:object, strings:str|object, debugLevel:DebugLevel = Info, indentationSymbol:str = "\t", indentationCount:int = 0) -> bool:
		if isinstance(debugLevel, DebugLevel) and isinstance(indentationSymbol, str) and isinstance(indentationCount, int):
			if debugLevel >= self.__debugLevel:
				try:
					print("\n".join(["{0} {1}{2}".format(debugLevel, (indentationSymbol * indentationCount if indentationCount >= 1 else ""), string) for string in str(strings).split("\n")]))
					return True
				except: # avoid exceptions in __str__
					return None
			else:
				return False
		else:
			return None
	def __input(self:object, strings:str, indentationSymbol:str = "\t", indentationCount:int = 0) -> str:
		try:
			return input("\n".join(["{0} {1}{2}".format(Prompt, (indentationSymbol * indentationCount if isinstance(indentationSymbol, str) and isinstance(indentationCount, int) and indentationCount >= 1 else ""), string) for string in str(strings).splitlines()]))
		except KeyboardInterrupt:
			print() # print an empty line
			self.__print("The input process was interrupted by users. None will be returned as the default value. ", Warning)
			return None
		except BaseException as e:
			self.__print("The input process was interrupted by the following exceptions. ", Error)
			self.__print(e, Error, indentationCount = 1)
			return None
	def __skipSpaces(self:object, lineSwitch:bool = True) -> bool:
		if isinstance(lineSwitch, bool):
			while self.__pointer.hasNextChar(fileSwitch = False) and self.__pointer.getNextChar(fileSwitch = False) in (" ", "\t"): # skip spaces in the current line
				self.__pointer.nextChar(fileSwitch = False)
			if self.__pointer.hasNextChar(fileSwitch = False): # remaining non-space characters exist
				return True
			elif lineSwitch: # allow a line separator
				if self.__pointer.hasNextLine(fileSwitch = False):
					self.__pointer.nextLine(fileSwitch = False)
					while self.__pointer.hasNextChar(fileSwitch = False) and self.__pointer.getNextChar(fileSwitch = False) in (" ", "\t"): # skip spaces in the following line
						self.__pointer.nextChar(fileSwitch = False)
					if self.__pointer.hasNextChar(fileSwitch = False):
						return True
					else:
						self.__print("There should be only at most a line between the command definition command and the command but there are two more at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
						return False
				else:
					self.__print("While scanning the command, the file reports an EOF signal at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
					return False
			else:
				self.__print("There should be non-space characters at the end of the line at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
		else:
			return None
	def __fetchBraces(self:object, fileSwitch:bool = False) -> str:
		if not isinstance(fileSwitch, bool) or not self.__pointer.hasNextChar(fileSwitch = fileSwitch):
			return (False, "")
		if "{" == self.__pointer.getNextChar(fileSwitch = fileSwitch):
			layer, mainBody = 1, "{"
			self.__pointer.nextChar(fileSwitch = fileSwitch)
			while True:
				if self.__pointer.hasNextChar(fileSwitch = fileSwitch):
					ch = self.__pointer.getNextChar(fileSwitch = fileSwitch)
					mainBody += ch
					if "\\" == ch:
						if self.__pointer.hasNextChar(fileSwitch = fileSwitch):
							self.__pointer.nextChar(fileSwitch = fileSwitch)
							mainBody += self.__pointer.getCurrentChar()
						elif self.__pointer.hasNextLine(fileSwitch = fileSwitch):
							self.__pointer.nextLine(fileSwitch = fileSwitch)
							mainBody += "\n"
						else:
							self.__print("A missing \"}\" is detected when scanning the main body at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
							return False
					elif "{" == ch:
						layer += 1
					elif "}" == ch:
						layer -= 1
						if 0 == layer:
							break
					elif "%" == ch:
						if self.__pointer.hasNextLine(fileSwitch = fileSwitch):
							self.__pointer.nextLine(fileSwitch = fileSwitch)
						else:
							self.__print("There are not following lines after the \"%\" symbol at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
							return False
					self.__pointer.nextChar(fileSwitch = fileSwitch)
				elif self.__pointer.hasNextLine(fileSwitch = fileSwitch):
					self.__pointer.nextLine(fileSwitch = fileSwitch)
					mainBody += "\n"
				else:
					self.__print("An EOF signal is reported during scanning the main body at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
					return (False, mainBody)
			return (True, mainBody)
		else:
			self.__pointer.nextChar(fileSwitch = fileSwitch)
			return (True, self.__pointer.getCurrentChar())
	def __convertEscaped(self:object, string:str) -> str:
		if isinstance(string, str):
			vec = list(string)
			d = {"\\":"\\\\", "\"":"\\\"", "\'":"\\\'", "\a":"\\a", "\b":"\\b", "\f":"\\f", "\n":"\\n", "\r":"\\r", "\t":"\\t", "\v":"\\v"}
			for i, ch in enumerate(vec):
				if ch in d:
					vec[i] = d[ch]
				elif not ch.isprintable():
					vec[i] = "\\x" + hex(ord(ch))[2:]
			return "".join(vec)
		else:
			return str(string)
	def __handleBibTeX(self:object) -> bool:
		while True:
			if self.__pointer.hasNextChar(fileSwitch = False) and "@" == self.__pointer.getNextChar(fileSwitch = False):
				# Citation #
				line = self.__pointer.getCurrentLine()
				if "{" in line and "," in line:
					citation = line[line.index("{") + 1:line.index(",")]
				else:
					self.__print("A line starting with \"@\" contains unexpected citation information at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Warning)
					if self.__pointer.hasNextLine(fileSwitch = False):
						self.__pointer.nextLine(fileSwitch = False)
						continue # stop reading citation content and operating the dict
					else:
						self.__pointer.nextLine() # will switch to the parent pointer
						return True
				
				# Citation Content #
				citationContent = ""
				while self.__pointer.hasNextLine(fileSwitch = False):
					self.__pointer.nextLine(fileSwitch = False)
					if self.__pointer.hasNextChar(fileSwitch = False) and "}" == self.__pointer.getNextChar(fileSwitch = False):
						citationContent = citationContent[:-1] # remove the last "\n"
						break
					else:
						citationContent += self.__pointer.getCurrentLine().strip() + "\n"
				
				# Handle Dict #
				if citation in self.__citations:
					if self.__citations[citation][0] is None:
						self.__citations[citation][0] = citationContent
					elif isinstance(self.__citations[citation][0], list):
						self.__citations[citation][0].append(citationContent)
						self.__print(																													\
							"The citation \"{0}\" has already been defined {1} but is defined again at {2}. ".format(													\
								self.__convertEscaped(citation), "twice" if 2 == length else "for {0} times".format(length), self.__pointer.getCurrentLocationDescription()		\
							), Warning																												\
						)
					else:
						self.__citations[citation][0] = [self.__citations[citation][0], citationContent]
						self.__print("The citation \"{0}\" has already existed but is defined again at {1}. ".format(self.__convertEscaped(citation), self.__pointer.getCurrentLocationDescription()), Warning)
				else:
					self.__citations[citation] = [citationContent, 0] # [citationContent, citeCount]
					self.__print("A new citation \"{0}\" is added by BibTeX at {1}. ".format(self.__convertEscaped(citation), self.__pointer.getCurrentLocationDescription()), Debug)
			if self.__pointer.hasNextLine(fileSwitch = False):
				self.__pointer.nextLine(fileSwitch = False)
			else:
				self.__pointer.nextLine() # will switch to the parent pointer
				return True
	def __resolve(self:object) -> bool:
		self.__pointer = Pointer(self.__mainTexPath)
		self.__structure = Structure()
		self.__definitions.clear()
		self.__labels.clear()
		self.__citations.clear()
		self.__flag = False
		if not self.__pointer.initialize():
			self.__print("Failed to initialize the main tex file. Please check if the file can be read. ", Error)
			return False
		if not self.__structure.initialize():
			self.__print("Failed to initialize the root structure node. ", Error)
			return False
		buffer = "" # can also use a flag to control the buffer like {0:"plainTextBuffer", 1:"commandBuffer", 2:"mandatoryArgumentBuffer", 3:"optionalArguementBuffer"}
		stack = [] # indicate the layer of {}
		isLeftPart = True # indicate the "$" or "$$" got is the left part or not
		while True:
			if self.__pointer.hasNextChar(): # if there is a character following the current character in this line
				self.__pointer.nextChar() # move to the next character
				ch = self.__pointer.getCurrentChar() # get the currenct character
				if "\\" == ch: # use the active modes
					if self.__pointer.hasNextChar(fileSwitch = False):
						if self.__pointer.getNextChar(fileSwitch = False) in ("(", "["):
							self.__structure.addStructureNode("\\" + self.__pointer.getNextChar(fileSwitch = False))
							self.__pointer.nextChar(fileSwitch = False)
						elif self.__pointer.getNextChar(fileSwitch = False) in (")", "]"):
							if not (self.__structure.canLeaveCurrentStructureNode("\\" + self.__pointer.getNextChar(fileSwitch = False)) and self.__structure.leaveCurrentStructureNode("\\" + self.__pointer.getNextChar(fileSwitch = False))):
								self.__print("Cannot end the current environment ({0}) with command \"{1}\" at {2}. ".format(self.__structure.getCurrentStructureNodeDescription(), buffer, self.__pointer.getCurrentLocationDescription()), Error)
								return False
						else:
							buffer = "\\" # initial a buffer to obtain the command
							while self.__pointer.hasNextChar(fileSwitch = False): # fetch the whole command
								ch = self.__pointer.getNextChar(fileSwitch = False)
								if "A" <= ch <= "Z" or "a" <= ch <= "z":
									buffer += ch
									self.__pointer.nextChar(fileSwitch = False)
								else:
									break
							if buffer in ("\\section", "\\subsection", "\\subsubsection") and self.__pointer.hasNextChar(fileSwitch = False) and "*" == self.__pointer.getNextChar(fileSwitch = False): # section*-like structures
								buffer += "*"
								self.__pointer.nextChar(fileSwitch = False)
							if "\\" == buffer: # "\\"
								if "0" <= ch <= "9": # e.g. "\\0" (ch must be defined since judging whether there is a following character is done before)
									self.__pointer.nextChar(fileSwitch = False) # for printing purposes
									self.__print("A command should only contain letters but it does not at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
									return False
								else: # e.g. "\\%" (absorb the next character)
									self.__pointer.nextChar(fileSwitch = False)
									self.__structure.addStructureNode("\\" + self.__pointer.getCurrentChar())
							elif "\\documentclass" == buffer:
								if self.__structure.addStructureNode(buffer):
									self.__print("A new structure node [{0}] is added. ".format(self.__structure.getCurrentStructureNodeDescription()), Debug)
								else:
									self.__print("Failed to initialize a new structure node via \"{0}\" at {1}. ".format(buffer, self.__pointer.getCurrentLocationDescription()), Error)
									return False
							elif buffer in ("\\begin", "\\bibliography", "\\end", "\\input", "\\section", "\\section*", "\\subsection", "\\subsection*", "\\subsubsection", "\\subsubsection*"):
								commandName = buffer[1:] # for judging environments
								if not self.__skipSpaces():
									return False
								flag, mainBody = self.__fetchBraces()
								buffer = "\\" + commandName + mainBody if mainBody.startswith("{") else "\\" + commandName + "{" + mainBody + "}"
								if "begin" == commandName:
									if self.__structure.addStructureNode(buffer):
										self.__print("A new structure node [{0}] is added. ".format(self.__structure.getCurrentStructureNodeDescription()), Debug)
										if buffer == "\\begin{thebibliography}":
											self.__pointer.nextChar()
											if not self.__skipSpaces():
												return False
											self.__pointer.nextChar()
											ch = self.__pointer.getCurrentChar()
											if "{" == ch:
												layerCount = 1
												while layerCount:
													if self.__pointer.hasNextChar():
														self.__pointer.nextChar()
														ch = self.__pointer.getCurrentChar()
														if "{" == ch:
															layerCount += 1
														elif "}" == ch:
															layerCount -= 1
														elif "\\" == ch:
															if self.__pointer.hasNextChar():
																self.__pointer.nextChar()
															elif self.__pointer.hasNextLine():
																self.__pointer.nextLine()
															else:
																self.__print(																													\
																	"An EOF signal is reported while scanning the escaped placeholder(s) in the \"{{}}\" for the \"\\begin{thebibliography}\" command at {0}. ".format(	\
																		self.__pointer.getCurrentLocationDescription()																				\
																	), Error																													\
																)
																return False
													elif self.__pointer.hasNextLine():
														self.__pointer.nextLine()
													else:
														self.__print(																														\
															"An EOF signal is reported while scanning the non-escaped placeholder(s) in the \"{{}}\" for the \"\\begin{thebibliography}\" command at {0}. ".format(	\
																self.__pointer.getCurrentLocationDescription()																					\
															), Error																														\
														)
											else:
												if "\\" == ch: # handle the placeholder in the form of "\\#"
													if self.__pointer.hasNextChar():
														self.__pointer.nextChar()
													elif self.__pointer.hasNextLine():
														self.__pointer.nextLine()
													else:
														self.__print("An EOF signal is reported while scanning the character after \"\\\" at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
														return False
												while True:
													if self.__pointer.hasNextChar():
														ch = self.__pointer.getNextChar()
														if ch in (" ", "\t"):
															self.__pointer.nextChar()
														elif "\\" == ch:
															remainingLine = self.__pointer.getRemainingCharactersInTheCurrentLineOfTheCurrentFile()
															if remainingLine.startswith("\\bibitem"):
																break
															else:
																self.__print(																											\
																	"Without a pair of \"{{}}\" surrounded, the \"\\bibitem\" command instead of others should be right after the placeholder at {0}. ".format(	\
																		self.__pointer.getCurrentLocationDescription()																		\
																	), Error																											\
																)
														else:
															self.__print(																								\
																"Without a pair of \"{{}}\" surrounded, the \"\\bibitem\" command should be right after the placeholder at {0}. ".format(		\
																	self.__pointer.getCurrentLocationDescription()															\
																), Error																								\
															)
															return False
													elif self.__pointer.hasNextLine():
														self.__pointer.nextLine()
													else:
														self.__print("An EOF signal is reported while scanning the citations at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
														return False
									else:
										self.__print("Failed to initialize a new structure node via \"{0}\" at {1}. ".format(buffer, self.__pointer.getCurrentLocationDescription()), Error)
										return False
								elif "bibliography" == commandName:
									if self.__pointer.addPointerNode(buffer[buffer.index("{") + 1:-1]) or self.__pointer.addPointerNode(buffer[buffer.index("{") + 1:-1] + ".bib"):
										self.__print("A new Bib pointer node \"{0}\" is added. ".format(self.__pointer.getCurrentLocation()[0]), Debug)
										if self.__handleBibTeX():
											break # break the loop for fetching the string within the {} to avoid moving to the next character repeatedly
										else:
											return False
									else:
										self.__print("Failed to add a Bib pointer node at {0}. Details are as follows. \n{1}".format(self.__pointer.getCurrentLocationDescription(), self.__pointer.getLastError()), Warning)
								elif "end" == commandName:
									if self.__structure.canLeaveCurrentStructureNode(buffer):
										leavingQueue = []
										self.__structure.leaveCurrentStructureNode(buffer, leavingQueue = leavingQueue)
										if len(leavingQueue) >= 2:
											self.__print("With \"{0}\": ".format(buffer), Debug)
											for nodeDescription in leavingQueue:
												self.__print("Leave current structure node [{0}]. ".format(nodeDescription), Debug, indentationCount = 1)
										else:
											self.__print("Leave current structure node [{0}] with \"{1}\". ".format(leavingQueue[0], buffer), Debug)
									else:
										self.__print(																							\
											"Cannot end the current environment [{0}] with command \"{1}\" at {2}. ".format(								\
												self.__structure.getCurrentStructureNodeDescription(), buffer, self.__pointer.getCurrentLocationDescription()		\
											), Error																							\
										)
										return False
								elif "input" == commandName:
									if self.__pointer.addPointerNode(buffer[buffer.index("{") + 1:-1]):
										self.__print("A new TeX pointer node \"{0}\" is added. ".format(self.__pointer.getCurrentLocation()[0]), Debug)
									else:
										self.__print("Failed to add a TeX pointer node at {0}. Details are as follows. ".format(self.__pointer.getCurrentLocationDescription()), Warning)
										self.__print(self.__pointer.getLastError(), Warning, indentationCount = 1)
								else:
									self.__structure.addStructureNode(buffer)
							elif buffer in ("\\bibitem", "\\label", "\\ref", "\\eqref"):
								if not self.__skipSpaces():
									return False
								
								# Fetch #
								self.__pointer.nextChar(fileSwitch = False)
								if "{" == self.__pointer.getCurrentChar():
									label = ""
									while True:
										if self.__pointer.hasNextChar(fileSwitch = False):
											self.__pointer.nextChar(fileSwitch = False)
											ch = self.__pointer.getCurrentChar()
											if ch in ("\\", "{"):
												self.__print(																									\
													"An unexpected character \"{0}\" appears while scanning the {1} at {2}. ".format(										\
														self.__convertEscaped(ch), "citation" if "\\bibitem" == buffer else "label", self.__pointer.getCurrentLocationDescription()	\
													), Error																									\
												)
												return False
											elif "}" == ch:
												break
											else:
												label += ch
										elif self.__pointer.hasNextLine(fileSwitch = False):
											self.__pointer.nextLine(fileSwitch = False)
											label += "\n"
											while self.__pointer.hasNextChar(fileSwitch = False) and self.__pointer.getNextChar(fileSwitch = False) in (" ", "\t"):
												self.__pointer.nextChar(fileSwitch = False)
												citation += self.__pointer.getCurrentChar()
											if not self.__pointer.hasNextChar(fileSwitch = False):
												self.__print("Two or more consecutive line breaks are not allowed during the label scanning at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
												return False
										else:
											self.__print("An EOF signal is reported while scanning the label at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
											return False
								elif "\\label" == buffer and self.__structure.getCurrentStructureNodeDescription() in ("Environment(equation)", "Environment(equation*)"):
									self.__print("Must use a \"{{\" to follow the \"\\label\" command in the equation environment at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
									return False
								else:
									label = self.__pointer.getCurrentChar()
								
								# Handle #
								if "\\bibitem" == buffer:
									# Environment Check #
									if "Environment(thebibliography)" != self.__structure.getCurrentStructureNodeDescription():
										self.__print("The command \"\\bibitem\" should only be used in the \"thebibliography\" environment. ", Warning)
										# return False
									
									# Fetch Citation Content #
									citationContent = ""
									while True:
										if self.__pointer.hasNextChar():
											ch = self.__pointer.getNextChar()
											if "\\" == ch:
												remainingLine = self.__pointer.getRemainingCharactersInTheCurrentLineOfTheCurrentFile()
												if remainingLine.startswith("\\begin") or remainingLine.startswith("\\bibitem") or remainingLine.startswith("\\end"):
	 												break
												else:
													citationContent += "\\"
													self.__pointer.nextChar()
													if self.__pointer.hasNextChar(): # ``hasNextLine`` will be handled in the new loop automatically
														self.__pointer.nextChar()
														citationContent += self.__pointer.getCurrentChar()
											elif "&" == ch:
												citationContent += "&"
												self.__print("Please use \"\\&\" in the citation content at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Warning)
											else:
												citationContent += ch
											self.__pointer.nextChar()
										elif self.__pointer.hasNextLine():
											self.__pointer.nextLine()
											citationContent += "\n"
									
									# Handle Dict #
									if label in self.__citations:
										if self.__citations[label][0] is None:
											self.__citations[label][0] = citationContent
										elif isinstance(self.__citations[label][0], list):
											length = len(self.__citations[label][0])
											self.__print(																													\
												"The citation \"{0}\" has already been defined {1} but is defined again at {2}. ".format(													\
													self.__convertEscaped(label), "twice" if 2 == length else "for {0} times".format(length), self.__pointer.getCurrentLocationDescription()		\
												), Warning																												\
											)
										else:
											self.__citations[label][0] = [self.__citations[label][0], False]
											self.__print("The citation \"{0}\" has already existed but is defined again at {1}. ".format(self.__convertEscaped(label), self.__pointer.getCurrentLocationDescription()), Warning)
									else:
										self.__citations[label] = [citationContent, 0] # [citationContent, citeCount]
										self.__print("A new citation \"{0}\" is added via the \"\\bibitem\" command at {1}. ".format(self.__convertEscaped(label), self.__pointer.getCurrentLocationDescription()), Debug)
								elif "\\label" == buffer:
									if label in 	self.__labels:
										if self.__labels[label][0] is None:
											self.__labels[label][0] = self.__structure.getCurrentStructureNodeDescription()
										elif isinistance(self.__labels[label][0], list):
											length = len(self.__labels[label][0])
											self.__print(																													\
												"The label \"{0}\" has already been defined {1} but is defined again at {2}. ".format(														\
													self.__convertEscaped(label), "twice" if 2 == length else "for {0} times".format(length), self.__pointer.getCurrentLocationDescription()		\
												), Warning																												\
											)
											self.__labels[label][0].append(self.__pointer.getCurrentLocationDescription())
										else:
											self.__labels[label][0] = [self.__labels[label][0], self.__pointer.getCurrentLocationDescription()]
											self.__print("The label \"{0}\" has already existed but is defined again at {1}. ".format(self.__convertEscaped(label), self.__pointer.getCurrentLocationDescription()), Warning)
									else:
										self.__labels[label] = [self.__structure.getCurrentStructureNodeDescription(), 0, 0] # [type, refCount, eqrefCount]
										self.__print("A new label \"{0}\" is added at {1}. ".format(self.__convertEscaped(label), self.__pointer.getCurrentLocationDescription()), Debug)
								else:
									if label in self.__labels:
										self.__labels[label][2 if "\\eqref" == buffer else 1] += 1
									elif "\\eqref" == buffer:
										self.__labels[label] = [None, 0, 1] # [undefined, refCount, eqrefCount]
									else:
										self.__labels[label] = [None, 1, 0] # [undefined, refCount, eqrefCount]
							elif "\\cite" == buffer:
								if not self.__skipSpaces():
									return False
								
								# Fetch #
								if "{" == self.__pointer.getNextChar(fileSwitch = False):
									self.__pointer.nextChar(fileSwitch = False)
									citation = ""
									while True:
										if self.__pointer.hasNextChar(fileSwitch = False):
											self.__pointer.nextChar(fileSwitch = False)
											ch = self.__pointer.getCurrentChar()
											if ch in ("\\", "{"):
												self.__print("An unexpected character \"{0}\" appears while scanning the citation at {1}. ".format(self.__convertEscaped(ch), self.__pointer.getCurrentLocationDescription()), Error)
												return False
											elif "}" == ch:
												citations = [item.lstrip() for item in citation.split(",")]
												break
											else:
												citation += ch
										elif self.__pointer.hasNextLine(fileSwitch = False):
											self.__pointer.nextLine(fileSwitch = False)
											citation += "\n"
											while self.__pointer.hasNextChar(fileSwitch = False) and self.__pointer.getNextChar(fileSwitch = False) in (" ", "\t"):
												self.__pointer.nextChar(fileSwitch = False)
												citation += self.__pointer.getCurrentChar()
											if not self.__pointer.hasNextChar(fileSwitch = False):
												self.__print("Two or more consecutive line breaks are not allowed during the citation scanning at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
												return False
										else:
											self.__print("An EOF signal is reported while scanning the citation at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
											return False
								else:
									self.__pointer.nextChar(fileSwitch = False)
									citations = [self.__pointer.getCurrentChar()]
								
								# Handle #
								for citation in citations:
									if citation in self.__citations:
										self.__citations[citation][1] += 1
									else:
										self.__citations[citation] = [None, 1] # [undefined, citeCount]
									self.__structure.addMedia("Citation")
							elif buffer in ("\\newcommand", "\\renewcommand"):
								# Command #
								if not self.__skipSpaces(False):
									return False
								if "{" == self.__pointer.getNextChar(fileSwitch = False):
									self.__pointer.nextChar(fileSwitch = False)
									if self.__pointer.hasNextChar(fileSwitch = False) and "\\" == self.__pointer.getNextChar(fileSwitch = False):
										self.__pointer.nextChar(fileSwitch = False)
										bucketFlag = True
									else:
										self.__print("The command to be defined should be right after the \"\\newcommand{{\" or the \"\\renewcommand{{\" at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
										return False
								elif "\\" == self.__pointer.getNextChar(fileSwitch = False):
									self.__pointer.nextChar(fileSwitch = False)
									bucketFlag= False
								else:
									self.__print("The command to be defined should be right after the \"\\newcommand\" or the \"\\renewcommand\" at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
									return False
								newcommand = "\\"
								while self.__pointer.hasNextChar(fileSwitch = False):
									ch = self.__pointer.getNextChar(fileSwitch = False)
									if "A" <= ch <= "Z" or "a" <= ch <= "z":
										newcommand += ch
										self.__pointer.nextChar(fileSwitch = False)
									else:
										break
								if not self.__skipSpaces(): # check after the command
									return False
								if bucketFlag:
									if "}" == self.__pointer.getNextChar(fileSwitch = False):
										self.__pointer.nextChar(fileSwitch = False)
									else:
										self.__print("A missing \"}\" is detected when scanning the command definition command at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
										return False
								
								# Option count #
								if not self.__skipSpaces():
									return False
								if "[" == self.__pointer.getNextChar(fileSwitch = False):
									self.__pointer.nextChar(fileSwitch = False)
									if not self.__skipSpaces():
										return False
									counter = ""
									while True:
										if self.__pointer.hasNextChar(fileSwitch = False):
											ch = self.__pointer.getNextChar(fileSwitch = False)
											self.__pointer.nextChar(fileSwitch = False)
											if "0" <= ch <= "9":
												counter += ch
											elif "]" == ch:
												break
											else:
												self.__print(																				\
													"Only digits instead of the character \"{0}\" can be used to indicate the option count at {1}. ".format(	\
														"\\" + ch if ch in ("\\", "\"", "\'") else ch, self.__pointer.getCurrentLocationDescription()			\
													), Error																				\
												)
												return False
										elif not self.__skipSpaces():
											return False
									try:
										counter = int(counter)
									except:
										self.__print("Cannot convert the counter \"{0}\" into a positive integer at {1}. ".format(counter, self.__pointer.getCurrentLocationDescription()), Error)
										return False
									if counter < 1 or counter > 9:
										self.__print("The count of options must be a positive integer not greater than 9. ", Error)
										return False
								else:
									counter = None
								
								# Default value for the first option #
								if not self.__skipSpaces():
									return False
								if "[" == self.__pointer.getNextChar(fileSwitch = False): # must be the default value option
									layer, defaultValue = 1, "["
									self.__pointer.nextChar(fileSwitch = False)
									while layer:
										if self.__pointer.hasNextChar(fileSwitch = False):
											self.__pointer.nextChar(fileSwitch = False)
											ch = self.__pointer.getCurrentChar()
											defaultValue += ch
											if "\\" == ch:
												if self.__pointer.hasNextChar(fileSwitch = False):
													self.__pointer.nextChar(fileSwitch = False)
													defaultValue += self.__pointer.getCurrentChar()
												elif self.__pointer.hasNextLine(fileSwitch = False):
													self.__pointer.nextLine(fileSwitch = False)
													defaultValue += "\n"
												else:
													self.__print("A missing \"]\" is detected when scanning the default value option at {0}. ".self.__pointer.getCurrentLocationDescription(), Error)
													return False
											elif "[" == ch:
												layer += 1
											elif "]" == ch:
												layer -= 1
										elif self.__pointer.hasNextLine(fileSwitch = False):
											self.__pointer.nextLine(fileSwitch = False)
											defaultValue += "\n"
										else:
											self.__print("An EOF signal is reported during scanning the default value at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
											return False
									defaultValue = defaultValue[1:-1]
								else:
									defaultValue = None
								
								# Main command body #
								if not self.__skipSpaces():
									return False
								flag, mainBody = self.__fetchBraces()
								if flag:
									if (not mainBody.startswith("{") or not mainBody.endswith("}")) and self.__pointer.hasNextChar(fileSwitch = False):
										self.__print("Only a character is accepted for the main body when there is not a pair of \"{{}}\" at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
										return False
								else:
									return False
								mainBody = mainBody[1:-1]
								self.__definitions[newcommand] = [counter, defaultValue, mainBody]
								self.__print("The value of the command \"{0}\" has been set to {1}. ".format(newcommand, self.__definitions[newcommand]), Debug)
							elif buffer in ("\\newenvironment", "\\newenvironment"):
								if not self.__skipSpaces():
									return False
								self.__pointer.nextChar()
								if self.__pointer.getCurrentChar() == "{":
									environmentName = ""
									while True:
										if self.__pointer.hasNextChar(fileSwitch = False):
											ch = self.__pointer.getNextChar(fileSwitch = False)
											if "A" <= ch <= "Z" or "a" <= ch <= "z":
												environmentName += ch
												self.__pointer.nextChar(fileSwitch = False)
											elif "}" == ch:
												self.__pointer.nextChar(fileSwitch = False)
												break
											else:
												self.__print("The environment should only contain letters but it is not at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
												return False
								else:
									ch = self.__pointer.getNextChar()
									if "A" <= ch <= "Z" or "a" <= ch <= "z":
										environmentName = ch
									else:
										self.__print("The one-character environment name should only be a letter but it is not at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
										return False
								for _ in range(2):
									if not self.__skipSpaces():
										return False
									self.__pointer.nextChar()
									if self.__pointer.getCurrentChar() == "{":
										layer = 1
										while layer:
											if self.__pointer.hasNextChar():
												self.__pointer.nextChar()
												ch = self.__pointer.getCurrentChar()
												if "{" == ch:
													layer += 1
												elif "}" == ch:
													layer -= 1
												elif "%" == ch:
													if self.__pointer.hasNextLine():
														self.__pointer.nextLine()
													else:
														self.__print("An EOF signal is reported while scanning a newly defined environment after an annotation symbol at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
														return False
												elif "\\" == ch:
													if self.__pointer.hasNextChar():
														self.__pointer.nextChar()
													elif self.__pointer.hasNextLine():
														self.__pointer.nextLine()
													else:
														self.__print("An EOF signal is reported while scanning the escaped character of a newly defined environment at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
														return False
											elif self.__pointer.hasNextLine():
												self.__pointer.nextLine()
											else:
												self.__print("An EOF signal is reported while scanning a newly defined environment at {0}. ".format(self.__pointer.getCurrentLocationDescription()), Error)
												return False
								self.__print("A new environment \"{0}\" is added. ".format(environmentName), Debug)
							else: # the command is not special
								self.__structure.addPlainText(buffer)
					else: # "\\\n"
						self.__structure.addPlainText("\\")
				elif "%" == ch: # the '\\' will be absorbed in the codes for avoiding "\\%" above if the previous char is '\\'
					self.__structure.addPlainText("\n")
					self.__pointer.nextLine()
				elif "$" == ch: # the '\\' will be absorbed in the codes for avoiding "\\$" above if the previous char is '\\'
					if self.__pointer.hasNextChar(fileSwitch = False) and "$" == self.__pointer.getNextChar(fileSwitch = False):
						self.__pointer.nextChar(fileSwitch = False)
						ch = "$$" # else: ch = "$"
					if isLeftPart:
						if self.__structure.addStructureNode(ch):
							self.__print("A new structure node [{0}] is added. ".format(self.__structure.getCurrentStructureNodeDescription()), Debug)
						else:
							self.__print("Failed to initialize a new structure node via \"{0}\" at {1}. ".format(ch, self.__pointer.getCurrentLocationDescription()), Error)
							return False
					else:
						if self.__structure.canLeaveCurrentStructureNode(ch) and self.__structure.leaveCurrentStructureNode(ch):
							self.__print("Leave current structure node with \"{0}\". ".format(ch), Debug)
						else:
							self.__print("Cannot end the current environment [{0}] with command \"{1}\" at {2}. ".format(self.__structure.getCurrentStructureNodeDescription(), ch, self.__pointer.getCurrentLocationDescription()), Error)
							return False
					isLeftPart = not isLeftPart # switch to the other part
				else:
					self.__structure.addPlainText(ch)
			elif self.__pointer.hasNextLine(): # there is not a following character but a following line
				self.__structure.addPlainText("\n")
				self.__pointer.nextLine() # switch to the next line
			else: # EOF
				if self.__structure.endStructure():
					break
				else:
					self.__print("An overall EOF signal is reported with unclosed structure [{0}]. ".format(self.__structure.getCurrentStructureNodeDescription()), Error)
					return False
		self.__flag = True
		return True
	def __printPointer(self:object) -> None:
		self.__print("Pointer: ", Info)
		self.__print(self.__pointer.getTree(), Info)
	def __printStructure(self:object, mode:str = "A") -> None:
		self.__print("Structure: ", Info)
		self.__print(self.__structure.getTree(mode), Info)
	def __setup(self:object) -> bool:
		try:
			if not isinstance(self.__mainTexPath, str):
				tmpMainTexPath = self.__input("Please input the main tex path: ", Prompt)
				if tmpMainTexPath is None:
					self.__print("Setup canceled. ", Error)
					return False
				else:
					self.__mainTexPath = tmpMainTexPath.replace("\"", "")
					self.__print("The main tex path is set to \"{0}\". ".format(self.__mainTexPath), Info)
					return self.__setup()
			elif os.path.isfile(self.__mainTexPath):
				if os.path.splitext(self.__mainTexPath)[1].lower() != ".tex":
					self.__print("The main tex path is set to a file whose extension is not \".tex\". ", Warning)
				return self.__resolve()
			elif os.path.isdir(self.__mainTexPath):
				self.__print("Since a folder was specified, the program is scanning the folder now. ", Info)
				self.__print("If it takes too long time, please use \"Ctrl+C\" to stop the scanning. ", Info)
				possibleTargets = []
				try:
					for root, dirs, files in os.walk(self.__mainTexPath):
						for fileName in files:
							if os.path.splitext(fileName)[1].lower() in (".tex", ):
								possibleTargets.append(os.path.join(root, fileName))
				except KeyboardInterrupt:
					self.__print("Scanning is interrupted by users. The results may be incomplete. ", Warning)
				if len(possibleTargets) > 1:
					self.__print("Possible targets are listed as follows. ", Prompt)
					try:
						length = len(str(len(possibleTargets)))
						for i, target in enumerate(possibleTargets):
							self.__print("{{0:>{0}}} = \"{{1}}\"".format(length).format(i + 1, target), Prompt, indentationCount = 1)
						self.__print("{{0:>{0}}} = I do not wish to select any of them. ".format(length).format(0), Prompt, indentationCount = 1)
					except KeyboardInterrupt:
						print() # print an empty line
						self.__print("\nPrinting is interrupted by users. The results may be incomplete. ", Warning)
					choice = self.__input("Please select a tex file as the main file to continue: ", Prompt)
					try:
						choice = int(choice)
					except:
						pass
					if choice is None or 0 == choice:
						self.__print("The main tex selection is canceled by users. ", Warning)
						return False
					elif isinstance(choice, int) and 1 <= choice <= len(possibleTargets): # ID matches
						self.__mainTexPath = possibleTargets[choice - 1]
						self.__print("The main tex path is set to \"{0}\". ".format(self.__mainTexPath), Info)
						return self.__resolve()
					elif isinstance(choice, str) and choice in possibleTargets: # exact matches (robust matches are not designed here since some operating systems are case sensitive)
						self.__mainTexPath = choice
						self.__print("The main tex path is set to \"{0}\". ".format(self.__mainTexPath), Info)
						return self.__resolve()
					elif isinstance(choice, str) and choice.strip("\"") in possibleTargets: # exact matches (remove the quotation marks)
						self.__mainTexPath = choice.strip("\"")
						self.__print("The main tex path is set to \"{0}\". ".format(self.__mainTexPath), Info)
						return self.__resolve()
					else:
						try:
							self.__mainTexPath = choice[int(choice) - 1]
							self.__print("The main tex path is set to \"{0}\". ".format(self.__mainTexPath), Info)
							return self.__resolve()
						except:
							self.__print("An invalid choice was selected. Failed to resolve main tex. ", Error)
							return False
				elif len(possibleTargets) == 1:
					self.__mainTexPath = possibleTargets[0]
					self.__print("The main tex path is set to \"{0}\" automatically since there is only one tex file detected. ".format(self.__mainTexPath), Info)
					return self.__resolve()
				else:
					self.__print("No tex files are detected under the specified folder.. ", Error)
					return False
			else:
				self.__print("Setup failed since the main tex cannot be read. ", Error)
				return False
		except KeyboardInterrupt:
			self.__print("The setup procedure is interrupted by users. ", Error)
			return False
		except BaseException as e:
			self.__print("Exceptions occurred during the setup. Details are as follows. ", Critical)
			self.__print(e, Critical, indentationCount = 1)
			return False
	def __selectAnOption(self:object, flag:bool) -> str:
		self.__print("\n" + "#" * 100, Prompt)
		if isinstance(flag, bool) and flag:
			d = {"C":"Clear", "D":"Debug", "E":"Exit", "N":"New", "R":"Reload", "S":"Statistics", "T":"Tree"}
			self.__print(																												\
				"Obtained {0} pointer node(s), {1} abstracted/brief structure node(s), and {2} detailed structure node(s). ".format(									\
					self.__pointer.getTree().count("\n") + 1, self.__structure.getTree("B").count("\n") + 1, self.__structure.getTree("D").count("\n") + 1		\
				), Info																												\
			)
		else:
			d = {"C":"Clear", "D":"Debug", "E":"Exit", "N":"New", "R":"Reload"}
		self.__print("Possible choices are listed as follows. ", Prompt)
		for key, value in d.items():
			self.__print("{0} = {1}".format(key, value), Prompt, indentationCount = 1)
		choices = self.__input("Please input your choice(s) to continue (non case-sensitive): ")
		if choices is None:
			choices = ""
		else:
			choices = choices.upper()
			invalid = True
			while invalid:
				for ch in choices:
					if ch not in d:
						choices = self.__input("At least one invalid choice exists. Please retry (non case-sensitive): ")
						if choices is None:
							self.__print("#" * 100 + "\n", Prompt)
							return ""
						else:
							choices = choices.upper()
							break
				else: # end naturally
					invalid = False
		self.__print("#" * 100 + "\n", Prompt)
		return choices
	def __doSetDebugLevel(self:object) -> bool:
		self.__print("Current debug level is {0} ({1}). ".format(self.__debugLevel.name, self.__debugLevel.value), Prompt)
		d = {"P":Prompt, "C":Critical, "E":Error, "W":Warning, "I":Info, "D":Debug}
		for key, value in d.items():
			self.__print("{0} = {1} ({2})".format(key, value.name, value.value), Prompt, indentationCount = 1)
		ch = self.__input("Please select a debug level to continue: ")
		while ch is not None and ch not in d:
			ch = self.__input("The debug level is invalid. Please retry: ")
		if ch is None:
			self.__print("The debug level has not been changed due to the cancellation caused by users. ", Warning)
			return False
		elif d[ch] == self.__debugLevel:
			self.__print("No changes are made to the debug level. ", Info)
			return True
		else:
			self.__debugLevel = d[ch]
			self.__print("The debug level has been changed to {0}. ".format(self.__debugLevel), Info)
			return True
	def __printStatistics(self:object) -> bool:
		pointerTree, structureBriefTree, structureDetailedTree = self.__pointer.getTree().split("\n"), self.__structure.getTree("B").split("\n"), self.__structure.getTree("D").split("\n")
		self.__print(																							\
			"Obtained {0} pointer node(s), {1} abstracted/brief structure node(s), and {2} detailed structure node(s). ".format(		\
				len(pointerTree), len(structureBriefTree), len(structureDetailedTree)										\
			), Info																							\
		)
		
		# Tree #
		d = {}
		for line in structureDetailedTree:
			key = line.strip()
			if not key.startswith("Text(") and key not in ("Root", "Environment(algorithmic)", "Environment(tabular)"):
				if key.startswith("Environment("):
					key = key[12:-1]
					if key not in ("document", "thebibliography"):
						key = key.replace("*", "") + "(*)"
				elif "(" in key:
					key = key[:key.index("(")].replace("*", "")
				d.setdefault(key, 0)
				d[key] += 1
		self.__print(d, Info)
		
		# Commands #
		self.__print("There are {0} commands defined. ".format(len(self.__definitions)) if len(self.__definitions) > 1 else "There is 1 command defined. ", Info)
		self.__print("Commands defined: {0}".format(self.__definitions), Debug)
		
		# Labels #
		repeatedLabels, undefinedLabels, unreferencedLabels, hybridReferencedLabels = [], [], [], []
		for key, value in self.__labels.items():
			if isinstance(value[0], list):
				repeatedLabels.append(key)
			elif value[0] is None:
				undefinedLabels.append(key)
			elif value[0] in ("Environment(equation)", "Environment(equation*)") and value[1] or value[0] not in ("Environment(equation)", "Environment(equation*)") and value[2]:
				hybridReferencedLabels.append(key)
			if not (value[1] or value[2]): # can be lots of labels without being referenced
				unreferencedLabels.append(key)
		
		# Labels/Repeated #
		length = len(repeatedLabels)
		if length >= 2:
			self.__print("There are {0} repeated labels found, which are listed as follows. ".format(length), Warning)
			self.__print(repeatedLabels, Warning, indentationCount = 1)
		elif 1 == length:
			self.__print("There is a repeated label found: \"{0}\". ".format(self.__convertEscaped(repeatedLabels[0])), Warning)
		
		# Labels/Undefined #
		length = len(undefinedLabels)
		if length >= 2:
			self.__print("There are {0} undefined labels found, which are listed as follows. ".format(length), Warning)
			self.__print(undefinedLabels, Warning, indentationCount = 1)
		elif 1 == length:
			self.__print("There is an undefined label found: \"{0}\". ".format(self.__convertEscaped(undefinedLabels[0])), Warning)
		
		# Labels/Unreferenced #
		length = len(unreferencedLabels)
		if length >= 2:
			self.__print("There are {0} unreferenced labels found, which are listed as follows. ".format(length), Warning)
			self.__print(unreferencedLabels, Warning, indentationCount = 1)
		elif 1 == length:
			self.__print("There is an unreferenced label found: \"{0}\". ".format(self.__convertEscaped(unreferencedLabels[0])), Warning)
		
		# Labels/Hybrid #
		length = len(hybridReferencedLabels)
		if length >= 2:
			self.__print("There are {0} hybrid referenced labels found, which are listed as follows. ".format(length), Warning)
			self.__print(hybridReferencedLabels, Warning, indentationCount = 1)
		elif 1 == length:
			self.__print("There is a hybrid referenced label found: \"{0}\". ".format(self.__convertEscaped(hybridReferencedLabels[0])), Warning)
		
		self.__print("Labels defined for referencing: {0}".format(self.__labels), Info)
		
		# Citations #
		repeatedCitations, undefinedCitations, uncitedCitations, similarCitations, citationBodies = [], [], [], [], {}
		for key, value in self.__citations.items():
			if isinstance(value[0], list):
				repeatedCitations.append(key)
			elif value[0] is None:
				undefinedCitations.append(key)
			else:
				stripped = value[0].strip()
				citationBodies.setdefault(stripped, [])
				citationBodies[stripped].append(key)
			if not value[1]:
				uncitedCitations.append(key)
		for value in citationBodies.values():
			if len(value) >= 2:
				similarCitations.append(value)
		
		# Citations/Repeated #
		length = len(repeatedCitations)
		if length >= 2:
			self.__print("There are {0} repeated citation entries found, which are listed as follows. ".format(length), Warning)
			self.__print(repeatedCitations, Warning, indentationCount = 1)
		elif 1 == length:
			self.__print("There is a repeated citation entry found: \"{0}\". ".format(self.__convertEscaped(repeatedCitations[0])), Warning)
		
		# Citations/Undefined #
		length = len(undefinedCitations)
		if length >= 2:
			self.__print("There are {0} undefined citations found, which are listed as follows. ".format(length), Warning)
			self.__print(undefinedCitations, Warning, indentationCount = 1)
		elif 1 == length:
			self.__print("There is an undefined citation found: \"{0}\". ".format(self.__convertEscaped(undefinedCitations[0])), Warning)
		
		# Citations/Uncited #
		length = len(uncitedCitations)
		if length >= 2:
			self.__print("There are {0} uncited citations found, which are listed as follows. ".format(length), Warning)
			self.__print(uncitedCitations, Warning, indentationCount = 1)
		elif 1 == length:
			self.__print("There is an uncited citation found: \"{0}\". ".format(self.__convertEscaped(uncitedCitations[0])), Warning)
		
		# Citations/Similar #
		length = len(similarCitations)
		if length >= 2:
			self.__print("There are {0} citations found with similar content, which are listed as follows. ".format(length), Warning)
			self.__print(similarCitations, Warning, indentationCount = 1)
		elif 1 == length:
			self.__print("There is a citation found with similar content: {0}. ".format(self.__convertEscaped(similarCitations[0])), Warning)
		
		# Citations/Overall #
		self.__print("Here are the statistics for the medium type \"Citation\". \n{0}".format(self.__structure.getMedia("Citation")), Info)
		self.__print("Citations defined for citing: {0}".format(self.__citations), Debug)
		
		self.__input("Please press the enter key to continue. ")
		return True
	def __handleFolder(self:object, fd:str) -> bool:
		folder = str(fd)
		if not folder:
			return True
		elif os.path.exists(folder):
			return os.path.isdir(folder)
		else:
			try:
				os.makedirs(folder)
				return True
			except:
				return False
	def __drawTree(self:object, strings:str, path:str) -> bool:
		if isinstance(strings, str) and isinstance(path, str):
			try:
				Digraph = __import__("graphviz").Digraph
			except:
				self.__print("Cannot import ``Digraph`` from ``graphviz``. Please resolve the environment before drawing. ", Error)
				return False
			g = Digraph("Tree", filename = path, node_attr = {"shape":"plain"})
			style = "<<table border=\"1\" cellspacing=\"0\"><tr><td port=\"f0\">{0}</td></tr><tr><td port=\"f1\">{1}</td></tr><tr><td port=\"f2\">{2}</td></tr></table>>"
			g.node("0", label = style.format("0", "Root", " "))
			nodeCount, stack = 0, [(0, 0)] # (node, tab)
			for string in strings.splitlines()[1:]:
				tabCount = string.count("\t")
				if 1 <= tabCount <= stack[-1][1] + 1:
					while stack and stack[-1][1] >= tabCount: # find the parent node
						stack.pop()
					nodeCount += 1
					g.node(str(nodeCount), label = style.format(str(nodeCount), string.lstrip(), " "))
					g.edge(str(stack[-1][0]), str(nodeCount), headport = "n", tailport = "s")
					stack.append((nodeCount, tabCount))
				else:
					self.__print("Failed to draw the tree since the tree indentation is incorrect. ", Error)
					return False
			g.attr(margin = "0", rankdir = "TB", splines = "line", overlap = "scale")
			try:
				g.view()
				return True
			except BaseException as e:
				self.__print("Cannot save the tree due to the following exception. ", Error)
				self.__print(e, Error, indentationCount = 1)
				return False
		else:
			self.__print("As no trees are passed to the drawer, the drawing has not been done. \nPlease make sure that the checker has already constructed the trees correctly. ", Error)
			return False
	def __doPrintTree(self:object) -> bool:
		if self.__flag:
			self.__print("Possible tree drawing options are as follows. ", Prompt)
			d = {																																		\
				"PP":"Print the pointer", "PSA":"Print the structure abstract", "PSB":"Print the structure briefly", "PSD":"Print the structure in detail", 								\
				"DP":"Draw/Dump the pointer", "DSA":"Draw/Dump the structure abstract", "DSB":"Draw/Dump the structure briefly", "DSD":"Draw/Dump the structure in detail", 	\
				"R":"Return"																																\
			}
			for key, value in d.items():
				self.__print("{0} = {1}".format(key, value), Prompt, indentationCount = 1)
			ch = self.__input("Please select an option to continue (non case-sensitive): ")
			while ch is not None and ch.upper() not in d:
				ch = self.__input("The option is invalid. Please retry (non case-sensitive): ")
			if ch is None:
				self.__print("Nothing has been proceeded due to the cancellation caused by users. ", Warning)
				return False
			else:
				ch = ch.upper()
			if "P" == ch[0]:
				if "P" == ch[1]:
					self.__printPointer()
				else:
					self.__printStructure(ch[2])
				return True
			elif "D" == ch[0]:
				path = self.__input("Please input a \".gv\" file path to save the figure (leave it empty for default values): ")
				# while not (isinstance(path, str) and (not path or path.endswith(".gv"))):
				#	path = self.__input("The previous input is invalid. Please input a \".gv\" file path to save the figure (leave it empty for default values): ")
				if path is None:
					self.__print("The operation is canceled by users. ", Warning)
					return False
				else:
					path = path.replace("\"", "")
					if path.lower().endswith(".gv.pdf"):
						path = path[:-4]
					elif not path.lower().endswith(".gv"):
						path += ".gv"
					self.__print("The path for drawing or dumping the tree is set to \"{0}\". ".format(path), Debug)
					if (os.path.exists(path) or os.path.exists(path + ".pdf")):
						answer = self.__input("The target path seems to exist. Overwrite or not [yN]? ")
						if answer is None or answer.upper() not in ("Y", "YES", "TRUE", "OK", "1"):
							self.__print("Users cancel the operation due to the file's existence. ", Warning)
							return False
					if self.__handleFolder(os.path.split(path)[0]):
						return self.__drawTree(self.__pointer.getTree() if "P" == ch[1] else self.__structure.getTree(ch[2]), path if path else ("pointer.gv" if "P" == ch[1] else "structure.gv"))
					else:
						self.__print("Failed to save the tree since the parent folder is not created. ", Error)
						return False
			else:
				return False
		else:
			self.__print("No trees were built since the resolving has not been processed yet. ", Error)
			return False
	def mainBoard(self:object) -> bool:
		self.__setup()
		while True:
			choices = self.__selectAnOption(self.__flag)
			for ch in choices:
				if "C" == ch:
					clearScreen()
				elif "D" == ch:
					self.__doSetDebugLevel()
				elif "E" == ch:
					return self.__flag
				elif "N" == ch:
					self.__mainTexPath = None
					self.__pointer = None
					self.__structure = None
					self.__flag = False
					self.__setup()
				elif "R" == ch:
					self.__pointer = None
					self.__structure = None
					self.__flag = False
					self.__setup() # still call ``__setup``
				elif "S" == ch:
					self.__printStatistics()
				elif "T" == ch:
					self.__doPrintTree()


def clearScreen(fakeClear:int = 120) -> bool:
	if CLEAR_SCREEN_COMMAND is not None and not os.system(CLEAR_SCREEN_COMMAND):
		return True
	else:
		try:
			print("\n" * int(fakeClear))
		except:
			print("\n" * 120)
		return False

def preExit(countdownTime:int = 5) -> None:
	try:
		cntTime = int(countdownTime)
		length = len(str(cntTime))
	except:
		return
	print()
	while cntTime > 0:
		print("\rProgram ended, exiting in {{0:>{0}}} second(s). ".format(length).format(cntTime), end = "")
		try:
			sleep(1)
		except:
			print("\rProgram ended, exiting in {{0:>{0}}} second(s). ".format(length).format(0))
			return
		cntTime -= 1
	print("\rProgram ended, exiting in {{0:>{0}}} second(s). ".format(length).format(cntTime))

def main() -> int:
	clearScreen()
	if len(argv) > 2:
		processPool = [os.system(STARTUP_COMMAND_FORMAT.format(executable, __file__, mainTexPath)) for mainTexPath in argv[1:]]
		print(																									\
			"As multiple options were given, {0} child processes have been launched, where {1} succeeded and {2} failed. ".format(		\
				len(processPool), 																					\
				processPool.count(EXIT_SUCCESS), 																	\
				len(processPool) - processPool.count(EXIT_SUCCESS)													\
			)																									\
		)
		preExit()
		return EXIT_SUCCESS if not any(processPool) else EXIT_FAILURE
	else:
		checker = Checker(argv[1] if 2 == len(argv) else None)
		checker.mainBoard()
		preExit()
		return EXIT_SUCCESS if checker else EXIT_FAILURE



if __name__ == "__main__":
	exit(main())

效果图


http://www.kler.cn/a/454901.html

相关文章:

  • C#控件开发3—文本显示、文本设值
  • Ubuntu 22.04.5 修改IP
  • 面试突击-JAVA集合类(持续更新...)
  • 【SQL】筛选某一列字段中,截取含有关键词“XX”字段位置的前4个字段,去重后查看字段
  • PDF书籍《手写调用链监控APM系统-Java版》第11章 插件与链路的结合:HttpClient插件实现跨进程传输TraceSegment
  • OpenCV-Python实战(8)——图像变换
  • 探索基金聚合平台的背景与发展:Finanzen.net、Franklin Templeton、Finect
  • STM32完全学习——FATFS0.15移植SD卡
  • AI客服机器人如何帮助企业应对高峰期客户咨询压力?
  • LeetCode--239.滑动窗口最大值(使用双端队列解决)
  • docker拉取rabbitmq镜像时报Client.Timeout exceeded while awaiting header错误处理办法
  • Linux-Ubuntu之串口通信
  • ChatGPT助力数据可视化与数据分析效率的提升(二)
  • lua debug相关方法详解
  • leetcode82:删除链表中的重复元素II
  • 【蓝桥杯】走迷宫
  • 题海拾贝:蓝桥杯 2020 省AB 乘法表
  • 免费资源网站
  • ANSYS EMC Plus:谐振腔中的天线
  • Markdown语法字体字号讲解