From 2bd3bb3f127578826a14ab38c45befeb127f6494 Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Fri, 7 Jun 2024 20:09:31 +0300 Subject: [PATCH] Initial commit --- VBAMake.txt | 42 +++ VERSION | 1 + distr/models/ActionVerbs.txt | 463 ++++++++++++++++++++++++++++++++ script/addinManifest.txt | 102 +++++++ script/databaseManifest.txt | 82 ++++++ skeleton/!Майнинг.xlsm | Bin 0 -> 14051 bytes skeleton/Parsers.dotm | Bin 0 -> 23159 bytes src/addin/Declarations.bas | 63 +++++ src/addin/DevHelper.bas | 20 ++ src/addin/IteratorDetected.cls | 158 +++++++++++ src/addin/Main.bas | 178 ++++++++++++ src/addin/MainImpl.bas | 174 ++++++++++++ src/addin/ManualSubs.bas | 29 ++ src/addin/UIState.cls | 38 +++ src/addin/z_UIMessages.bas | 85 ++++++ src/addin/z_UIRibbon.bas | 117 ++++++++ src/database/DataAccess.bas | 66 +++++ src/database/Declarations.bas | 38 +++ src/database/DevHelper.bas | 50 ++++ src/database/ImportDlg.frm | 92 +++++++ src/database/ImportDlg.frx | Bin 0 -> 4120 bytes src/database/Main.bas | 62 +++++ src/database/MainImpl.bas | 152 +++++++++++ src/database/ManualSubs.bas | 6 + src/database/z_UIMessages.bas | 69 +++++ src/database/z_UIRibbon.bas | 11 + src/test/s_WordInteractions.cls | 56 ++++ test/TestBasic.docx | Bin 0 -> 21622 bytes ui/addin/.rels | 2 + ui/addin/customUI.xml | 108 ++++++++ ui/database/.rels | 2 + ui/database/customUI.xml | 29 ++ 32 files changed, 2295 insertions(+) create mode 100644 VBAMake.txt create mode 100644 VERSION create mode 100644 distr/models/ActionVerbs.txt create mode 100644 script/addinManifest.txt create mode 100644 script/databaseManifest.txt create mode 100644 skeleton/!Майнинг.xlsm create mode 100644 skeleton/Parsers.dotm create mode 100644 src/addin/Declarations.bas create mode 100644 src/addin/DevHelper.bas create mode 100644 src/addin/IteratorDetected.cls create mode 100644 src/addin/Main.bas create mode 100644 src/addin/MainImpl.bas create mode 100644 src/addin/ManualSubs.bas create mode 100644 src/addin/UIState.cls create mode 100644 src/addin/z_UIMessages.bas create mode 100644 src/addin/z_UIRibbon.bas create mode 100644 src/database/DataAccess.bas create mode 100644 src/database/Declarations.bas create mode 100644 src/database/DevHelper.bas create mode 100644 src/database/ImportDlg.frm create mode 100644 src/database/ImportDlg.frx create mode 100644 src/database/Main.bas create mode 100644 src/database/MainImpl.bas create mode 100644 src/database/ManualSubs.bas create mode 100644 src/database/z_UIMessages.bas create mode 100644 src/database/z_UIRibbon.bas create mode 100644 src/test/s_WordInteractions.cls create mode 100644 test/TestBasic.docx create mode 100644 ui/addin/.rels create mode 100644 ui/addin/customUI.xml create mode 100644 ui/database/.rels create mode 100644 ui/database/customUI.xml diff --git a/VBAMake.txt b/VBAMake.txt new file mode 100644 index 0000000..77ec724 --- /dev/null +++ b/VBAMake.txt @@ -0,0 +1,42 @@ +# == Properties Section == +# configuration properties +# use .ini format to define properties +# mandatory properties: name, artifact_home, source_home + +id = Concept-Mining +name = Концепт-Майнинг +description = Модуль извлечения (парсинга) данных из текстов +artifact_home = Концепт-Майнинг +source_home = Concept-Mining +install_home = \\fs1.concept.ru\projects\10 Автоматизация деятельности\01 Высокие технологии\Концепт-Майнинг + +%% +# === Build section === +# Available commands: +# build LOCAL_MANIFEST +# copy LOCAL_SOURCE -> [LOCAL_ARTIFACT] +# save_as LOCAL_ARTIFACT -> LOCAL_ARTIFACT +# run LOCAL_SOURCE.bat + +copy test +copy distr\models\ActionVerbs.txt -> models\ActionVerbs.txt + +build script\addinManifest.txt +build script\databaseManifest.txt +save_as !Майнинг.xlsm -> 55 Майнинг.xltm + +%% +# === Install section == +# Available commands: +# install LOCAL_ARTIFACT -> [INSTALL_PATH] +# add_template LOCAL_ARTIFACT -> [LOCAL_TEMPLATE] +# run LOCAL_ARTIFACT.bat <- [PARAMETERS] +# run APPLICATION <- [PARAMETERS] + +install Надстройка\Parsers.dotm -> Надстройка\Parsers.dotm +install !Майнинг.xlsm +install 55 Майнинг.xltm + +install Надстройка\Parsers.dotm -> \\fs1.concept.ru\Exchange\ConceptDistr\data\Add-ins\Word\Parsers.dotm +install models\ActionVerbs.txt -> \\fs1.concept.ru\Exchange\ConceptDistr\models\ActionVerbs.txt +add_template 55 Майнинг.xltm \ No newline at end of file diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..88c5fb8 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.4.0 diff --git a/distr/models/ActionVerbs.txt b/distr/models/ActionVerbs.txt new file mode 100644 index 0000000..a72b19c --- /dev/null +++ b/distr/models/ActionVerbs.txt @@ -0,0 +1,463 @@ +аккредитирует +анализирует +аннулирует +апробирует +вводит +ведет +взаимодействует +владеет +внедряет +вносит +возглавляет +возмещает +возобновляет +вступает +входит +выбирает +выдает +выплачивает +выполняет +выражает +выступает +выявляет +голосует +готовит +дает +действует +делегирует +доводит +доставляет +заключает +закрепляет +закрывает +заполняет +запрашивает +запрещает +заслушивает +защищает +заявляет +знакомит +знакомится +избирает +издает +изменяет +изучает +изымает +имеет +инициирует +информирует +исполняет +использует +истребует +комплектует +консультирует +контролирует +координирует +награждает +назначает +налагает +направляет +несет +обеспечивает +обжалует +обладает +обнародует +обобщает +оборудует +образует +обращается +обязывает +ограничивает +одобряет +оказывает +открывает +оповещает +определяет +организовывает +организует +освещает +освобождает +осуществляет +отвечает +отклоняет +открывает +отменяет +отрешает +отстраняет +отчитывается +оформляет +оценивает +передает +переоформляет +перечисляет +планирует +подготавливает +поддерживает +подписывает +получает +пользуется +поощряет +посещает +предлагает +предоставляет +председательствует +представляет +предъявляет +прекращает +привлекает +приглашает +признает +применяет +принимает +приобретает +приостанавливает +присваивает +проверяет +проводит +прогнозирует +продлевает +производит +публикует +развивает +размещает +разрабатывает +разрешает +разъясняет +распоряжается +распределяет +распускает +рассматривает +рассчитывает +расторгает +реализовывает +реализует +регистрирует +регулирует +решает +руководит +санкционирует +совершает +совершенствует +согласовывает +согласует +содействует +содержит +создает +созывает +составляет +способствует +требует +уведомляет +удостоверяет +уполномочивает +управляет +устанавливает +утверждает +участвует +учреждает +формирует +является +аккредитовать +анализировать +аннулировать +апробировать +вводить +вести +взаимодействовать +владеть +внедрять +вносить +возглавлять +возмещать +возобновлять +вступать +входить +выбирать +выдавать +выплачивать +выполнять +выражать +выступать +выявлять +голосовать +готовить +давать +действовать +делегировать +доводить +доставлять +заключать +закреплять +закрывать +заполнять +запрашивать +запрещать +заслушивать +защищать +заявлять +знакомить +знакомиться +избирать +издавать +изменять +изучать +изымать +иметь +инициировать +информировать +исполнять +использовать +истребовать +комплектовать +консультировать +контролировать +координировать +награждать +назначать +налагать +направлять +нести +обеспечивать +обжаловать +обладать +обнародовать +обобщать +оборудовать +образовывать +обращаться +обязывать +ограничивать +одобрять +оказывать +открывать +оповещать +определять +организовывать +организовать +освещать +освобождать +осуществлять +отвечать +отклонять +открывать +отменять +отрешать +отстранять +отчитываться +оформлять +оценивать +передать +переоформлять +перечислять +планировать +подготавливать +поддерживать +подписывать +получать +пользоваться +поощрять +посещать +предлагать +предоставлять +председательствовать +представлять +предъявлять +прекращать +привлекать +приглашать +признать +применять +принимать +приобретать +приостанавливать +присваивать +проверять +проводить +прогнозировать +продлевать +производить +публиковать +развивать +размещать +разрабатывать +разрешать +разъяснять +распоряжаться +распределять +распускать +рассматривать +рассчитывать +расторгать +реализовывать +реализовать +регистрировать +регулировать +решать +руководить +санкционировать +совершать +совершенствовать +согласовывать +согласовать +содействовать +содержать +создавать +созывать +составлять +способствовать +требовать +уведомлять +удостоверять +уполномочивать +управлять +устанавливать +утверждать +участвовать +учреждать +формировать +являться +аккредитация +анализирование +аннулирование +апробирование +введение +ведение +взаимодействие +владение +внедрение +внесение +возглавление +возмещение +возобновление +вступление +вхождение +выбирание +выдавание +выплачивание +выполнение +выражение +выступление +выявление +голосование +дача +действие +делегирование +доведение +доставление +заключение +закрепление +закрывание +заполнение +запрашивание +запрещение +заслушивание +защита +заявление +знакомство +ознакомление +избирание +издание +изменение +изучение +изъятие +имение +иницирование +информирование +исполнение +использование +требование +комплектование +консультирование +контролирование +координирование +награждение +назначение +налагание +направление +несение +обеспечение +обжалование +обладание +обнародование +обобщение +оборудование +обращение +ограничение +одобрение +оказание +открывание +оповещение +определение +освещение +освобождение +осуществление +отвечание +отклонение +открывание +отрешение +отстранение +оформление +передача +переоформление +перечисление +планирование +поддерживание +подписывание +получение +пользование +поощрение +посещение +предложение +предоставление +председательствование +представление +предъявление +прекращение +привлечение +приглашение +признание +применение +приобретение +приостановление +присваивание +проверка +проведение +прогнозирование +продление +производство +публикование +развитие +размещение +разрабатывание +разрешение +разъяснение +распоряжение +распределение +распускание +рассмотрение +расторжение +реализация +регистрирование +регулирование +решение +руководство +санкционирование +совершение +совершенствование +согласование +содействие +содержание +создание +составление +способствование +требование +уведомление +удостоверение +уполномочивание +управление +установление +утверждение +участие +учреждение +формирование +явление \ No newline at end of file diff --git a/script/addinManifest.txt b/script/addinManifest.txt new file mode 100644 index 0000000..9b7cb59 --- /dev/null +++ b/script/addinManifest.txt @@ -0,0 +1,102 @@ +# == Properties Section == +# configuration properties +# use .ini format to define properties +# mandatory properties: name, artifact + +name = Parsers.dotm +artifact = Надстройка\Parsers.dotm + +%% +# === Imports Section === +# Hierarchy of folders and files +# Use Tabulator to mark next level in hierarchy +# All folders are nested into SharedHome path + +api + ex_Python.bas + ex_WinAPI.bas + + API_Python.cls + API_Ribbon.cls + API_XLWrapper.cls + API_UserInteraction.cls + +word + ex_Word.bas + +parsers + ParserDeclarations.bas + z_ParserRegex.bas + PC_ParsedData.cls + PC_Fragment.cls + ExtractionOptions.cls + + PC_Tools.cls + DetectorClassifier.cls + DetectorListWords.cls + DetectorRegex.cls + DetectorMorpho.cls + + ParserDate.cls + ParserNPA.cls + + PC_InfoNPA.cls + + +utility + ex_VBA.bas + ex_Regex.bas + ex_DataPreparation.bas + + API_Config.cls + API_JSON.cls + API_Timer.cls + +ui + CSE_ProgressBar.frm + +dev + DevTester.bas + +%% +# === Source Code Section == +# Hierarchy of folders and files +# Use Tabulator to mark next level in hierarchy +# All folders are nested into SourceHome path + +src + addin + DevHelper.bas + + Declarations.bas + Main.bas + MainImpl.bas + z_UIRibbon.bas + z_UIMessages.bas + + UIState.cls + IteratorDetected.cls + + +%% +# ===== UI Section ======= +# Pairs of path to UI elements, use " -> " delimiter +# First component is a path relative to SourceHome\ui folders +# Second component is internal path inside project file + +addin\.rels -> _rels\.rels +addin\customUI.xml -> customUI\customUI.xml + +%% +# === References Section === +# List dependencies in one of the formats +# global : GLOBAL_NAME +# guid : {REGISTERED_GUID} +# file : PATH_TO_LIBRARY + +global : VBScript_RegExp_55 +global : Scripting +global : Shell32 +global : MSForms +global : ADODB +global : Excel \ No newline at end of file diff --git a/script/databaseManifest.txt b/script/databaseManifest.txt new file mode 100644 index 0000000..273b2da --- /dev/null +++ b/script/databaseManifest.txt @@ -0,0 +1,82 @@ +# == Properties Section == +# configuration properties +# use .ini format to define properties +# mandatory properties: name, artifact + +name = !Майнинг.xlsm +artifact = !Майнинг.xlsm + +%% +# === Imports Section === +# Hierarchy of folders and files +# Use Tabulator to mark next level in hierarchy +# All folders are nested into SharedHome path + +api + ex_WinAPI.bas + API_WordWrapper.cls + API_XLWrapper.cls + API_UserInteraction.cls + +parsers + ParserDeclarations.bas + ExtractionOptions.cls + +utility + ex_VBA.bas + ex_DataPreparation.bas + ex_Version.bas + + API_DistrManifest.cls + API_JSON.cls + API_Timer.cls + +ui + CSE_ProgressBar.frm + +dev + DevTester.bas + +%% +# === Source Code Section == +# Hierarchy of folders and files +# Use Tabulator to mark next level in hierarchy +# All folders are nested into SourceHome path + +src + database + ImportDlg.frm + + DevHelper.bas + + Declarations.bas + DataAccess.bas + Main.bas + MainImpl.bas + ManualSubs.bas + z_UIRibbon.bas + z_UIMessages.bas + + test + s_WordInteractions.cls + +%% +# ===== UI Section ======= +# Pairs of path to UI elements, use " -> " delimiter +# First component is a path relative to SourceHome\ui folders +# Second component is internal path inside project file + +database\.rels -> _rels\.rels +database\customUI.xml -> customUI\customUI.xml + +%% +# === References Section === +# List dependencies in one of the formats +# global : GLOBAL_NAME +# guid : {REGISTERED_GUID} +# file : PATH_TO_LIBRARY + +global : Word +global : Shell32 +global : Scripting +global : MSForms \ No newline at end of file diff --git a/skeleton/!Майнинг.xlsm b/skeleton/!Майнинг.xlsm new file mode 100644 index 0000000000000000000000000000000000000000..506a9bcc0043908ab2e56b03976cacaac40aa936 GIT binary patch literal 14051 zcmeIZWl&vR)-8;?yIXK~3lQAh-Q8V6aCdiicXzh{!Cis`2@XMn-IH|pmu~v$s`vYM zZ@s;LY^k&7sI}LcW6GNI$V-8Op#ebvK>+~)5d&!m4f#X>0|C8&0|B7|L4jxq+1Waq z*gESed)S*e>Cn5|SQF)gfl%fGfdJnB@A1EQ1}2opWCs|Ly53zP#???dP@xExQb8ua zQ?`5nTFPcu#b3LUmU_ICR^<0>mq&-D58D~*G!gag$}l;t2PbXpOWEl>42A1N82)KN zl`V$&kTe>b$nhB*u}B>_JmclNgk58}FSeT}t%wL(O_8vUKvgXiGLITL@k01Li;Ri# z$Kqn5j;t?5^L?o9pUe7?o;UHO=hqcnd%o>9#n;`Cbm+V;$tMX89agFi3hwo1*pYgq zN>@UlUU!-vf*66A)Pm%$qBCijN(&Jg8c}`|{Q`!7_|D5o#omktjt2KKvdYfDoO(^C z>xTD|JDx2TUO8V(rNOPu`HHM?g)>m24!00nCW?!etu_BfZ(g#*{(NB>cq#Gb9L9~Y zDT{J#=>d_lr~1)hAmv-N&dIkm z?^r+lbfq6xluRq2&})$s6Hb-4#v-^_${%O>1%0K4D^ljre2XR3+_ji({Z%+WI&RFC zhLcVfhw(nV!VNG#|4yc8HukD)P#_>JfPX{;yy0%m;AZD&WoTz-^~)Jns?6A~v7>nr z*80eYbyB04jJXCb2cfK}PZz_^(t9u=O{3r=CyPhr8$pWr*)n)89}@(3oq|H3;X}X zDNyzs#ly&Y9wuWPTD%_DARGwFjtEM_18s>Ql1c7$o1iNQvM8QJWIC#+L8T#sD|y(U zw-u7oqe!YGYew6&R7EmITY~BFfeA^I)8^-enqu#jL&UL?!5m^jk4HezMG%0bAaiBy znUdDy5e?It95sW8h+;fYENP1~%L?3rcn%Bpx4?g}9@t`y)3=~d9wwoJVN!SmvRNo} zyEa|rLdsP?F}c}`T-$L4^=!uQc4 zO68^QsqT~7i|S_5`FPJ~x1TXv5kxA=j`@dmTQJtG@i#@u6}2&_zz`f?zTdHsQ_Z6< z!xr|7ljKB3u;il{k;-9D{Ar3$mE?NjZ_w-p#lDwTr~+N36)s1Ewn zQiV0T!_F_k9Y4yk01Fp=Rlk&$06mlIKd{GR7=y<{P$$w|ID%bsfr5pwbc zU2+EPFoBSrX6l-T^_TVCDDZoRP#$e2gl1M6JL+XKMQpQge=k*HJX5K^mQ#1n_hRLF z!*p|#kf-5UkaV=@+IQI5+7g_N6CpKh#8gwb&VE-7*}uQgPZps9((TB7_H%_JQHP=z z!_!p?)6bc;f?(AUbVK<(G3{ z^^K*tZxWGB-ik9;33RZ6(vYb|B$_>{2^Cw>l7>f02)ICtk$SBhg4_oVF)i59&VNOFHD*{I zc-hoPB-F&+iir+vd3RcP(s0RM5FoEl_OqjA9V1ye!59kV*@p6c_HA&>|iWI2Px&tCt*qI(=PrjwF z`;Z{i&Ob({?BXNZvO%u_iYLbqOk%tWu)eL;& z2p&t(ug4uQmSS9EGZ;PeU3edoD%f6Wxv1;!p@|+GLLcl60~d7@|Gb-iizf%oaArg` z;6cf?Ox&=|U_=`^r#Qur zJ%_~t38E}KDq*)MFLxzeie?H^iA+0Xl_=4%uBpRZF>17HGy;`qQdLq-TpqrCc<|wy z_-0OLc^*wQX`)1Q5;AH8pG0Q25B~G|a)(GLo4t@qf(}rC*XHwE$daxks+nkofmVVSHhOR0#hI2$#UR)ez!}a3xpr z?~1j<&PYR+hWtIc7pjP?0(X7T`L-#iz)AVwgON+?)_7mO!AVP%Nh|hlP!Gwqb89Hl~*+M|?l0}hit~7^? z(1Vt0VZ^0p)4i(a|Jwh;c*PY%H5_&fcCC~6vXl3;&}URH)bRUOBFsX z>$4{H6n|Tvra#XBu2Sx_oTK{=ty}@mz&53nJYyCZuPrwfmvVGea*4W1t`QI4h-=8& zMOs6WH}maQCax-rC6@WfXh(|Ty1bzj$cVj0^La}1^s)D%Zgg5=3&tG*oRIu_e6YuH z28QHB9D7Gz+$p4LnvyE$78r?K>9*Sm3@r?6!mzl*btV95_onuWS zMvi_MG$1&S0*XTb_KFdIokK=ChMGnM%b1~uzzTka666kKifYL6Umq!8^g@xqZ0jxB z`W4r_zWzQLPbt6zFv3A>RV3o+H)XgGSr{}B6wdc&uQQ6YtTE(M7Rf=6-Cewj*I*_5 z7}Lt!j7Wu}M7q9wce!YZq^I&Yjqm}JYLE%LlwJ)UW5AM-hTR^t4{_(%`kc}%g85`e zBb*ykhsv2=n@XAfrwk>5SnNJge1=0|!x7>CH->r%TDmc}@d<#MD^g0zG^jYTHafB? zE0$8$Q%_3Qsg$$(g6I(0K$E~Bfk{`ZN__oNOvJ>yz)?rZ%Tl9GOu<$)s5-_!r7e+1 z$-)adOh3i|6p>C%MY@-g`0b$I9lI=ys!W6^yDnZ()0=Oa4GCdd`}yiA|70g}ejerh zFN^>0VIaAU-~BJc=At?L@rHIA9-o{5&pRZgOwhjNU+zfU=T&-dk<^}FLOghu3YDkyj(6#fAf zs&o;7Ztt`kW)!UP^!SBxKY#1K*oNY$nf|S0 zi*&A~qIo3A8kq^Rsa6Y-%+`eK(+^C$cz-JsWfLJ% zvG+OysFkmx%NIaSd$YSql(1JUXVC2=H^k4a^X**q->*hL#t;L9B=7Tc?_gk0k4is zI<)qS6 zey1!*jnRv(svt_=rwI`4apRKR@$)Ol_Ny}jY*ZLi$%Ig_)XwmL8jWD(A>$l)9qf~0 zs|&^SE)!qHBJ3E!Kd@{--J&w`dB}-5ye@MOZKeg(HB{s3mJQVI8m#}AmMy`5d`c=S zcxHxsT%q|{W(VIB_zH+;{!a76X$6FSqF*$RCE$mcvq%=)5k41)9W1ecSh>E%Fk83p zzF(YXD0*hn?u;20t*Gljs8k~I4a%?qRdE47pu51g&Yu`EpbFPcRj@Yefr?Am6?b*IY8v}Kl4yZ0yW~R>>mxi(Dy^Z? z`~74IjW6iu?}+U;^;n|>VEXytL~5A70jVM*7tM7=RDbjzEo2eM3S=d5|EOi)`s(HW zdKaE1+Bo3?Etk&Q@9h?>pD8s?|I1>C-Ml^^q&c->AF~U8-_ON9m?5RpXVT}Am zF%TAvsrcxSmd!uBjrj4W+a6k!8?5oc_-B%YDCQLffRlW1k(;Xt_2 zQ|F)V&sP?7=MS@gx%{nZSVqHNE`JgHTYDg}8jb8q zQ=wKL&YS>RMVQ}{%a9iDG*}lHMd+&a8uBlXAHTGYhlln~;K!QFJAk8-zvGL97V!jk z1DkS(+I`6pLCK^p+xzU!f7_8cH9qw;L2)h4UGqvJjrAD5;nd+bReI<`6*Lxy&O0z% z6IdjcYLoK0rIYqzDz`_JG58ie5@gyx1HM_$ozIE;Ar~n$ZUY(E^Wy&C@_cS!?wm;- zUW4A44xO2ZqgIBIxswSTq0=GRB!M)I+yGWP3OCkZdns3QnX3;kXvm4UgV zpYHziifqor3?kJKg=8gIKN`Zw<>MEajwhw%vy1;@qtVQeaLC zMl$CwWFXOh#RnNCaM=0jc6TuMGW@Mp*xApOO!9Ri6Rdlvh*XT z;+|^JU`D&G=-xN>X_~tobMt z`qWJqoW=EZ4&#GN_eFsQRJcJ1y;;QS4w%f7If1|7a-Pw^OF>FAr-0Ijb{GMslj*u` zU%;=EPlw!KL-sNKIyj`BvtO&XUyXN%ya2TNyFvQDjPz)uJON!0KtMe>zsexLcYU1A zO>9gUexHBG_$L}0wzwi_-C{fVs-EXR?c8j{T&O*5IHn7$a9T=Jj66@WOkGD-@hO_h zB~?BS!9$f1*~a?f(j$?eh$x4KtBL0jE7DLR_NA>fxshkbz7&?`k}K8|_#8}d9ArN4 zpFahy5jVLyWFld(onE!Y+iaVUijt4vD!eQ>6A7LW&!0CPDLG z8?Zz4;d?s0=xt{vNFZ)r!hGpK;|_Bt!3k#F#Mr#-Kg^(b41o-ZK_ny~;Aw-LirIcG znRRVc!oV2gxwNo8C0T9EJ4RkR-d$U7rj+FTG}9<`H9}vV!;s%J5qgDcjoy*{G118@ce!SSOyQK9q0f-f^e^iOST&`uq+-)7M!gp3I;9Ry! ziO$M>$j2qfX&k)=N7rBR^$Se*(huVBU-z9>p%PU)?|W13ZwL(Jl$CAjBL`;>qM+VCu z^5q*Np;{$KgHR4qg{y;17D<+=mG|pi9*FetlZzLz;&>RCoHZPC+&pisCPp`dOUFZdVFy{$K6pJREW;#a1aMQhnh&w3_ zhmfJ>7?eO?A(nIq%zMY3gk@UU7lqv4K`bx}vF_!F*Ug-ebenM~YX@l80auCdMAS1ylB zJ!r^>-7I%ggL48O5vkoEC-WORqojAM)P&cbLY(nr|67C)DY_I}(k zgIj`mDPb_Z9_$vlUgIq9lIg1>2NP_|GUdip^0?u7dbotY`=+jA%4zN07S(FSo@Zl` zXWMbsXxELuZ><+LiliBLSh=6D%?y;r0|knsu~~n&?tZu0~TZ6U3? zH=E6p#1yoCL=YuG7*CNHM8rw>w@iy-IT&&k8X#5l2$)fp4J2(jylgBTO-qJYi>N?F zTW;tgI8fGp4VQ9A8nd)x(QL)2Y^b3fn^BATBRi5MLFH%*&?x1i+_gH*9XtK3YP$XHbqmJP-;b+ zA;W*Lzy{q@VmF04w1k30W$Np`t@QkAPrVz&MiLLGm0Fn*hE?D zwbemmn%Nx$)6z0~AS)#f?nTM8w(VzQ~~t&c2Y0ME4Ts(g&HEI38SSx~Lna$da= z9JpHziauE4?utHN#O>;6ZldXvtvGtG+oF5%xm(Y^Gd@?(4$V@wXx7oP>f@5d#$+-R z-WM4>)$6}P(olupq=lv9q_pi_5I+<8>Yj+-lk>TfE{6Fg!K?!re*9&kF2V;Fh;3^ zQut!8Pw!yLN9g^sLX;pCZnuvm1*%+2%Xpwz?6vl#pG#KWwNGhcOJ+jEpA>Fr;)|l6 zLLyaO;A-}+^9dNTDF)6@iDh94|G_m8%OD$cUlJin#tYNzRlj+DHXv1Cs_^=loPGRym*0`jt1iP_>3iH zSd#X2J17dw=)!an3WA6Aske>mcwumc{1sQ3&FOQ<@}J4Up7O|Tf~xpA@uU~ZEnfwv z{GKUD`_e9PzGCZLfz^d@!aTjVx3+l*YtfpgTkoqasC35(Z(6~Ym!Z^w6q0(tCl3Z4 z#R&t)9q6UZp%_Q&{6f?REBD<+OmzxOvpxH(HqmaPFwD?N{h5`_U}NSx+J3HRkcI-# zv1$VqW+RjwlN`*BsbRf+}V_86#1)T1@ZZy}jdgE@v4QOAdIZTnF*XxEf= zwtFK|PoCKbT2%#p%fgStUkXps%lbQ3{qZ7t^WubajvtL&%iQR00fgrxe03r0XgOHQ zkPBhFSG#uU9$CDN&QR$NY_f%;<)<=##G8&>xvcB>!15EJ!0+pmyU(4z?OD~IXjz@; zN8b~PY(w{TV99=;L*ir8iwQ^SU35Ck6^r;Z4Jj6&&L#FEPJG3njXG5hi9V@}BKRt; zT7gL?xn+2}OQ*h^v|_eRT3n~M+${?A>O(9DqLoKf-thN@z&v;gNZA;lo^M!gAVsK| zYDGWwM}1a5dZ9GpBxvnzi(h(oTJxyVTdqGd0Aa2K09uYvd=WEimEk$M!@{?n=Uh>-^TL$!DJ ztNrWqksQ)X)#Bhh7@@Nr5dCM4=Q3}a1#7O%{3}x9x22iITPvFs`B!0cr zYF)%F+Lq{kZ64l5e8}t3{s`Ew)B)K9-cdnBeh-s3hZ^TcA5S!N%;uX`=}xqGeVk1m zPRC~!7FrM7zwAA(cJ+T~RheEQnhtF{^%mVe6G$9R-m+tN^3%XYyKo_dtUX@#)E)8I z?ucfy<*Hsd%dK8GZTw_)(y^&h!rOj3;tk*7vws?3)M4uolh^y`1)X!0So>KmDdebO zixi81vA>$f!0s$H*yne)@YZ%A6BJ(C0IXUGpxFWK@3za_z|q85$=T7u*6gmYk=HLW6dlwPFpNuK)?P13&fsCDWOlg(|j60r==OsdndOf|S7`}-z& zp55x26-f(Gw<1S(f#cb3lQL@oux!a7GU6~U#Dd_swMo2|B)Y$JP>J+wyWN} zc)2rcj74aK*TCfyUmPMIoMu^utVDVVsDj5R1#1=%fn?R~RKIG;a}U6VtCK88E(mZ< zLb{S%dQ=Z#YNoUmOivYv`K(%ctN-1`yVoq$5Zq3jujN!p_L$*oKH=o^U-qKBw_%p* z11yUmV2R%SZFzoG+x}iL`CIwz_rveq)8ECnz?i=9envErJJ1&)ch7{yAUGi<4w6=t z15m@Cs}O4`v3F?Co(*^=y?cB1Q+w3Rg}IFHuR zpy_yG4d&>((?MlrCBsHy)zM+=f)q%fu?D;cSL&jn%VPQVS0utb7{?wWaHlKFEp4v! zAR8HEo6c#!d8q|(^D1h*oJ4yO^4J#id=bBz3Y6fA)#u9>=Nc6`z4A2_ICi_uw(>#% zE4G;!|8Wc0A^P|IBh*1!)c*g;4E)|j`o|0a%5F%)=Rr>a%buj~L*&EB^dyOw$e)2k zbl384CCyvN9v%;u0|lc8j5Zl{d^9j`B7%%aV5@BTiGx^$(`i3t@io`vs`J2mO9Wz6 zk4|??O5KG;q-gGNnW`=bS{q=D*$vyLI^0w$iIy8no7^ZGrAgmzugRIa$~187)qt*N zDd9uF?lCsz$}VZA7h~1{T{ltTgJf7Ha za{Eh;-q20{C<07@Iv~cN_?r^g1IX!Y;;3Ze?EH(R3~!#9=nXAwVf5_{?2YVADGkL< z#EmHpMj`9Pfg@=rtlh9IQv<$oFfudSGf(WWZ;Kn~8*iC~>KlU0B1=#3&&Kryk}6%& zm3q)pFd3hD-uI&Cy>yP?*kxI`isT>|No`T z|BrsD2-UElC4kgE0K1C}D5e|R8Ob}^**h^9**Tj0iW>o~X8*0B1C-=bJQJYR3MIDwAqdmz`E$nGwX&6?Vp%F1+29Ru;Rz(ls z&!oA$V)5KtB7qpgXu!ki3`9olyLhu<4caCXtp(}3{+E#Kkn3c|zATK=j05)I*Ac;? zuHt>T#+RkDI7FPmXw0#qMyq-EBUbz-Kd1aOEw~y6D`A!_Fk2=uKGCqB70KclX(VUW ztBu${P<3Z$>n^E03#)eQ`0mJG_|oW;tJQfm9ZxnquXiptJztz;JlCGz(hSncHBBX2 zusBrYkP&@Rz1%#&{}OWFve2cPvc?D9@8l3*)7Is<_P!@IT49|`XGdx0?l2Jn)DdNR z+bijtes_3J*Ql$<3x%r}^M!?vtbic4Jb&WOwWFk zWMFaZc6YrGOMsrNRx&?$3%+egOfofe-@-Lg8^{z+IzwX3J`~sR{-XCyOWz!iPJNiXcVLVNT$LI~vpEzc{Ie zNWe2=rMTZfEV&BDtMv;-B*bZQk~#BNsxFdsv^Mf_64dNAdn|R@Rbz<=sI3PHoieJ% z+`Cis>gz4zI1?=|i)ok2`}=521j61S#e@vL501X$*d4hi+Z@c;r}oBGyzRVHdN24i z^ND|j-8*LO(IjVgYM<_-*K>-qo&Sf_lHz(nJNrhykJZ;!KDLoSf}!R3sSwaq~<2lM?0Z<`}Vtr$lz%t9L88;UNi*6(azr4}#$k zeUUgaU5w|f&tm4Z!4D9MYb7EkRz~>b*F<*oVbu%#G2sH;eV2A`>f?9f_}zPtr|u8D zcHWZ=Sy{WBg80;=yI~P6NAQv6C>Iz~f|zFIUdQyCE8QLg(9C7-Plkwn55n#D@@3Zq z#LS$9bri=;uwW^jRY$-v&~_~v{9~u0y9gH6af%sCk-cU6Q2UQORLN{&9^H*Jf#Gb1 zbeQL!JCsrx29)N9RP>DDJUOEDi+b0J3={9Oj-}?Rj%$7j%H0UcZDA+v>tn-Y7cG`z zQyxmb%=m!p&1a@p6C8y-s7qKz=THry(yT@((a%fKYht;i8hZ=tR0@kS#h<`ezLN{Q zP^7j>a?wMafSzZWma6Sx)cFD2hd-nv^9QSQ_bG<-9Ehw?SNKR3WZWV=UliSbbAnMHq^27H=0)RC6~EO`Je ztp($})X`6A3Hw_Hiu-47ZQi@w1~C!K?3qx_m2k*!S1^%v$Tz?(6p z6zK|$$lZK}MH5jn12~#rZTR<9G=ujWM1_JuGnE$Q#zs6wyEdH7HpTz$jk-e3E>&X7d4>0@(wwkRCin%H(Sv2EM7ZQHhOPi)(mIGNbCzsx!Je($_Gr(V_jec!I#yL$IR zqZYb*b<0TtgP;I_0YCr%01yB~x%F?C00IEufdc>_13&<23fkH@8QVDND!JPkJ8ILq zSzF;3fB=!_0RVmD|G(#d@CY;}PuQ%}BM3i%-ogX51Phf$7LZe$#urVH@@eO3W?-)h zJp-9$4f<~9<5?iG#UqMh0U{mcc(NyiEL56aRfvw78JZ0l|3pLu!HI^QPG6?9U(w@{o+&(OplHe^P8Kp}c zQ!O{dhfq?~4_mzl<7jeG+T^+0(@uba6L#*RqSmRFqhPN@y=nJ?ISiKUay3(HtL{sd zeoluD;yRU6(P3RVyKH4%#Vos2F3BBtO!kUc6+?bl`_Z#1MjNHc-A`xl1`T1cdlr~Sw#@9I}{DqsA1Oid?`D$Ntf^N zYR~E^K$6{p&nU}PIu;kkztP?gFIR5_rx~0OEecUj7B}h|BvQAq_!*uDy(ltXpon2h zkRm6jUvzgNYBn{{ zaNnm$nRLHAk2y3sb9;uSQG1LX36Ej8MGwEK+)llkyUeH)9z>tc1@03OcURQZrqIn& zjSH6lBB+-vWo{-U|0vNwU^Q+-xS>(LPTa!GIQ#1Dp3M^!;xyJ zZEj)uq&y}-Y?r2a4DAYGdyZNn`yQEpSED`0fyVuB6~cxF06_f4aJ6+XqBF8JbhiF> zm4AI?QOc&x8a>L$71;%j+@&TN2#`>*GKKY0q_~+CydHLWmIFIdXQrLT(M)xntSWl z>j5o15kFj(i3v!URA?KqBpHK92!wfpT+9QFsSqU1<6#O!%%IN3IUbDy8@M5K7Jh=B@o~Aao2p=mng5~otQ!D?4BKNn`Im)~UsVO1 z86yNp1xL{d{X8$+4;Z58j-pligl&?k(0GoW!45!L;we1`I+PKxc1jWK%BR3iz`DVZ z3$^h{j5HfLL;8s=aB`r^klv4Di+&h;#JR;-iwHFi@Z{j+4{1Z&;?QF6hjJRF+`ClL zvkEp8GSP^VKhW_fL1Ux?FLIbtq|E!za|AJ5)yoI-XX&(GRI+FD$uxDb@8e%wG|9eF z_ZzsZ28hwZc#yuA>Qp-j!571UT8=wfwWfgOfO~NG_>HE0wHS91{$iQ=DFYrQB2yH$ zLk?>!e4?ZhH3d&IKi~my>i}#CtO&{gp`ItMe;I zX13Pq6BodHP%6oCsp2)fWG0zS?_ zxT~0Z8v_$&WCg!IB+@d2kf`3l_%WzUWG^S~#wkR0e1}%#ZJ9N>d1d}=CWl@_MRwES zEPHFT-91!ctdVn~O9eR7+k!1S#ln}L&~9r{i>QQ@3|cKFztfln^Op+_Stxws5s2x$38y1Y1_e1O6oLIh?0 zERbvY(XZvS7f8`=(}sgs9wJY};BT4HmJBr@CBbSx7s7g4*|3aSLS2Yu0zv*52;^rm zgEDnHE%#Q{^#%K%R02liIPd4TXORN^Rto?Kz<;R3Ki$i}D#m}i8o=)vaG%>=A&*O`6f0C4^qWp5FtY_PeTZLjFYPfL}sTO^yRHw zkdxLSPzLAYJs)H%0U=jk1k8lt#e<>3<%Y~D1Vf#j`!Nh+D1>Bbh}iu!0!y`<0}m$R za|zX-6%}SJJe(~*_1~4pudEQwh$hlr-SEPuI8VGBjxXRbXNXypB>OFEM*!K9$eEAM z_$YrtAb5I#PliSvpI^&VdT5pY**T0qzAZxDpabhKd@>cpIVvP^OI}T3016jDmi$$8 z<2s)h>FZq1y%_hQtHlC@hrUy?!LCHyQjefzs>FgiJv@JIq#_uDXT3Xm``RS{20U?~ zdtvSfX$*}`*b~7!K{w7z3ij!TY#%mk7#7sCT$N0p8GO-SD6jx&Ltp2==vN1xX2@8# zvm7Nn>GKBNw5!r*dda7>m{+y;X>SGohitUDpv!Sw=5$mc6)ThEzymk6wM){0 z+C(QM2DA-1Z<6)fL6_H8ga@yWkA_I>_Gjg5B_+?A_z{2Y*MAgSbV*sL;BT>A{TAE* zfBIl-WAwjCZRUSTt)9~r1U*ksc(!?FN#c5mH-PY{2(lBpILqU@YcP9j-;|z$E<~pR z?}x?(?Zzga5aM}yz1A3NgpB|Sy}0u5flIK@mq!-0ydF+G21Sw)m6H+LW-YI8JC!2o z&vNWvfcQnylG3W=t}>70`jlG zHD`%&(s#VGFKU(5h$51HD>`6PpvxBG?MpU6B4r(i*!@$xYQR5vCL;?_6~0H;`R5X1 z(r!x+BDIzX#a~qtVd31O9|$_R-XMb+rAdI3XkpEF{?+s3WUQx~3D;!wQW85@eL8F0hiB$WWPy9l;#{x8i>jvwTW|v~T)ggxE(+Pf6>_0n zZ@8P#8G}&&wk3-Mh>?EFb=hndC@ba)A12=h@FWG+$~D$-gNfdna@o)>nVTa6O)ez2*der@+Ves`pS@xybyJ2H# zO?C3;-|2(4+>x*iFXA)RTW^0#s!mre<7(e6)b9C) zTq=|hairmi+UgU3>(LrJ)1Nsu@3XYg%dV{rePxyPD(>R+%JcSZl^&0Gnm0S37Ym}~ zr!Usex=p%DH`6@ro*kdS|H;(mb)pX=g8={pVgUf4{`sgglk6TBu0dbz`@dh5DL%VWz&gu4V}EKG8u;K&bO;tte~v+AXiwIgjZe{ zlY0QV(#s*BQI;gp$^S~m76RpXib%NRa> z<%k;--1*9*)Z1iLP+_Mv6eCuA6AS4?Tg~&U>&{I%n3xda*sv|)Os7n#L4#5pFQuMq z0vTyQCpQv;DlH;!5!nszS){5_j+QQ&P|6T~hz+Q@@Jgg73O5>VuE{6&bx?ESr3Kw< zNa7SUHymdwdtYdqhaDX!1~(Qr1}=BQ|HjbG$HL6B1N9?LaMcLtLSN;H&nfoGb=3NR zAfz}Tx?Pn8ueQ;pn~Bum6nzayd)l~>>CvG${*Kyjeuw(Xq)nw}?HTB62;*I%aIUOc%?(&0bP40#!M(h_ zTW<%J9Ot*mgKZUSrXEJBp#K!L!BU7`#CTmWUDH(s6~1L?{i>nWKnJYdbBWANZRuv$?h!m`G)v=P-&&j zH~Ky~CgjmOQC=S3djK39E-sGehvUo5!oXY{rBpj?l=2+!uhs3RtGpguueWvK4OzHo2bmXT1I&ehLtz+z# zb~}wH4Jpd^{rGSj{X=%(p{BpvLi+rRPsH>MZnLqY}>93%Nh?fDD};*gu|!xD z$lK2Zbo#P>#AA82VC<6HpGxi3g?;c{3Pcz*4kar>+Ng%*2x8lLS+&~A83ex_GY{_ZaQUZ-5+Oe zJF;HmZxO~FUTIu*q`a{-Z|t4AZ;5c8R3AQP;U&QKm|Y?}<}x-HhD2$_mq<#|7c7Se z|74IF27e?D(kI>O2L;MxK+^ISQf@`w1sPO{3gR=V0uD!_-O12P$?kjr>m*TXSDBy- z^BcBEe40NP+8wf=9G(PnRG$K+u>iY+(&Tbw)K5lXD{YLBw^mtOF)C%c8|{Q#@~`_W z&TD9liwJ?~kGgN%Rs?fbli?O(9OOD8_*KtBn9w;T(}gkS5)D>?mk-XMo% zB|9bgwjNBA5Q$(3EJa_!xS5dR6(8`F+_i<>x=i(@uK`uBsvnOJ>|g%bxz6jtJzCbh-8^3( zg6;>?GmnNH+RbM^>s>rOSC2=&zIWSS4||&j-B_X?R=y&VR!^y!LuYsE_8ZzdTIWAn zHcK|YX0O_5{8>Nh%tc=vpWW)sLuErZD}{SjoAypUnslO{egrT$e%48Sy}r|1(cz`I zHL25t_-t=SEhJ5R9-1^A<9>2Zp?4c^?yzS?98-=+fM`KEXFW z0Y}~`FlGOGSx!wlyVB(IYVuQe^7Y!ryws0|&UiWCs62S8HpE?4=Y~wl@bboj1VHR2_%ab-~V*m2E z*NnGz#vCtL;wZAln^~UuNkSm=HO1S(dw3%bcVtU0@?LqxYU-NWVWSpTDCZ}C{%!yi zVzc0Txuy8$q~+rE;!zBOeeZ{v%?PbkDxONF4en;_)7hnW$Hn-&oqF|%P2Li3%U~{b zRPF-ag@-pYcT4G1Nzaeaj}==PK`TtcS~7zi*eS`YZ5!WQ5T4i#mkSh-T<#`rtD26T zgJZU_gVj+7W~ay-u>%xB-_1{#l^lpyOs{RYi;?`z&EI%02aS#o;qb_)u~M}iE^Y#N zA|pOzz8=%Rxjx;Sbi0i5vyD1P_@@ec8Z(I)&-7O}8$%z&ICbU{$UxN&W3QWieOG9Q z2dbvO3YE=zFw{1X3ZXTgES71_=pWYYkC#h9Q$FZE(#RQLP+DRnH)%sF*Ikwm_F~>x zc^^~X%>VcYeXJf$T*5b0+O%O($X}>TNK5hyixUOq{BGro+|b^-|7g7!x%{|3&=nd( zkyQB*-y`Mu{%v%s98Z)Nsp2dMhG?%=epw6j53~E?NK5fh{rbrE!U@5_J9#|;o0aGJ6 ziCXA*JqQKh-8g0pZWu>B2!%dm)Sxv~iAsSY6a}H+rItjI=oUdsA_gaJO$s3g0Kzz0tU$Jf2_@kwp=^#=elk|S+kcIeY-~j& z+Bn%JBF;#!lSRdDqYY`E+)Q~`B6tQqY6vnkQ#H>d39Q?hCyZnrEFpJ`<>V zS!hm3tysRKcG0#ZUj=(2FRoO;M+`DyV2OGznr6Y9@@uZm=DzE9N>pk}Rez&jw(JUKc=)h`XbwJhh>?dBZE4S9`-8#u{D zDh$dnm1mfFun4-@Eh`iZQw^;0)Ltbr1(4?KBA`d=%&6g`aF)c7IvggJKOQ4!#gu3Y zO2bR-G#WeS=V|n~5+m}P;d}mjMx3)hMxDy;Lm-?-(PrW`^gh@~oM7ddE6TkbIK`l1S7L`N3C=i7LwU^~cOtQYckpSH z;m`lsI1UJhTPusC#**US2g-$P6EkMo3N7H}akZB(QONHd_M;eGfOG(bTnW@ZEY^b_ zYv^OHDujvZ5S&rFj#HFHrLL(h2dxtNL8sKWl$a{8cjrb|z8moFMhTA|m14v}F@U~X z`pfy4L?_)Wzlys_q!p&mS&z%4=2e zY%ySD!oa^w>?;01k+{*?(V%sXt-uAeAeyElq@mC&ARsA#GDL?UvIuP{nMsDIKANiy zrn4{&8}h;m(`Wety#Vo)hDmvXl3;=g^KgY`0G&h2%Y2{+p-f;~5Kvy)E5&5kdo@Ul z4T%sv97D}8aA}}Z6wJYXG7?}W+WTUb?+l~i(F8-G3+_7YMj2Z>7sJQ=*02JP_xP!W zh>Ud2S$NDeBG0Zc-;bjPGpvUd-N)h8j5}6kClVVEgmdI*D_rQHO4-w}47iDB-~H~~X3Mvme~Gr4_n{RRZX^IP^{(*OoN zB*FEVOMwIUNDHy`fw$qjB32lp{EGycxpty|Jac%02u?}#E49`t2^k|#XOWahy? zc98E$B|`Vi5~TuVXET{_lKF}VQu0!nq7hKlSsQM%hk?GZh+ujBL@-1dOw;WKWsfR^hW!y)ngvQz4{}Jk2DeD7M*{-I zspdEb;ZG4ZtRG3I9t>k>kna@oQ^Y)N(3IR5$}Vj|L_O;aT3PG)LXv`O=d>9mK8AT{ z2yw0F#om!!`!dVs6x&x?)nJ-s1fzUY3%>I4eADUU&cI{W;~7PD9oc7d^Iq_Y#KF0h z$G=tw$N!^5yLMuhR;ITqXX?PAY5U;P7uV*Wd#{CV zy@OHVL@Zx(w4~F=C!u(@Otu!KG`}Mz$u6{O*F-G}ucE;?>@I%Ox^BTk`2Vb-9?0 z`LOH%WHHo2+>UJ7x41tutCCh4zcGY3^6186n^sL4rT00rG3dBT5f$ayWcI!L&l~YQ zs-}V?-tI&&vu>wj!%;ujDDkmuolBmJMBs<=Lj0eYAa z-J#=dwzK@GxFVD_+;#ru92a|R5{4Cpx~2H;kIv)^NEejXPxy}tSIg^DWJ3ojSgs!= zq=v>`;wRng*@~X(vOSWZ75F*^mf@E-9H;Y0_8<`f zH(iAco(tucbJ#E^oReVQtjx=FtN*~6WaOjVAh9+X*%cG`(T>vgwz4*t3*%Ut*KBH$ zbvzd7a^#=uCd=m7{Yf~pjvpY&yWs_b6PSw%BI*=ocXByB`J)!8GeU5N5_gDuWg^zb z8_((;T<6|(jK(m)kKVRPXgV}19YGiboK-1REw(NJ9#eN6A60K|k(P}lXe2pRtpp0shcHZXSh-tYLwhPn1s%w8*E zH~1(g6ATkQ`=nX{xrBw)Rew(;Bx&Z*IrSs;`f2m*D+6(x=WFu{mjG=WNy|Pc3nYRO zynDbj)jsGWs(=D-ig7xU`FNp@6W&_01a-Wwa{Lfw!s~ftY`3fZl~6qv*fQ`#G-In> zfN`rs;QCo#3*KSvRG)CxO5gZne{UNu7st(?$Qj>{${F06fa)GQLa#m0L&QN<_{@U` zy=>yl${l*08l3Hz?dMBIsJ&}a_g^p{`fiSUwAnj?=+z;7@~E%Y$ib3iF$~{33Vo4S06?{RnWWmYfnF1ak1|<{MSBaf_#Zh$sd@ zmd?H6KlYRs*ocQAu^ku(*~~+;q!?ttPxtXC-CFH|q&gvqwq>qoj$Mw|=QdsLFn?~X zgm>6+v$_v*p(g#R-~1z!C(^3a*SD;)fgP2PSA#99d7#yQ*2e->({NiGd0TXH_xi;{9N0eH}mQ zJUG-cXlf70@)YFV=?b7iQ1 zqlOUtO&PH*%|pI0b1-~3-f%l!6CxF#bf&GnrR%IO>oa{#PmKKYx8#QFbl0A@+XmYU zcM)KYAYEw-$>;Dycu7H26hm4u%2f`6)_Xi}lnQB&y7W~)?$!n!Ep2no5wY+K{Pak` z{T=)ldCiwgy6!_(=K15)GGaBn2;tl0RbOncotCu#H5-WeT}TJkrXK*`bCJ@!p*48R zBKuNY!e+L8Y(uO&Ix&Mkw@$knYmfW=k9^U{82SRw9~%Hab=X;1hJPB0w85B59-E50 zMJFiB##K>Js{@z<_+y}?o{y`ycW2kvggq*F6Y)24>Mw<-$YB(qCV{c>W2V$GF%`um zA*S80Oe+l;i3cW-Xs#jgJ_ep~-VCWu4-CD{wgl3`#N8uo5UQf&Rmfq~3w8Tw30
N$hrp!f#y3LbP77KebtWV{O<+{2 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 literal 0 HcmV?d00001 diff --git a/src/addin/Declarations.bas b/src/addin/Declarations.bas new file mode 100644 index 0000000..656bcbe --- /dev/null +++ b/src/addin/Declarations.bas @@ -0,0 +1,63 @@ +Attribute VB_Name = "Declarations" +Option Private Module +Option Explicit + +Public Const MAX_FIND_LEN = 250 + +Public Const TEMP_FILE_NAME = "conceptParsers" + +' Markup color +Public Enum TColor + [_First] = 1 + + T_COLOR_YELLOW = 1 ' = 7 + T_COLOR_GREEN = 2 ' = 4 + T_COLOR_TEAL = 3 ' = 10 + T_COLOR_GREY = 4 ' = 15 + T_COLOR_BLUE = 5 ' = 9 + + [_Last] = 5 +End Enum + +' _E_ - export +Public Enum ExportStruct + [_First] = 1 + + S_E_ID = 1 + S_E_START = 2 + S_E_FINISH = 3 + S_E_TYPE = 4 + S_E_TEXT = 5 + S_E_DATA = 6 + + [_Last] = 6 +End Enum + +Public Function ColorToStr(iColor As TColor) As String + Select Case iColor + Case T_COLOR_YELLOW: ColorToStr = "" + Case T_COLOR_GREEN: ColorToStr = "" + Case T_COLOR_TEAL: ColorToStr = "" + Case T_COLOR_GREY: ColorToStr = "" + Case T_COLOR_BLUE: ColorToStr = "" + End Select +End Function + +Public Function ColorToColorIndex(iColor As TColor) As Integer + Select Case iColor + Case T_COLOR_YELLOW: ColorToColorIndex = WdColorIndex.wdYellow + Case T_COLOR_GREEN: ColorToColorIndex = WdColorIndex.wdBrightGreen + Case T_COLOR_TEAL: ColorToColorIndex = WdColorIndex.wdTurquoise + Case T_COLOR_GREY: ColorToColorIndex = WdColorIndex.wdGray25 + Case T_COLOR_BLUE: ColorToColorIndex = WdColorIndex.wdBlue + End Select +End Function + +Public Function PTools() As PC_Tools + Static s_Parsers As PC_Tools + If s_Parsers Is Nothing Then + Set s_Parsers = New PC_Tools + End If + Set PTools = s_Parsers +End Function + diff --git a/src/addin/DevHelper.bas b/src/addin/DevHelper.bas new file mode 100644 index 0000000..acf519b --- /dev/null +++ b/src/addin/DevHelper.bas @@ -0,0 +1,20 @@ +Attribute VB_Name = "DevHelper" +Option Explicit + +Public Function Dev_PrepareSkeleton() + Call ThisDocument.Range.Delete +End Function + +Public Sub Dev_ManualRunTest() + Dim sSuite$: sSuite = "s_Database" + Dim sTest$: sTest = "t_RenameLawFile" + Dim sMsg$: sMsg = Dev_RunTestDebug(sSuite, sTest) + Debug.Print sMsg + Call MsgBox(sMsg) +End Sub + +Public Function Dev_GetTestSuite(sName$) As Object + Select Case sName + ' Case "s_ActiveStateExporter": Set Dev_GetTestSuite = New s_ActiveStateExporter + End Select +End Function diff --git a/src/addin/IteratorDetected.cls b/src/addin/IteratorDetected.cls new file mode 100644 index 0000000..f99fa86 --- /dev/null +++ b/src/addin/IteratorDetected.cls @@ -0,0 +1,158 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "IteratorDetected" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +' +Option Explicit + +Private Const NO_CURRENT_ID = -1 + +Private selection_ As Word.Range +Private detector_ As Object + +Private start_ As Long +Private data_ As PC_ParsedData +Private currentID_ As Long + +Public Function Init(theDoc As Word.Document, iDetector As Object, Optional nStart& = 0) + Set selection_ = theDoc.Range(nStart, nStart) + Set detector_ = iDetector + + start_ = selection_.Paragraphs.First.Range.Start + Dim sText$: sText = selection_.Paragraphs.First.Range.Text + Set data_ = detector_.ExtractFragments(sText) + currentID_ = IIf(data_.Count = 0, 0, NO_CURRENT_ID) +End Function + +Public Function Range() As Word.Range + Set Range = selection_ +End Function + +Public Function Fragment() As PC_Fragment + If currentID_ <> NO_CURRENT_ID Then _ + Set Fragment = data_.data_.Item(currentID_) +End Function + +Public Function MoveNext() As Word.Range + If currentID_ = NO_CURRENT_ID Then + If Not InitNext Then _ + Exit Function + ElseIf currentID_ = data_.Count Then + If Not NextChunk Then _ + Exit Function + currentID_ = 1 + Else + currentID_ = currentID_ + 1 + End If + + Set selection_ = RangeForFragment(data_.data_.Item(currentID_)) + Set MoveNext = selection_ +End Function + +Public Function MovePrev() As Word.Range + If currentID_ = NO_CURRENT_ID Then + If Not InitPrev Then _ + Exit Function + ElseIf currentID_ <= 1 Then + If Not PrevChunk Then _ + Exit Function + currentID_ = data_.Count + Else + currentID_ = currentID_ - 1 + End If + + Set selection_ = RangeForFragment(data_.data_.Item(currentID_)) + Set MovePrev = selection_ +End Function + +' ====== +Private Function RangeForFragment(iFragment As PC_Fragment) As Word.Range + Dim iRange As Word.Range: Set iRange = selection_.Document.Range(start_, start_) + Call iRange.MoveStart(wdCharacter, iFragment.start_) + Call iRange.Collapse(wdCollapseStart) + Call iRange.MoveEnd(wdCharacter, iFragment.end_ - iFragment.start_) + Set RangeForFragment = iRange +End Function + +Private Function InitNext() As Boolean + Dim nTarget&: nTarget = selection_.End + Dim iRange As Word.Range + Dim nFragment& + For nFragment = 1 To data_.Count Step 1 + Set iRange = RangeForFragment(data_.data_.Item(nFragment)) + If iRange.Start >= nTarget Then + InitNext = True + currentID_ = nFragment + Exit Function + End If + Next nFragment + + If Not NextChunk Then _ + Exit Function + InitNext = True + currentID_ = 1 +End Function + +Private Function InitPrev() As Boolean + Dim nTarget&: nTarget = selection_.Start + Dim iRange As Word.Range + Dim nFragment& + For nFragment = data_.Count To 1 Step -1 + Set iRange = RangeForFragment(data_.data_.Item(nFragment)) + If iRange.End <= nTarget Then + InitPrev = True + currentID_ = nFragment + Exit Function + End If + Next nFragment + + If Not PrevChunk Then _ + Exit Function + InitPrev = True + currentID_ = data_.Count +End Function + +Private Function NextChunk() As Boolean + Dim iRange As Word.Range: Set iRange = selection_.Document.Range(start_, start_) + Dim oldStart& + Do + oldStart = iRange.Start + Call iRange.Move(wdParagraph, 1) + If iRange.Start = oldStart Or iRange.Start + 1 >= selection_.Document.Range.End Then + NextChunk = False + Exit Function + End If + Dim sText$: sText = iRange.Paragraphs.First.Range.Text + Dim iData As PC_ParsedData: Set iData = detector_.ExtractFragments(sText) + If Not iData.IsEmpty Then + NextChunk = True + start_ = iRange.Start + Set data_ = iData + Exit Function + End If + Loop +End Function + +Private Function PrevChunk() As Boolean + Dim iRange As Word.Range: Set iRange = selection_.Document.Range(start_, start_) + Do + If iRange.Start = 0 Then + PrevChunk = False + Exit Function + End If + Call iRange.Move(wdParagraph, -1) + Dim sText$: sText = iRange.Paragraphs.First.Range.Text + Dim iData As PC_ParsedData: Set iData = detector_.ExtractFragments(sText) + If Not iData.IsEmpty Then + PrevChunk = True + start_ = iRange.Start + Set data_ = iData + Exit Function + End If + Loop +End Function diff --git a/src/addin/Main.bas b/src/addin/Main.bas new file mode 100644 index 0000000..697c071 --- /dev/null +++ b/src/addin/Main.bas @@ -0,0 +1,178 @@ +Attribute VB_Name = "Main" +Option Explicit + +Public Function ExportAllData(iParamData() As String) As Variant + Dim oData As New Collection + Dim params As New ExtractionOptions: Call params.FromFlatData(iParamData) + + Dim iDoc As Word.Document: Set iDoc = Word.ActiveDocument + Dim oItem As Collection + + On Error GoTo RETURN_EMPTY + Dim iDetector As Object: Set iDetector = PTools().Detector(params.detector_, params.param_) + Dim oIterator As New IteratorDetected: Call oIterator.Init(iDoc, iDetector) + Do While Not oIterator.MoveNext Is Nothing + Set oItem = New Collection + Call oItem.Add(oIterator.Range.Start) + Call oItem.Add(oIterator.Range.End) + Call oItem.Add(IIf(oIterator.Fragment.type_ <> 0, oIterator.Fragment.type_, params.loadCategory_)) + Call oItem.Add(SubstituteWhitespace(oIterator.Range.Text)) + + Call oData.Add(oItem) + Loop + On Error GoTo 0 + +RETURN_EMPTY: + ExportAllData = ConverToFlat2D(oData) +End Function + +Public Sub RunNextFragment() + Dim rSelection As Word.Range: Set rSelection = Word.Selection.Range + Dim iDetector As Object: Set iDetector = PTools().Detector(GetUIState.detector_, GetUIState.detectionParam_) + Dim oIterator As New IteratorDetected: Call oIterator.Init(rSelection.Document, iDetector, rSelection.End) + If Not oIterator.MoveNext Is Nothing Then + Call oIterator.Range.Select + Else + Call VBA.Beep + End If +End Sub + +Public Sub RunPrevFragment() + Dim rSelection As Word.Range: Set rSelection = Word.Selection.Range + Dim iDetector As Object: Set iDetector = PTools().Detector(GetUIState.detector_, GetUIState.detectionParam_) + Dim oIterator As New IteratorDetected: Call oIterator.Init(rSelection.Document, iDetector, rSelection.Start) + If Not oIterator.MovePrev Is Nothing Then + Call oIterator.Range.Select + Else + Call VBA.Beep + End If +End Sub + +Public Sub RunMarkWord() + Dim iDoc As Word.Document: Set iDoc = Word.ActiveDocument + Dim nCount&: nCount = MarkAllDetected(iDoc, GetUIState.detector_, GetUIState.color_, GetUIState.detectionParam_) + Call iDoc.Application.ScreenRefresh + Call UserInteraction.ShowMessage(IM_MARKDOWN_COMPLETE, nCount) +End Sub + +Public Sub RunMarkParagraph() + Dim iDoc As Word.Document: Set iDoc = Word.ActiveDocument + Dim nCount&: nCount = MarkAllParagraphs(iDoc, GetUIState.detector_, GetUIState.color_, GetUIState.detectionParam_) + Call iDoc.Application.ScreenRefresh + Call UserInteraction.ShowMessage(IM_MARKDOWN_COMPLETE, nCount) +End Sub + +Public Sub RunBrowseFile() + Dim sFile$: sFile = UserInteraction.PromptFileFilter(ActiveDocument.Path & "\", "All files", "*.*") + If sFile = vbNullString Then _ + Exit Sub + + GetUIState.detectionParam_ = sFile + GetUIState.ribbon_.Value.Invalidate +End Sub + +Public Sub RunMorphoParse() + Dim sText$: sText = EnsureSelectedText() + If sText = vbNullString Then _ + Exit Sub + Dim sTags$: sTags = MorphoParse(sText) + Call UserInteraction.ShowMessage(IM_PARSE_MORPHO, sTags) +End Sub + +Public Sub RunContextInfo() + Dim sText$: sText = EnsureSelectedText() + If sText = vbNullString Then _ + Exit Sub + + Dim iDetector As Object: Set iDetector = PTools().Detector(GetUIState.detector_, GetUIState.detectionParam_) + If Not iDetector.Test(sText) Then + Call UserInteraction.ShowMessage(IM_TEST_FAILED, DetectorToStr(GetUIState.detector_)) + Exit Sub + End If + + Dim iParser As Object: Set iParser = PTools().Parser(GetUIState.detector_, GetUIState.detectionParam_) + If iParser Is Nothing Then + Call UserInteraction.ShowMessage(IM_NO_PARSER_DATA, DetectorToStr(GetUIState.detector_)) + Exit Sub + End If + If Not iParser.Parse(sText) Then + Call UserInteraction.ShowMessage(IM_NO_PARSER_DATA, DetectorToStr(GetUIState.detector_)) + Exit Sub + End If + + Call UserInteraction.ShowMessage(IM_PARSED_DESCRIPTION, sText, DetectorToStr(GetUIState.detector_), iParser.GetDataDescription()) +End Sub + +Public Sub RunTransformSingle() + Dim rSelection As Word.Range: Set rSelection = WordAdjustRange(Word.Selection.Range) + Call rSelection.MoveEndWhile(" ", -1) + Dim sText$: sText = VBA.Trim(rSelection.Text) + If sText = vbNullString Then + Call UserInteraction.ShowMessage(EM_SELECTION_EMPTY) + Exit Sub + End If + + Dim iDetector As Object: Set iDetector = PTools().Detector(GetUIState.detector_, GetUIState.detectionParam_) + If Not iDetector.Test(sText) Then + Call UserInteraction.ShowMessage(IM_TEST_FAILED, DetectorToStr(GetUIState.detector_)) + Exit Sub + End If + + Dim iParser As Object: Set iParser = PTools().Parser(GetUIState.detector_, GetUIState.detectionParam_) + If iParser Is Nothing Then _ + Exit Sub + + Dim sNewText$: sNewText = iParser.Transform(sText, GetUIState.transformParam_) + If sText <> sNewText Then _ + rSelection.Text = sNewText + Call rSelection.Select +End Sub + +Public Sub RunTransformAll() + Dim iDoc As Word.Document: Set iDoc = Word.ActiveDocument + + Dim nCount&: nCount = TransformAll(iDoc, GetUIState.GetOptions) + + Call UserInteraction.ShowMessage(IM_TRANSFORM_COMPLETE, nCount) +End Sub + +Public Sub RunExportRanges() + Dim iDoc As Word.Document: Set iDoc = Word.ActiveDocument + + Dim xlApp As New API_XLWrapper + Dim outWB As Excel.Workbook: Set outWB = xlApp.NewDocument + Call xlApp.PauseUI + + Call OutputRanges(iDoc, outWB.Sheets(1), GetUIState.GetOptions) + + Call xlApp.ResumeUI + + Call UserInteraction.ShowMessage(IM_EXPORT_COMPLETE) +End Sub + +Public Sub RunExportAll() + Dim iDoc As Word.Document: Set iDoc = Word.ActiveDocument + + Dim xlApp As New API_XLWrapper + Dim outWB As Excel.Workbook: Set outWB = xlApp.NewDocument + Call xlApp.PauseUI + + Call OutputAllData(iDoc, outWB.Sheets(1), GetUIState.GetOptions) + + Call xlApp.ResumeUI + + Call UserInteraction.ShowMessage(IM_EXPORT_COMPLETE) +End Sub + +Public Sub RunHelp() + MsgBox "TODO" +End Sub + +' ====== +Private Function EnsureSelectedText() As String + Dim rSelection As Word.Range: Set rSelection = WordAdjustRange(Word.Selection.Range) + Dim sText$: sText = VBA.Trim(rSelection.Text) + If sText = vbNullString Then _ + Call UserInteraction.ShowMessage(EM_SELECTION_EMPTY) + EnsureSelectedText = sText +End Function diff --git a/src/addin/MainImpl.bas b/src/addin/MainImpl.bas new file mode 100644 index 0000000..3c0221a --- /dev/null +++ b/src/addin/MainImpl.bas @@ -0,0 +1,174 @@ +Attribute VB_Name = "MainImpl" +Option Explicit + +Public Function MarkAllDetected(iDoc As Word.Document, nDetector As TDetector, iColor As TColor, sParam$) As Long + Dim nColor&: nColor = ColorToColorIndex(iColor) + Dim iDetector As Object: Set iDetector = PTools().Detector(nDetector, sParam) + Dim oIterator As New IteratorDetected: Call oIterator.Init(iDoc, iDetector) + Dim iRange As Word.Range + Dim fragmentColor& + Dim nCount&: nCount = 0 + Do + Set iRange = oIterator.MoveNext + If iRange Is Nothing Then _ + Exit Do + + Dim nType&: nType = oIterator.Fragment.type_ + fragmentColor = IIf(nType = 0, 0, ColorToColorIndex(TColor.[_First] + ((nType - 1) Mod TColor.[_Last]))) + If nCount = 0 Then + Dim oldColor&: oldColor = iRange.HighlightColorIndex + If oldColor = nColor Or (oldColor = fragmentColor And fragmentColor <> 0) Then _ + nColor = wdAuto + End If + If nColor <> wdAuto And fragmentColor <> 0 Then _ + nColor = fragmentColor + iRange.HighlightColorIndex = nColor + nCount = nCount + 1 + Loop + MarkAllDetected = nCount +End Function + +Public Function MarkAllParagraphs(iDoc As Word.Document, nDetector As TDetector, iColor As TColor, sParam$) As Long + Dim nColor&: nColor = ColorToColorIndex(iColor) + Dim iDetector As Object: Set iDetector = PTools().Detector(nDetector, sParam) + Dim oIterator As New IteratorDetected: Call oIterator.Init(iDoc, iDetector) + Dim iRange As Word.Range + Dim nCount&: nCount = 0 + Dim oldStart&: oldStart = -1 + Do + Set iRange = oIterator.MoveNext + If iRange Is Nothing Then _ + Exit Do + If nCount = 0 And iRange.HighlightColorIndex = nColor Then _ + nColor = wdAuto + iRange.Paragraphs.First.Range.HighlightColorIndex = nColor + If oldStart <> iRange.Start Then + oldStart = iRange.Start + nCount = nCount + 1 + End If + Loop + MarkAllParagraphs = nCount +End Function + +Public Function MorphoParse(sText$) As String + MorphoParse = AccessPython.CallFunction(PY_MODULE_TEXT, "parse", Array(sText, "")) +End Function + +Public Function ConverToFlat2D(oData As Collection) As Variant + Dim iData() As Variant + If oData.Count = 0 Then + ConverToFlat2D = iData + Exit Function + End If + + ReDim iData(0 To oData.Count - 1, 0 To oData.Item(1).Count - 1) + Dim nRow&: nRow = 0 + Dim oItem As Object + Dim vElement As Variant + For Each oItem In oData + Dim nCol&: nCol = 0 + For Each vElement In oItem + iData(nRow, nCol) = vElement + nCol = nCol + 1 + Next vElement + nRow = nRow + 1 + Next oItem + + ConverToFlat2D = iData +End Function + +Public Function TransformAll(iDoc As Word.Document, iOptions As ExtractionOptions) As Long + Dim iParser As Object: Set iParser = PTools().Parser(iOptions.detector_, iOptions.param_) + If iParser Is Nothing Then _ + Exit Function + Dim iDetector As Object: Set iDetector = PTools().Detector(iOptions.detector_, iOptions.param_) + Dim oIterator As New IteratorDetected: Call oIterator.Init(iDoc, iDetector, iDoc.Range.End - 1) + + Dim nCount&: nCount = 0 + Do While Not oIterator.MovePrev Is Nothing + Dim sText$: sText = oIterator.Range.Text + Dim sNewText$: sNewText = iParser.Transform(sText, iOptions.transform_) + If sText <> sNewText Then + oIterator.Range.Text = sNewText + Call oIterator.Init(iDoc, iDetector, oIterator.Range.Start) + nCount = nCount + 1 + End If + Loop + TransformAll = nCount +End Function + +Public Function OutputRanges(iSource As Word.Document, wsOut As Excel.Worksheet, iOptions As ExtractionOptions) + Dim iDetector As Object: Set iDetector = PTools().Detector(iOptions.detector_, iOptions.param_) + Dim oIterator As New IteratorDetected: Call oIterator.Init(iSource, iDetector) + + With wsOut + .Cells(1, S_E_ID) = "ID" + .Cells(1, S_E_START) = "" + .Cells(1, S_E_FINISH) = "" + .Cells(1, S_E_TYPE) = "" + End With + + Dim nRow&: nRow = 2 + Do While Not oIterator.MoveNext Is Nothing + With wsOut + .Cells(nRow, S_E_ID) = nRow - 1 + .Cells(nRow, S_E_START) = oIterator.Range.Start + .Cells(nRow, S_E_FINISH) = oIterator.Range.End + .Cells(nRow, S_E_TYPE) = IIf(oIterator.Fragment.type_ = 0, iOptions.loadCategory_, oIterator.Fragment.type_) + End With + nRow = nRow + 1 + Loop +End Function + +Public Function OutputAllData(iSource As Word.Document, wsOut As Excel.Worksheet, iOptions As ExtractionOptions) + Dim iDetector As Object: Set iDetector = PTools().Detector(iOptions.detector_, iOptions.param_) + Dim iParser As Object: Set iParser = PTools().Parser(iOptions.detector_, iOptions.param_) + Dim oIterator As New IteratorDetected: Call oIterator.Init(iSource, iDetector) + + With wsOut + .Cells(1, S_E_ID) = "ID" + .Cells(1, S_E_START) = "" + .Cells(1, S_E_FINISH) = "" + .Cells(1, S_E_TYPE) = "" + .Cells(1, S_E_TEXT) = "" + End With + + Dim nRow&: nRow = 2 + Dim iData As Collection + Do While Not oIterator.MoveNext Is Nothing + Dim sText$: sText = oIterator.Range.Text + With wsOut + .Cells(nRow, 1) = nRow - 1 + .Cells(nRow, 2) = oIterator.Range.Start + .Cells(nRow, 3) = oIterator.Range.End + .Cells(nRow, 4) = IIf(oIterator.Fragment.type_ = 0, iOptions.loadCategory_, oIterator.Fragment.type_) + .Cells(nRow, 5) = sText + End With + If iParser Is Nothing Then _ + GoTo NEXT_ROW + If Not iParser.Parse(sText) Then _ + GoTo NEXT_ROW + Set iData = iParser.GetData() + If iData.Count = 0 Then _ + GoTo NEXT_ROW + + Dim nCol&: nCol = S_E_DATA + Dim vItem As Variant + For Each vItem In iData + wsOut.Cells(nRow, nCol) = vItem + nCol = nCol + 1 + Next vItem + + If nRow <> 2 Then _ + GoTo NEXT_ROW + Dim iHeader As Scripting.Dictionary: Set iHeader = iParser.GetDataDescription() + nCol = S_E_DATA + For Each vItem In iHeader.Keys + wsOut.Cells(1, nCol) = vItem + nCol = nCol + 1 + Next vItem + +NEXT_ROW: + nRow = nRow + 1 + Loop +End Function diff --git a/src/addin/ManualSubs.bas b/src/addin/ManualSubs.bas new file mode 100644 index 0000000..5f4098d --- /dev/null +++ b/src/addin/ManualSubs.bas @@ -0,0 +1,29 @@ +Attribute VB_Name = "ManualSubs" +Option Explicit + +'Public Function P_GetNPAFromActive() As Long() +' Dim frags As New PC_ParsedData +' Dim npaRegex As RegExp: Set npaRegex = GlobalNPARegex +' +' Dim aPar As Word.Paragraph +' Dim parRng As Word.Range +' Dim matches As Object +' Dim nMatch& +' For Each aPar In ActiveDocument.Paragraphs +' Set parRng = aPar.Range +' Dim sTxt$: sTxt = FixSpecialSymbols(parRng.Text) +' Set matches = npaRegex.Execute(sTxt) +' For nMatch = 1 To matches.Count Step 1 +' Dim findRng As Word.Range: Set findRng = parRng.Duplicate +' Call findRng.MoveStart(wdCharacter, InStr(1, sTxt, matches.Item(nMatch - 1).SubMatches(0))) +' Call findRng.Collapse(wdCollapseStart) +' Call findRng.MoveEnd(wdCharacter, Len(matches.Item(nMatch - 1).SubMatches(0))) +' Call frags.AddItem(findRng.Start, findRng.End, 0) +' Next nMatch +' Next aPar +' +' If frags.data_.Count > 0 Then _ +' P_GetNPAFromActive = frags.AsFlatData +'End Function +' +' diff --git a/src/addin/UIState.cls b/src/addin/UIState.cls new file mode 100644 index 0000000..b8d9e7e --- /dev/null +++ b/src/addin/UIState.cls @@ -0,0 +1,38 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "UIState" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public ribbon_ As API_Ribbon + +Public detector_ As TDetector +Public detectionParam_ As String + +Public color_ As TColor + +Public transformParam_ As String + +Public category_ As Long + +Private Sub Class_Initialize() + detector_ = T_DETECTOR_REGEX + color_ = T_COLOR_YELLOW + transformParam_ = vbNullString + detectionParam_ = vbNullString + category_ = 0 + Set ribbon_ = New API_Ribbon +End Sub + +Public Function GetOptions() As ExtractionOptions + Set GetOptions = New ExtractionOptions + GetOptions.detector_ = detector_ + GetOptions.param_ = detector_ + GetOptions.transform_ = transformParam_ + GetOptions.loadCategory_ = category_ +End Function diff --git a/src/addin/z_UIMessages.bas b/src/addin/z_UIMessages.bas new file mode 100644 index 0000000..7a03ad2 --- /dev/null +++ b/src/addin/z_UIMessages.bas @@ -0,0 +1,85 @@ +Attribute VB_Name = "z_UIMessages" +' Messaging module +Option Private Module +Option Explicit + +Public Enum MsgCode + ' EM_COMBO_TAKEN = ERR_COMBO_TAKEN + + EM_RIBBON_NOT_REACHABLE = 1 + EM_SELECTION_EMPTY + + IM_MARKDOWN_COMPLETE + IM_PARSE_MORPHO + IM_NO_PARSER_DATA + IM_TEST_FAILED + IM_PARSED_DESCRIPTION + IM_EXPORT_COMPLETE + IM_TRANSFORM_COMPLETE + + ' QM_CONFIG_EDIT_CONFIRM +End Enum + +Private g_UI As API_UserInteraction + +Public Function UserInteraction() As API_UserInteraction + If g_UI Is Nothing Then _ + Set g_UI = New API_UserInteraction + Set UserInteraction = g_UI +End Function + +Public Function SetUserInteraction(newUI As API_UserInteraction) + Set g_UI = newUI +End Function + +Public Function ProcessErrorMessages(expectedErrors As Scripting.Dictionary) + If Err.Number = 0 Then _ + Exit Function + + Call Unload(CSE_ProgressBar) + If Not expectedErrors.Exists(Err.Number) Then _ + Call Err.Raise(Err.Number) + + Call UserInteraction.ShowMessage(Err.Number, Err.Source) +End Function + +Public Function UIShowMessage(theCode As MsgCode, ParamArray params() As Variant) + Dim unwrapped As Variant: unwrapped = params + unwrapped = FixForwardedParams(unwrapped) + + Select Case theCode + Case EM_RIBBON_NOT_REACHABLE + Call MsgBox(" !" & vbNewLine & _ + ", Word ", vbExclamation) + Case EM_SELECTION_EMPTY: Call MsgBox(" ", vbExclamation) + + Case IM_MARKDOWN_COMPLETE: Call MsgBox(Fmt(" : {1}", unwrapped), vbInformation) + Case IM_PARSE_MORPHO: Call MsgBox(Fmt(" : {1}", unwrapped), vbInformation) + Case IM_NO_PARSER_DATA: Call MsgBox(Fmt(" : {1}", unwrapped), vbInformation) + Case IM_TEST_FAILED: Call MsgBox(Fmt(" : {1}", unwrapped), vbInformation) + Case IM_EXPORT_COMPLETE: Call MsgBox(" ", vbInformation) + Case IM_TRANSFORM_COMPLETE: Call MsgBox(Fmt(" : {1}", unwrapped), vbInformation) + + Case IM_PARSED_DESCRIPTION: Call MsgBox(Fmt(" : {1}" & vbNewLine & _ + " : {2}" & vbNewLine & _ + " " & vbNewLine & "{3}", unwrapped), vbInformation) + + Case Else: Call MsgBox(" ", vbCritical) + End Select +End Function + +Public Function UIAskQuestion(theCode As MsgCode, ParamArray params() As Variant) As Boolean + Dim unwrapped As Variant: unwrapped = params + unwrapped = FixForwardedParams(unwrapped) + + Dim answer&: answer = vbNo + Select Case theCode + ' Case QM_CONFIG_EDIT_CONFIRM + ' answer = MsgBox("! . " & _ + ' vbNewLine & " ", vbYesNo + vbQuestion) + + Case Else + Call MsgBox("Invalid message code", vbCritical) + End Select + UIAskQuestion = answer = vbYes +End Function diff --git a/src/addin/z_UIRibbon.bas b/src/addin/z_UIRibbon.bas new file mode 100644 index 0000000..3f3b0ab --- /dev/null +++ b/src/addin/z_UIRibbon.bas @@ -0,0 +1,117 @@ +Attribute VB_Name = "z_UIRibbon" +' +Option Explicit + +Global g_UIState As UIState + +Public Function GetUIState() As UIState + If g_UIState Is Nothing Then + Set g_UIState = New UIState + Call g_UIState.ribbon_.LoadFrom(RibbonTempFile) +' Call UIUpdateConfigList + End If + Set GetUIState = g_UIState + If g_UIState.ribbon_.Value Is Nothing Then _ + Call UserInteraction.ShowMessage(EM_RIBBON_NOT_REACHABLE) +End Function + +Public Sub Parse_LoadRibbon(aRibbon As IRibbonUI) + Set g_UIState = New UIState + Call g_UIState.ribbon_.Init(aRibbon, RibbonTempFile) + 'Call UIUpdateConfigList +End Sub + +Public Sub Parse_OnBtn(iControl As IRibbonControl) + Call EnsureGlobalState + Select Case iControl.ID + Case "NextFragment": Call RunNextFragment + Case "PrevFragment": Call RunPrevFragment + Case "BrowseFile": Call RunBrowseFile + Case "MorphoParse": Call RunMorphoParse + Case "ContextInfo": Call RunContextInfo + + Case "MarkWord": Call RunMarkWord + Case "MarkParagraph": Call RunMarkParagraph + + Case "TransformSingle": Call RunTransformSingle + Case "TransformAll": Call RunTransformAll + + Case "ExportAll": Call RunExportAll + Case "ExportRanges": Call RunExportRanges + + Case "ViewHelp": Call RunHelp + End Select +End Sub + +Public Function ParseUI_DetectorCount(iControl As IRibbonControl, ByRef nCount) + nCount = TDetector.[_Last] +End Function + +Public Function ParseUI_DetectorLabel(iControl As IRibbonControl, nIndex%, ByRef vLabel) + vLabel = DetectorToStr(nIndex + 1) +End Function + +Public Function ParseUI_DetectorGetSelectedIndex(iControl As IRibbonControl, ByRef nValue) + Call EnsureGlobalState + nValue = g_UIState.detector_ - 1 +End Function + +Public Function ParseUI_DetectorChange(iControl As IRibbonControl, sID$, nIndex As Variant) + Call EnsureGlobalState + g_UIState.detector_ = nIndex + 1 + ' Do any UI updates needed on entity switch +End Function + +Public Function ParseUI_ParamText(iControl As IRibbonControl, ByRef vLabel) + vLabel = GetUIState.detectionParam_ +End Function + +Public Function ParseUI_ParamChange(iControl As IRibbonControl, sParam$) + Call EnsureGlobalState + g_UIState.detectionParam_ = sParam +End Function + +Public Function ParseUI_TransformText(iControl As IRibbonControl, ByRef vLabel) + vLabel = GetUIState.transformParam_ +End Function + +Public Function ParseUI_TransformChange(iControl As IRibbonControl, sParam$) + Call EnsureGlobalState + g_UIState.transformParam_ = sParam +End Function + +Public Function ParseUI_CategoryText(iControl As IRibbonControl, ByRef vLabel) + vLabel = GetUIState.category_ +End Function + +Public Function ParseUI_CategoryChange(iControl As IRibbonControl, sParam$) + Call EnsureGlobalState + g_UIState.category_ = sParam +End Function + +Public Function ParseUI_MarkupCount(iControl As IRibbonControl, ByRef nCount) + nCount = TColor.[_Last] +End Function + +Public Function ParseUI_MarkupLabel(iControl As IRibbonControl, nIndex%, ByRef vLabel) + vLabel = ColorToStr(nIndex + 1) +End Function + +Public Function ParseUI_MarkupGetSelectedIndex(iControl As IRibbonControl, ByRef nValue) + Call EnsureGlobalState + nValue = g_UIState.color_ - 1 +End Function + +Public Function ParseUI_MarkupChange(iControl As IRibbonControl, sID$, nIndex As Variant) + Call EnsureGlobalState + g_UIState.color_ = nIndex + 1 +End Function + +' ============= +Private Function EnsureGlobalState() As Boolean + EnsureGlobalState = Not GetUIState Is Nothing +End Function + +Private Function RibbonTempFile() As String + RibbonTempFile = Environ("TEMP") & "/" & TEMP_FILE_NAME & CStr(GetCurrentProcessId) & ".txt" +End Function diff --git a/src/database/DataAccess.bas b/src/database/DataAccess.bas new file mode 100644 index 0000000..16299f8 --- /dev/null +++ b/src/database/DataAccess.bas @@ -0,0 +1,66 @@ +Attribute VB_Name = "DataAccess" +Option Explicit +Option Private Module + +' TODO: incapsulate into DB class + +Private Const SHEET_SOURCE = "" +Private Const SHEET_DATA = "" + +Public Function DataSheet() As Excel.Worksheet + Set DataSheet = ThisWorkbook.Sheets(SHEET_DATA) +End Function + +Public Function SourceSheet() As Excel.Worksheet + Set SourceSheet = ThisWorkbook.Sheets(SHEET_SOURCE) +End Function + +Public Function AddSource(target As Word.Document) As Long + AddSource = SourceSheet.Cells(GetRowFor(target.FullName), SS_ID) +End Function + +Public Function SourceFileFor(sourceID&) As String + Dim foundRng As Excel.Range + Set foundRng = SourceSheet.Columns(SS_ID).Find(sourceID, LookAt:=xlWhole) + If foundRng Is Nothing Then _ + Exit Function + + SourceFileFor = SourceSheet.Cells(foundRng.Row, SS_PATH) +End Function + +Public Function RowFor(sourceID&) As Long + Dim foundRng As Excel.Range + Set foundRng = SourceSheet.Columns(SS_ID).Find(sourceID, LookAt:=xlWhole) + If foundRng Is Nothing Then _ + Exit Function + + RowFor = foundRng.Row +End Function + +' ==== +Private Function GetNextID() As Long + Dim docsSht As Excel.Worksheet: Set docsSht = SourceSheet + Dim nRow&: nRow = DEFAULT_FIRST_ROW + Dim theID&: theID = 1 + Do While docsSht.Cells(nRow, SS_ID) <> vbNullString + If theID >= docsSht.Cells(nRow, SS_ID) Then _ + theID = docsSht.Cells(nRow, SS_ID) + 1 + nRow = nRow + 1 + Loop + GetNextID = theID +End Function + +Private Function GetRowFor(target$) As Long + Dim theSheet As Excel.Worksheet: Set theSheet = SourceSheet + Dim foundRng As Excel.Range + Set foundRng = theSheet.Columns(SS_PATH).Find(target, LookAt:=xlWhole) + If foundRng Is Nothing Then + Dim nRow&: nRow = theSheet.Columns(SS_ID).Find(vbNullString, LookAt:=xlWhole).Row + theSheet.Cells(nRow, SS_ID) = GetNextID + theSheet.Cells(nRow, SS_PATH) = target + Call theSheet.Cells(nRow, SS_PATH).Hyperlinks.Add(theSheet.Cells(nRow, SS_PATH), target) + GetRowFor = nRow + Else + GetRowFor = foundRng.Row + End If +End Function diff --git a/src/database/Declarations.bas b/src/database/Declarations.bas new file mode 100644 index 0000000..b662fd5 --- /dev/null +++ b/src/database/Declarations.bas @@ -0,0 +1,38 @@ +Attribute VB_Name = "Declarations" +Option Explicit +Option Private Module + +' +Public Enum DataStruct + [_First] = 1 + + DS_ID = 1 + DS_START = 2 + DS_END = 3 + DS_TYPE = 4 + DS_TEXT = 5 + + [_Last] = 5 +End Enum + +' +Public Enum SourceStruct + [_First] = 1 + + SS_ID = 1 + SS_PATH = 2 + SS_NAME = 3 + SS_PARS = 4 + SS_TIME = 5 + SS_COUNT = 6 + + [_Last] = 6 +End Enum + +Public Const FUNC_PARSE = "ExportAllData" + +Public Const ADDIN_FILENAME = "Parsers.dotm" +Public Const DOC_MASK = "*.doc*" + +Public Const DEFAULT_FIRST_ROW = 2 +Public Const INVALID_ROW = -1 diff --git a/src/database/DevHelper.bas b/src/database/DevHelper.bas new file mode 100644 index 0000000..fdcc60f --- /dev/null +++ b/src/database/DevHelper.bas @@ -0,0 +1,50 @@ +Attribute VB_Name = "DevHelper" +Option Explicit + +Private Const TEST_BASIC_PARSE = "TestBasic.docx" +Private Const ADDIN_FILE = "Parsers.dotm" + +Private g_RemoveAddin As Boolean + +Public Function Dev_PrepareSkeleton() + Call ClearAll +End Function + +Public Sub Dev_ManualRunTest() + Dim sSuite$: sSuite = "s_WordInteractions" + Dim sTest$: sTest = "t_ImportDates" + Dim sMsg$: sMsg = Dev_RunTestDebug(sSuite, sTest) + Debug.Print sMsg + Call MsgBox(sMsg) +End Sub + +Public Function Dev_GetTestSuite(sName$) As Object + Select Case sName + Case "s_WordInteractions": Set Dev_GetTestSuite = New s_WordInteractions + End Select +End Function + +Public Function GetTestFile() As String + Dim fso As New Scripting.FileSystemObject + GetTestFile = ThisWorkbook.Path & "\test\" & TEST_BASIC_PARSE +End Function + +Public Function SetupTestAddin() + Dim fso As New Scripting.FileSystemObject + Dim sLocal$: sLocal = ThisWorkbook.Path & "\\" & ADDIN_FILE + Dim sAppAddin$: sAppAddin = GetGlobalAddin + g_RemoveAddin = Not fso.FileExists(sAppAddin) + Call CopyFileOrFolder(sLocal, sAppAddin, fso) +End Function + +Public Function TeardownTestAddin() + Dim fso As New Scripting.FileSystemObject + On Error Resume Next + If g_RemoveAddin Then _ + Call fso.DeleteFile(GetGlobalAddin) +End Function + +' ====== +Private Function GetGlobalAddin() As String + GetGlobalAddin = VBA.Environ$("APPDATA") & "\Microsoft\Word\STARTUP\" & ADDIN_FILE +End Function diff --git a/src/database/ImportDlg.frm b/src/database/ImportDlg.frm new file mode 100644 index 0000000..028e365 --- /dev/null +++ b/src/database/ImportDlg.frm @@ -0,0 +1,92 @@ +VERSION 5.00 +Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} ImportDlg + Caption = " " + ClientHeight = 2805 + ClientLeft = 120 + ClientTop = 465 + ClientWidth = 6780 + OleObjectBlob = "ImportDlg.frx":0000 + StartUpPosition = 1 'CenterOwner +End +Attribute VB_Name = "ImportDlg" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = True +Attribute VB_Exposed = False +Option Explicit + +Public isCancelled_ As Boolean +Public isFolder_ As Boolean + +Private Sub UserForm_Initialize() + Dim nType As TDetector + For nType = TDetector.[_First] To TDetector.[_Last] + Call CBType.AddItem(DetectorToStr(nType)) + Next nType + + isFolder_ = False + isCancelled_ = True + TBCategory.Value = 0 +End Sub + +Private Sub UserForm_Terminate() + isCancelled_ = True + Call Me.Hide +End Sub + +Public Property Get FileName() As String + FileName = TBDocument.Text +End Property + +Public Property Get Options() As ExtractionOptions + Set Options = New ExtractionOptions + Options.detector_ = CBType.ListIndex + 1 + Options.param_ = TBParam.Text + Options.transform_ = TBTransform.Text + Options.loadCategory_ = TBCategory.Value +End Property + +' ============ +Private Sub DocumentBtn_Click() + Dim sFile$: sFile = UserInteraction.PromptFileFilter( _ + ThisWorkbook.Path & "\", _ + sDescription:=" Word", _ + sFilter:="*.docx;*.doc;*.docm") + If sFile = vbNullString Then _ + Exit Sub + isFolder_ = False + TBDocument.Text = sFile +End Sub + +Private Sub ParameterBtn_Click() +Dim sFile$: sFile = UserInteraction.PromptFileFilter( _ + ThisWorkbook.Path & "\", _ + "All files", _ + "*.*") + If sFile = vbNullString Then _ + Exit Sub + TBParam.Text = sFile +End Sub + +Private Sub FolderBtn_Click() + Dim sFile$: sFile = UserInteraction.PromptFolder(ThisWorkbook.Path & "\") + If sFile = vbNullString Then _ + Exit Sub + isFolder_ = True + TBDocument.Text = sFile +End Sub + +Private Sub CancelBtn_Click() + isCancelled_ = True + Call Me.Hide +End Sub + +Private Sub OkBtn_Click() + If FileName = vbNullString Or CBType.ListIndex = -1 Then + Call UserInteraction.ShowMessage(EM_VALIDATION_FAIL) + Exit Sub + End If + + isCancelled_ = False + Call Me.Hide +End Sub diff --git a/src/database/ImportDlg.frx b/src/database/ImportDlg.frx new file mode 100644 index 0000000000000000000000000000000000000000..d19dbe333170611f4ae873cb7597c8731ebce953 GIT binary patch literal 4120 zcmeHJO=w(I6h80GP0}>e+SHiT)HZJsOOP@oX$R9r;&djd!H$_42f8ZwlDyQZGw-!8 zQ%Vr=tpq`+;>Jw`g>Kv_NL>Wnh)^iig$ox2t;M}ie>S$$@4IhqGpW|hWaAIH!+H14 zd*`0>opaC6&1biX?2`MVyjNsmhsZZycmMF=C;d0?f2c1eBq4WeozmX$!NQuP2V~h; z@c-Rftrou&!E$T-hdgixSb2UVg+puu@yz*uYyAH`Fe?FmRdHlWe9WONn^bOU?nT5H zFR>o{TzcxO8@<=NKX_k0mQ&9c8ihocSHCa4`JI)(%cvv%ve?M5iM2u%GWt0;E8;f6 zVg&KD=_mACtbw$tS^ZS~2RC3?k^_c{oRfmg$=Ud23(wt~u>T3j_v<={eK&}38uOpW zgN`AcpzWY8P!fceAv-~jfY?sDL97*zV(tMw2I>X%f%bqN2la!{ZZzM%`b=MKjep() zqK0HTxhu)@R2jx_d~Wi($d!~xkq28JiP>9S?_Rpzy>bh=ed6;Mt!pChSd&>x+KcWh zLB);3;%&zFB?i`z{DcYN6Y<|+Ev8S3*~wu3LdEl|<7!3h9zg;{{I+*GI`|TT=QRqj z_edHOdQo&4^EJFPO89LE?--v0DyOn4uYN=D#`_$(g7=73D$U%~dsppaVaY8!Gj13L z7sYn;e#Y^j8dgWtxJn6sA}dfh!(;%@1x)Zu6w63Ru~hMvoEh(;GYeyV%|jfN(+4?6 zRYslAXhbFfuAt=%$?tVJYj_f}N_+k!uSerz9`0BDYN~Y^C`W@@26J)08UdnVJSTwa zIW-1^U*I&F_fwB#thNZEq{Fi1EhPNLXS1fiAG))kap*r(_b_exEB}i9aR0K-3_uQT zc}ArnGt;sijWPBSiqgh*tdGm9xEO7`XH5J4fX17FzmKU=*gmFX!jYreA8Ghv0v3#^ z865Oy2(VVpqtT9M9kciEa^;}L#2P#Rt!e0vqvC0-Mm3^5aJ+{C-#m_o<&Upg$RCC| z^ZQ%Q0QoEOgXzxf9|dJV78*+4o%6~gF`waOT>;{V88!{F(TGe{$yWW~OZ1sWUbL}@ z`c8rp;LW-IyjKP%YEb$~ksq1EGN|hnPC5d~BuIg`@GAJQ-iN*!^Q~b8UX3-6xy@h& zcjwGH0NUWq#L@i{+MsAR5H9<{Y4rLB z?FW3V|7FZwMn5(ar{Ohc#j6A5CbisR==w{GL0Ads1BPE?4DdK;R}r+s$VES(T*FJb z*c!2Q(f*zAm$u5QYM0$OAab5gx0CuBa{CQSYS^=GSh$rW;2Cz}2k)-DySz}E4}+y( yvFaQL)+J{&HLO?q@-tPL^A;Ao1*b4~)|;>D%aLYF@tGQtUygiwex161-Jg4CpRlR` literal 0 HcmV?d00001 diff --git a/src/database/Main.bas b/src/database/Main.bas new file mode 100644 index 0000000..aa9b7c2 --- /dev/null +++ b/src/database/Main.bas @@ -0,0 +1,62 @@ +Attribute VB_Name = "Main" +Option Explicit +Option Private Module + +Public Const PRODUCT_VERSION = "1.3.0" +Public Const PRODUCT_NAME = "Concept-Mining" + +Public g_VersionTimer As Long + +Public Sub Auto_Open() + Dim sCmd$: sCmd = OfficeCommandLine + If VBA.InStr(1, sCmd, "/automation", vbTextCompare) <> 0 Then _ + Exit Sub + + g_VersionTimer = SetTimer(0, 0, CP_VERSION_MSG_DELAY, AddressOf OnVersionCheck) +End Sub + +Public Function OnVersionCheck(ByVal nHwnd As Long, ByVal uMsg As Long, ByVal nEvent As Long, ByVal nTime As Long) + Call KillTimer(0, g_VersionTimer) + Call VersionValidate(PRODUCT_NAME, PRODUCT_VERSION) +End Function + +Public Sub RunImportWord() + If Not EnsureAddinInstalled Then _ + Exit Sub + + Call ImportDlg.Show + If ImportDlg.isCancelled_ Then + Unload ImportDlg + Exit Sub + End If + + Dim iParams As ExtractionOptions: Set iParams = ImportDlg.Options + Dim sPath$: sPath = ImportDlg.FileName + Dim bIsFolder As Boolean: bIsFolder = ImportDlg.isFolder_ + Call Unload(ImportDlg) + + Dim nCount&: nCount = ProcessImports(sPath, bIsFolder, iParams) + + Call UserInteraction.ShowMessage(IM_IMPORT_COMPLETE, nCount) +End Sub + +Public Sub RunFollowLink() + Dim theSheet As Excel.Worksheet: Set theSheet = DataSheet + If ThisWorkbook.ActiveSheet.Name <> theSheet.Name Then + Call UserInteraction.ShowMessage(EM_EXPECTED_DATASHEET) + Exit Sub + End If + + Dim nRow&: nRow = ThisWorkbook.Application.Selection.Cells(1, 1).Row + Dim srcID$: srcID = theSheet.Cells(nRow, DS_ID) + If srcID = vbNullString Or Not IsNumeric(srcID) Then + Call UserInteraction.ShowMessage(EM_INVALID_SOURCE_ID) + Exit Sub + End If + Call FollowLinkAt(CLng(srcID), theSheet.Cells(nRow, DS_START), theSheet.Cells(nRow, DS_END)) +End Sub + +Public Sub RunClearAll() + Call ClearAll + Call UserInteraction.ShowMessage(IM_CLEAR_ALL) +End Sub diff --git a/src/database/MainImpl.bas b/src/database/MainImpl.bas new file mode 100644 index 0000000..072e5fe --- /dev/null +++ b/src/database/MainImpl.bas @@ -0,0 +1,152 @@ +Attribute VB_Name = "MainImpl" +Option Explicit +Option Private Module + +Public Function EnsureAddinInstalled() As Boolean + Dim sPath$: sPath = Environ("APPDATA") & "\Microsoft\Word\STARTUP\" & ADDIN_FILENAME + Dim fso As New Scripting.FileSystemObject + EnsureAddinInstalled = fso.FileExists(sPath) + If Not EnsureAddinInstalled Then _ + Call UserInteraction.ShowMessage(EM_NO_ADDIN) +End Function + +Public Function ClearAll() + SourceSheet.UsedRange.Offset(1).ClearContents + DataSheet.UsedRange.Offset(1).ClearContents +End Function + +Private Function ScanDocsFolder(target$) As Collection + Set ScanDocsFolder = New Collection + + Dim sFolder$: sFolder = target & "\" + Dim sFile$: sFile = Dir(sFolder & DOC_MASK) + Do While Len(sFile) > 0 And sFile <> sFolder + Call ScanDocsFolder.Add(sFolder & sFile) + sFile = Dir + Loop +End Function + +Public Function ProcessImports(sPath$, bIsFolder As Boolean, params As ExtractionOptions, Optional bSilent As Boolean = False) As Long + Dim wordApp As New API_WordWrapper: Call wordApp.CreateApplication(bIsVisible:=False) + If bSilent Then _ + Call wordApp.DisableMessages + + Dim xlUI As New API_XLWrapper: Call xlUI.SetApplication(ThisWorkbook.Application) + Call xlUI.PauseUI + ThisWorkbook.Application.DisplayAlerts = False + + Dim nCount&: nCount = 0 + Dim theDoc As Word.Document + If Not bIsFolder Then + Set theDoc = wordApp.OpenDocument(sPath, bReadOnly:=True) + If theDoc Is Nothing Then _ + GoTo SAFE_EXIT + nCount = nCount + ScanDataFrom(wordApp, params) + Call wordApp.ReleaseDocument + Else + Dim docs As Collection: Set docs = ScanDocsFolder(sPath) + Call CSE_ProgressBar.Init(" ", maxVal:=docs.Count - 1, canInterrupt:=True) + If Not bSilent Then _ + Call CSE_ProgressBar.Show + + Dim sFile As Variant + For Each sFile In docs + Set theDoc = wordApp.OpenDocument(CStr(sFile), bReadOnly:=True) + If theDoc Is Nothing Then _ + GoTo SAFE_EXIT + + theDoc.Range.NoProofing = True + + CSE_ProgressBar.Description = theDoc.Name + nCount = nCount + ScanDataFrom(wordApp, params) + Call wordApp.ReleaseDocument(bCloseApplication:=False) + + Call CSE_ProgressBar.IncrementA + If CSE_ProgressBar.Interrupted Then _ + Exit For + Next sFile + + Call Unload(CSE_ProgressBar) + End If + +SAFE_EXIT: + ThisWorkbook.Application.DisplayAlerts = True + Call xlUI.ResumeUI + Call wordApp.ReleaseApplication + ProcessImports = nCount +End Function + +Public Function FollowLinkAt(target&, nStart&, nEnd&) + Dim sPath$: sPath = SourceFileFor(target) + If sPath = vbNullString Then + Call UserInteraction.ShowMessage(EM_INVALID_SOURCE_ID) + Exit Function + End If + + Dim fso As New Scripting.FileSystemObject + If Not fso.FileExists(sPath) Then + Call UserInteraction.ShowMessage(EM_INVALID_PATH, sPath) + Exit Function + End If + + Dim wordApp As New API_WordWrapper + Dim theDoc As Word.Document: Set theDoc = wordApp.OpenDocument(sPath) + If theDoc Is Nothing Then _ + Exit Function + + If theDoc.Range.End < nEnd Then + Call UserInteraction.ShowMessage(EM_INVALID_LINK, sPath) + Exit Function + End If + + Dim targetRng As Word.Range: Set targetRng = theDoc.Range(nStart, nEnd) + Call targetRng.Select + Call theDoc.ActiveWindow.ScrollIntoView(targetRng) + + Call wordApp.Application.Activate +End Function + +' ====== +Private Function ScanDataFrom(target As API_WordWrapper, params As ExtractionOptions) As Long + Dim dataSht As Excel.Worksheet: Set dataSht = DataSheet + Dim srcSht As Excel.Worksheet: Set srcSht = SourceSheet + Dim theTimer As New API_Timer + + Call theTimer.Start + Call target.Document.Activate + + Dim srcID&: srcID = AddSource(target.Document) + Dim srcRow: srcRow = RowFor(srcID) + Dim nCount&: nCount = 0 + Dim newData As Variant + + Call target.Document.Activate + On Error GoTo EXIT_FUNCTION + newData = target.Run(FUNC_PARSE, params.AsFlatData) + On Error GoTo 0 + If ArraySize(newData) <= 0 Then _ + Exit Function + + Dim nRow&: nRow = dataSht.Columns(DS_ID).Find(vbNullString, LookAt:=xlWhole).Row + Dim nDataRow& + Dim nDataCol& + For nDataRow = LBound(newData, 1) To UBound(newData, 1) Step 1 + dataSht.Cells(nRow, DS_ID) = srcID + Dim nCol&: nCol = DS_ID + 1 + For nDataCol = LBound(newData, 2) To UBound(newData, 2) Step 1 + dataSht.Cells(nRow, nCol) = newData(nDataRow, nDataCol) + nCol = nCol + 1 + Next nDataCol + + nRow = nRow + 1 + nCount = nCount + 1 + Next nDataRow + + srcSht.Cells(srcRow, SS_TIME) = theTimer.TimeElapsed + srcSht.Cells(srcRow, SS_COUNT) = nCount + srcSht.Cells(srcRow, SS_NAME) = target.Document.Name + srcSht.Cells(srcRow, SS_PARS) = target.Document.Paragraphs.Count + +EXIT_FUNCTION: + ScanDataFrom = nCount +End Function diff --git a/src/database/ManualSubs.bas b/src/database/ManualSubs.bas new file mode 100644 index 0000000..30bef38 --- /dev/null +++ b/src/database/ManualSubs.bas @@ -0,0 +1,6 @@ +Attribute VB_Name = "ManualSubs" +Option Explicit + +Public Sub RunClear() + Call ClearAll +End Sub diff --git a/src/database/z_UIMessages.bas b/src/database/z_UIMessages.bas new file mode 100644 index 0000000..4f19bdf --- /dev/null +++ b/src/database/z_UIMessages.bas @@ -0,0 +1,69 @@ +Attribute VB_Name = "z_UIMessages" +' Messaging module +Option Private Module +Option Explicit + +Public Enum MsgCode + MSG_OK = 0 + + EM_EXPECTED_DATASHEET + EM_NO_ADDIN + EM_INVALID_SOURCE_ID + EM_INVALID_PATH + EM_INVALID_LINK + EM_VALIDATION_FAIL + + IM_IMPORT_COMPLETE + IM_CLEAR_ALL + + 'QM_ADD_TITLELINK +End Enum + +Private g_UI As API_UserInteraction + +Public Function UserInteraction() As API_UserInteraction + If g_UI Is Nothing Then _ + Set g_UI = New API_UserInteraction + Set UserInteraction = g_UI +End Function + +Public Function SetUserInteraction(newUI As API_UserInteraction) + Set g_UI = newUI +End Function + +Public Function UIShowMessage(theCode As MsgCode, ParamArray params() As Variant) + Dim unwrapped As Variant: unwrapped = params + unwrapped = FixForwardedParams(unwrapped) + + Select Case theCode + Case EM_EXPECTED_DATASHEET: Call MsgBox(" """"", vbExclamation) + Case EM_NO_ADDIN: Call MsgBox("! Word", vbCritical) + Case EM_INVALID_SOURCE_ID: Call MsgBox(" ", vbExclamation) + Case EM_INVALID_PATH: Call MsgBox(Fmt(" z: {1}", unwrapped), vbExclamation) + Case EM_INVALID_LINK: Call MsgBox(Fmt(" , {1}", unwrapped), vbExclamation) + Case EM_VALIDATION_FAIL: Call MsgBox(" ", vbExclamation) + + Case IM_IMPORT_COMPLETE: Call MsgBox(Fmt(" " & vbNewLine & " : {1}", unwrapped), vbInformation) + Case IM_CLEAR_ALL: Call MsgBox(" ", vbInformation) + + Case Else + Call MsgBox(" ", vbCritical) + End Select +End Function + +Public Function UIAskQuestion(theCode As MsgCode, ParamArray params() As Variant) As Boolean + Dim unwrapped As Variant: unwrapped = params + unwrapped = FixForwardedParams(unwrapped) + + Dim answer As Long: answer = vbNo + Select Case theCode + ' Case QM_ADD_TITLELINK + ' answer = MsgBox("! " & vbNewLine & _ + ' " ID: " & p1 & vbNewLine & _ + ' " . ?", vbYesNo + vbQuestion) + + Case Else + Call MsgBox(" ", vbCritical) + End Select + UIAskQuestion = answer = vbYes +End Function diff --git a/src/database/z_UIRibbon.bas b/src/database/z_UIRibbon.bas new file mode 100644 index 0000000..525bf2e --- /dev/null +++ b/src/database/z_UIRibbon.bas @@ -0,0 +1,11 @@ +Attribute VB_Name = "z_UIRibbon" +' +Option Explicit + +Sub OnRibbonBtn(control As IRibbonControl) + Select Case control.ID + Case "ImportWord": Call RunImportWord + Case "FollowLink": Call RunFollowLink + Case "ClearAll": Call RunClearAll + End Select +End Sub diff --git a/src/test/s_WordInteractions.cls b/src/test/s_WordInteractions.cls new file mode 100644 index 0000000..1efc728 --- /dev/null +++ b/src/test/s_WordInteractions.cls @@ -0,0 +1,56 @@ +VERSION 1.0 CLASS +BEGIN + MultiUse = -1 'True +END +Attribute VB_Name = "s_WordInteractions" +Attribute VB_GlobalNameSpace = False +Attribute VB_Creatable = False +Attribute VB_PredeclaredId = False +Attribute VB_Exposed = False +Option Explicit + +Public Function Setup() + Call SetupTestAddin +End Function + +Public Function Teardown() + Call TeardownTestAddin + Call ClearAll +End Function + +Public Function t_ImportInvalid() + On Error GoTo PROPAGATE_ERROR + + Dim iParams As New ExtractionOptions + iParams.detector_ = T_DETECTOR_DATE + + Call Dev_NewCase("No file") + Call Dev_ExpectEQ(ProcessImports("invalid.txt", False, iParams, bSilent:=True), 0) + Call Dev_ExpectEQ(SourceSheet.Cells(2, 1), "") + + Call Dev_NewCase("Invalid detector") + iParams.detector_ = T_DETECTOR_UNKNOWN + Call Dev_ExpectEQ(ProcessImports(GetTestFile, False, iParams, bSilent:=True), 0) + + Call Dev_NewCase("Invalid parameter") + iParams.detector_ = T_DETECTOR_LIST + iParams.param_ = "invalid.txt" + Call Dev_ExpectEQ(ProcessImports(GetTestFile, False, iParams, bSilent:=True), 0) + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function + +Public Function t_ImportDates() + On Error GoTo PROPAGATE_ERROR + + Dim iParams As New ExtractionOptions + iParams.detector_ = T_DETECTOR_DATE + Call Dev_ExpectEQ(ProcessImports(GetTestFile, False, iParams, bSilent:=True), 4) + Call Dev_ExpectNE(SourceSheet.Cells(2, 1), "") + + Exit Function +PROPAGATE_ERROR: + Call Dev_LogError(Err.Number, Err.Description) +End Function diff --git a/test/TestBasic.docx b/test/TestBasic.docx new file mode 100644 index 0000000000000000000000000000000000000000..e082c9308524d3ac89a26b3a468e5ecf78621174 GIT binary patch literal 21622 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 TGc2h2F31qf!oa{A0OA1v8|F$9 literal 0 HcmV?d00001 diff --git a/ui/addin/.rels b/ui/addin/.rels new file mode 100644 index 0000000..2b00f63 --- /dev/null +++ b/ui/addin/.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/ui/addin/customUI.xml b/ui/addin/customUI.xml new file mode 100644 index 0000000..84a8a54 --- /dev/null +++ b/ui/addin/customUI.xml @@ -0,0 +1,108 @@ + + + + + + + + + + +