diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f4d37599..de3b3569 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,14 +3,8 @@ name: "Build repo" on: push: branches: ["main"] - paths-ignore: - - ".github/**" - - "**/*.md" pull_request: branches: ["main"] - paths-ignore: - - ".github/**" - - "**/*.md" jobs: build: @@ -36,7 +30,7 @@ jobs: - uses: gradle/actions/setup-gradle@v3 with: - gradle-version: 7.6 + gradle-version: 8.7 - name: Build with Gradle run: ./gradlew build --no-build-cache diff --git a/.github/workflows/push-docker.yml b/.github/workflows/push-docker.yml index f8fc4200..8bcf7803 100644 --- a/.github/workflows/push-docker.yml +++ b/.github/workflows/push-docker.yml @@ -24,7 +24,7 @@ jobs: - uses: gradle/actions/setup-gradle@v3 with: - gradle-version: 7.6 + gradle-version: 8.7 - name: Run Gradle Command run: ./gradlew clean build diff --git a/.github/workflows/releaseArtifacts.yml b/.github/workflows/releaseArtifacts.yml index f9b5f2b0..151e126e 100644 --- a/.github/workflows/releaseArtifacts.yml +++ b/.github/workflows/releaseArtifacts.yml @@ -29,7 +29,7 @@ jobs: - uses: gradle/actions/setup-gradle@v3 with: - gradle-version: 7.6 + gradle-version: 8.7 - name: Generate jar (With Security=${{ matrix.enable_security }}) run: ./gradlew clean createExe diff --git a/build.gradle b/build.gradle index 4c4bfaaf..0b3c26da 100644 --- a/build.gradle +++ b/build.gradle @@ -94,7 +94,13 @@ dependencies { implementation("io.github.pixee:java-security-toolkit:1.1.3") implementation 'org.yaml:snakeyaml:2.2' - implementation 'org.springframework.boot:spring-boot-starter-web:3.2.4' + + // Exclude Tomcat and include Jetty + implementation('org.springframework.boot:spring-boot-starter-web:3.2.4') { + exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat' + } + implementation 'org.springframework.boot:spring-boot-starter-jetty:3.2.4' + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf:3.2.4' if (System.getenv('DOCKER_ENABLE_SECURITY') != 'false') { diff --git a/cucumber/exampleFiles/example.docx b/cucumber/exampleFiles/example.docx new file mode 100644 index 00000000..7d2981d6 Binary files /dev/null and b/cucumber/exampleFiles/example.docx differ diff --git a/cucumber/exampleFiles/example.odp b/cucumber/exampleFiles/example.odp new file mode 100644 index 00000000..a154af7b Binary files /dev/null and b/cucumber/exampleFiles/example.odp differ diff --git a/cucumber/exampleFiles/example.odt b/cucumber/exampleFiles/example.odt new file mode 100644 index 00000000..5be61338 Binary files /dev/null and b/cucumber/exampleFiles/example.odt differ diff --git a/cucumber/exampleFiles/example.pptx b/cucumber/exampleFiles/example.pptx new file mode 100644 index 00000000..fdecfb86 Binary files /dev/null and b/cucumber/exampleFiles/example.pptx differ diff --git a/cucumber/exampleFiles/example.rtf b/cucumber/exampleFiles/example.rtf new file mode 100644 index 00000000..4aa19a5d --- /dev/null +++ b/cucumber/exampleFiles/example.rtf @@ -0,0 +1,158 @@ +{\rtf1\ansi\ansicpg1252\uc0\stshfdbch0\stshfloch0\stshfhich0\stshfbi0\deff0\adeff0{\fonttbl{\f0\froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f1\froman\fcharset2\fprq2{\*\panose 05050102010706020507}Symbol;}{\f2\fswiss\fcharset0\fprq2{\*\panose 020b0604020202020204}Arial;}}{\colortbl;\red0\green0\blue0;\red67\green67\blue67; +\red102\green102\blue102;}{\stylesheet{\s0\snext0\sqformat\spriority0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 Normal;}{\s1\sbasedon0\snext0\styrsid15694742 +\sqformat\spriority0\keep\keepn\fi0\sb400\sa120\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs40\ltrch\b0\i0\fs40\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 heading 1;}{\s2\sbasedon0\snext0\styrsid15694742 +\sqformat\spriority0\keep\keepn\fi0\sb360\sa120\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs32\ltrch\b0\i0\fs32\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 heading 2;}{\s3\sbasedon0\snext0\styrsid15694742 +\sqformat\spriority0\keep\keepn\fi0\sb320\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs28\ltrch\b0\i0\fs28\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf2 heading 3;}{\s4\sbasedon0\snext0\styrsid15694742 +\sqformat\spriority0\keep\keepn\fi0\sb280\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs24\ltrch\b0\i0\fs24\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 heading 4;}{\s5\sbasedon0\snext0\styrsid15694742 +\sqformat\spriority0\keep\keepn\fi0\sb240\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 heading 5;}{\s6\sbasedon0\snext0\styrsid15694742 +\sqformat\spriority0\keep\keepn\fi0\sb240\sa80\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai\af2\afs22\ltrch\b0\i\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 heading 6;}{\*\cs10\additive\ssemihidden\spriority0 Default Paragraph Font; +}{\*\ts11\tsrowd\snext11\ssemihidden\spriority0\aspalpha\aspnum\adjustright\ltrpar\li0\lin0\ri0\rin0\ql\faauto\tsvertalt\tsbrdrl\tsbrdrr\tsbrdrt\tsbrdrb\tsbrdrdgr\tsbrdrdgl\tsbrdrh\tsbrdrv\trpaddl108\trpaddfl3\trwWidthB0\trftsWidthB3\trpaddt0\trpaddft3\trpaddb0 +\trpaddfb3\trpaddr108\trpaddfr3 Normal Table;}{\s15\sbasedon0\snext15\styrsid15694742\sqformat\spriority0\keep\keepn\fi0\sb0\sa60\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs52\ltrch\b0\i0\fs52\loch\af2 +\dbch\af2\hich\f2\strike0\ulnone\cf1 Title;}{\s16\sbasedon0\snext16\styrsid15694742\sqformat\spriority0\keep\keepn\fi0\sb0\sa320\aspalpha\aspnum\adjustright\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl240\slmult1\rtlch\ab0\ai0\af2\afs30\ltrch\b0\i0\fs30 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf3 Subtitle;}}{\*\rsidtbl\rsid10976062\rsid13249109}{\*\generator Aspose.Words for Java 23.4.0;}{\info\version1\edmins0\nofpages1\nofwords0\nofchars0\nofcharsws0}\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0{ +\mmathPr\mbrkBin0\mbrkBinSub0\mdefJc1\mdispDef1\minterSp0\mintLim0\mintraSp0\mlMargin0\mmathFont0\mnaryLim1\mpostSp0\mpreSp0\mrMargin0\msmallFrac0\mwrapIndent1440\mwrapRight0}\deflang1033\deflangfe2052\adeflang1025\jexpand\showxmlerrors1\validatexml1{ +\*\wgrffmtfilter 013f}\viewkind1\viewscale100\fet0\ftnbj\aenddoc\ftnrstcont\aftnrstcont\ftnnar\aftnnrlc\widowctrl\nospaceforul\nolnhtadjtbl\alntblind\lyttblrtgr\dntblnsbdb\noxlattoyen\wrppunct\nobrkwrptbl\expshrtn\snaptogridincell\asianbrkrule\htmautsp\noultrlspc +\useltbaln\splytwnine\ftnlytwnine\lytcalctblwd\allowfieldendsel\lnbrkrule\nouicompat\nofeaturethrottle1\utinl\formshade\nojkernpunct\dghspace180\dgvspace180\dghorigin1800\dgvorigin1440\dghshow1\dgvshow1\dgmargin\pgbrdrhead\pgbrdrfoot\rsidroot10976062\sectd\sectlinegrid360\pgwsxn12240\pghsxn15840\marglsxn1440\margrsxn1440\margtsxn1440\margbsxn1440\guttersxn0\headery720\footery720\colsx720\ltrsect\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar +\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\alang1025\afs22\ltrch\b0\i0\fs22\lang1033\langnp1033\langfe1033\langfenp1033\loch\af2\dbch\af2 +\hich\f2\strike0\ulnone\cf1 A}{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar +\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\alang1025\afs22\ltrch\b0\i0\fs22\lang1033\langnp1033\langfe1033\langfenp1033\loch\af2 +\dbch\af2\hich\f2\strike0\ulnone\cf1 B}{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar +\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard +\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0 +\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0 +\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0 +\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0 +\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2 +\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw +\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par} +\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22 +\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}\pard\plain\itap0\s0\ilvl0\fi0\sb0\sa0\aspalpha\aspnum\adjustright\brdrt\brdrl\brdrb +\brdrr\brdrbtw\brdrbar\widctlpar\ltrpar\li0\lin0\ri0\rin0\ql\faauto\sl276\slmult1\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1{\rtlch\ab0\ai0\af2\alang1025\afs22\ltrch\b0\i0\fs22\lang1033\langnp1033\langfe1033\langfenp1033 +\loch\af2\dbch\af2\hich\f2\strike0\ulnone\cf1 C}{\rtlch\ab0\ai0\af2\afs22\ltrch\b0\i0\fs22\loch\af2\dbch\af2\hich\f2\insrsid10976062\strike0\ulnone\cf1\par}{ +\*\latentstyles\lsdstimax267\lsdlockeddef0\lsdsemihiddendef0\lsdunhideuseddef0\lsdqformatdef0\lsdprioritydef0{\lsdlockedexcept\lsdqformat1 Normal;\lsdqformat1 heading 1;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 2;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 3; +\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 4;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 5;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 6;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 7;\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 8; +\lsdsemihidden1\lsdunhideused1\lsdqformat1 heading 9;\lsdsemihidden1\lsdunhideused1\lsdqformat1 caption;\lsdqformat1 Title;\lsdqformat1 Subtitle;\lsdqformat1 Strong;\lsdqformat1 Emphasis;\lsdsemihidden1\lsdpriority99 Placeholder Text;\lsdqformat1\lsdpriority1 No Spacing; +\lsdpriority60 Light Shading;\lsdpriority61 Light List;\lsdpriority62 Light Grid;\lsdpriority63 Medium Shading 1;\lsdpriority64 Medium Shading 2;\lsdpriority65 Medium List 1;\lsdpriority66 Medium List 2;\lsdpriority67 Medium Grid 1;\lsdpriority68 Medium Grid 2; +\lsdpriority69 Medium Grid 3;\lsdpriority70 Dark List;\lsdpriority71 Colorful Shading;\lsdpriority72 Colorful List;\lsdpriority73 Colorful Grid;\lsdpriority60 Light Shading Accent 1;\lsdpriority61 Light List Accent 1;\lsdpriority62 Light Grid Accent 1;\lsdpriority63 Medium Shading 1 Accent 1; +\lsdpriority64 Medium Shading 2 Accent 1;\lsdpriority65 Medium List 1 Accent 1;\lsdsemihidden1\lsdpriority99 Revision;\lsdqformat1\lsdpriority34 List Paragraph;\lsdqformat1\lsdpriority29 Quote;\lsdqformat1\lsdpriority30 Intense Quote;\lsdpriority66 Medium List 2 Accent 1; +\lsdpriority67 Medium Grid 1 Accent 1;\lsdpriority68 Medium Grid 2 Accent 1;\lsdpriority69 Medium Grid 3 Accent 1;\lsdpriority70 Dark List Accent 1;\lsdpriority71 Colorful Shading Accent 1;\lsdpriority72 Colorful List Accent 1;\lsdpriority73 Colorful Grid Accent 1; +\lsdpriority60 Light Shading Accent 2;\lsdpriority61 Light List Accent 2;\lsdpriority62 Light Grid Accent 2;\lsdpriority63 Medium Shading 1 Accent 2;\lsdpriority64 Medium Shading 2 Accent 2;\lsdpriority65 Medium List 1 Accent 2;\lsdpriority66 Medium List 2 Accent 2; +\lsdpriority67 Medium Grid 1 Accent 2;\lsdpriority68 Medium Grid 2 Accent 2;\lsdpriority69 Medium Grid 3 Accent 2;\lsdpriority70 Dark List Accent 2;\lsdpriority71 Colorful Shading Accent 2;\lsdpriority72 Colorful List Accent 2;\lsdpriority73 Colorful Grid Accent 2; +\lsdpriority60 Light Shading Accent 3;\lsdpriority61 Light List Accent 3;\lsdpriority62 Light Grid Accent 3;\lsdpriority63 Medium Shading 1 Accent 3;\lsdpriority64 Medium Shading 2 Accent 3;\lsdpriority65 Medium List 1 Accent 3;\lsdpriority66 Medium List 2 Accent 3; +\lsdpriority67 Medium Grid 1 Accent 3;\lsdpriority68 Medium Grid 2 Accent 3;\lsdpriority69 Medium Grid 3 Accent 3;\lsdpriority70 Dark List Accent 3;\lsdpriority71 Colorful Shading Accent 3;\lsdpriority72 Colorful List Accent 3;\lsdpriority73 Colorful Grid Accent 3; +\lsdpriority60 Light Shading Accent 4;\lsdpriority61 Light List Accent 4;\lsdpriority62 Light Grid Accent 4;\lsdpriority63 Medium Shading 1 Accent 4;\lsdpriority64 Medium Shading 2 Accent 4;\lsdpriority65 Medium List 1 Accent 4;\lsdpriority66 Medium List 2 Accent 4; +\lsdpriority67 Medium Grid 1 Accent 4;\lsdpriority68 Medium Grid 2 Accent 4;\lsdpriority69 Medium Grid 3 Accent 4;\lsdpriority70 Dark List Accent 4;\lsdpriority71 Colorful Shading Accent 4;\lsdpriority72 Colorful List Accent 4;\lsdpriority73 Colorful Grid Accent 4; +\lsdpriority60 Light Shading Accent 5;\lsdpriority61 Light List Accent 5;\lsdpriority62 Light Grid Accent 5;\lsdpriority63 Medium Shading 1 Accent 5;\lsdpriority64 Medium Shading 2 Accent 5;\lsdpriority65 Medium List 1 Accent 5;\lsdpriority66 Medium List 2 Accent 5; +\lsdpriority67 Medium Grid 1 Accent 5;\lsdpriority68 Medium Grid 2 Accent 5;\lsdpriority69 Medium Grid 3 Accent 5;\lsdpriority70 Dark List Accent 5;\lsdpriority71 Colorful Shading Accent 5;\lsdpriority72 Colorful List Accent 5;\lsdpriority73 Colorful Grid Accent 5; +\lsdpriority60 Light Shading Accent 6;\lsdpriority61 Light List Accent 6;\lsdpriority62 Light Grid Accent 6;\lsdpriority63 Medium Shading 1 Accent 6;\lsdpriority64 Medium Shading 2 Accent 6;\lsdpriority65 Medium List 1 Accent 6;\lsdpriority66 Medium List 2 Accent 6; +\lsdpriority67 Medium Grid 1 Accent 6;\lsdpriority68 Medium Grid 2 Accent 6;\lsdpriority69 Medium Grid 3 Accent 6;\lsdpriority70 Dark List Accent 6;\lsdpriority71 Colorful Shading Accent 6;\lsdpriority72 Colorful List Accent 6;\lsdpriority73 Colorful Grid Accent 6; +\lsdqformat1\lsdpriority19 Subtle Emphasis;\lsdqformat1\lsdpriority21 Intense Emphasis;\lsdqformat1\lsdpriority31 Subtle Reference;\lsdqformat1\lsdpriority32 Intense Reference;\lsdqformat1\lsdpriority33 Book Title;\lsdsemihidden1\lsdunhideused1\lsdpriority37 Bibliography; +\lsdsemihidden1\lsdunhideused1\lsdqformat1\lsdpriority39 TOC Heading;}}} \ No newline at end of file diff --git a/cucumber/features/examples.feature b/cucumber/features/examples.feature index c18e0516..04e4081f 100644 --- a/cucumber/features/examples.feature +++ b/cucumber/features/examples.feature @@ -1,5 +1,7 @@ +@example Feature: API Validation + @positive @password Scenario: Remove password Given I generate a PDF file as "fileInput" And the pdf contains 3 pages @@ -12,7 +14,8 @@ Feature: API Validation And the response file should have size greater than 0 And the response PDF is not passworded And the response status code should be 200 - + + @negative @password Scenario: Remove password wrong password Given I generate a PDF file as "fileInput" And the pdf contains 3 pages @@ -24,6 +27,7 @@ Feature: API Validation Then the response status code should be 500 And the response should contain error message "Internal Server Error" + @positive @info Scenario: Get info Given I generate a PDF file as "fileInput" When I send the API request to the endpoint "/api/v1/security/get-info-on-pdf" @@ -31,6 +35,7 @@ Feature: API Validation And the response file should have size greater than 100 And the response status code should be 200 + @positive @password Scenario: Add password Given I generate a PDF file as "fileInput" And the pdf contains 3 pages @@ -43,6 +48,7 @@ Feature: API Validation And the response PDF is passworded And the response status code should be 200 + @positive @password Scenario: Add password with other params Given I generate a PDF file as "fileInput" And the pdf contains 3 pages @@ -59,7 +65,7 @@ Feature: API Validation And the response PDF is passworded And the response status code should be 200 - + @positive @watermark Scenario: Add watermark Given I generate a PDF file as "fileInput" And the pdf contains 3 pages @@ -76,18 +82,8 @@ Feature: API Validation Then the response content type should be "application/pdf" And the response file should have size greater than 100 And the response status code should be 200 - - - - Scenario: Repair PDF - Given I generate a PDF file as "fileInput" - When I send the API request to the endpoint "/api/v1/misc/repair" - Then the response content type should be "application/pdf" - And the response file should have size greater than 0 - And the response status code should be 200 - - + @positive Scenario: Remove blank pages Given I generate a PDF file as "fileInput" And the pdf contains 3 blank pages @@ -100,80 +96,8 @@ Feature: API Validation And the response file should have size greater than 0 And the response PDF should contain 0 pages And the response status code should be 200 - - @ocr - Scenario: Process PDF with OCR - Given I generate a PDF file as "fileInput" - And the request data includes - | parameter | value | - | languages | eng | - | sidecar | false | - | deskew | true | - | clean | true | - | cleanFinal | true | - | ocrType | Normal | - | ocrRenderType | hocr | - | removeImagesAfter| false | - When I send the API request to the endpoint "/api/v1/misc/ocr-pdf" - Then the response content type should be "application/pdf" - And the response file should have size greater than 0 - And the response status code should be 200 - @ocr - Scenario: Process PDF with text and OCR with type normal - Given I generate a PDF file as "fileInput" - And the pdf contains 3 pages with random text - And the request data includes - | parameter | value | - | languages | eng | - | sidecar | false | - | deskew | true | - | clean | true | - | cleanFinal | true | - | ocrType | Normal | - | ocrRenderType | hocr | - | removeImagesAfter| false | - When I send the API request to the endpoint "/api/v1/misc/ocr-pdf" - Then the response status code should be 500 - - @ocr - Scenario: Process PDF with OCR - Given I generate a PDF file as "fileInput" - And the request data includes - | parameter | value | - | languages | eng | - | sidecar | false | - | deskew | true | - | clean | true | - | cleanFinal | true | - | ocrType | Force | - | ocrRenderType | hocr | - | removeImagesAfter| false | - When I send the API request to the endpoint "/api/v1/misc/ocr-pdf" - Then the response content type should be "application/pdf" - And the response file should have size greater than 0 - And the response status code should be 200 - - @ocr - Scenario: Process PDF with OCR with sidecar - Given I generate a PDF file as "fileInput" - And the request data includes - | parameter | value | - | languages | eng | - | sidecar | true | - | deskew | true | - | clean | true | - | cleanFinal | true | - | ocrType | Force | - | ocrRenderType | hocr | - | removeImagesAfter| false | - When I send the API request to the endpoint "/api/v1/misc/ocr-pdf" - Then the response content type should be "application/octet-stream" - And the response file should have extension ".zip" - And the response file should have size greater than 0 - And the response status code should be 200 - - + @positive @flatten Scenario: Flatten PDF Given I generate a PDF file as "fileInput" And the request data includes @@ -184,6 +108,7 @@ Feature: API Validation And the response file should have size greater than 0 And the response status code should be 200 + @positive @metadata Scenario: Update metadata Given I generate a PDF file as "fileInput" And the request data includes @@ -202,41 +127,4 @@ Feature: API Validation And the response PDF metadata should include "Title" as "Sample Title" And the response status code should be 200 - @libre - Scenario: Convert PDF to DOCX - Given I generate a PDF file as "fileInput" - And the pdf contains 3 pages with random text - And the request data includes - | parameter | value | - | outputFormat | docx | - When I send the API request to the endpoint "/api/v1/convert/pdf/word" - Then the response status code should be 200 - And the response file should have size greater than 100 - And the response file should have extension ".docx" -# And the response DOCX should contain 3 pages - - @libre - Scenario: Convert PDF to ODT - Given I generate a PDF file as "fileInput" - And the pdf contains 3 pages with random text - And the request data includes - | parameter | value | - | outputFormat | odt | - When I send the API request to the endpoint "/api/v1/convert/pdf/word" - Then the response status code should be 200 - And the response file should have size greater than 100 - And the response file should have extension ".odt" -# And the response ODT should contain 3 pages - - @libre - Scenario: Convert PDF to DOC - Given I generate a PDF file as "fileInput" - And the pdf contains 3 pages with random text - And the request data includes - | parameter | value | - | outputFormat | doc | - When I send the API request to the endpoint "/api/v1/convert/pdf/word" - Then the response status code should be 200 - And the response file should have extension ".doc" - And the response file should have size greater than 100 -# And the response DOC should contain 3 pages \ No newline at end of file + \ No newline at end of file diff --git a/cucumber/features/external.feature b/cucumber/features/external.feature new file mode 100644 index 00000000..1d503135 --- /dev/null +++ b/cucumber/features/external.feature @@ -0,0 +1,228 @@ +Feature: API Validation + + + @libre @positive + Scenario: Repair PDF + Given I generate a PDF file as "fileInput" + When I send the API request to the endpoint "/api/v1/misc/repair" + Then the response content type should be "application/pdf" + And the response file should have size greater than 0 + And the response status code should be 200 + + + @ocr @positive + Scenario: Process PDF with OCR + Given I generate a PDF file as "fileInput" + And the request data includes + | parameter | value | + | languages | eng | + | sidecar | false | + | deskew | true | + | clean | true | + | cleanFinal | true | + | ocrType | Normal | + | ocrRenderType | hocr | + | removeImagesAfter| false | + When I send the API request to the endpoint "/api/v1/misc/ocr-pdf" + Then the response content type should be "application/pdf" + And the response file should have size greater than 0 + And the response status code should be 200 + + + @ocr @positive + Scenario: Extract Image Scans + Given I generate a PDF file as "fileInput" + And the pdf contains 3 images on 2 pages + And the request data includes + | parameter | value | + | angleThreshold | 5 | + | tolerance | 20 | + | minArea | 8000 | + | minContourArea | 500 | + | borderSize | 1 | + When I send the API request to the endpoint "/api/v1/misc/extract-image-scans" + Then the response content type should be "application/octet-stream" + And the response file should have extension ".zip" + And the response ZIP should contain 2 files + And the response file should have size greater than 0 + And the response status code should be 200 + + + + @ocr @negative + Scenario: Process PDF with text and OCR with type normal + Given I generate a PDF file as "fileInput" + And the pdf contains 3 pages with random text + And the request data includes + | parameter | value | + | languages | eng | + | sidecar | false | + | deskew | true | + | clean | true | + | cleanFinal | true | + | ocrType | Normal | + | ocrRenderType | hocr | + | removeImagesAfter| false | + When I send the API request to the endpoint "/api/v1/misc/ocr-pdf" + Then the response status code should be 500 + + @ocr @positive + Scenario: Process PDF with OCR + Given I generate a PDF file as "fileInput" + And the request data includes + | parameter | value | + | languages | eng | + | sidecar | false | + | deskew | true | + | clean | true | + | cleanFinal | true | + | ocrType | Force | + | ocrRenderType | hocr | + | removeImagesAfter| false | + When I send the API request to the endpoint "/api/v1/misc/ocr-pdf" + Then the response content type should be "application/pdf" + And the response file should have size greater than 0 + And the response status code should be 200 + + @ocr @positive + Scenario: Process PDF with OCR with sidecar + Given I generate a PDF file as "fileInput" + And the request data includes + | parameter | value | + | languages | eng | + | sidecar | true | + | deskew | true | + | clean | true | + | cleanFinal | true | + | ocrType | Force | + | ocrRenderType | hocr | + | removeImagesAfter| false | + When I send the API request to the endpoint "/api/v1/misc/ocr-pdf" + Then the response content type should be "application/octet-stream" + And the response file should have extension ".zip" + And the response ZIP should contain 2 files + And the response file should have size greater than 0 + And the response status code should be 200 + + + @libre @positive + Scenario Outline: Convert PDF to various word formats + Given I generate a PDF file as "fileInput" + And the pdf contains 3 pages with random text + And the request data includes + | parameter | value | + | outputFormat | | + When I send the API request to the endpoint "/api/v1/convert/pdf/word" + Then the response status code should be 200 + And the response file should have size greater than 100 + And the response file should have extension "" + + Examples: + | format | extension | + | docx | .docx | + | odt | .odt | + | doc | .doc | + + @ocr + Scenario: PDFA + Given I generate a PDF file as "fileInput" + And the pdf contains 3 pages with random text + And the request data includes + | parameter | value | + | outputFormat | pdfa | + When I send the API request to the endpoint "/api/v1/convert/pdf/pdfa" + Then the response status code should be 200 + And the response file should have extension ".pdf" + And the response file should have size greater than 100 + + @ocr + Scenario: PDFA1 + Given I generate a PDF file as "fileInput" + And the pdf contains 3 pages with random text + And the request data includes + | parameter | value | + | outputFormat | pdfa-1 | + When I send the API request to the endpoint "/api/v1/convert/pdf/pdfa" + Then the response status code should be 200 + And the response file should have extension ".pdf" + And the response file should have size greater than 100 + + @compress @ghostscript @positive + Scenario: Compress + Given I generate a PDF file as "fileInput" + And the pdf contains 3 pages with random text + And the request data includes + | parameter | value | + | optimizeLevel | 4 | + When I send the API request to the endpoint "/api/v1/misc/compress-pdf" + Then the response status code should be 200 + And the response file should have extension ".pdf" + And the response file should have size greater than 100 + + @compress @ghostscript @positive + Scenario: Compress + Given I generate a PDF file as "fileInput" + And the pdf contains 3 pages with random text + And the request data includes + | parameter | value | + | optimizeLevel | 1 | + | expectedOutputSize | 5KB | + When I send the API request to the endpoint "/api/v1/misc/compress-pdf" + Then the response status code should be 200 + And the response file should have extension ".pdf" + And the response file should have size greater than 100 + + + @compress @ghostscript @positive + Scenario: Compress + Given I generate a PDF file as "fileInput" + And the pdf contains 3 pages with random text + And the request data includes + | parameter | value | + | optimizeLevel | 1 | + | expectedOutputSize | 5KB | + When I send the API request to the endpoint "/api/v1/misc/compress-pdf" + Then the response status code should be 200 + And the response file should have extension ".pdf" + And the response file should have size greater than 100 + + @libre @positive + Scenario Outline: Convert PDF to various types + Given I generate a PDF file as "fileInput" + And the pdf contains 3 pages with random text + And the request data includes + | parameter | value | + | outputFormat | | + When I send the API request to the endpoint "/api/v1/convert/pdf/" + Then the response status code should be 200 + And the response file should have size greater than 100 + And the response file should have extension "" + + Examples: + | type | format | extension | + | text | rtf | .rtf | + | text | txt | .txt | + | presentation | ppt | .ppt | + | presentation | pptx | .pptx | + | presentation | odp | .odp | + | html | html | .zip | + + + @libre @positive @topdf + Scenario Outline: Convert PDF to various types + Given I use an example file at "exampleFiles/example" as parameter "fileInput" + When I send the API request to the endpoint "/api/v1/convert/file/pdf" + Then the response status code should be 200 + And the response file should have size greater than 100 + And the response file should have extension ".pdf" + + Examples: + | extension | + | .docx | + | .odp | + | .odt | + | .pptx | + | .rtf | + + + diff --git a/cucumber/features/general.feature b/cucumber/features/general.feature new file mode 100644 index 00000000..229d37b5 --- /dev/null +++ b/cucumber/features/general.feature @@ -0,0 +1,96 @@ +@general +Feature: API Validation + + + @split-pdf-by-sections @positive + Scenario Outline: split-pdf-by-sections with different parameters + Given I generate a PDF file as "fileInput" + And the pdf contains 2 pages + And the request data includes + | parameter | value | + | horizontalDivisions | | + | verticalDivisions | | + | merge | true | + When I send the API request to the endpoint "/api/v1/general/split-pdf-by-sections" + Then the response content type should be "application/pdf" + And the response file should have size greater than 200 + And the response status code should be 200 + And the response PDF should contain pages + + Examples: + | horizontalDivisions | verticalDivisions | page_count | + | 0 | 1 | 4 | + | 1 | 1 | 8 | + | 1 | 2 | 12 | + | 2 | 2 | 18 | + + @split-pdf-by-sections @positive + Scenario Outline: split-pdf-by-sections with different parameters + Given I generate a PDF file as "fileInput" + And the pdf contains 2 pages + And the request data includes + | parameter | value | + | horizontalDivisions | | + | verticalDivisions | | + | merge | true | + When I send the API request to the endpoint "/api/v1/general/split-pdf-by-sections" + Then the response content type should be "application/pdf" + And the response file should have size greater than 200 + And the response status code should be 200 + And the response PDF should contain pages + + Examples: + | horizontalDivisions | verticalDivisions | page_count | + | 0 | 1 | 4 | + | 1 | 1 | 8 | + | 1 | 2 | 12 | + | 2 | 2 | 18 | + + + + @split-pdf-by-pages @positive + Scenario Outline: split-pdf-by-pages with different parameters + Given I generate a PDF file as "fileInput" + And the pdf contains 20 pages + And the request data includes + | parameter | value | + | fileInput | fileInput | + | pageNumbers | | + When I send the API request to the endpoint "/api/v1/general/split-pages" + Then the response content type should be "application/octet-stream" + And the response status code should be 200 + And the response file should have size greater than 200 + And the response ZIP should contain files + + Examples: + | pageNumbers | file_count | + | 1,3,5-9 | 8 | + | all | 20 | + | 2n+1 | 11 | + | 3n | 7 | + + + + @split-pdf-by-size-or-count @positive + Scenario Outline: split-pdf-by-size-or-count with different parameters + Given I generate a PDF file as "fileInput" + And the pdf contains 20 pages + And the request data includes + | parameter | value | + | fileInput | fileInput | + | splitType | | + | splitValue | | + When I send the API request to the endpoint "/api/v1/general/split-by-size-or-count" + Then the response content type should be "application/octet-stream" + And the response status code should be 200 + And the response file should have size greater than 200 + And the response ZIP file should contain documents each having pages + + Examples: + | splitType | splitValue | doc_count | pages_per_doc | + | 1 | 5 | 4 | 5 | + | 2 | 2 | 2 | 10 | + | 2 | 4 | 4 | 5 | + | 1 | 10 | 2 | 10 | + + diff --git a/cucumber/features/steps/step_definitions.py b/cucumber/features/steps/step_definitions.py index 50022d4e..60479b6e 100644 --- a/cucumber/features/steps/step_definitions.py +++ b/cucumber/features/steps/step_definitions.py @@ -9,6 +9,8 @@ from reportlab.lib.pagesizes import letter from reportlab.pdfgen import canvas import mimetypes import requests +import zipfile +import shutil ######### # GIVEN # @@ -26,6 +28,23 @@ def step_generate_pdf(context, fileInput): context.files = {} context.files[context.param_name] = open(context.file_name, 'rb') + +@given('I use an example file at "{filePath}" as parameter "{fileInput}"') +def step_use_example_file(context, filePath, fileInput): + context.param_name = fileInput + context.file_name = filePath.split('/')[-1] + if not hasattr(context, 'files'): + context.files = {} + + # Ensure the file exists before opening + try: + example_file = open(filePath, 'rb') + context.files[context.param_name] = example_file + except FileNotFoundError: + raise FileNotFoundError(f"The example file '{filePath}' does not exist.") + + + @given('the pdf contains {page_count:d} pages') def step_pdf_contains_pages(context, page_count): writer = PdfWriter() @@ -47,6 +66,59 @@ def step_pdf_contains_blank_pages(context, page_count): context.files[context.param_name].close() context.files[context.param_name] = open(context.file_name, 'rb') + + +def create_black_box_image(file_name, size): + can = canvas.Canvas(file_name, pagesize=size) + width, height = size + can.setFillColorRGB(0, 0, 0) + can.rect(0, 0, width, height, fill=1) + can.showPage() + can.save() + +def create_pdf_with_black_boxes(file_name, image_count, page_count): + page_width, page_height = letter + box_size = 72 # 1 inch by 1 inch black box + boxes_per_page = image_count // page_count + (1 if image_count % page_count != 0 else 0) + + writer = PdfWriter() + box_counter = 0 + + for page in range(page_count): + packet = io.BytesIO() + can = canvas.Canvas(packet, pagesize=letter) + + for i in range(boxes_per_page): + if box_counter >= image_count: + break + x = (i % (page_width // box_size)) * box_size + y = page_height - ((i // (page_width // box_size) + 1) * box_size) + can.setFillColorRGB(0, 0, 0) + can.rect(x, y, box_size, box_size, fill=1) + box_counter += 1 + + can.showPage() + can.save() + packet.seek(0) + new_pdf = PdfReader(packet) + writer.add_page(new_pdf.pages[0]) + + with open(file_name, 'wb') as f: + writer.write(f) + +@given('the pdf contains {image_count:d} images on {page_count:d} pages') +def step_pdf_contains_images(context, image_count, page_count): + if not hasattr(context, 'param_name'): + context.param_name = "default" + context.file_name = "genericNonCustomisableName.pdf" + create_pdf_with_black_boxes(context.file_name, image_count, page_count) + if not hasattr(context, 'files'): + context.files = {} + if context.param_name in context.files: + context.files[context.param_name].close() + context.files[context.param_name] = open(context.file_name, 'rb') + + @given('the pdf contains {page_count:d} pages with random text') def step_pdf_contains_pages_with_random_text(context, page_count): buffer = io.BytesIO() @@ -129,7 +201,7 @@ def step_send_api_request(context, endpoint): for key, file in files.items(): mime_type, _ = mimetypes.guess_type(file.name) mime_type = mime_type or 'application/octet-stream' - print("form_data " + file.name + " with " + mime_type) + print(f"form_data {file.name} with {mime_type}") form_data.append((key, (file.name, file, mime_type))) response = requests.post(url, files=form_data) @@ -205,3 +277,31 @@ def step_save_response_file(context, filename): with open(filename, 'wb') as f: f.write(context.response.content) print(f"Saved response content to {filename}") + + +@then('the response PDF should contain {page_count:d} pages') +def step_check_response_pdf_page_count(context, page_count): + response_file = io.BytesIO(context.response.content) + reader = PdfReader(io.BytesIO(response_file.getvalue())) + actual_page_count = len(reader.pages) + assert actual_page_count == page_count, f"Expected {page_count} pages but got {actual_page_count} pages" + +@then('the response ZIP should contain {file_count:d} files') +def step_check_response_zip_file_count(context, file_count): + response_file = io.BytesIO(context.response.content) + with zipfile.ZipFile(io.BytesIO(response_file.getvalue())) as zip_file: + actual_file_count = len(zip_file.namelist()) + assert actual_file_count == file_count, f"Expected {file_count} files but got {actual_file_count} files" + +@then('the response ZIP file should contain {doc_count:d} documents each having {pages_per_doc:d} pages') +def step_check_response_zip_doc_page_count(context, doc_count, pages_per_doc): + response_file = io.BytesIO(context.response.content) + with zipfile.ZipFile(io.BytesIO(response_file.getvalue())) as zip_file: + actual_doc_count = len(zip_file.namelist()) + assert actual_doc_count == doc_count, f"Expected {doc_count} documents but got {actual_doc_count} documents" + + for file_name in zip_file.namelist(): + with zip_file.open(file_name) as pdf_file: + reader = PdfReader(pdf_file) + actual_pages_per_doc = len(reader.pages) + assert actual_pages_per_doc == pages_per_doc, f"Expected {pages_per_doc} pages per document but got {actual_pages_per_doc} pages in document {file_name}" diff --git a/src/main/java/stirling/software/SPDF/controller/api/MergeController.java b/src/main/java/stirling/software/SPDF/controller/api/MergeController.java index 1719d325..5e9f97df 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/MergeController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/MergeController.java @@ -135,7 +135,9 @@ public class MergeController { throw ex; } finally { for (File file : filesToDelete) { - file.delete(); + if (file != null) { + Files.deleteIfExists(file.toPath()); + } } } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java b/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java index a6148d4a..48c987cf 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/PdfOverlayController.java @@ -87,12 +87,12 @@ public class PdfOverlayController { } finally { for (File overlayPdfFile : overlayPdfFiles) { if (overlayPdfFile != null) { - overlayPdfFile.delete(); + Files.deleteIfExists(overlayPdfFile.toPath()); } } for (File tempFile : tempFiles) { // Delete temporary files if (tempFile != null) { - tempFile.delete(); + Files.deleteIfExists(tempFile.toPath()); } } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java index 51efa644..ab80cad3 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPDFController.java @@ -121,7 +121,7 @@ public class SplitPDFController { logger.info("Successfully created zip file with split documents: {}", zipFile.toString()); byte[] data = Files.readAllBytes(zipFile); - Files.delete(zipFile); + Files.deleteIfExists(zipFile); // return the Resource in the response return WebResponseUtils.bytesToWebResponse( diff --git a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java index 3682aaf7..d9077d9e 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/SplitPdfBySectionsController.java @@ -63,10 +63,7 @@ public class SplitPdfBySectionsController { MergeController mergeController = new MergeController(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); mergeController.mergeDocuments(splitDocuments).save(baos); - return WebResponseUtils.bytesToWebResponse( - baos.toByteArray(), - filename + "_split.pdf", - MediaType.APPLICATION_OCTET_STREAM); + return WebResponseUtils.bytesToWebResponse(baos.toByteArray(), filename + "_split.pdf"); } for (PDDocument doc : splitDocuments) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -98,7 +95,7 @@ public class SplitPdfBySectionsController { e.printStackTrace(); } finally { data = Files.readAllBytes(zipFile); - Files.delete(zipFile); + Files.deleteIfExists(zipFile); } return WebResponseUtils.bytesToWebResponse( diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java index c6740b5f..5e6b3dfd 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertOfficeController.java @@ -3,7 +3,6 @@ package stirling.software.SPDF.controller.api.converters; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -41,34 +40,35 @@ public class ConvertOfficeController { // Save the uploaded file to a temporary location Path tempInputFile = Files.createTempFile("input_", "." + FilenameUtils.getExtension(originalFilename)); - Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING); + inputFile.transferTo(tempInputFile); // Prepare the output file path Path tempOutputFile = Files.createTempFile("output_", ".pdf"); - // Run the LibreOffice command - List command = - new ArrayList<>( - Arrays.asList( - "unoconv", - "-vvv", - "-f", - "pdf", - "-o", - tempOutputFile.toString(), - tempInputFile.toString())); - ProcessExecutorResult returnCode = - ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE) - .runCommandWithOutputHandling(command); + try { + // Run the LibreOffice command + List command = + new ArrayList<>( + Arrays.asList( + "unoconv", + "-vvv", + "-f", + "pdf", + "-o", + tempOutputFile.toString(), + tempInputFile.toString())); + ProcessExecutorResult returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.LIBRE_OFFICE) + .runCommandWithOutputHandling(command); - // Read the converted PDF file - byte[] pdfBytes = Files.readAllBytes(tempOutputFile); - - // Clean up the temporary files - Files.delete(tempInputFile); - Files.delete(tempOutputFile); - - return pdfBytes; + // Read the converted PDF file + byte[] pdfBytes = Files.readAllBytes(tempOutputFile); + return pdfBytes; + } finally { + // Clean up the temporary files + if (tempInputFile != null) Files.deleteIfExists(tempInputFile); + Files.deleteIfExists(tempOutputFile); + } } private boolean isValidFileExtension(String fileExtension) { diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java index aec4e347..e3578826 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPDFToPDFA.java @@ -61,8 +61,8 @@ public class ConvertPDFToPDFA { byte[] pdfBytes = Files.readAllBytes(tempOutputFile); // Clean up the temporary files - Files.delete(tempInputFile); - Files.delete(tempOutputFile); + Files.deleteIfExists(tempInputFile); + Files.deleteIfExists(tempOutputFile); // Return the optimized PDF as a response String outputFilename = diff --git a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java index 815018e8..8bce1b22 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java +++ b/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertWebsiteToPDF.java @@ -59,7 +59,7 @@ public class ConvertWebsiteToPDF { pdfBytes = Files.readAllBytes(tempOutputFile); } finally { // Clean up the temporary files - Files.delete(tempOutputFile); + Files.deleteIfExists(tempOutputFile); } // Convert URL to a safe filename String outputFilename = convertURLToFileName(URL); diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java index 0b578d8f..23c08f26 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/AutoSplitPdfController.java @@ -118,7 +118,7 @@ public class AutoSplitPdfController { e.printStackTrace(); } finally { data = Files.readAllBytes(zipFile); - Files.delete(zipFile); + Files.deleteIfExists(zipFile); } return WebResponseUtils.bytesToWebResponse( diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java index 9e3d6a99..12dd625c 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java @@ -136,10 +136,10 @@ public class CompressController { // Increase optimization level for next iteration optimizeLevel++; if (autoMode && optimizeLevel > 4) { - System.out.println("Skipping level 5 due to bad results in auto mode"); + logger.info("Skipping level 5 due to bad results in auto mode"); sizeMet = true; } else { - System.out.println( + logger.info( "Increasing ghostscript optimisation level to " + optimizeLevel); } } @@ -230,10 +230,10 @@ public class CompressController { if (currentSize > expectedOutputSize) { // Log the current file size and scaleFactor - System.out.println( + logger.info( "Current file size: " + FileUtils.byteCountToDisplaySize(currentSize)); - System.out.println("Current scale factor: " + scaleFactor); + logger.info("Current scale factor: " + scaleFactor); // The file is still too large, reduce scaleFactor and try again scaleFactor *= 0.9f; // reduce scaleFactor by 10% @@ -256,7 +256,6 @@ public class CompressController { } } } - // Read the optimized PDF file pdfBytes = Files.readAllBytes(tempOutputFile); @@ -269,17 +268,18 @@ public class CompressController { // Read the original file again pdfBytes = Files.readAllBytes(tempInputFile); } + // Return the optimized PDF as a response + String outputFilename = + Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + .replaceFirst("[.][^.]+$", "") + + "_Optimized.pdf"; + return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); + } finally { // Clean up the temporary files - Files.delete(tempInputFile); - Files.delete(tempOutputFile); + // deleted by multipart file handler deu to transferTo? + // Files.deleteIfExists(tempInputFile); + Files.deleteIfExists(tempOutputFile); } - - // Return the optimized PDF as a response - String outputFilename = - Filenames.toSimpleFileName(inputFile.getOriginalFilename()) - .replaceFirst("[.][^.]+$", "") - + "_Optimized.pdf"; - return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java index 9ed00dd8..4a6acaee 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/ExtractImageScansController.java @@ -5,7 +5,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -103,10 +102,7 @@ public class ExtractImageScansController { } } else { tempInputFile = Files.createTempFile("input_", "." + extension); - Files.copy( - form.getFileInput().getInputStream(), - tempInputFile, - StandardCopyOption.REPLACE_EXISTING); + form.getFileInput().transferTo(tempInputFile); // Add input file path to images list images.add(tempInputFile.toString()); } @@ -176,11 +172,14 @@ public class ExtractImageScansController { byte[] zipBytes = Files.readAllBytes(tempZipFile); // Clean up the temporary zip file - Files.delete(tempZipFile); + Files.deleteIfExists(tempZipFile); return WebResponseUtils.bytesToWebResponse( zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM); - } else { + } if (processedImageBytes.size() == 0) { + throw new IllegalArgumentException("No images detected"); + }else { + // Return the processed image as a response byte[] imageBytes = processedImageBytes.get(0); return WebResponseUtils.bytesToWebResponse( @@ -201,7 +200,7 @@ public class ExtractImageScansController { if (tempZipFile != null && Files.exists(tempZipFile)) { try { - Files.delete(tempZipFile); + Files.deleteIfExists(tempZipFile); } catch (IOException e) { logger.error("Failed to delete temporary zip file: " + tempZipFile, e); } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java index 07947587..bb8a18cd 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/OCRController.java @@ -5,7 +5,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -91,139 +90,145 @@ public class OCRController { } // Save the uploaded file to a temporary location Path tempInputFile = Files.createTempFile("input_", ".pdf"); - Files.copy(inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING); - - // Prepare the output file path Path tempOutputFile = Files.createTempFile("output_", ".pdf"); - - // Prepare the output file path Path sidecarTextPath = null; - // Run OCR Command - String languageOption = String.join("+", selectedLanguages); + try { + inputFile.transferTo(tempInputFile.toFile()); - List command = - new ArrayList<>( - Arrays.asList( - "ocrmypdf", - "--verbose", - "2", - "--output-type", - "pdf", - "--pdf-renderer", - ocrRenderType)); + // Run OCR Command + String languageOption = String.join("+", selectedLanguages); - if (sidecar != null && sidecar) { - sidecarTextPath = Files.createTempFile("sidecar", ".txt"); - command.add("--sidecar"); - command.add(sidecarTextPath.toString()); - } - - if (deskew != null && deskew) { - command.add("--deskew"); - } - if (clean != null && clean) { - command.add("--clean"); - } - if (cleanFinal != null && cleanFinal) { - command.add("--clean-final"); - } - if (ocrType != null && !"".equals(ocrType)) { - if ("skip-text".equals(ocrType)) { - command.add("--skip-text"); - } else if ("force-ocr".equals(ocrType)) { - command.add("--force-ocr"); - } else if ("Normal".equals(ocrType)) { + List command = + new ArrayList<>( + Arrays.asList( + "ocrmypdf", + "--verbose", + "2", + "--output-type", + "pdf", + "--pdf-renderer", + ocrRenderType)); + if (sidecar != null && sidecar) { + sidecarTextPath = Files.createTempFile("sidecar", ".txt"); + command.add("--sidecar"); + command.add(sidecarTextPath.toString()); } - } - command.addAll( - Arrays.asList( - "--language", - languageOption, - tempInputFile.toString(), - tempOutputFile.toString())); + if (deskew != null && deskew) { + command.add("--deskew"); + } + if (clean != null && clean) { + command.add("--clean"); + } + if (cleanFinal != null && cleanFinal) { + command.add("--clean-final"); + } + if (ocrType != null && !"".equals(ocrType)) { + if ("skip-text".equals(ocrType)) { + command.add("--skip-text"); + } else if ("force-ocr".equals(ocrType)) { + command.add("--force-ocr"); + } else if ("Normal".equals(ocrType)) { - // Run CLI command - ProcessExecutorResult result = - ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF) - .runCommandWithOutputHandling(command); - if (result.getRc() != 0 - && result.getMessages().contains("multiprocessing/synchronize.py") - && result.getMessages().contains("OSError: [Errno 38] Function not implemented")) { - command.add("--jobs"); - command.add("1"); - result = + } + } + + command.addAll( + Arrays.asList( + "--language", + languageOption, + tempInputFile.toString(), + tempOutputFile.toString())); + + // Run CLI command + ProcessExecutorResult result = ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF) .runCommandWithOutputHandling(command); - } - - // Remove images from the OCR processed PDF if the flag is set to true - if (removeImagesAfter != null && removeImagesAfter) { - Path tempPdfWithoutImages = Files.createTempFile("output_", "_no_images.pdf"); - - List gsCommand = - Arrays.asList( - "gs", - "-sDEVICE=pdfwrite", - "-dFILTERIMAGE", - "-o", - tempPdfWithoutImages.toString(), - tempOutputFile.toString()); - - ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT) - .runCommandWithOutputHandling(gsCommand); - tempOutputFile = tempPdfWithoutImages; - } - // Read the OCR processed PDF file - byte[] pdfBytes = Files.readAllBytes(tempOutputFile); - // Clean up the temporary files - Files.delete(tempInputFile); - - // Return the OCR processed PDF as a response - String outputFilename = - Filenames.toSimpleFileName(inputFile.getOriginalFilename()) - .replaceFirst("[.][^.]+$", "") - + "_OCR.pdf"; - - if (sidecar != null && sidecar) { - // Create a zip file containing both the PDF and the text file - String outputZipFilename = - Filenames.toSimpleFileName(inputFile.getOriginalFilename()) - .replaceFirst("[.][^.]+$", "") - + "_OCR.zip"; - Path tempZipFile = Files.createTempFile("output_", ".zip"); - - try (ZipOutputStream zipOut = - new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) { - // Add PDF file to the zip - ZipEntry pdfEntry = new ZipEntry(outputFilename); - zipOut.putNextEntry(pdfEntry); - Files.copy(tempOutputFile, zipOut); - zipOut.closeEntry(); - - // Add text file to the zip - ZipEntry txtEntry = new ZipEntry(outputFilename.replace(".pdf", ".txt")); - zipOut.putNextEntry(txtEntry); - Files.copy(sidecarTextPath, zipOut); - zipOut.closeEntry(); + if (result.getRc() != 0 + && result.getMessages().contains("multiprocessing/synchronize.py") + && result.getMessages() + .contains("OSError: [Errno 38] Function not implemented")) { + command.add("--jobs"); + command.add("1"); + result = + ProcessExecutor.getInstance(ProcessExecutor.Processes.OCR_MY_PDF) + .runCommandWithOutputHandling(command); } - byte[] zipBytes = Files.readAllBytes(tempZipFile); + // Remove images from the OCR processed PDF if the flag is set to true + if (removeImagesAfter != null && removeImagesAfter) { + Path tempPdfWithoutImages = Files.createTempFile("output_", "_no_images.pdf"); - // Clean up the temporary zip file - Files.delete(tempZipFile); - Files.delete(tempOutputFile); - Files.delete(sidecarTextPath); + List gsCommand = + Arrays.asList( + "gs", + "-sDEVICE=pdfwrite", + "-dFILTERIMAGE", + "-o", + tempPdfWithoutImages.toString(), + tempOutputFile.toString()); + + ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT) + .runCommandWithOutputHandling(gsCommand); + tempOutputFile = tempPdfWithoutImages; + } + // Read the OCR processed PDF file + byte[] pdfBytes = Files.readAllBytes(tempOutputFile); - // Return the zip file containing both the PDF and the text file - return WebResponseUtils.bytesToWebResponse( - zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM); - } else { // Return the OCR processed PDF as a response - Files.delete(tempOutputFile); - return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); + String outputFilename = + Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + .replaceFirst("[.][^.]+$", "") + + "_OCR.pdf"; + + if (sidecar != null && sidecar) { + // Create a zip file containing both the PDF and the text file + String outputZipFilename = + Filenames.toSimpleFileName(inputFile.getOriginalFilename()) + .replaceFirst("[.][^.]+$", "") + + "_OCR.zip"; + Path tempZipFile = Files.createTempFile("output_", ".zip"); + + try (ZipOutputStream zipOut = + new ZipOutputStream(new FileOutputStream(tempZipFile.toFile()))) { + // Add PDF file to the zip + ZipEntry pdfEntry = new ZipEntry(outputFilename); + zipOut.putNextEntry(pdfEntry); + Files.copy(tempOutputFile, zipOut); + zipOut.closeEntry(); + + // Add text file to the zip + ZipEntry txtEntry = new ZipEntry(outputFilename.replace(".pdf", ".txt")); + zipOut.putNextEntry(txtEntry); + Files.copy(sidecarTextPath, zipOut); + zipOut.closeEntry(); + } + + byte[] zipBytes = Files.readAllBytes(tempZipFile); + + // Clean up the temporary zip file + Files.deleteIfExists(tempZipFile); + Files.deleteIfExists(tempOutputFile); + Files.deleteIfExists(sidecarTextPath); + + // Return the zip file containing both the PDF and the text file + return WebResponseUtils.bytesToWebResponse( + zipBytes, outputZipFilename, MediaType.APPLICATION_OCTET_STREAM); + } else { + // Return the OCR processed PDF as a response + Files.deleteIfExists(tempOutputFile); + return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); + } + } finally { + // Clean up the temporary files + Files.deleteIfExists(tempOutputFile); + // Comment out as transferTo makes multipart handle cleanup + // Files.deleteIfExists(tempInputFile); + if (sidecarTextPath != null) { + Files.deleteIfExists(sidecarTextPath); + } } } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java index 85d4560c..812a8cae 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/RepairController.java @@ -41,34 +41,36 @@ public class RepairController { MultipartFile inputFile = request.getFileInput(); // Save the uploaded file to a temporary location Path tempInputFile = Files.createTempFile("input_", ".pdf"); - inputFile.transferTo(tempInputFile.toFile()); - - // Prepare the output file path Path tempOutputFile = Files.createTempFile("output_", ".pdf"); + byte[] pdfBytes = null; + inputFile.transferTo(tempInputFile.toFile()); + try { - List command = new ArrayList<>(); - command.add("gs"); - command.add("-o"); - command.add(tempOutputFile.toString()); - command.add("-sDEVICE=pdfwrite"); - command.add(tempInputFile.toString()); + List command = new ArrayList<>(); + command.add("gs"); + command.add("-o"); + command.add(tempOutputFile.toString()); + command.add("-sDEVICE=pdfwrite"); + command.add(tempInputFile.toString()); - ProcessExecutorResult returnCode = - ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT) - .runCommandWithOutputHandling(command); - - // Read the optimized PDF file - byte[] pdfBytes = Files.readAllBytes(tempOutputFile); - - // Clean up the temporary files - Files.delete(tempInputFile); - Files.delete(tempOutputFile); + ProcessExecutorResult returnCode = + ProcessExecutor.getInstance(ProcessExecutor.Processes.GHOSTSCRIPT) + .runCommandWithOutputHandling(command); + // Read the optimized PDF file + pdfBytes = Files.readAllBytes(tempOutputFile); + // Return the optimized PDF as a response String outputFilename = Filenames.toSimpleFileName(inputFile.getOriginalFilename()) .replaceFirst("[.][^.]+$", "") + "_repaired.pdf"; return WebResponseUtils.bytesToWebResponse(pdfBytes, outputFilename); + } finally { + // Clean up the temporary files + Files.deleteIfExists(tempInputFile); + Files.deleteIfExists(tempOutputFile); + } + } } diff --git a/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java b/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java index e5d326a9..ecf41557 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/misc/StampController.java @@ -185,10 +185,12 @@ public class StampController { try (InputStream is = classPathResource.getInputStream(); FileOutputStream os = new FileOutputStream(tempFile)) { IOUtils.copy(is, os); + font = PDType0Font.load(document, tempFile); + } finally { + if (tempFile != null) { + Files.deleteIfExists(tempFile.toPath()); + } } - - font = PDType0Font.load(document, tempFile); - tempFile.deleteOnExit(); } contentStream.setFont(font, fontSize); diff --git a/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java b/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java index 9506822c..2133ae26 100644 --- a/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java +++ b/src/main/java/stirling/software/SPDF/controller/api/security/WatermarkController.java @@ -150,10 +150,10 @@ public class WatermarkController { try (InputStream is = classPathResource.getInputStream(); FileOutputStream os = new FileOutputStream(tempFile)) { IOUtils.copy(is, os); + font = PDType0Font.load(document, tempFile); + } finally { + if (tempFile != null) Files.deleteIfExists(tempFile.toPath()); } - - font = PDType0Font.load(document, tempFile); - tempFile.deleteOnExit(); } contentStream.setFont(font, fontSize); diff --git a/src/main/java/stirling/software/SPDF/utils/FileToPdf.java b/src/main/java/stirling/software/SPDF/utils/FileToPdf.java index da408a23..2a891c08 100644 --- a/src/main/java/stirling/software/SPDF/utils/FileToPdf.java +++ b/src/main/java/stirling/software/SPDF/utils/FileToPdf.java @@ -79,8 +79,8 @@ public class FileToPdf { } finally { // Clean up temporary files - Files.delete(tempOutputFile); - Files.delete(tempInputFile); + Files.deleteIfExists(tempOutputFile); + Files.deleteIfExists(tempInputFile); } return pdfBytes; diff --git a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java index 61b72da6..03ae55ce 100644 --- a/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java +++ b/src/main/java/stirling/software/SPDF/utils/GeneralUtils.java @@ -38,14 +38,14 @@ public class GeneralUtils { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { - Files.delete(file); + Files.deleteIfExists(file); return FileVisitResult.CONTINUE; } @Override public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { - Files.delete(dir); + Files.deleteIfExists(dir); return FileVisitResult.CONTINUE; } }); diff --git a/src/main/java/stirling/software/SPDF/utils/PDFToFile.java b/src/main/java/stirling/software/SPDF/utils/PDFToFile.java index 38579aa8..f5297a10 100644 --- a/src/main/java/stirling/software/SPDF/utils/PDFToFile.java +++ b/src/main/java/stirling/software/SPDF/utils/PDFToFile.java @@ -6,7 +6,6 @@ import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -47,8 +46,7 @@ public class PDFToFile { try { // Save the uploaded file to a temporary location tempInputFile = Files.createTempFile("input_", ".pdf"); - Files.copy( - inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING); + inputFile.transferTo(tempInputFile); // Prepare the output directory tempOutputDir = Files.createTempDirectory("output_"); @@ -85,7 +83,7 @@ public class PDFToFile { } finally { // Clean up the temporary files - if (tempInputFile != null) Files.delete(tempInputFile); + if (tempInputFile != null) Files.deleteIfExists(tempInputFile); if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile()); } @@ -127,8 +125,7 @@ public class PDFToFile { try { // Save the uploaded file to a temporary location tempInputFile = Files.createTempFile("input_", ".pdf"); - Files.copy( - inputFile.getInputStream(), tempInputFile, StandardCopyOption.REPLACE_EXISTING); + inputFile.transferTo(tempInputFile); // Prepare the output directory tempOutputDir = Files.createTempDirectory("output_"); @@ -180,7 +177,7 @@ public class PDFToFile { } finally { // Clean up the temporary files - if (tempInputFile != null) Files.delete(tempInputFile); + Files.deleteIfExists(tempInputFile); if (tempOutputDir != null) FileUtils.deleteDirectory(tempOutputDir.toFile()); } System.out.println("fileBytes=" + fileBytes.length); diff --git a/src/main/resources/templates/misc/ocr-pdf.html b/src/main/resources/templates/misc/ocr-pdf.html index a74242ae..65dd17da 100644 --- a/src/main/resources/templates/misc/ocr-pdf.html +++ b/src/main/resources/templates/misc/ocr-pdf.html @@ -232,7 +232,7 @@ // Step 3: Apply the function to your labels document.addEventListener('DOMContentLoaded', () => { - const labels = document.querySelectorAll('#languages .form-check-label'); + const labels = document.querySelectorAll('#languages label'); labels.forEach(label => { const languageCode = label.getAttribute('for').split('-')[1]; label.textContent = getFullLanguageName(languageCode);