ModValidate.vb 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. '提供不同的输入验证方法,名称以 Validate 开头
  2. Public Module ModValidate
  3. ''' <summary>
  4. ''' 进行输入验证,并返回错误原因。
  5. ''' 如果没有错误则返回空字符串。
  6. ''' </summary>
  7. Public Function Validate(Text As String, ValidateRules As IEnumerable(Of Validate)) As String
  8. Dim Result As String = ""
  9. For Each ValidateRule As Validate In ValidateRules
  10. Result = ValidateRule.Validate(Text)
  11. If Result Is Nothing Then Return ""
  12. If Result <> "" Then Return Result
  13. Next
  14. Return Result
  15. End Function
  16. End Module
  17. ''' <summary>
  18. ''' 输入验证规则基类。要查看所有的输入验证规则,可在输入 Validate 后查看自动补全。
  19. ''' </summary>
  20. Public MustInherit Class Validate
  21. ''' <summary>
  22. ''' 验证某字符串是否符合验证要求。若符合,返回空字符串;若不符合,返回错误原因;若需要中断检查并直接通过,返回 Nothing。
  23. ''' </summary>
  24. Public MustOverride Function Validate(Str As String) As String
  25. End Class
  26. ''' <summary>
  27. ''' 若为空则直接通过检查。
  28. ''' </summary>
  29. Public Class ValidateNullable
  30. Inherits Validate
  31. Public Sub New()
  32. End Sub
  33. Public Overrides Function Validate(Str As String) As String
  34. If IsNothing(Str) OrElse String.IsNullOrEmpty(Str) Then Return Nothing
  35. Return ""
  36. End Function
  37. End Class
  38. ''' <summary>
  39. ''' 不能为 Nothing 或空字符串(不包括全空格检查)。
  40. ''' </summary>
  41. Public Class ValidateNullOrEmpty
  42. Inherits Validate
  43. Public Sub New()
  44. End Sub
  45. Public Overrides Function Validate(Str As String) As String
  46. If IsNothing(Str) OrElse String.IsNullOrEmpty(Str) Then Return "输入内容不能为空!"
  47. Return ""
  48. End Function
  49. End Class
  50. ''' <summary>
  51. ''' 不能为 Nothing 或空字符串(包括全空格检查)。
  52. ''' </summary>
  53. Public Class ValidateNullOrWhiteSpace
  54. Inherits Validate
  55. Public Sub New()
  56. End Sub
  57. Public Overrides Function Validate(Str As String) As String
  58. If IsNothing(Str) OrElse String.IsNullOrWhiteSpace(Str) Then Return "输入内容不能为空!"
  59. Return ""
  60. End Function
  61. End Class
  62. ''' <summary>
  63. ''' 自定义的 Validate。
  64. ''' </summary>
  65. Public Class ValidateFunc
  66. Inherits Validate
  67. Private ReadOnly _func As Func(Of String, String)
  68. Public Sub New(F As Func(Of String, String))
  69. _func = F
  70. End Sub
  71. Public Overrides Function Validate(Str As String) As String
  72. Return _func(Str)
  73. End Function
  74. End Class
  75. ''' <summary>
  76. ''' 必须满足正则表达式。
  77. ''' </summary>
  78. Public Class ValidateRegex
  79. Inherits Validate
  80. Public Property Regex As String
  81. Public Property ErrorDescription As String = "正则检查失败!"
  82. Public Sub New()
  83. End Sub '用于 XAML 初始化
  84. Public Sub New(Regex As String, Optional ErrorDescription As String = "正则检查失败!")
  85. Me.Regex = Regex
  86. Me.ErrorDescription = ErrorDescription
  87. End Sub
  88. Public Overrides Function Validate(Str As String) As String
  89. If Not Str.RegexCheck(Regex) Then Return ErrorDescription
  90. Return ""
  91. End Function
  92. End Class
  93. ''' <summary>
  94. ''' 必须是一个完整网址。
  95. ''' </summary>
  96. Public Class ValidateHttp
  97. Inherits Validate
  98. Public Sub New()
  99. End Sub '用于 XAML 初始化
  100. Public Overrides Function Validate(Str As String) As String
  101. If Str.EndsWithF("/") Then Str = Str.Substring(0, Str.Length - 1)
  102. If Not Str.RegexCheck("^(http[s]?)\://") Then Return "输入的网址无效!"
  103. Return ""
  104. End Function
  105. End Class
  106. ''' <summary>
  107. ''' 必须为整数。
  108. ''' </summary>
  109. Public Class ValidateInteger
  110. Inherits Validate
  111. Public Property Min As Integer
  112. Public Property Max As Integer = Integer.MaxValue
  113. Public Sub New()
  114. End Sub '用于 XAML 初始化
  115. Public Sub New(Min As Integer, Max As Integer)
  116. Me.Min = Min
  117. Me.Max = Max
  118. End Sub
  119. Public Overrides Function Validate(Str As String) As String
  120. If Str.Length > 9 Then Return "请输入一个大小合理的数字!"
  121. Dim Valed As Integer
  122. If Not Integer.TryParse(Str, Valed) Then Return "请输入一个整数!"
  123. If Valed > Max Then Return "不可超过 " & Max & "!"
  124. If Valed < Min Then Return "不可低于 " & Min & "!"
  125. Return ""
  126. End Function
  127. End Class
  128. ''' <summary>
  129. ''' 长度限制。
  130. ''' </summary>
  131. Public Class ValidateLength
  132. Inherits Validate
  133. Public Property Min As Integer = 0
  134. Public Property Max As Integer = Integer.MaxValue
  135. Public Sub New()
  136. End Sub '用于 XAML 初始化
  137. Public Sub New(Min As Integer, Optional Max As Integer = Integer.MaxValue)
  138. Me.Min = Min
  139. Me.Max = Max
  140. End Sub
  141. Public Overrides Function Validate(Str As String) As String
  142. If Len(Str) <> Max AndAlso Max = Min Then Return $"长度必须为 {Max} 个字符!"
  143. If Len(Str) > Max Then Return $"长度最长为 {Max} 个字符!"
  144. If Len(Str) < Min Then Return $"长度至少需 {Min} 个字符!"
  145. Return ""
  146. End Function
  147. End Class
  148. ''' <summary>
  149. ''' 不能包含某些特定字符串。忽略大小写。
  150. ''' </summary>
  151. Public Class ValidateExcept
  152. Inherits Validate
  153. Public Property Excepts As New ObjectModel.Collection(Of String)
  154. Public Property ErrorMessage As String
  155. Public Sub New()
  156. ErrorMessage = "输入内容不能包含 %"
  157. End Sub '用于 XAML 初始化
  158. Public Sub New(Excepts As ObjectModel.Collection(Of String), Optional ErrorMessage As String = "输入内容不能包含 %")
  159. Me.Excepts = Excepts
  160. Me.ErrorMessage = ErrorMessage
  161. End Sub
  162. Public Sub New(Excepts As IEnumerable(Of String), Optional ErrorMessage As String = "输入内容不能包含 %")
  163. Me.New(
  164. New ObjectModel.Collection(Of String)(New List(Of String)(Excepts)),
  165. ErrorMessage)
  166. End Sub
  167. Public Sub New(Excepts As IEnumerable(Of Char), Optional ErrorMessage As String = "输入内容不能包含 %")
  168. Me.New(
  169. New ObjectModel.Collection(Of String)(Excepts.Select(Function(c) c.ToString).ToList),
  170. ErrorMessage)
  171. End Sub
  172. Public Overrides Function Validate(Str As String) As String
  173. For Each Ch As String In Excepts
  174. If Str.IndexOfF(Ch, True) >= 0 Then
  175. If IsNothing(ErrorMessage) Then ErrorMessage = ""
  176. Return ErrorMessage.Replace("%", Ch)
  177. End If
  178. Next
  179. Return ""
  180. End Function
  181. End Class
  182. ''' <summary>
  183. ''' 不能与某些特定字符串相同。
  184. ''' </summary>
  185. Public Class ValidateExceptSame
  186. Inherits Validate
  187. Public Property Excepts As New ObjectModel.Collection(Of String)
  188. Public Property ErrorMessage As String
  189. Public Property IgnoreCase As Boolean = False
  190. Public Sub New()
  191. End Sub
  192. Public Sub New(Excepts As ObjectModel.Collection(Of String), Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False)
  193. Me.Excepts = Excepts
  194. Me.ErrorMessage = ErrorMessage
  195. Me.IgnoreCase = IgnoreCase
  196. End Sub
  197. Public Sub New(Excepts As IEnumerable(Of String), Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False)
  198. Me.New(
  199. New ObjectModel.Collection(Of String)(New List(Of String)(Excepts)),
  200. ErrorMessage, IgnoreCase)
  201. End Sub
  202. Public Sub New(Except As String, Optional ErrorMessage As String = "输入内容不能为 %", Optional IgnoreCase As Boolean = False)
  203. Me.New(
  204. New ObjectModel.Collection(Of String) From {Except},
  205. ErrorMessage, IgnoreCase)
  206. End Sub
  207. Public Overrides Function Validate(Str As String) As String
  208. If Str Is Nothing Then Return ErrorMessage.Replace("%", "null")
  209. For Each Ch As String In Excepts
  210. If IgnoreCase Then
  211. If Str.Lower = Ch.Lower Then Return ErrorMessage.Replace("%", Ch)
  212. Else
  213. If Str.Equals(Ch) Then Return ErrorMessage.Replace("%", Ch) '使用 = 不确定是否会忽略大小写
  214. End If
  215. Next
  216. Return ""
  217. End Function
  218. End Class
  219. ''' <summary>
  220. ''' 必须为特定字符串。
  221. ''' </summary>
  222. Public Class ValidateSame
  223. Inherits Validate
  224. Public Property Required As String
  225. Public Property ErrorMessage As String
  226. Public Property IgnoreCase As Boolean = False
  227. Public Sub New()
  228. End Sub
  229. Public Sub New(Required As String, Optional ErrorMessage As String = "输入内容有误", Optional IgnoreCase As Boolean = False)
  230. Me.Required = Required
  231. Me.ErrorMessage = ErrorMessage
  232. Me.IgnoreCase = IgnoreCase
  233. End Sub
  234. Public Overrides Function Validate(Str As String) As String
  235. Return If(String.Compare(Str, Required, IgnoreCase) = 0, "", ErrorMessage)
  236. End Function
  237. End Class
  238. ''' <summary>
  239. ''' 对文件夹名的粗略的特化检测。
  240. ''' </summary>
  241. Public Class ValidateFolderName
  242. Inherits Validate
  243. Public Property Folder As String
  244. Public Property UseMinecraftCharCheck As Boolean = True
  245. Private ReadOnly PathIgnore As IEnumerable(Of String)
  246. Private ReadOnly IgnoreList As List(Of String)
  247. Public Sub New()
  248. End Sub
  249. Public Sub New(Folder As String, Optional UseMinecraftCharCheck As Boolean = True, Optional IgnoreList As List(Of String) = Nothing)
  250. Me.Folder = Folder
  251. Me.UseMinecraftCharCheck = UseMinecraftCharCheck
  252. On Error Resume Next
  253. PathIgnore = DirectoryUtils.EnumerateDirectories(Folder)
  254. Me.IgnoreList = IgnoreList
  255. End Sub
  256. Public Overrides Function Validate(Str As String) As String
  257. Try
  258. '检查是否为空
  259. Dim LengthCheck As String = New ValidateNullOrWhiteSpace().Validate(Str)
  260. If LengthCheck <> "" Then Return LengthCheck
  261. '检查空格
  262. If Str.StartsWithF(" ") Then Return "文件夹名不能以空格开头!"
  263. If Str.EndsWithF(" ") Then Return "文件夹名不能以空格结尾!"
  264. '检查长度
  265. LengthCheck = New ValidateLength(1, 100).Validate(Str)
  266. If LengthCheck <> "" Then Return LengthCheck
  267. '检查尾部小数点
  268. If Str.EndsWithF(".") Then Return "文件夹名不能以小数点结尾!"
  269. '检查特殊字符
  270. Dim CharactCheck As String = New ValidateExcept(Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "文件夹名不可包含 % 字符!").Validate(Str)
  271. If CharactCheck <> "" Then Return CharactCheck
  272. '检查特殊字符串
  273. Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件夹名不可为 %!", True).Validate(Str)
  274. If InvalidStrCheck <> "" Then Return InvalidStrCheck
  275. '检查 NTFS 8.3 文件名(#4505)
  276. If Str.RegexCheck(".{2,}~\d") Then Return "文件夹名不能包含这一特殊格式!"
  277. '检查文件夹重名
  278. Dim Arr As New List(Of String)
  279. Dim folderName As String
  280. If PathIgnore IsNot Nothing Then
  281. For Each FolderPath In PathIgnore
  282. folderName = PathUtils.GetLastPart(FolderPath)
  283. If IgnoreList Is Nothing OrElse Not IgnoreList.Contains(folderName, StringComparer.OrdinalIgnoreCase) Then Arr.Add(folderName)
  284. Next
  285. End If
  286. Dim SameNameCheck = New ValidateExceptSame(Arr, "不可与现有文件夹重名!", True).Validate(Str)
  287. If Not SameNameCheck = "" Then Return SameNameCheck
  288. Return ""
  289. Catch ex As Exception
  290. Logger.Warn(ex, "检查文件夹名出错")
  291. Return "错误:" & ex.Message
  292. End Try
  293. End Function
  294. End Class
  295. ''' <summary>
  296. ''' 对文件名的粗略的特化检测。
  297. ''' </summary>
  298. Public Class ValidateFileName
  299. Inherits Validate
  300. Public Property Name As String
  301. Public Property UseMinecraftCharCheck As Boolean = True
  302. Public Property IgnoreCase As Boolean = True
  303. Public Property ParentFolder As String = Nothing
  304. Public Property RequireParentFolderExists = True
  305. Public Sub New()
  306. End Sub
  307. Public Sub New(Name As String, Optional UseMinecraftCharCheck As Boolean = True, Optional IgnoreCase As Boolean = True)
  308. Me.Name = Name
  309. Me.IgnoreCase = IgnoreCase
  310. Me.UseMinecraftCharCheck = UseMinecraftCharCheck
  311. End Sub
  312. Public Overrides Function Validate(Str As String) As String
  313. Try
  314. '检查是否为空
  315. Dim LengthCheck As String = New ValidateNullOrWhiteSpace().Validate(Str)
  316. If Not LengthCheck = "" Then Return LengthCheck
  317. '检查空格
  318. If Str.StartsWithF(" ") Then Return "文件名不能以空格开头!"
  319. If Str.EndsWithF(" ") Then Return "文件名不能以空格结尾!"
  320. '检查长度
  321. LengthCheck = New ValidateLength(1, 253).Validate(Str & If(ParentFolder, ""))
  322. If LengthCheck <> "" Then Return LengthCheck
  323. '检查尾部小数点
  324. If Str.EndsWithF(".") Then Return "文件名不能以小数点结尾!"
  325. '检查特殊字符
  326. Dim CharactCheck As String = New ValidateExcept(Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "文件名不可包含 % 字符!").Validate(Str)
  327. If CharactCheck <> "" Then Return CharactCheck
  328. '检查特殊字符串
  329. Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件名不可为 %!", True).Validate(Str)
  330. If InvalidStrCheck <> "" Then Return InvalidStrCheck
  331. '检查 NTFS 8.3 文件名(#4505)
  332. If Str.RegexCheck(".{2,}~\d") Then Return "文件名不能包含这一特殊格式!"
  333. '检查文件重名
  334. If ParentFolder IsNot Nothing Then
  335. Dim DirInfo = DirectoryUtils.GetInfo(ParentFolder)
  336. If DirInfo.Exists Then
  337. Dim SameNameCheck = New ValidateExceptSame(DirInfo.EnumerateFiles("*").Select(Function(f) f.Name),
  338. "不可与现有文件重名!", IgnoreCase).Validate(Str)
  339. If Not SameNameCheck = "" Then Return SameNameCheck
  340. Else
  341. If RequireParentFolderExists Then Return $"父文件夹不存在:{ParentFolder}"
  342. End If
  343. End If
  344. Return ""
  345. Catch ex As Exception
  346. Logger.Warn(ex, "检查文件名出错")
  347. Return "错误:" & ex.Message
  348. End Try
  349. End Function
  350. End Class
  351. ''' <summary>
  352. ''' 要求输入一个可用的文件夹路径。
  353. ''' </summary>
  354. Public Class ValidateFolderPath
  355. Inherits Validate
  356. Public Property UseMinecraftCharCheck As Boolean = True
  357. Public Sub New()
  358. End Sub
  359. Public Sub New(UseMinecraftCharCheck As Boolean)
  360. Me.UseMinecraftCharCheck = UseMinecraftCharCheck
  361. End Sub
  362. Public Overrides Function Validate(Str As String) As String
  363. '去除尾部斜线,统一为 \
  364. Str = Str.Replace("/", "\")
  365. If Not Str.TrimEnd("\").EndsWithF(":") Then Str = Str.TrimEnd("\")
  366. '检查是否为空
  367. Dim LengthCheck As String = New ValidateNullOrWhiteSpace().Validate(Str)
  368. If LengthCheck <> "" Then Return LengthCheck
  369. '检查长度
  370. LengthCheck = New ValidateLength(1, 254).Validate(Str)
  371. If LengthCheck <> "" Then Return LengthCheck
  372. '检查 NTFS 8.3 文件名(#4505)
  373. Dim Match = Str.RegexSeek(".{2,}~\d")
  374. If Match IsNot Nothing Then Return $"文件夹名不能包含 {Match} 这一特殊格式!"
  375. '检查开头
  376. If Str.StartsWithF("\\Mac\") Then GoTo Fin
  377. For Each Drive In Environment.GetLogicalDrives().Where(Function(p) DirectoryUtils.Exists(p))
  378. Dim DriveName As String = Drive.Upper.First & ":\"
  379. If Str.Upper = DriveName Then Return ""
  380. If Str.StartsWithF(DriveName, True) Then GoTo Fin
  381. Next
  382. Return "文件夹路径头存在错误!"
  383. Fin:
  384. '对首层以外的路径检查
  385. For i = If(Str.StartsWithF("\\Mac\"), 2, 1) To Str.Split("\").Count - 1
  386. Dim SubStr As String = Str.Split("\")(i)
  387. '检查是否为空
  388. Dim SubLengthCheck As String = New ValidateNullOrWhiteSpace().Validate(SubStr)
  389. If Not SubLengthCheck = "" Then Return "文件夹路径存在错误!"
  390. '检查特殊字符
  391. Dim CharactCheck As String = New ValidateExcept(Path.GetInvalidFileNameChars() & If(UseMinecraftCharCheck, "!;", ""), "路径中存在无效字符!").Validate(SubStr)
  392. If Not CharactCheck = "" Then Return CharactCheck
  393. '检查头部空格
  394. If SubStr.StartsWithF(" ") Then Return "文件夹名不能以空格开头!"
  395. If SubStr.EndsWithF(" ") Then Return "文件夹名不能以空格结尾!"
  396. '检查尾部小数点
  397. If SubStr.EndsWithF(".") Then Return "文件夹名不能以小数点结尾!"
  398. '检查特殊字符串
  399. Dim InvalidStrCheck As String = New ValidateExceptSame({"CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}, "文件夹名不可为 %!").Validate(SubStr)
  400. If Not InvalidStrCheck = "" Then Return InvalidStrCheck
  401. Next
  402. Return ""
  403. End Function
  404. End Class