Desktop.razor.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. using HyperCube.Models;
  2. using Microsoft.AspNetCore.Components;
  3. using Microsoft.AspNetCore.Components.Forms;
  4. using Pullenti.Unitext;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.ComponentModel.DataAnnotations;
  8. using System.IO;
  9. using System.Linq;
  10. using System.Reflection;
  11. using System.Security.Cryptography;
  12. using System.Text;
  13. using System.Threading.Tasks;
  14. using System.Xml;
  15. using Console = HyperCube.Utils.AdvConsole;
  16. namespace HyperCube.Pages
  17. {
  18. public partial class Desktop : ComponentBase
  19. {
  20. [Inject]
  21. NavigationManager _navigationManager { get; set; }
  22. [Inject]
  23. AppData _appData { get; set; }
  24. const string STORAGE_FOLDER_NAME = "articles_storage";
  25. const long MAX_FILE_SIZE = 5120000; //bytes
  26. const string ACTIVE_BUTTON_CLASS = "btn_white tab-button active";
  27. const string ACTIVE_TAB_CLASS = "second-block__form visible";
  28. const string BUTTON_CLASS = "btn_white tab-button";
  29. const string TAB_CLASS = "second-block__form";
  30. string _uploadButtonClass = ACTIVE_BUTTON_CLASS;
  31. string _uploadTabClass = ACTIVE_TAB_CLASS;
  32. string _verifyButtonClass = BUTTON_CLASS;
  33. string _verifyTabClass = TAB_CLASS;
  34. string _otherButtonClass = BUTTON_CLASS;
  35. string _otherTabClass = TAB_CLASS;
  36. int _counter = 1;
  37. //string _event = "";
  38. string _status;
  39. //string _header;
  40. //string _storageFolderPath;
  41. MemoryStream _memoryStream;
  42. ModalInfo _modalInfo_error { get; set; }
  43. ModalLoading _modalLoading { get; set; }
  44. ArticleModel _articleClone = new();
  45. ArticleModel _article = new();
  46. UnitextDocument _document;
  47. protected override async Task OnInitializedAsync()
  48. {
  49. ///tmp
  50. await AppData.LoadArticles();
  51. }
  52. protected override void OnAfterRender(bool firstRender) => _counter = 1;
  53. void SwitchDesktopTab(int tabIndex)
  54. {
  55. switch(tabIndex)
  56. {
  57. case 0:
  58. _uploadButtonClass = ACTIVE_BUTTON_CLASS;
  59. _uploadTabClass = ACTIVE_TAB_CLASS;
  60. _verifyButtonClass = BUTTON_CLASS;
  61. _verifyTabClass = TAB_CLASS;
  62. _otherButtonClass = BUTTON_CLASS;
  63. _otherTabClass = TAB_CLASS;
  64. break;
  65. case 1:
  66. _uploadButtonClass = BUTTON_CLASS;
  67. _uploadTabClass = TAB_CLASS;
  68. _verifyButtonClass = ACTIVE_BUTTON_CLASS;
  69. _verifyTabClass = ACTIVE_TAB_CLASS;
  70. _otherButtonClass = BUTTON_CLASS;
  71. _otherTabClass = TAB_CLASS;
  72. break;
  73. case 2:
  74. _uploadButtonClass = BUTTON_CLASS;
  75. _uploadTabClass = TAB_CLASS;
  76. _verifyButtonClass = BUTTON_CLASS;
  77. _verifyTabClass = TAB_CLASS;
  78. _otherButtonClass = ACTIVE_BUTTON_CLASS;
  79. _otherTabClass = ACTIVE_TAB_CLASS;
  80. break;
  81. }
  82. }
  83. async Task HandleUpload(InputFileChangeEventArgs e)
  84. {
  85. _modalLoading.Open();
  86. IBrowserFile file = e.File;
  87. if (file != null)
  88. {
  89. Stream stream = file.OpenReadStream(MAX_FILE_SIZE);
  90. _memoryStream = new();
  91. await stream.CopyToAsync(_memoryStream);
  92. _status = $"Finished loading {_memoryStream.Length} bytes from {file.Name}";
  93. Console.WriteLine(_status);
  94. /// calculating hash
  95. string hash = await CalculateHashSum(_memoryStream);
  96. Console.WriteLine($"Hash: {hash}");
  97. /// checking hash
  98. MySQLConnector dbCon = MySQLConnector.Instance();
  99. string stringSQL;
  100. stringSQL = $"SELECT COUNT(*) FROM articles WHERE file_hash='{hash}'";
  101. int count = await dbCon.SQLSelectCount(stringSQL);
  102. if (count < 1)
  103. {
  104. ReportModel report = _appData.Report;
  105. report.FileName = file.Name;
  106. report.FileSize = _memoryStream.Length.ToString();
  107. _memoryStream.Position = 0;
  108. byte[] content = _memoryStream.ToArray();
  109. _document = UnitextService.CreateDocument(null, content, null);
  110. if (_document.ErrorMessage != null)
  111. {
  112. // скорее всего, этот формат не поддерживается на данный момент
  113. Console.WriteLine($"error, sorry: {_document.ErrorMessage}");
  114. _memoryStream.Close();
  115. stream.Close();
  116. _modalLoading.Close();
  117. _modalInfo_error.Open("Не удается прочитать документ, формат не поддерживается или файл поврежден.");
  118. return;
  119. }
  120. // восстанавливаем имя исходного файла, извлечённого из ресурсов
  121. _document.SourceFileName = file.Name;
  122. for (int i = file.Name.Length - 7; i > 0; i--)
  123. {
  124. if (file.Name[i] == '.')
  125. {
  126. _document.SourceFileName = file.Name.Substring(i + 1);
  127. break;
  128. }
  129. }
  130. //// записываем результат в XML
  131. //using (FileStream fs = new(doc.SourceFileName + ".xml", FileMode.Create, FileAccess.Write))
  132. //{
  133. // XmlWriterSettings xmlParams = new();
  134. // xmlParams.Encoding = Encoding.UTF8;
  135. // xmlParams.Indent = true;
  136. // xmlParams.IndentChars = " ";
  137. // using (XmlWriter xml = XmlWriter.Create(fs, xmlParams))
  138. // {
  139. // xml.WriteStartDocument();
  140. // doc.GetXml(xml);
  141. // xml.WriteEndDocument();
  142. // }
  143. //}
  144. //Console.WriteLine("XML write done");
  145. // получаем плоский текст
  146. string plainText = _document.GetPlaintextString(null);
  147. if (plainText == null)
  148. plainText = "Текст не выделен";
  149. _articleClone = DocParse.GetBaseProperties(plainText);
  150. _articleClone.Filename = file.Name;
  151. _articleClone.HashSum = hash;
  152. _article = (ArticleModel)_articleClone.Clone();
  153. Console.WriteLine($"Initializing SDK Pullenti ver {Pullenti.Sdk.Version} ({Pullenti.Sdk.VersionDate})... ");
  154. Pullenti.Sdk.InitializeAll();
  155. //Console.WriteLine($"OK (by ... ms), version {Pullenti.Ner.ProcessorService.Version}");
  156. List<string> npt_tokens = new();
  157. // запускаем обработку на пустом процессоре (без анализаторов NER)
  158. Pullenti.Ner.AnalysisResult are = Pullenti.Ner.ProcessorService.EmptyProcessor.Process(new Pullenti.Ner.SourceOfAnalysis(plainText), null, null);
  159. System.Console.Write("Noun groups: ");
  160. // перебираем токены
  161. for (Pullenti.Ner.Token t = are.FirstToken; t != null; t = t.Next)
  162. {
  163. // выделяем именную группу с текущего токена
  164. Pullenti.Ner.Core.NounPhraseToken npt = Pullenti.Ner.Core.NounPhraseHelper.TryParse(t, Pullenti.Ner.Core.NounPhraseParseAttr.No, 0, null);
  165. // не получилось
  166. if (npt == null)
  167. continue;
  168. // получилось, выводим в нормализованном виде
  169. //System.Console.Write($"[{npt.GetSourceText()}=>{npt.GetNormalCaseText(null, Pullenti.Morph.MorphNumber.Singular, Pullenti.Morph.MorphGender.Undefined, false)}] ");
  170. report.NounGroups += $"[{npt.GetSourceText()}=>{npt.GetNormalCaseText(null, Pullenti.Morph.MorphNumber.Singular, Pullenti.Morph.MorphGender.Undefined, false)}] ";
  171. npt_tokens.Add(npt.GetNormalCaseText(null, Pullenti.Morph.MorphNumber.Singular, Pullenti.Morph.MorphGender.Undefined, false));
  172. // указатель на последний токен именной группы
  173. t = npt.EndToken;
  174. }
  175. using (Pullenti.Ner.Processor proc = Pullenti.Ner.ProcessorService.CreateProcessor())
  176. {
  177. // анализируем текст
  178. Pullenti.Ner.AnalysisResult ar = proc.Process(new Pullenti.Ner.SourceOfAnalysis(plainText), null, null);
  179. // результирующие сущности
  180. //Console.WriteLine("\r\n==========================================\r\nEntities: ");
  181. foreach (Pullenti.Ner.Referent en in ar.Entities)
  182. {
  183. //Console.WriteLine($"{en.TypeName}: {en}");
  184. report.Entities += $"{en.TypeName}: {en}\r\n";
  185. foreach (Pullenti.Ner.Slot s in en.Slots)
  186. {
  187. //Console.WriteLine($" {s.TypeName}: {s.Value}");
  188. report.Entities += $" {s.TypeName}: {s.Value}<br>";
  189. }
  190. }
  191. // пример выделения именных групп
  192. //Console.WriteLine("\r\n==========================================\r\nNoun groups: ");
  193. for (Pullenti.Ner.Token t = ar.FirstToken; t != null; t = t.Next)
  194. {
  195. // токены с сущностями игнорируем
  196. if (t.GetReferent() != null)
  197. continue;
  198. // пробуем создать именную группу
  199. Pullenti.Ner.Core.NounPhraseToken npt = Pullenti.Ner.Core.NounPhraseHelper.TryParse(t, Pullenti.Ner.Core.NounPhraseParseAttr.AdjectiveCanBeLast, 0, null);
  200. // не получилось
  201. if (npt == null)
  202. continue;
  203. //Console.WriteLine(npt.ToString());
  204. report.EntitiesNounGroups += $"{npt}<br>";
  205. // указатель перемещаем на последний токен группы
  206. t = npt.EndToken;
  207. }
  208. }
  209. using (Pullenti.Ner.Processor proc = Pullenti.Ner.ProcessorService.CreateSpecificProcessor(Pullenti.Ner.Keyword.KeywordAnalyzer.ANALYZER_NAME))
  210. {
  211. Pullenti.Ner.AnalysisResult ar = proc.Process(new Pullenti.Ner.SourceOfAnalysis(plainText), null, null);
  212. //Console.WriteLine("\r\n==========================================\r\nKeywords1: ");
  213. foreach (Pullenti.Ner.Referent en in ar.Entities)
  214. {
  215. if (en is Pullenti.Ner.Keyword.KeywordReferent)
  216. //Console.WriteLine(en.ToString());
  217. report.Keywords1 += $"{en}<br>";
  218. }
  219. //Console.WriteLine("\r\n==========================================\r\nKeywords2: ");
  220. for (Pullenti.Ner.Token t = ar.FirstToken; t != null; t = t.Next)
  221. {
  222. if (t is Pullenti.Ner.ReferentToken)
  223. {
  224. Pullenti.Ner.Keyword.KeywordReferent kw = t.GetReferent() as Pullenti.Ner.Keyword.KeywordReferent;
  225. if (kw == null)
  226. continue;
  227. string kwstr = Pullenti.Ner.Core.MiscHelper.GetTextValueOfMetaToken(t as Pullenti.Ner.ReferentToken, Pullenti.Ner.Core.GetTextAttr.FirstNounGroupToNominativeSingle | Pullenti.Ner.Core.GetTextAttr.KeepRegister);
  228. //Console.WriteLine($"{kwstr} = {kw}");
  229. report.Keywords2 += $"{kwstr} = {kw}<br>";
  230. }
  231. }
  232. }
  233. int res = (from x in npt_tokens
  234. select x).Distinct().Count();
  235. Console.WriteLine($"npt_tokens.count={npt_tokens.Count}, distinct.Count={res}");
  236. Console.WriteLine("Analysis is over!");
  237. var query = from x in npt_tokens
  238. group x by x into g
  239. let count1 = g.Count()
  240. orderby count1 descending
  241. select new { Name = g.Key, Count = count1 };
  242. foreach (var result in query)
  243. {
  244. report.NounGroupsSorted += $"{result.Name}, Count: {result.Count}<br>";
  245. //Console.WriteLine($"Name: {result.Name}, Count: {result.Count}");
  246. }
  247. _navigationManager.NavigateTo("report");
  248. }
  249. else
  250. {
  251. _status = $"File duplicate founded, hash: {hash}.";
  252. Console.WriteLine(_status);
  253. _document = null;
  254. _memoryStream.Close();
  255. _modalInfo_error.Open("Загрузка не удалась, такой документ уже есть в системе.");
  256. }
  257. file = null;
  258. stream.Close();
  259. }
  260. _modalLoading.Close();
  261. }
  262. private async Task SaveDocument()
  263. {
  264. Console.WriteLine($"SaveDocument, docID: {DocID}.");
  265. _modalLoading.Open();
  266. /// all is fine, continue
  267. MySQLConnector dbCon = MySQLConnector.Instance();
  268. long id;
  269. string stringSQL;
  270. if (DocID > 0)
  271. {
  272. id = DocID;
  273. stringSQL = $"UPDATE articles " +
  274. $"SET filename='{_article.Filename}', article_name='{_article.Name}', authors='{_article.Authors}', " +
  275. $"date_publish='{_article.PublishDate:yyyy-MM-dd}', annotation='{_article.Annotation}', " +
  276. $"keywords='{_article.Keywords}', rating={_article.Rating}, file_hash='{_article.HashSum}' " +
  277. $"WHERE id={DocID}";
  278. await dbCon.SQLInsert(stringSQL);
  279. }
  280. else
  281. {
  282. stringSQL = $"INSERT INTO articles (filename, article_name, authors, date_publish, annotation, keywords, file_hash) " +
  283. $"VALUES ('{_article.Filename}', '{_article.Name}', '{_article.Authors}', '{_article.PublishDate:yyyy-MM-dd}'," +
  284. $"'{_article.Annotation}', '{_article.Keywords}', '{_article.HashSum}')";
  285. id = await dbCon.SQLInsert(stringSQL);
  286. }
  287. /// tmp
  288. int action_type = DocID > 0 ? 2 : 1;
  289. stringSQL = $"INSERT INTO actions_history (article_id, action_type, acc_id) " +
  290. $"VALUES ('{id}', '{action_type}', '{currentAcc.UUID}')";
  291. await dbCon.SQLInsert(stringSQL);
  292. Dictionary<string, PropertyInfo> propDict = Compare.SimpleCompare<ArticleModel>(_article, _articleClone);
  293. foreach (KeyValuePair<string, PropertyInfo> prop in propDict)
  294. {
  295. //Console.WriteLine($"property name: {prop.Key}, value: {prop.Value.GetValue(articleModel, null)}");
  296. stringSQL = $"INSERT INTO articles_edit_log (article_id, acc_id, field_name, field_prevvalue, field_newvalue) " +
  297. $"VALUES ('{id}', '{currentAcc.UUID}', '{prop.Key}', '{prop.Value.GetValue(_articleClone, null)}', '{prop.Value.GetValue(_article, null)}')";
  298. await dbCon.SQLInsert(stringSQL);
  299. }
  300. if (DocID > 0)
  301. {
  302. _status = propDict.Count > 0 ? "All changes saved, article has veryfied." : "Article verifyed without any changes.";
  303. //transactionId = await Verify();
  304. //Console.WriteLine("transactionId found " + transactionId);
  305. ///tmp
  306. //editsCount = await article.GetEditsCount(currentAcc.UUID);
  307. //modalInfo_transac.Open();
  308. }
  309. else
  310. {
  311. // получаем html
  312. GetHtmlParam htmlParams = new();
  313. htmlParams.OutHtmlAndBodyTags = true;
  314. string html = _document.GetHtmlString(htmlParams);
  315. string fullpath;
  316. string htmldirectorypath;
  317. string docdirectorypath;
  318. #if DEBUG
  319. htmldirectorypath = Path.Combine(Environment.CurrentDirectory, "wwwroot", STORAGE_FOLDER_NAME, "html");
  320. docdirectorypath = Path.Combine(Environment.CurrentDirectory, "wwwroot", STORAGE_FOLDER_NAME, "source");
  321. #else
  322. htmldirectorypath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", STORAGE_FOLDER_NAME, "html");
  323. docdirectorypath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "wwwroot", STORAGE_FOLDER_NAME, "source");
  324. #endif
  325. ///saving html
  326. fullpath = Path.Combine(htmldirectorypath, _document.SourceFileName + ".html");
  327. Console.WriteLine($"Saving file [{fullpath}]");
  328. Directory.CreateDirectory(htmldirectorypath);
  329. File.WriteAllBytes(fullpath, Encoding.UTF8.GetBytes(html));
  330. ///saving original files
  331. fullpath = Path.Combine(docdirectorypath, $"{id}_{_article.Filename}");
  332. Directory.CreateDirectory(docdirectorypath);
  333. FileStream fs = new(fullpath, FileMode.Create, FileAccess.Write);
  334. _memoryStream.Position = 0;
  335. await _memoryStream.CopyToAsync(fs);
  336. _status = $"User has saved new article data: [{id}_{_article.Filename}], memory size:{_memoryStream.Length}b, file size: {fs.Length}b";
  337. Console.WriteLine(_status);
  338. _memoryStream.Close();
  339. fs.Close();
  340. //bool confirmed = await JsRuntime.InvokeAsync<bool>("confirm", "Хотите загрузить еще статью?");
  341. //if (confirmed)
  342. // NavigationManager.NavigateTo("docedit", true);
  343. //else
  344. // NavigationManager.NavigateTo("");
  345. }
  346. /// reloading articles
  347. await AppData.LoadArticles();
  348. }
  349. async Task<string> CalculateHashSum(MemoryStream ms)
  350. {
  351. MD5CryptoServiceProvider md5Provider = new();
  352. ms.Position = 0;
  353. byte[] hash = await md5Provider.ComputeHashAsync(ms);
  354. return Convert.ToBase64String(hash);
  355. }
  356. List<string> ValidateForm<T>(T obj)
  357. {
  358. var props = typeof(T).GetProperties().Where(pi => Attribute.IsDefined(pi, typeof(RequiredAttribute)));
  359. List<string> result = new();
  360. foreach (var prop in props)
  361. {
  362. var val = prop.GetValue(obj, null);
  363. if (val == null || val?.ToString().Length == 0)
  364. result.Add(prop.Name);
  365. //Console.WriteLine($"Required field '{prop.Name}' is not filled.");
  366. }
  367. return result;
  368. }
  369. static string GetDisplayName(Enum enumValue)
  370. {
  371. return enumValue.GetType()
  372. .GetMember(enumValue.ToString())
  373. .First()
  374. .GetCustomAttribute<DisplayAttribute>()
  375. .GetName();
  376. }
  377. }
  378. }