├── .classpath ├── .gitattributes ├── .gitignore ├── .project ├── LICENSE ├── README.md ├── build.xml ├── css └── custom.css ├── html ├── about.html └── main.html ├── icons ├── TMXValidator.icns ├── TMXValidator.ico └── TMXValidator.png ├── img ├── info.png ├── tmxvalidator.png └── working.gif ├── jars ├── bcp47j.jar ├── json.jar └── xmljava.jar ├── package.json ├── src ├── com │ └── maxprograms │ │ ├── server │ │ └── CheckURL.java │ │ └── tmxvalidation │ │ ├── Constants.java │ │ ├── Messages.java │ │ ├── TMXCopyHandler.java │ │ ├── TMXResolver.java │ │ ├── TMXValidatingHandler.java │ │ ├── TMXValidator.java │ │ ├── ValidationServer.java │ │ ├── tmx11.dtd │ │ ├── tmx12.dtd │ │ ├── tmx13.dtd │ │ ├── tmx14.dtd │ │ ├── tmxvalidation.properties │ │ └── tmxvalidation_es.properties └── module-info.java ├── tmxvalidator_es.tmx ├── tmxvalidator_es.xlf ├── ts ├── about.ts ├── app.ts └── main.ts └── tsconfig.json /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /dist/ 3 | .DS_Store 4 | /.scannerwork/ 5 | /sonar-project.properties 6 | /sonarqube.sh 7 | /node_modules 8 | /js 9 | /target 10 | package-lock.json 11 | /out 12 | /lib 13 | /legal 14 | /include 15 | /conf 16 | /release 17 | /TMXValidator-darwin-x64 18 | /TMXValidator-win32-x64 19 | /TMXValidator-darwin-arm64 20 | .vscode/launch.json -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | TMXValidator 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | 19 | 1669754474412 20 | 21 | 30 22 | 23 | org.eclipse.core.resources.regexFilterMatcher 24 | node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 1.0 2 | 3 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE 4 | PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF 5 | THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 6 | 7 | 1. DEFINITIONS 8 | 9 | "Contribution" means: 10 | 11 | a) in the case of the initial Contributor, the initial code and 12 | documentation distributed under this Agreement, and 13 | 14 | b) in the case of each subsequent Contributor: 15 | 16 | i) changes to the Program, and 17 | 18 | ii) additions to the Program; 19 | 20 | where such changes and/or additions to the Program originate from 21 | and are distributed by that particular Contributor. A Contribution 22 | 'originates' from a Contributor if it was added to the Program by 23 | such Contributor itself or anyone acting on such Contributor's 24 | behalf. Contributions do not include additions to the Program 25 | which: (i) are separate modules of software distributed in 26 | conjunction with the Program under their own license agreement, 27 | and (ii) are not derivative works of the Program. 28 | 29 | "Contributor" means any person or entity that distributes the Program. 30 | 31 | "Licensed Patents" mean patent claims licensable by a Contributor 32 | which are necessarily infringed by the use or sale of its Contribution 33 | alone or when combined with the Program. 34 | 35 | "Program" means the Contributions distributed in accordance with this 36 | Agreement. 37 | 38 | "Recipient" means anyone who receives the Program under this 39 | Agreement, including all Contributors. 40 | 41 | 2. GRANT OF RIGHTS 42 | 43 | a) Subject to the terms of this Agreement, each Contributor hereby 44 | grants Recipient a non-exclusive, worldwide, royalty-free 45 | copyright license to reproduce, prepare derivative works of, 46 | publicly display, publicly perform, distribute and sublicense the 47 | Contribution of such Contributor, if any, and such derivative 48 | works, in source code and object code form. 49 | 50 | b) Subject to the terms of this Agreement, each Contributor hereby 51 | grants Recipient a non-exclusive, worldwide, royalty-free patent 52 | license under Licensed Patents to make, use, sell, offer to sell, 53 | import and otherwise transfer the Contribution of such 54 | Contributor, if any, in source code and object code form. This 55 | patent license shall apply to the combination of the Contribution 56 | and the Program if, at the time the Contribution is added by the 57 | Contributor, such addition of the Contribution causes such 58 | combination to be covered by the Licensed Patents. The patent 59 | license shall not apply to any other combinations which include 60 | the Contribution. No hardware per se is licensed hereunder. 61 | 62 | c) Recipient understands that although each Contributor grants the 63 | licenses to its Contributions set forth herein, no assurances are 64 | provided by any Contributor that the Program does not infringe the 65 | patent or other intellectual property rights of any other 66 | entity. Each Contributor disclaims any liability to Recipient for 67 | claims brought by any other entity based on infringement of 68 | intellectual property rights or otherwise. As a condition to 69 | exercising the rights and licenses granted hereunder, each 70 | Recipient hereby assumes sole responsibility to secure any other 71 | intellectual property rights needed, if any. For example, if a 72 | third party patent license is required to allow Recipient to 73 | distribute the Program, it is Recipient's responsibility to 74 | acquire that license before distributing the Program. 75 | 76 | d) Each Contributor represents that to its knowledge it has 77 | sufficient copyright rights in its Contribution, if any, to grant 78 | the copyright license set forth in this Agreement. 79 | 80 | 3. REQUIREMENTS 81 | 82 | A Contributor may choose to distribute the Program in object code form 83 | under its own license agreement, provided that: 84 | 85 | a) it complies with the terms and conditions of this Agreement; 86 | and 87 | 88 | b) its license agreement: 89 | 90 | i) effectively disclaims on behalf of all Contributors all 91 | warranties and conditions, express and implied, including 92 | warranties or conditions of title and non-infringement, and 93 | implied warranties or conditions of merchantability and fitness 94 | for a particular purpose; 95 | 96 | ii) effectively excludes on behalf of all Contributors all 97 | liability for damages, including direct, indirect, special, 98 | incidental and consequential damages, such as lost profits; 99 | 100 | iii) states that any provisions which differ from this Agreement 101 | are offered by that Contributor alone and not by any other party; 102 | and 103 | 104 | iv) states that source code for the Program is available from such 105 | Contributor, and informs licensees how to obtain it in a 106 | reasonable manner on or through a medium customarily used for 107 | software exchange. 108 | 109 | When the Program is made available in source code form: 110 | 111 | a) it must be made available under this Agreement; and 112 | 113 | b) a copy of this Agreement must be included with each copy of the 114 | Program. 115 | 116 | Contributors may not remove or alter any copyright notices contained 117 | within the Program. 118 | 119 | Each Contributor must identify itself as the originator of its 120 | Contribution, if any, in a manner that reasonably allows subsequent 121 | Recipients to identify the originator of the Contribution. 122 | 123 | 4. COMMERCIAL DISTRIBUTION 124 | 125 | Commercial distributors of software may accept certain 126 | responsibilities with respect to end users, business partners and the 127 | like. While this license is intended to facilitate the commercial use 128 | of the Program, the Contributor who includes the Program in a 129 | commercial product offering should do so in a manner which does not 130 | create potential liability for other Contributors. Therefore, if a 131 | Contributor includes the Program in a commercial product offering, 132 | such Contributor ("Commercial Contributor") hereby agrees to defend 133 | and indemnify every other Contributor ("Indemnified Contributor") 134 | against any losses, damages and costs (collectively "Losses") arising 135 | from claims, lawsuits and other legal actions brought by a third party 136 | against the Indemnified Contributor to the extent caused by the acts 137 | or omissions of such Commercial Contributor in connection with its 138 | distribution of the Program in a commercial product offering. The 139 | obligations in this section do not apply to any claims or Losses 140 | relating to any actual or alleged intellectual property 141 | infringement. In order to qualify, an Indemnified Contributor must: a) 142 | promptly notify the Commercial Contributor in writing of such claim, 143 | and b) allow the Commercial Contributor to control, and cooperate with 144 | the Commercial Contributor in, the defense and any related settlement 145 | negotiations. The Indemnified Contributor may participate in any such 146 | claim at its own expense. 147 | 148 | For example, a Contributor might include the Program in a commercial 149 | product offering, Product X. That Contributor is then a Commercial 150 | Contributor. If that Commercial Contributor then makes performance 151 | claims, or offers warranties related to Product X, those performance 152 | claims and warranties are such Commercial Contributor's responsibility 153 | alone. Under this section, the Commercial Contributor would have to 154 | defend claims against the other Contributors related to those 155 | performance claims and warranties, and if a court requires any other 156 | Contributor to pay any damages as a result, the Commercial Contributor 157 | must pay those damages. 158 | 159 | 5. NO WARRANTY 160 | 161 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS 162 | PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 163 | KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY 164 | WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY 165 | OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely 166 | responsible for determining the appropriateness of using and 167 | distributing the Program and assumes all risks associated with its 168 | exercise of rights under this Agreement , including but not limited to 169 | the risks and costs of program errors, compliance with applicable 170 | laws, damage to or loss of data, programs or equipment, and 171 | unavailability or interruption of operations. 172 | 173 | 6. DISCLAIMER OF LIABILITY 174 | 175 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR 176 | ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, 177 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING 178 | WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF 179 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 180 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR 181 | DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED 182 | HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 183 | 184 | 7. GENERAL 185 | 186 | If any provision of this Agreement is invalid or unenforceable under 187 | applicable law, it shall not affect the validity or enforceability of 188 | the remainder of the terms of this Agreement, and without further 189 | action by the parties hereto, such provision shall be reformed to the 190 | minimum extent necessary to make such provision valid and enforceable. 191 | 192 | If Recipient institutes patent litigation against any entity 193 | (including a cross-claim or counterclaim in a lawsuit) alleging that 194 | the Program itself (excluding combinations of the Program with other 195 | software or hardware) infringes such Recipient's patent(s), then such 196 | Recipient's rights granted under Section 2(b) shall terminate as of 197 | the date such litigation is filed. 198 | 199 | All Recipient's rights under this Agreement shall terminate if it 200 | fails to comply with any of the material terms or conditions of this 201 | Agreement and does not cure such failure in a reasonable period of 202 | time after becoming aware of such noncompliance. If all Recipient's 203 | rights under this Agreement terminate, Recipient agrees to cease use 204 | and distribution of the Program as soon as reasonably 205 | practicable. However, Recipient's obligations under this Agreement and 206 | any licenses granted by Recipient relating to the Program shall 207 | continue and survive. 208 | 209 | Everyone is permitted to copy and distribute copies of this Agreement, 210 | but in order to avoid inconsistency the Agreement is copyrighted and 211 | may only be modified in the following manner. The Agreement Steward 212 | reserves the right to publish new versions (including revisions) of 213 | this Agreement from time to time. No one other than the Agreement 214 | Steward has the right to modify this Agreement. The Eclipse Foundation 215 | is the initial Agreement Steward. The Eclipse Foundation may assign 216 | the responsibility to serve as the Agreement Steward to a suitable 217 | separate entity. Each new version of the Agreement will be given a 218 | distinguishing version number. The Program (including Contributions) 219 | may always be distributed subject to the version of the Agreement 220 | under which it was received. In addition, after a new version of the 221 | Agreement is published, Contributor may elect to distribute the 222 | Program (including its Contributions) under the new version. Except as 223 | expressly stated in Sections 2(a) and 2(b) above, Recipient receives 224 | no rights or licenses to the intellectual property of any Contributor 225 | under this Agreement, whether expressly, by implication, estoppel or 226 | otherwise. All rights in the Program not expressly granted under this 227 | Agreement are reserved. 228 | 229 | This Agreement is governed by the laws of the State of New York and 230 | the intellectual property laws of the United States of America. No 231 | party to this Agreement will bring a legal action under this Agreement 232 | more than one year after the cause of action arose. Each party waives 233 | its rights to a jury trial in any resulting litigation. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TMXValidator 2 | 3 | ![alt text](https://maxprograms.com/images/Red_squares.png "TMXValidator Icon") 4 | 5 | Check the validity of your TMX documents on Windows, Linux or macOS with TMXValidator. 6 | 7 | Most CAT (Computer Aided Translation) tools rely on TMX (Translation Memory eXchange) standard to exchange translation memory data. Unfortunately, some tools produce files that are not valid and others do not accept correctly formatted TMX documents. 8 | 9 | TMXValidator validates your documents against the TMX DTD and also verifies that they comply with the requirements described in the TMX specifications. 10 | 11 | TMXValidator supports TMX versions 1.1, 1.2, 1.3 and 1.4. 12 | 13 | The source code of TMXValidator was originally published on SourceForge at [https://sourceforge.net/p/tmxvalidator/code](https://sourceforge.net/p/tmxvalidator/code). 14 | 15 | The original version of TMXValidator loaded the TMX file into memory for validation. Validation of very large TMX files was limited by the amount of available memory. 16 | 17 | This version of TMXValidator does not need to load the whole file into memory and has no size limitation. 18 | 19 | ## Releases 20 | 21 | Version | Comment | Release Date 22 | --------|---------|------------- 23 | 2.7.0 | Added support for huge files | March 22, 2024 24 | 2.6.0 | Tighter checking of "x" and "i" attributes and language codes | July 4, 2023 25 | 2.5.0 | Updated code and libraries | May 22, 2023Ø 26 | 2.4.0 | Updated libraries | December 8, 2022 27 | 2.3.0 | Updated code and libraries | February 17, 2022 28 | 2.2.0 | Updated libraries and TypeScript code | January 2, 2021 29 | 2.1.0 | Added UI written in TypeScript and improved validation | February 5, 2020 30 | 2.0.2 | Switched to ant for building and updated OpenXLIFF| August 8, 2019 31 | 2.0.1 | Fixed date validation and updated libraries | June 24, 2019 32 | 2.0.0 | New version that supports validation of very large files | November 28, 2018 33 | 34 | Ready to use installers are available at [https://www.maxprograms.com/products/tmxvalidator.html](https://www.maxprograms.com/products/tmxvalidator.html) 35 | 36 | ## Requirements 37 | 38 | - JDK 21 or newer is required for compiling and building. Get it from [Adoptium](https://adoptium.net/). 39 | - Apache Ant 1.10.14 or newer. Get it from [https://ant.apache.org/](https://ant.apache.org/) 40 | - Node.js 20.11.0 LTS or newer. Get it from [https://nodejs.org/](https://nodejs.org/) 41 | - TypeScript 5.4.2 or newer. Get it from [https://www.typescriptlang.org/](https://www.typescriptlang.org/) 42 | 43 | ## Building 44 | 45 | - Checkout this repository. 46 | - Point your `JAVA_HOME` environment variable to JDK 21 47 | - Run `ant` to compile the Java code 48 | - Run `npm install` to download and install NodeJS dependencies 49 | - Run `npm start` to launch TMXValidator 50 | 51 | ``` bash 52 | git clone https://github.com/rmraya/TMXValidator.git 53 | cd TMXValidator 54 | ant 55 | npm install 56 | npm start 57 | ``` 58 | 59 | Compile once and then simply run `npm start` to start TMXValidator 60 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | Build jar file 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | Build Java binaries 42 | 43 | 44 | 45 | 46 | 47 | 48 | Move java binaries to work folder 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | Move java binaries to work folder 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | Prepare distribution 91 | 92 | 93 | -------------------------------------------------------------------------------- /css/custom.css: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2018-2019 Maxprograms. 3 | * 4 | * This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License 1.0 6 | * which accompanies this distribution, and is available at 7 | * https://www.eclipse.org/org/documents/epl-v10.html 8 | * 9 | * Contributors: 10 | * Maxprograms - initial API and implementation 11 | *******************************************************************************/ 12 | 13 | * { 14 | font-family: sans-serif; 15 | } 16 | 17 | body { 18 | margin: 0px; 19 | padding: 8px; 20 | background: #2d2d2e; 21 | } 22 | 23 | .panel { 24 | background: #2d2d2e; 25 | padding: 8px; 26 | border: none; 27 | margin: 0px; 28 | } 29 | 30 | .right { 31 | float: right; 32 | } 33 | 34 | .left { 35 | float: left; 36 | } 37 | 38 | hr { 39 | margin: 1px; 40 | } 41 | 42 | h1 { 43 | color: #cdcdcd; 44 | font-family: sans-serif; 45 | padding: 2px; 46 | font-size: 1em; 47 | font-weight: lighter; 48 | white-space: nowrap; 49 | margin: 3px; 50 | } 51 | 52 | img { 53 | border: none; 54 | background: #2d2d2e; 55 | padding: 0px; 56 | margin: 0px; 57 | } 58 | 59 | label { 60 | color: #bdbdbd; 61 | font-family: sans-serif; 62 | padding: 3px; 63 | font-size: 0.8em; 64 | white-space: nowrap; 65 | } 66 | 67 | p { 68 | color: #bdbdbd; 69 | font-family: sans-serif; 70 | font-size: 0.8em; 71 | } 72 | 73 | .black { 74 | color: black; 75 | } 76 | 77 | .dark { 78 | color: #444444; 79 | } 80 | 81 | input [type="text"] { 82 | font-family: sans-serif; 83 | font-size: 1.2em; 84 | margin-bottom: 2px; 85 | padding: 2px; 86 | width: 100%; 87 | } 88 | 89 | .fill { 90 | width: 100%; 91 | } 92 | 93 | 94 | div { 95 | margin: 0px; 96 | padding: 3px; 97 | overflow: auto; 98 | } 99 | 100 | button { 101 | background: #bebbb8; 102 | padding: 4px 8px; 103 | font-family: sans-serif; 104 | border: none; 105 | border-radius: 4px; 106 | } 107 | 108 | button.dark { 109 | background: #0d47a1; 110 | color: #eeeeee; 111 | } 112 | 113 | button.disabled { 114 | background: #979593; 115 | color: #bbbbbb; 116 | } 117 | 118 | button.primary { 119 | background: #1e88e5; 120 | color: #eeeeee; 121 | } 122 | 123 | button.primary:hover { 124 | background: #1976d2; 125 | } 126 | 127 | button.dark:hover { 128 | background: #1565c0; 129 | } 130 | 131 | button:hover { 132 | background: #979593; 133 | border: none; 134 | } 135 | 136 | table { 137 | width: 100%; 138 | border: none; 139 | margin: 0px; 140 | padding: 0px; 141 | overflow-y: auto; 142 | } 143 | 144 | .icon { 145 | height: 16px; 146 | margin-right: 5px; 147 | vertical-align: bottom; 148 | } 149 | 150 | .icon.right { 151 | height: 16px; 152 | margin-left: 5px; 153 | margin-right: 0px; 154 | vertical-align: bottom; 155 | } 156 | 157 | th { 158 | font-family: sans-serif; 159 | font-weight: bold; 160 | background: #cccccc; 161 | text-align: center; 162 | border: none; 163 | padding: 3px; 164 | vertical-align: top; 165 | } 166 | 167 | td { 168 | border: none; 169 | padding: 5px; 170 | vertical-align: middle; 171 | } 172 | 173 | .highlight { 174 | background: #f5f0d2; 175 | color: #d32f2f; 176 | padding-left: 3px; 177 | padding-right: 3px; 178 | } -------------------------------------------------------------------------------- /html/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | About... 8 | 9 | 10 | 11 | 12 |
13 |

TMXValidator
Build

14 |

15 |

Copyright © 2005-2025 Maxprograms

16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /html/main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | TMXValidator 8 | 9 | 10 | 11 | 12 |
13 |   14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 28 | 31 | 32 |
26 | 27 | 29 | 30 |
33 |
34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /icons/TMXValidator.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmraya/TMXValidator/5d15f305f9f28cc663b4f83b4a65c14e082f1cc5/icons/TMXValidator.icns -------------------------------------------------------------------------------- /icons/TMXValidator.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmraya/TMXValidator/5d15f305f9f28cc663b4f83b4a65c14e082f1cc5/icons/TMXValidator.ico -------------------------------------------------------------------------------- /icons/TMXValidator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmraya/TMXValidator/5d15f305f9f28cc663b4f83b4a65c14e082f1cc5/icons/TMXValidator.png -------------------------------------------------------------------------------- /img/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmraya/TMXValidator/5d15f305f9f28cc663b4f83b4a65c14e082f1cc5/img/info.png -------------------------------------------------------------------------------- /img/tmxvalidator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmraya/TMXValidator/5d15f305f9f28cc663b4f83b4a65c14e082f1cc5/img/tmxvalidator.png -------------------------------------------------------------------------------- /img/working.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmraya/TMXValidator/5d15f305f9f28cc663b4f83b4a65c14e082f1cc5/img/working.gif -------------------------------------------------------------------------------- /jars/bcp47j.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmraya/TMXValidator/5d15f305f9f28cc663b4f83b4a65c14e082f1cc5/jars/bcp47j.jar -------------------------------------------------------------------------------- /jars/json.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmraya/TMXValidator/5d15f305f9f28cc663b4f83b4a65c14e082f1cc5/jars/json.jar -------------------------------------------------------------------------------- /jars/xmljava.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rmraya/TMXValidator/5d15f305f9f28cc663b4f83b4a65c14e082f1cc5/jars/xmljava.jar -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tmxvalidator", 3 | "productName": "TMXValidator", 4 | "version": "2.8.0", 5 | "description": "TMX Validator", 6 | "main": "js/app.js", 7 | "scripts": { 8 | "build": "tsc", 9 | "start": "npm run build && electron ." 10 | }, 11 | "author": { 12 | "name": "Rodolfo M. Raya", 13 | "email": "rmraya@maxprograms.com", 14 | "url": "https://www.maxprograms.com" 15 | }, 16 | "license": "EPL-1.0", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/rmraya/TMXValidator.git" 20 | }, 21 | "homepage": "https://www.maxprograms.com/products/tmxvalidator.html", 22 | "devDependencies": { 23 | "electron": "^35.0.3", 24 | "typescript": "^5.8.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/com/maxprograms/server/CheckURL.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005-2025 Maxprograms. 3 | * 4 | * This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License 1.0 6 | * which accompanies this distribution, and is available at 7 | * https://www.eclipse.org/org/documents/epl-v10.html 8 | * 9 | * Contributors: 10 | * Maxprograms - initial API and implementation 11 | *******************************************************************************/ 12 | package com.maxprograms.server; 13 | 14 | import java.io.IOException; 15 | import java.lang.System.Logger; 16 | import java.lang.System.Logger.Level; 17 | import java.net.HttpURLConnection; 18 | import java.net.URI; 19 | import java.net.URISyntaxException; 20 | import java.net.URL; 21 | 22 | public class CheckURL { 23 | 24 | protected static final Logger LOGGER = System.getLogger(CheckURL.class.getName()); 25 | 26 | public static void main(String[] args) { 27 | if (args.length < 1) { 28 | return; 29 | } 30 | checkURL(args[0]); 31 | } 32 | 33 | protected static void checkURL(String string) { 34 | boolean waiting = true; 35 | int count = 0; 36 | while (waiting && count < 40) { 37 | try { 38 | connect(string); 39 | waiting = false; 40 | } catch (IOException | URISyntaxException e) { 41 | try { 42 | Thread.sleep(500); 43 | count++; 44 | } catch (InterruptedException e1) { 45 | LOGGER.log(Level.ERROR, e1.getMessage(), e1); 46 | Thread.currentThread().interrupt(); 47 | } 48 | } 49 | } 50 | if (count < 40) { 51 | LOGGER.log(Level.INFO, "ready"); 52 | } else { 53 | System.exit(1); 54 | } 55 | } 56 | 57 | private static void connect(String string) throws IOException, URISyntaxException { 58 | URL url = new URI(string).toURL(); 59 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 60 | connection.setConnectTimeout(1000); 61 | connection.connect(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/com/maxprograms/tmxvalidation/Constants.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005-2025 Maxprograms. 3 | * 4 | * This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License 1.0 6 | * which accompanies this distribution, and is available at 7 | * https://www.eclipse.org/org/documents/epl-v10.html 8 | * 9 | * Contributors: 10 | * Maxprograms - initial API and implementation 11 | *******************************************************************************/ 12 | package com.maxprograms.tmxvalidation; 13 | 14 | public class Constants { 15 | 16 | public static final String VERSION="2.8.0"; 17 | public static final String BUILD="20250324_0632"; 18 | 19 | public static final String SUCCESS = "Success"; 20 | public static final String ERROR = "Error"; 21 | public static final String RUNNING = "Running"; 22 | public static final String COMPLETED = "Completed"; 23 | 24 | private Constants() { 25 | // private for security 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/com/maxprograms/tmxvalidation/Messages.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005-2025 Maxprograms. 3 | * 4 | * This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License 1.0 6 | * which accompanies this distribution, and is available at 7 | * https://www.eclipse.org/org/documents/epl-v10.html 8 | * 9 | * Contributors: 10 | * Maxprograms - initial API and implementation 11 | *******************************************************************************/ 12 | package com.maxprograms.tmxvalidation; 13 | 14 | import java.io.IOException; 15 | import java.io.InputStream; 16 | import java.io.InputStreamReader; 17 | import java.nio.charset.StandardCharsets; 18 | import java.util.Locale; 19 | import java.util.Properties; 20 | 21 | public class Messages { 22 | 23 | private static Properties props; 24 | 25 | private Messages() { 26 | // do not instantiate this class 27 | } 28 | 29 | public static String getString(String key) { 30 | String resourceName = "tmxvalidation"; 31 | try { 32 | if (props == null) { 33 | Locale locale = Locale.getDefault(); 34 | String language = locale.getLanguage(); 35 | String extension = "_" + language + ".properties"; 36 | // check if there is a resource for full language code 37 | if (Messages.class.getResource(resourceName + extension) == null) { 38 | // if not, check if there is a resource for language only 39 | extension = "_" + language.substring(0, 2) + ".properties"; 40 | } 41 | if (Messages.class.getResource(resourceName + extension) == null) { 42 | // if not, use the default resource 43 | extension = ".properties"; 44 | } 45 | try (InputStream is = Messages.class.getResourceAsStream(resourceName + extension)) { 46 | try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8)) { 47 | props = new Properties(); 48 | props.load(reader); 49 | } 50 | } 51 | } 52 | return props.getProperty(key, '!' + key + '!'); 53 | } catch (IOException | NullPointerException e) { 54 | return '!' + key + '!'; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/com/maxprograms/tmxvalidation/TMXCopyHandler.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005-2025 Maxprograms. 3 | * 4 | * This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License 1.0 6 | * which accompanies this distribution, and is available at 7 | * https://www.eclipse.org/org/documents/epl-v10.html 8 | * 9 | * Contributors: 10 | * Maxprograms - initial API and implementation 11 | *******************************************************************************/ 12 | package com.maxprograms.tmxvalidation; 13 | 14 | import java.io.FileOutputStream; 15 | import java.io.IOException; 16 | import java.nio.charset.StandardCharsets; 17 | 18 | import org.xml.sax.Attributes; 19 | import org.xml.sax.Locator; 20 | import org.xml.sax.SAXException; 21 | 22 | import com.maxprograms.xml.Catalog; 23 | import com.maxprograms.xml.Document; 24 | import com.maxprograms.xml.IContentHandler; 25 | 26 | public class TMXCopyHandler implements IContentHandler { 27 | 28 | private FileOutputStream out; 29 | private boolean inCDATA; 30 | 31 | public TMXCopyHandler(FileOutputStream out) { 32 | this.out = out; 33 | } 34 | 35 | private void writeString(String string) throws IOException { 36 | out.write(string.getBytes(StandardCharsets.UTF_8)); 37 | } 38 | 39 | @Override 40 | public void startDTD(String name, String publicId, String systemId) throws SAXException { 41 | // do nothing 42 | } 43 | 44 | @Override 45 | public void endDTD() throws SAXException { 46 | // do nothing 47 | } 48 | 49 | @Override 50 | public void startEntity(String name) throws SAXException { 51 | // do nothing 52 | } 53 | 54 | @Override 55 | public void endEntity(String name) throws SAXException { 56 | // do nothing 57 | } 58 | 59 | @Override 60 | public void startCDATA() throws SAXException { 61 | inCDATA = true; 62 | } 63 | 64 | @Override 65 | public void endCDATA() throws SAXException { 66 | inCDATA = false; 67 | } 68 | 69 | @Override 70 | public void comment(char[] ch, int start, int length) throws SAXException { 71 | // do nothing 72 | } 73 | 74 | @Override 75 | public void setDocumentLocator(Locator locator) { 76 | // do nothing 77 | } 78 | 79 | @Override 80 | public void startDocument() throws SAXException { 81 | // do nothing 82 | } 83 | 84 | @Override 85 | public void endDocument() throws SAXException { 86 | // do nothing 87 | } 88 | 89 | @Override 90 | public void startPrefixMapping(String prefix, String uri) throws SAXException { 91 | // do nothing 92 | } 93 | 94 | @Override 95 | public void endPrefixMapping(String prefix) throws SAXException { 96 | // do nothing 97 | } 98 | 99 | @Override 100 | public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { 101 | try { 102 | writeString("<" + qName); 103 | for (int i = 0; i < atts.getLength(); i++) { 104 | writeString(" " + atts.getQName(i) + "=\"" + cleanString(atts.getValue(i)) + "\""); 105 | } 106 | writeString(">"); 107 | } catch (IOException e) { 108 | throw new SAXException(e.getMessage()); 109 | } 110 | 111 | } 112 | 113 | @Override 114 | public void endElement(String uri, String localName, String qName) throws SAXException { 115 | try { 116 | writeString(""); 117 | } catch (IOException e) { 118 | throw new SAXException(e.getMessage()); 119 | } 120 | } 121 | 122 | @Override 123 | public void characters(char[] ch, int start, int length) throws SAXException { 124 | if (!inCDATA) { 125 | try { 126 | writeString(cleanString(new String(ch, start, length))); 127 | } catch (IOException e) { 128 | throw new SAXException(e.getMessage()); 129 | } 130 | } 131 | } 132 | 133 | private static String cleanString(String string) { 134 | String result = string.replace("&", "&"); 135 | result = result.replace("<", "<"); 136 | result = result.replace(">", ">"); 137 | result = result.replace("\"", """); 138 | return result; 139 | } 140 | 141 | @Override 142 | public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { 143 | // do nothing 144 | } 145 | 146 | @Override 147 | public void processingInstruction(String target, String data) throws SAXException { 148 | // do nothing 149 | } 150 | 151 | @Override 152 | public void skippedEntity(String name) throws SAXException { 153 | // do nothing 154 | } 155 | 156 | @Override 157 | public Document getDocument() { 158 | return null; 159 | } 160 | 161 | @Override 162 | public void setCatalog(Catalog arg0) { 163 | // do nothing 164 | } 165 | 166 | } 167 | -------------------------------------------------------------------------------- /src/com/maxprograms/tmxvalidation/TMXResolver.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005-2025 Maxprograms. 3 | * 4 | * This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License 1.0 6 | * which accompanies this distribution, and is available at 7 | * https://www.eclipse.org/org/documents/epl-v10.html 8 | * 9 | * Contributors: 10 | * Maxprograms - initial API and implementation 11 | *******************************************************************************/ 12 | package com.maxprograms.tmxvalidation; 13 | 14 | import java.io.IOException; 15 | import java.net.URL; 16 | 17 | import org.xml.sax.EntityResolver; 18 | import org.xml.sax.InputSource; 19 | import org.xml.sax.SAXException; 20 | 21 | public class TMXResolver implements EntityResolver { 22 | 23 | @Override 24 | public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { 25 | 26 | if (publicId != null) { 27 | if (publicId.equals("-//LISA OSCAR:1998//DTD for Translation Memory eXchange//EN")) { 28 | URL url = TMXResolver.class.getResource("tmx14.dtd"); 29 | return new InputSource(url.openStream()); 30 | } 31 | if (publicId.equals("http://www.lisa.org/tmx14")) { 32 | URL url = TMXResolver.class.getResource("tmx14.dtd"); 33 | return new InputSource(url.openStream()); 34 | } 35 | if (publicId.equals("http://www.lisa.org/tmx")) { 36 | URL url = TMXResolver.class.getResource("tmx13.dtd"); 37 | return new InputSource(url.openStream()); 38 | } 39 | } 40 | if (systemId != null) { 41 | if (systemId.toLowerCase().endsWith("tmx14.dtd")) { 42 | URL url = TMXResolver.class.getResource("tmx14.dtd"); 43 | return new InputSource(url.openStream()); 44 | } 45 | if (systemId.toLowerCase().endsWith("tmx13.dtd")) { 46 | URL url = TMXResolver.class.getResource("tmx13.dtd"); 47 | return new InputSource(url.openStream()); 48 | } 49 | if (systemId.toLowerCase().endsWith("tmx12.dtd")) { 50 | URL url = TMXResolver.class.getResource("tmx12.dtd"); 51 | return new InputSource(url.openStream()); 52 | } 53 | if (systemId.toLowerCase().endsWith("tmx11.dtd")) { 54 | URL url = TMXResolver.class.getResource("tmx11.dtd"); 55 | return new InputSource(url.openStream()); 56 | } 57 | } 58 | return null; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/com/maxprograms/tmxvalidation/TMXValidatingHandler.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005-2025 Maxprograms. 3 | * 4 | * This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License 1.0 6 | * which accompanies this distribution, and is available at 7 | * https://www.eclipse.org/org/documents/epl-v10.html 8 | * 9 | * Contributors: 10 | * Maxprograms - initial API and implementation 11 | *******************************************************************************/ 12 | package com.maxprograms.tmxvalidation; 13 | 14 | import java.io.IOException; 15 | import java.lang.System.Logger; 16 | import java.lang.System.Logger.Level; 17 | import java.text.MessageFormat; 18 | import java.util.Enumeration; 19 | import java.util.HashSet; 20 | import java.util.Hashtable; 21 | import java.util.Iterator; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.Set; 25 | import java.util.Stack; 26 | 27 | import javax.xml.parsers.ParserConfigurationException; 28 | 29 | import org.xml.sax.Attributes; 30 | import org.xml.sax.Locator; 31 | import org.xml.sax.SAXException; 32 | 33 | import com.maxprograms.languages.Language; 34 | import com.maxprograms.languages.LanguageUtils; 35 | import com.maxprograms.xml.Attribute; 36 | import com.maxprograms.xml.Catalog; 37 | import com.maxprograms.xml.Document; 38 | import com.maxprograms.xml.Element; 39 | import com.maxprograms.xml.IContentHandler; 40 | 41 | public class TMXValidatingHandler implements IContentHandler { 42 | 43 | public static final String RELOAD = Messages.getString("TMXValidatingHandler.0"); 44 | private Element current; 45 | Stack stack; 46 | Map> xMap; 47 | private boolean inCDATA = false; 48 | private String srcLang; 49 | private Element root; 50 | 51 | private static final Logger LOGGER = System.getLogger(TMXValidatingHandler.class.getName()); 52 | 53 | private Hashtable ids; 54 | private int balance; 55 | private String version; 56 | private String publicId; 57 | private String systemId; 58 | private String currentLang; 59 | 60 | public TMXValidatingHandler() { 61 | stack = new Stack<>(); 62 | } 63 | 64 | @Override 65 | public void startDTD(String name, String publicId1, String systemId1) throws SAXException { 66 | this.publicId = publicId1; 67 | this.systemId = systemId1; 68 | } 69 | 70 | @Override 71 | public void endDTD() throws SAXException { 72 | // do nothing 73 | } 74 | 75 | @Override 76 | public void startEntity(String name) throws SAXException { 77 | // do nothing, let the EntityResolver handle this 78 | } 79 | 80 | @Override 81 | public void endEntity(String name) throws SAXException { 82 | // do nothing, let the EntityResolver handle this 83 | } 84 | 85 | @Override 86 | public void startCDATA() throws SAXException { 87 | inCDATA = true; 88 | } 89 | 90 | @Override 91 | public void endCDATA() throws SAXException { 92 | inCDATA = false; 93 | } 94 | 95 | @Override 96 | public void comment(char[] ch, int start, int length) throws SAXException { 97 | // do nothing 98 | } 99 | 100 | @Override 101 | public void setDocumentLocator(Locator locator) { 102 | // do nothing 103 | } 104 | 105 | @Override 106 | public void startDocument() throws SAXException { 107 | // do nothing 108 | } 109 | 110 | @Override 111 | public void endDocument() throws SAXException { 112 | stack.clear(); 113 | } 114 | 115 | @Override 116 | public void startPrefixMapping(String prefix, String uri) throws SAXException { 117 | // do nothing 118 | } 119 | 120 | @Override 121 | public void endPrefixMapping(String prefix) throws SAXException { 122 | // do nothing 123 | } 124 | 125 | @Override 126 | public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { 127 | if (current == null) { 128 | current = new Element(qName); 129 | stack.push(current); 130 | } else { 131 | Element child = new Element(qName); 132 | current.addContent(child); 133 | stack.push(current); 134 | current = child; 135 | } 136 | for (int i = 0; i < atts.getLength(); i++) { 137 | current.setAttribute(atts.getQName(i), atts.getValue(i)); 138 | } 139 | if (root == null) { 140 | if (qName.equals("tmx")) { 141 | root = current; 142 | version = root.getAttributeValue("version"); 143 | if (version.isEmpty()) { 144 | throw new SAXException(Messages.getString("TMXValidatingHandler.1")); 145 | } 146 | if (!(version.equals("1.1") || version.equals("1.2") || version.equals("1.3") 147 | || version.equals("1.4"))) { 148 | MessageFormat mf = new MessageFormat(Messages.getString("TMXValidatingHandler.2")); 149 | throw new SAXException(mf.format(new Object[] { version })); 150 | } 151 | if (systemId == null && publicId == null) { 152 | throw new SAXException(RELOAD); 153 | } 154 | } else { 155 | throw new SAXException(Messages.getString("TMXValidatingHandler.3")); 156 | } 157 | } 158 | if (qName.equals("header")) { 159 | srcLang = current.getAttributeValue("srclang"); 160 | if (srcLang.isEmpty()) { 161 | throw new SAXException(Messages.getString("TMXValidatingHandler.4")); 162 | } 163 | if (!srcLang.equals("*all*")) { 164 | try { 165 | if (!checkLang(srcLang)) { 166 | MessageFormat mf = new MessageFormat(Messages.getString("TMXValidatingHandler.5")); 167 | throw new SAXException(mf.format(new Object[] { srcLang })); 168 | } 169 | } catch (IOException | ParserConfigurationException e) { 170 | LOGGER.log(Level.ERROR, Messages.getString("TMXValidatingHandler.6"), e); 171 | throw new SAXException(Messages.getString("TMXValidatingHandler.6")); 172 | } 173 | } 174 | } 175 | if ("tu".equals(qName)) { 176 | xMap = new Hashtable<>(); 177 | } 178 | if ("tuv".equals(qName)) { 179 | currentLang = current.hasAttribute("xml:lang") ? current.getAttributeValue("xml:lang") 180 | : current.getAttributeValue("lang"); 181 | } 182 | if (current.hasAttribute("x") 183 | && ("bpt".equals(qName) || "it".equals(qName) || "ph".equals(qName) || "hi".equals(qName))) { 184 | String x = current.getAttributeValue("x"); 185 | if (!isNumber(x)) { 186 | MessageFormat mf = new MessageFormat(Messages.getString("TMXValidatingHandler.7")); 187 | throw new SAXException(mf.format(new Object[] { x })); 188 | } 189 | Set set = xMap.get(currentLang); 190 | if (set == null) { 191 | set = new HashSet<>(); 192 | xMap.put(currentLang, set); 193 | } 194 | if (set.contains(qName + x)) { 195 | MessageFormat mf = new MessageFormat(Messages.getString("TMXValidatingHandler.8")); 196 | throw new SAXException(mf.format(new Object[] { x, qName })); 197 | } 198 | set.add(qName + x); 199 | } 200 | } 201 | 202 | private boolean isNumber(String s) { 203 | try { 204 | Double.parseDouble(s); 205 | return true; 206 | } catch (NumberFormatException e) { 207 | return false; 208 | } 209 | } 210 | 211 | @Override 212 | public void endElement(String uri, String localName, String qName) throws SAXException { 213 | if (current == null) { 214 | return; 215 | } 216 | List attributes = current.getAttributes(); 217 | Iterator i = attributes.iterator(); 218 | while (i.hasNext()) { 219 | Attribute a = i.next(); 220 | String name = a.getName(); 221 | String value = a.getValue(); 222 | if (name.equals("lang") || name.equals("adminlang") || name.equals("xml:lang")) { 223 | try { 224 | if (!checkLang(value)) { 225 | MessageFormat mf = new MessageFormat(Messages.getString("TMXValidatingHandler.9")); 226 | throw new SAXException(mf.format(new Object[] { value })); 227 | } 228 | } catch (IOException | ParserConfigurationException e) { 229 | LOGGER.log(Level.ERROR, Messages.getString("TMXValidatingHandler.10"), e); 230 | throw new SAXException(Messages.getString("TMXValidatingHandler.10")); 231 | } 232 | } 233 | if (name.equals("usagecount") && !isNumber(value)) { 234 | MessageFormat mf = new MessageFormat(Messages.getString("TMXValidatingHandler.11")); 235 | throw new SAXException(mf.format(new Object[] { value })); 236 | } 237 | if ((name.equals("lastusagedate") || name.equals("changedate") || name.equals("creationdate")) 238 | && !checkDate(value)) { 239 | MessageFormat mf = new MessageFormat(Messages.getString("TMXValidatingHandler.12")); 240 | throw new SAXException(mf.format(new Object[] { value })); 241 | } 242 | } 243 | if (current.getName().equals("seg")) { 244 | balance = 0; 245 | ids = null; 246 | ids = new Hashtable<>(); 247 | recurse(current); 248 | if (balance != 0) { 249 | MessageFormat mf = new MessageFormat(Messages.getString("TMXValidatingHandler.13")); 250 | throw new SAXException(mf.format(new String[] { current.toString() })); 251 | } 252 | if (ids.size() > 0) { 253 | Enumeration en = ids.keys(); 254 | while (en.hasMoreElements()) { 255 | if (!ids.get(en.nextElement()).equals("0")) { 256 | MessageFormat mf = new MessageFormat(Messages.getString("TMXValidatingHandler.14")); 257 | throw new SAXException(mf.format(new String[] { current.toString() })); 258 | } 259 | } 260 | } 261 | } 262 | if (localName.equals("tu")) { 263 | if (!srcLang.equals("*all*")) { 264 | checkLanguageVariants(current); 265 | } 266 | Set xKeys = xMap.keySet(); 267 | if (!xKeys.isEmpty()) { 268 | if (current.getChildren("tuv").size() != xKeys.size()) { 269 | throw new SAXException(Messages.getString("TMXValidatingHandler.15")); 270 | } 271 | Set xValues = xMap.get(currentLang); 272 | Iterator it = xKeys.iterator(); 273 | while (it.hasNext()) { 274 | String key = it.next(); 275 | Set langSet = xMap.get(key); 276 | if (langSet.size() != xValues.size() || !langSet.containsAll(xValues)) { 277 | throw new SAXException(Messages.getString("TMXValidatingHandler.16")); 278 | } 279 | } 280 | } 281 | current = null; 282 | stack.clear(); 283 | } 284 | if (!stack.isEmpty()) { 285 | current = stack.pop(); 286 | } 287 | } 288 | 289 | private void checkLanguageVariants(Element tu) throws SAXException { 290 | List variants = tu.getChildren("tuv"); 291 | Iterator it = variants.iterator(); 292 | boolean found = false; 293 | while (it.hasNext() && !found) { 294 | Element tuv = it.next(); 295 | String lang = tuv.getAttributeValue("xml:lang"); 296 | if (lang.isEmpty() && (version.equals("1.1") || version.equals("1.2"))) { 297 | lang = tuv.getAttributeValue("lang"); 298 | } 299 | if (lang.isEmpty()) { 300 | throw new SAXException(Messages.getString("TMXValidatingHandler.17")); 301 | } 302 | if (lang.equals(srcLang)) { 303 | found = true; 304 | } 305 | } 306 | if (!found) { 307 | MessageFormat mf = new MessageFormat(Messages.getString("TMXValidatingHandler.18")); 308 | throw new SAXException(mf.format(new Object[] { srcLang })); 309 | } 310 | } 311 | 312 | @Override 313 | public void characters(char[] ch, int start, int length) throws SAXException { 314 | if (!inCDATA && current != null) { 315 | current.addContent(new String(ch, start, length)); 316 | } 317 | } 318 | 319 | @Override 320 | public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { 321 | // do nothing 322 | } 323 | 324 | @Override 325 | public void processingInstruction(String target, String data) throws SAXException { 326 | // do nothing 327 | } 328 | 329 | @Override 330 | public void skippedEntity(String name) throws SAXException { 331 | // do nothing 332 | } 333 | 334 | private boolean checkLang(String lang) throws IOException, SAXException, ParserConfigurationException { 335 | if (lang.startsWith("x-") || lang.startsWith("X-")) { 336 | // custom language code 337 | return true; 338 | } 339 | Language language = LanguageUtils.getLanguage(lang); 340 | if (language == null) { 341 | return false; 342 | } 343 | return language.getCode().equals(lang); 344 | } 345 | 346 | private static boolean checkDate(String date) { 347 | // YYYYMMDDThhmmssZ 348 | if (date.length() != 16) { 349 | return false; 350 | } 351 | if (date.charAt(8) != 'T') { 352 | return false; 353 | } 354 | if (date.charAt(15) != 'Z') { 355 | return false; 356 | } 357 | try { 358 | int year = Integer.parseInt("" + date.charAt(0) + date.charAt(1) + date.charAt(2) + date.charAt(3)); 359 | if (year < 0) { 360 | return false; 361 | } 362 | int month = Integer.parseInt("" + date.charAt(4) + date.charAt(5)); 363 | if (month < 1 || month > 12) { 364 | return false; 365 | } 366 | int day = Integer.parseInt("" + date.charAt(6) + date.charAt(7)); 367 | switch (month) { 368 | case 1, 3, 5, 7, 8, 10, 12 -> { 369 | if (day < 1 || day > 31) { 370 | return false; 371 | } 372 | } 373 | case 4, 6, 9, 11 -> { 374 | if (day < 1 || day > 30) { 375 | return false; 376 | } 377 | } 378 | case 2 -> { 379 | // check for leap years 380 | if (year % 4 == 0) { 381 | if (year % 100 == 0) { 382 | // not all centuries are leap years 383 | if (year % 400 == 0) { 384 | if (day < 1 || day > 29) { 385 | return false; 386 | } 387 | } else { 388 | // not leap year 389 | if (day < 1 || day > 28) { 390 | return false; 391 | } 392 | } 393 | } 394 | if (day < 1 || day > 29) { 395 | return false; 396 | } 397 | } else if (day < 1 || day > 28) { 398 | return false; 399 | } 400 | } 401 | default -> { 402 | return false; 403 | } 404 | } 405 | int hour = Integer.parseInt("" + date.charAt(9) + date.charAt(10)); 406 | if (hour < 0 || hour > 23) { 407 | return false; 408 | } 409 | int min = Integer.parseInt("" + date.charAt(11) + date.charAt(12)); 410 | if (min < 0 || min > 59) { 411 | return false; 412 | } 413 | int sec = Integer.parseInt("" + date.charAt(13) + date.charAt(14)); 414 | if (sec < 0 || sec > 59) { 415 | return false; 416 | } 417 | } catch (NumberFormatException e) { 418 | return false; 419 | } 420 | return true; 421 | } 422 | 423 | private void recurse(Element element) throws SAXException { 424 | List children = element.getChildren(); 425 | Iterator it = children.iterator(); 426 | while (it.hasNext()) { 427 | Element e = it.next(); 428 | if (e.getName().equals("bpt")) { 429 | balance += 1; 430 | if (version.equals("1.4")) { 431 | String s = e.getAttributeValue("i"); 432 | if (!isNumber(s)) { 433 | MessageFormat mf = new MessageFormat(Messages.getString("TMXValidatingHandler.19")); 434 | throw new SAXException( mf.format(new String[] { element.toString() })); 435 | } 436 | if (!ids.containsKey(s)) { 437 | ids.put(s, "1"); 438 | } else { 439 | if (ids.get(s).equals("-1")) { 440 | ids.put(s, "0"); 441 | } else { 442 | MessageFormat mf = new MessageFormat(Messages.getString("TMXValidatingHandler.20")); 443 | throw new SAXException(mf.format(new String[] { element.toString() })); 444 | } 445 | } 446 | } 447 | } 448 | if (e.getName().equals("ept")) { 449 | balance -= 1; 450 | if (version.equals("1.4")) { 451 | String s = e.getAttributeValue("i"); 452 | if (!isNumber(s)) { 453 | MessageFormat mf = new MessageFormat(Messages.getString("TMXValidatingHandler.21")); 454 | throw new SAXException(mf.format(new String[] { element.toString() })); 455 | } 456 | if (!ids.containsKey(s)) { 457 | ids.put(s, "-1"); 458 | } else { 459 | if (ids.get(s).equals("1")) { 460 | ids.put(s, "0"); 461 | } else { 462 | MessageFormat mf = new MessageFormat(Messages.getString("TMXValidatingHandler.22")); 463 | throw new SAXException(mf.format(new String[] { element.toString() })); 464 | } 465 | } 466 | } 467 | } 468 | recurse(e); 469 | } 470 | } 471 | 472 | @Override 473 | public Document getDocument() { 474 | return null; 475 | } 476 | 477 | public String getVersion() { 478 | return version; 479 | } 480 | 481 | @Override 482 | public void setCatalog(Catalog arg0) { 483 | // do nothing 484 | } 485 | } 486 | -------------------------------------------------------------------------------- /src/com/maxprograms/tmxvalidation/TMXValidator.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005-2025 Maxprograms. 3 | * 4 | * This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License 1.0 6 | * which accompanies this distribution, and is available at 7 | * https://www.eclipse.org/org/documents/epl-v10.html 8 | * 9 | * Contributors: 10 | * Maxprograms - initial API and implementation 11 | *******************************************************************************/ 12 | package com.maxprograms.tmxvalidation; 13 | 14 | import java.io.File; 15 | import java.io.FileOutputStream; 16 | import java.io.IOException; 17 | import java.lang.System.Logger; 18 | import java.lang.System.Logger.Level; 19 | import java.nio.charset.StandardCharsets; 20 | import java.text.MessageFormat; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | import javax.xml.parsers.ParserConfigurationException; 25 | 26 | import org.xml.sax.SAXException; 27 | 28 | import com.maxprograms.xml.CustomErrorHandler; 29 | import com.maxprograms.xml.SAXBuilder; 30 | 31 | public class TMXValidator { 32 | 33 | private static final Logger LOGGER = System.getLogger(TMXValidator.class.getName()); 34 | 35 | private SAXBuilder builder; 36 | private TMXValidatingHandler handler; 37 | private TMXResolver resolver; 38 | 39 | public TMXValidator() { 40 | handler = new TMXValidatingHandler(); 41 | resolver = new TMXResolver(); 42 | builder = new SAXBuilder(); 43 | builder.setValidating(true); 44 | builder.setContentHandler(handler); 45 | builder.setEntityResolver(resolver); 46 | builder.setErrorHandler(new CustomErrorHandler()); 47 | } 48 | 49 | public void validate(File file) throws IOException, SAXException, ParserConfigurationException { 50 | try { 51 | builder.build(file); 52 | } catch (SAXException sax) { 53 | if (sax.getMessage().equals(TMXValidatingHandler.RELOAD)) { 54 | // TMX DTD was not declared 55 | String version = handler.getVersion(); 56 | File copy = File.createTempFile("copy", ".tmx"); 57 | copy.deleteOnExit(); 58 | copyFile(file, copy, version); 59 | builder.build(copy); 60 | } else { 61 | throw sax; 62 | } 63 | } 64 | } 65 | 66 | private static void copyFile(File file, File copy, String version) throws IOException, SAXException, ParserConfigurationException { 67 | String systemID = "tmx14.dtd"; 68 | if (version.equals("1.3")) { 69 | systemID = "tmx13.dtd"; 70 | } else if (version.equals("1.2")) { 71 | systemID = "tmx12.dtd"; 72 | } else if (version.equals("1.1")) { 73 | systemID = "tmx11.dtd"; 74 | } 75 | try (FileOutputStream out = new FileOutputStream(copy)) { 76 | writeString(out, "\n"); 77 | writeString(out, "\n"); 78 | SAXBuilder copyBuilder = new SAXBuilder(); 79 | TMXCopyHandler copyHandler = new TMXCopyHandler(out); 80 | copyBuilder.setContentHandler(copyHandler); 81 | copyBuilder.build(file); 82 | } 83 | } 84 | 85 | private static void writeString(FileOutputStream out, String string) throws IOException { 86 | out.write(string.getBytes(StandardCharsets.UTF_8)); 87 | } 88 | 89 | public static void main(String[] args) { 90 | String[] commandLine = fixPath(args); 91 | String tmx = ""; 92 | for (int i = 0; i < commandLine.length; i++) { 93 | String arg = commandLine[i]; 94 | if (arg.equals("-version")) { 95 | MessageFormat mf = new MessageFormat(Messages.getString("TMXValidator.0") ); 96 | LOGGER.log(Level.INFO, () -> mf.format(new String[] {Constants.VERSION, Constants.BUILD})); 97 | return; 98 | } 99 | if (arg.equals("-help")) { 100 | help(); 101 | return; 102 | } 103 | if (arg.equals("-tmx") && (i + 1) < commandLine.length) { 104 | tmx = commandLine[i + 1]; 105 | } 106 | } 107 | if (tmx.isEmpty()) { 108 | help(); 109 | return; 110 | } 111 | try { 112 | TMXValidator validator = new TMXValidator(); 113 | validator.validate(new File(tmx)); 114 | LOGGER.log(Level.INFO, Messages.getString("TMXValidator.1")); 115 | } catch (IOException | SAXException | ParserConfigurationException e) { 116 | LOGGER.log(Level.ERROR, e.getMessage()); 117 | } 118 | } 119 | 120 | private static void help() { 121 | String launcher = "tmxvalidator.sh"; 122 | if (System.getProperty("file.separator").equals("\\")) { 123 | launcher = "tmxvalidator.bat"; 124 | } 125 | MessageFormat mf = new MessageFormat(Messages.getString("TMXValidator.2")); 126 | System.out.println(mf.format(new String[] {launcher})); 127 | } 128 | 129 | private static String[] fixPath(String[] args) { 130 | List result = new ArrayList<>(); 131 | StringBuilder current = new StringBuilder(); 132 | for (int i = 0; i < args.length; i++) { 133 | String arg = args[i]; 134 | if (arg.startsWith("-")) { 135 | if (current.length() > 0) { 136 | result.add(current.toString().trim()); 137 | current = new StringBuilder(); 138 | } 139 | result.add(arg); 140 | } else { 141 | current.append(' '); 142 | current.append(arg); 143 | } 144 | } 145 | if (current.length() > 0) { 146 | result.add(current.toString().trim()); 147 | } 148 | return result.toArray(new String[result.size()]); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/com/maxprograms/tmxvalidation/ValidationServer.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005-2025 Maxprograms. 3 | * 4 | * This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License 1.0 6 | * which accompanies this distribution, and is available at 7 | * https://www.eclipse.org/org/documents/epl-v10.html 8 | * 9 | * Contributors: 10 | * Maxprograms - initial API and implementation 11 | *******************************************************************************/ 12 | package com.maxprograms.tmxvalidation; 13 | 14 | import java.io.BufferedReader; 15 | import java.io.File; 16 | import java.io.IOException; 17 | import java.io.InputStream; 18 | import java.io.InputStreamReader; 19 | import java.io.OutputStream; 20 | import java.lang.System.Logger; 21 | import java.lang.System.Logger.Level; 22 | import java.net.InetSocketAddress; 23 | import java.nio.charset.StandardCharsets; 24 | import java.text.MessageFormat; 25 | import java.util.Hashtable; 26 | import java.util.Map; 27 | 28 | import javax.xml.parsers.ParserConfigurationException; 29 | 30 | import com.sun.net.httpserver.HttpExchange; 31 | import com.sun.net.httpserver.HttpHandler; 32 | import com.sun.net.httpserver.HttpServer; 33 | 34 | import org.json.JSONException; 35 | import org.json.JSONObject; 36 | import org.xml.sax.SAXException; 37 | 38 | public class ValidationServer implements HttpHandler { 39 | 40 | private static final Logger LOGGER = System.getLogger(ValidationServer.class.getName()); 41 | 42 | private HttpServer server; 43 | private Map running; 44 | private Map validationResults; 45 | 46 | public ValidationServer(int port) throws IOException { 47 | running = new Hashtable<>(); 48 | validationResults = new Hashtable<>(); 49 | server = HttpServer.create(new InetSocketAddress(port), 0); 50 | server.createContext("/ValidationServer", this); 51 | server.setExecutor(null); // creates a default executor 52 | } 53 | 54 | public static void main(String[] args) { 55 | String port = "8010"; 56 | for (int i = 0; i < args.length; i++) { 57 | String arg = args[i]; 58 | if (arg.equals("-version")) { 59 | MessageFormat mf = new MessageFormat(Messages.getString("ValidationServer.0") ); 60 | LOGGER.log(Level.INFO, () -> mf.format(new String[] {Constants.VERSION, Constants.BUILD})); 61 | return; 62 | } 63 | if (arg.equals("-port") && (i + 1) < args.length) { 64 | port = args[i + 1]; 65 | } 66 | } 67 | try { 68 | ValidationServer instance = new ValidationServer(Integer.valueOf(port)); 69 | instance.run(); 70 | } catch (Exception e) { 71 | LOGGER.log(Level.ERROR, e); 72 | } 73 | } 74 | 75 | private void run() { 76 | server.start(); 77 | LOGGER.log(Level.INFO, Messages.getString("ValidationServer.1")); 78 | } 79 | 80 | @Override 81 | public void handle(HttpExchange t) throws IOException { 82 | JSONObject json = null; 83 | 84 | String response = ""; 85 | String command = "version"; 86 | try { 87 | try (InputStream is = t.getRequestBody()) { 88 | json = readRequestBody(is); 89 | } 90 | if (json.has("command")) { 91 | command = json.getString("command"); 92 | if ("version".equals(command)) { 93 | JSONObject result = new JSONObject(); 94 | result.put("version", Constants.VERSION); 95 | result.put("build", Constants.BUILD); 96 | response = result.toString(); 97 | } else if ("validate".equals(command)) { 98 | response = validate(json); 99 | } else if ("status".equals(command)) { 100 | response = getStatus(json); 101 | } else if ("validationResult".equals(command)) { 102 | response = getValidationResult(json); 103 | } else { 104 | response = "{\"reason\":\"" + Messages.getString("ValidationServer.2") + "\"}"; 105 | } 106 | } else { 107 | response = "{\"reason\":\"" + Messages.getString("ValidationServer.3") + "\"}"; 108 | } 109 | t.getResponseHeaders().add("content-type", "application/json; charset=utf-8"); 110 | byte[] bytes = response.getBytes(StandardCharsets.UTF_8); 111 | t.sendResponseHeaders(200, bytes.length); 112 | try (OutputStream os = t.getResponseBody()) { 113 | os.write(bytes); 114 | } 115 | } catch (IOException | JSONException e) { 116 | response = e.getMessage(); 117 | t.sendResponseHeaders(500, response.length()); 118 | try (OutputStream os = t.getResponseBody()) { 119 | os.write(response.getBytes()); 120 | } 121 | } 122 | } 123 | 124 | private static JSONObject readRequestBody(InputStream is) throws IOException, JSONException { 125 | StringBuilder request = new StringBuilder(); 126 | try (BufferedReader rd = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { 127 | String line; 128 | while ((line = rd.readLine()) != null) { 129 | if (!request.isEmpty()) { 130 | request.append('\n'); 131 | } 132 | request.append(line); 133 | } 134 | } 135 | return new JSONObject(request.toString()); 136 | } 137 | 138 | private String getStatus(JSONObject json) { 139 | JSONObject result = new JSONObject(); 140 | if (!json.has("process")) { 141 | result.put("status", Constants.ERROR); 142 | result.put("reason", Messages.getString("ValidationServer.4")); 143 | return result.toString(); 144 | } 145 | String process = json.getString("process"); 146 | String status = running.get(process); 147 | if (status != null) { 148 | result.put("status", status); 149 | } else { 150 | result.put("status", Constants.ERROR); 151 | result.put("reason", Messages.getString("ValidationServer.5")); 152 | } 153 | return result.toString(); 154 | } 155 | 156 | private String getValidationResult(JSONObject json) { 157 | JSONObject result = new JSONObject(); 158 | if (!json.has("process")) { 159 | result.put("status", Constants.ERROR); 160 | result.put("reason", Messages.getString("ValidationServer.4")); 161 | return result.toString(); 162 | } 163 | String process = json.getString("process"); 164 | if (validationResults.containsKey(process)) { 165 | result = validationResults.get(process); 166 | validationResults.remove(process); 167 | return result.toString(); 168 | } 169 | result.put("status", Constants.ERROR); 170 | result.put("reason", Messages.getString("ValidationServer.6")); 171 | return result.toString(); 172 | } 173 | 174 | private String validate(JSONObject json) { 175 | JSONObject result = new JSONObject(); 176 | if (!json.has("file")) { 177 | result.put("status", Constants.ERROR); 178 | result.put("reason", Messages.getString("ValidationServer.7")); 179 | return result.toString(); 180 | } 181 | String file = json.getString("file"); 182 | String process = "" + System.currentTimeMillis(); 183 | new Thread(new Runnable() { 184 | 185 | @Override 186 | public void run() { 187 | running.put(process, Constants.RUNNING); 188 | JSONObject result = new JSONObject(); 189 | TMXValidator validator = new TMXValidator(); 190 | try { 191 | validator.validate(new File(file)); 192 | result.put("valid", true); 193 | result.put("comment", Messages.getString("ValidationServer.8")); 194 | } catch (IOException | SAXException | ParserConfigurationException e) { 195 | result.put("valid", false); 196 | String reason = e.getMessage(); 197 | if (reason.indexOf('\n') != -1) { 198 | reason = reason.substring(0, reason.indexOf('\n')); 199 | } 200 | result.put("reason", reason); 201 | } 202 | validationResults.put(process, result); 203 | if (running.get(process).equals(Constants.RUNNING)) { 204 | running.put(process, Constants.COMPLETED); 205 | } 206 | } 207 | }).start(); 208 | result.put("status", Constants.SUCCESS); 209 | result.put("process", process); 210 | return result.toString(); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/com/maxprograms/tmxvalidation/tmx11.dtd: -------------------------------------------------------------------------------- 1 | 13 | 14 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 90 | 91 | 92 | 93 | 94 | 96 | 97 | 98 | 99 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 122 | 123 | 124 | 125 | 126 | 129 | 131 | 132 | 133 | 134 | 139 | 140 | 141 | 142 | 146 | 147 | 148 | 149 | 164 | 165 | 166 | 167 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 192 | 193 | 194 | 196 | 197 | 198 | 199 | 200 | 204 | 205 | 206 | 210 | 211 | 212 | 214 | 215 | 216 | 217 | -------------------------------------------------------------------------------- /src/com/maxprograms/tmxvalidation/tmx12.dtd: -------------------------------------------------------------------------------- 1 | 13 | 14 | 33 | 34 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 111 | 112 | 113 | 114 | 115 | 117 | 118 | 119 | 120 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 143 | 144 | 145 | 146 | 147 | 150 | 152 | 153 | 154 | 155 | 160 | 161 | 162 | 163 | 167 | 168 | 169 | 170 | 185 | 186 | 187 | 188 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 213 | 214 | 215 | 217 | 218 | 219 | 222 | 223 | 224 | 228 | 229 | 230 | 234 | 235 | 236 | 238 | 239 | 240 | 243 | 244 | 245 | 246 | 247 | -------------------------------------------------------------------------------- /src/com/maxprograms/tmxvalidation/tmx13.dtd: -------------------------------------------------------------------------------- 1 | 13 | 14 | 27 | 28 | 47 | 48 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 125 | 126 | 127 | 128 | 129 | 131 | 132 | 133 | 134 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 158 | 159 | 160 | 161 | 162 | 165 | 167 | 168 | 169 | 170 | 175 | 176 | 177 | 178 | 182 | 183 | 184 | 185 | 200 | 201 | 202 | 203 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 230 | 231 | 232 | 234 | 235 | 236 | 239 | 240 | 241 | 245 | 246 | 247 | 251 | 252 | 253 | 255 | 256 | 257 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /src/com/maxprograms/tmxvalidation/tmx14.dtd: -------------------------------------------------------------------------------- 1 | 13 | 14 | 25 | 26 | 39 | 40 | 59 | 60 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 137 | 138 | 139 | 140 | 141 | 143 | 144 | 145 | 146 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 170 | 171 | 172 | 173 | 174 | 177 | 179 | 180 | 181 | 182 | 187 | 188 | 189 | 190 | 195 | 196 | 197 | 198 | 199 | 200 | 215 | 216 | 217 | 218 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 245 | 246 | 247 | 249 | 250 | 251 | 254 | 255 | 256 | 260 | 261 | 262 | 266 | 267 | 268 | 271 | 272 | 273 | 274 | 275 | 276 | 278 | 279 | 280 | 281 | 282 | 283 | -------------------------------------------------------------------------------- /src/com/maxprograms/tmxvalidation/tmxvalidation.properties: -------------------------------------------------------------------------------- 1 | TMXValidatingHandler.0=Reload with DTD 2 | TMXValidatingHandler.1=TMX version is missing 3 | TMXValidatingHandler.10=Error validating language 4 | TMXValidatingHandler.11=Invalid value for "usagecount": ''{0}'' 5 | TMXValidatingHandler.12=Invalid date format ''{0}'' 6 | TMXValidatingHandler.13=Unbalanced number of / elements\n\n{0} 7 | TMXValidatingHandler.14=/ element without matching /\n\n{0} 8 | TMXValidatingHandler.15=Incorrect "x" matching 9 | TMXValidatingHandler.16=Incorrect "x" matching 10 | TMXValidatingHandler.17= without language attribute 11 | TMXValidatingHandler.18= element lacks with language set to ''{0}'' 12 | TMXValidatingHandler.19=Invalid value for attribute 'i' in a element\n\n{0} 13 | TMXValidatingHandler.2=Incorrect TMX version: {0} 14 | TMXValidatingHandler.20=Duplicated value for attribute 'i' in a element\n\n{0} 15 | TMXValidatingHandler.21=Invalid value for attribute 'i' in a element\n\n{0} 16 | TMXValidatingHandler.22=Mismatched value for attribute 'i' in a / element\n\n{0} 17 | TMXValidatingHandler.3=Selected file is not a TMX document 18 | TMXValidatingHandler.4=Source language not declared 19 | TMXValidatingHandler.5=Invalid source language ''{0}'' 20 | TMXValidatingHandler.6=Error validating source language 21 | TMXValidatingHandler.7=Invalid value for "x" attribute: ''{0}'' 22 | TMXValidatingHandler.8=Duplicated value for "x" attribute: ''{0}'' in ''{1}'' 23 | TMXValidatingHandler.9=Invalid language code ''{0}'' 24 | TMXValidator.0=Version: {0} Build: {1} 25 | TMXValidator.1=Selected file is valid TMX 26 | TMXValidator.2=\n\nUsage:\n\n {0} [-help] [-version] -tmx tmxFile\n\nWhere:\n\n -help: (optional) Display this help information and exit\n -version: (optional) Display version & build information and exit\n -tmx: TMX file to validate\n\n 27 | ValidationServer.0=Version: {0} Build: {1} 28 | ValidationServer.1=Validation server started 29 | ValidationServer.2=Unknown command 30 | ValidationServer.3=Missing command 31 | ValidationServer.4=Missing 'process' parameter 32 | ValidationServer.5=Null 'status' 33 | ValidationServer.6=Validation result not found 34 | ValidationServer.7=Missing 'file' parameter 35 | ValidationServer.8=Selected file is valid TMX 36 | -------------------------------------------------------------------------------- /src/com/maxprograms/tmxvalidation/tmxvalidation_es.properties: -------------------------------------------------------------------------------- 1 | TMXValidatingHandler.0=Recargar con DTD 2 | TMXValidatingHandler.1=Falta versión de TMX 3 | TMXValidatingHandler.10=Error al validar idioma 4 | TMXValidatingHandler.11=Valor incorrecto para "usagecount": ''{0}'' 5 | TMXValidatingHandler.12=Formato de fecha no válido ''{0}'' 6 | TMXValidatingHandler.13=Número desequilibrado de elementos /\n\n{0} 7 | TMXValidatingHandler.14=Elemento / sin coincidencia /\n\n{0} 8 | TMXValidatingHandler.15=Correspondencia "x" incorrecta 9 | TMXValidatingHandler.16=Correspondencia "x" incorrecta 10 | TMXValidatingHandler.17= sin atributo de idioma 11 | TMXValidatingHandler.18= no tiene con idioma establecido en ''{0}'' 12 | TMXValidatingHandler.19=Valor incorrecto para para el atributo 'i' en un elemento \n\n{0} 13 | TMXValidatingHandler.2=Versión incorrecta de TMX: {0} 14 | TMXValidatingHandler.20=Valor duplicado para para el atributo 'i' en un elemento \n\n{0} 15 | TMXValidatingHandler.21=Valor incorrecto para para el atributo 'i' en un elemento \n\n{0} 16 | TMXValidatingHandler.22=Valor no coincidente para el atributo 'i' en un elemento /\n\n{0} 17 | TMXValidatingHandler.3=El archivo seleccionado no es un documento TMX 18 | TMXValidatingHandler.4=Idioma origen no declarado 19 | TMXValidatingHandler.5=Idioma de origen no válido ''{0}'' 20 | TMXValidatingHandler.6=Error al validar idioma origen 21 | TMXValidatingHandler.7=Valor incorrecto para el atributo "x": ''{0}'' 22 | TMXValidatingHandler.8=Valor duplicado para el atributo "x": ''{0}'' en ''{1}'' 23 | TMXValidatingHandler.9=Código de idioma no válido ''{0}'' 24 | TMXValidator.0=Versión: {0} Compilación: {1} 25 | TMXValidator.1=El archivo seleccionado es TMX válido 26 | TMXValidator.2=\n\nUso:\n\n {0} [-help] [-version] -tmx tmxFile\n\nDonde:\n\n -help: (opcional) Mostrar esta información de ayuda y salir\n -version: (opcional) Mostrar información de versión y salir\n -tmx: Archivo TMX a validar\n\n 27 | ValidationServer.0=Versión: {0} Compilación: {1} 28 | ValidationServer.1=Servidor de validación iniciado 29 | ValidationServer.2=Comando desconocido 30 | ValidationServer.3=Falta comando 31 | ValidationServer.4=Falta el parámetro 'process' 32 | ValidationServer.5='status' nulo 33 | ValidationServer.6=Resultado de validación no encontrado 34 | ValidationServer.7=Falta el parámetro 'file' 35 | ValidationServer.8=El archivo seleccionado es TMX válido 36 | -------------------------------------------------------------------------------- /src/module-info.java: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005-2025 Maxprograms. 3 | * 4 | * This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License 1.0 6 | * which accompanies this distribution, and is available at 7 | * https://www.eclipse.org/org/documents/epl-v10.html 8 | * 9 | * Contributors: 10 | * Maxprograms - initial API and implementation 11 | *******************************************************************************/ 12 | module tmxvalidator { 13 | 14 | exports com.maxprograms.server; 15 | exports com.maxprograms.tmxvalidation; 16 | 17 | requires transitive xmljava; 18 | requires transitive javabcp47; 19 | requires transitive jdk.httpserver; 20 | requires json; 21 | } -------------------------------------------------------------------------------- /tmxvalidator_es.tmx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
13 |
14 | 15 | 16 | 17 | Reload with DTD 18 | 19 | 20 | Recargar con DTD 21 | 22 | 23 | 24 | 25 | TMX version is missing 26 | 27 | 28 | Falta versión de TMX 29 | 30 | 31 | 32 | 33 | Error validating language 34 | 35 | 36 | Error al validar idioma 37 | 38 | 39 | 40 | 41 | Invalid value for "usagecount": ''{0}'' 42 | 43 | 44 | Valor incorrecto para "usagecount": ''{0}'' 45 | 46 | 47 | 48 | 49 | Invalid date format ''{0}'' 50 | 51 | 52 | Formato de fecha no válido ''{0}'' 53 | 54 | 55 | 56 | 57 | Unbalanced number of <bpt>/<ept> elements 58 | 59 | {0} 60 | 61 | 62 | Número desequilibrado de elementos <bpt>/<ept> 63 | 64 | {0} 65 | 66 | 67 | 68 | 69 | <bpt>/<ept> element without matching <ept>/<bpt> 70 | 71 | {0} 72 | 73 | 74 | Elemento <bpt>/<ept> sin coincidencia <ept>/<bpt> 75 | 76 | {0} 77 | 78 | 79 | 80 | 81 | Incorrect "x" matching 82 | 83 | 84 | Correspondencia "x" incorrecta 85 | 86 | 87 | 88 | 89 | Incorrect "x" matching 90 | 91 | 92 | Correspondencia "x" incorrecta 93 | 94 | 95 | 96 | 97 | <tuv> without language attribute 98 | 99 | 100 | <tuv> sin atributo de idioma 101 | 102 | 103 | 104 | 105 | <tu> element lacks <tuv> with language set to ''{0}'' 106 | 107 | 108 | <tu> no tiene <tuv> con idioma establecido en ''{0}'' 109 | 110 | 111 | 112 | 113 | Invalid value for attribute 'i' in a <bpt> element 114 | 115 | {0} 116 | 117 | 118 | Valor incorrecto para para el atributo 'i' en un elemento <bpt> 119 | 120 | {0} 121 | 122 | 123 | 124 | 125 | Incorrect TMX version: {0} 126 | 127 | 128 | Versión incorrecta de TMX: {0} 129 | 130 | 131 | 132 | 133 | Duplicated value for attribute 'i' in a <bpt> element 134 | 135 | {0} 136 | 137 | 138 | Valor duplicado para para el atributo 'i' en un elemento <bpt> 139 | 140 | {0} 141 | 142 | 143 | 144 | 145 | Invalid value for attribute 'i' in a <ept> element 146 | 147 | {0} 148 | 149 | 150 | Valor incorrecto para para el atributo 'i' en un elemento <ept> 151 | 152 | {0} 153 | 154 | 155 | 156 | 157 | Mismatched value for attribute 'i' in a <bpt>/<ept> element 158 | 159 | {0} 160 | 161 | 162 | Valor no coincidente para el atributo 'i' en un elemento <bpt>/<ept> 163 | 164 | {0} 165 | 166 | 167 | 168 | 169 | Selected file is not a TMX document 170 | 171 | 172 | El archivo seleccionado no es un documento TMX 173 | 174 | 175 | 176 | 177 | Source language not declared 178 | 179 | 180 | Idioma origen no declarado 181 | 182 | 183 | 184 | 185 | Invalid source language ''{0}'' 186 | 187 | 188 | Idioma de origen no válido ''{0}'' 189 | 190 | 191 | 192 | 193 | Error validating source language 194 | 195 | 196 | Error al validar idioma origen 197 | 198 | 199 | 200 | 201 | Invalid value for "x" attribute: ''{0}'' 202 | 203 | 204 | Valor incorrecto para el atributo "x": ''{0}'' 205 | 206 | 207 | 208 | 209 | Duplicated value for "x" attribute: ''{0}'' in ''{1}'' 210 | 211 | 212 | Valor duplicado para el atributo "x": ''{0}'' en ''{1}'' 213 | 214 | 215 | 216 | 217 | Invalid language code ''{0}'' 218 | 219 | 220 | Código de idioma no válido ''{0}'' 221 | 222 | 223 | 224 | 225 | Version: {0} Build: {1} 226 | 227 | 228 | Versión: {0} Compilación: {1} 229 | 230 | 231 | 232 | 233 | Selected file is valid TMX 234 | 235 | 236 | El archivo seleccionado es TMX válido 237 | 238 | 239 | 240 | 241 | 242 | 243 | Usage: 244 | 245 | {0} [-help] [-version] -tmx tmxFile 246 | 247 | Where: 248 | 249 | -help: (optional) Display this help information and exit 250 | -version: (optional) Display version & build information and exit 251 | -tmx: TMX file to validate 252 | 253 | 254 | 255 | 256 | 257 | 258 | Uso: 259 | 260 | {0} [-help] [-version] -tmx tmxFile 261 | 262 | Donde: 263 | 264 | -help: (opcional) Mostrar esta información de ayuda y salir 265 | -version: (opcional) Mostrar información de versión y salir 266 | -tmx: Archivo TMX a validar 267 | 268 | 269 | 270 | 271 | 272 | 273 | Version: {0} Build: {1} 274 | 275 | 276 | Versión: {0} Compilación: {1} 277 | 278 | 279 | 280 | 281 | Validation server started 282 | 283 | 284 | Servidor de validación iniciado 285 | 286 | 287 | 288 | 289 | Unknown command 290 | 291 | 292 | Comando desconocido 293 | 294 | 295 | 296 | 297 | Missing command 298 | 299 | 300 | Falta comando 301 | 302 | 303 | 304 | 305 | Missing 'process' parameter 306 | 307 | 308 | Falta el parámetro 'process' 309 | 310 | 311 | 312 | 313 | Null 'status' 314 | 315 | 316 | 'status' nulo 317 | 318 | 319 | 320 | 321 | Validation result not found 322 | 323 | 324 | Resultado de validación no encontrado 325 | 326 | 327 | 328 | 329 | Missing 'file' parameter 330 | 331 | 332 | Falta el parámetro 'file' 333 | 334 | 335 | 336 | 337 | Selected file is valid TMX 338 | 339 | 340 | El archivo seleccionado es TMX válido 341 | 342 | 343 | 344 |
-------------------------------------------------------------------------------- /tmxvalidator_es.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | VE1YVmFsaWRhdGluZ0hhbmRsZXIuMD0lJSUwJSUlClRNWFZhbGlkYXRpbmdIYW5kbGVyLjE9JSUl 5 | MSUlJQpUTVhWYWxpZGF0aW5nSGFuZGxlci4xMD0lJSUyJSUlClRNWFZhbGlkYXRpbmdIYW5kbGVy 6 | LjExPSUlJTMlJSUKVE1YVmFsaWRhdGluZ0hhbmRsZXIuMTI9JSUlNCUlJQpUTVhWYWxpZGF0aW5n 7 | SGFuZGxlci4xMz0lJSU1JSUlClRNWFZhbGlkYXRpbmdIYW5kbGVyLjE0PSUlJTYlJSUKVE1YVmFs 8 | aWRhdGluZ0hhbmRsZXIuMTU9JSUlNyUlJQpUTVhWYWxpZGF0aW5nSGFuZGxlci4xNj0lJSU4JSUl 9 | ClRNWFZhbGlkYXRpbmdIYW5kbGVyLjE3PSUlJTklJSUKVE1YVmFsaWRhdGluZ0hhbmRsZXIuMTg9 10 | JSUlMTAlJSUKVE1YVmFsaWRhdGluZ0hhbmRsZXIuMTk9JSUlMTElJSUKVE1YVmFsaWRhdGluZ0hh 11 | bmRsZXIuMj0lJSUxMiUlJQpUTVhWYWxpZGF0aW5nSGFuZGxlci4yMD0lJSUxMyUlJQpUTVhWYWxp 12 | ZGF0aW5nSGFuZGxlci4yMT0lJSUxNCUlJQpUTVhWYWxpZGF0aW5nSGFuZGxlci4yMj0lJSUxNSUl 13 | JQpUTVhWYWxpZGF0aW5nSGFuZGxlci4zPSUlJTE2JSUlClRNWFZhbGlkYXRpbmdIYW5kbGVyLjQ9 14 | JSUlMTclJSUKVE1YVmFsaWRhdGluZ0hhbmRsZXIuNT0lJSUxOCUlJQpUTVhWYWxpZGF0aW5nSGFu 15 | ZGxlci42PSUlJTE5JSUlClRNWFZhbGlkYXRpbmdIYW5kbGVyLjc9JSUlMjAlJSUKVE1YVmFsaWRh 16 | dGluZ0hhbmRsZXIuOD0lJSUyMSUlJQpUTVhWYWxpZGF0aW5nSGFuZGxlci45PSUlJTIyJSUlClRN 17 | WFZhbGlkYXRvci4wPSUlJTIzJSUlClRNWFZhbGlkYXRvci4xPSUlJTI0JSUlClRNWFZhbGlkYXRv 18 | ci4yPSUlJTI1JSUlClZhbGlkYXRpb25TZXJ2ZXIuMD0lJSUyNiUlJQpWYWxpZGF0aW9uU2VydmVy 19 | LjE9JSUlMjclJSUKVmFsaWRhdGlvblNlcnZlci4yPSUlJTI4JSUlClZhbGlkYXRpb25TZXJ2ZXIu 20 | Mz0lJSUyOSUlJQpWYWxpZGF0aW9uU2VydmVyLjQ9JSUlMzAlJSUKVmFsaWRhdGlvblNlcnZlci41 21 | PSUlJTMxJSUlClZhbGlkYXRpb25TZXJ2ZXIuNj0lJSUzMiUlJQpWYWxpZGF0aW9uU2VydmVyLjc9 22 | JSUlMzMlJSUKVmFsaWRhdGlvblNlcnZlci44PSUlJTM0JSUlCg== 23 | 24 | 25 | javapropertyresourcebundle 26 | 27 | 28 | OpenXLIFF 29 | OpenXLIFF Filters 30 | 3.11.0 20230726_0946 31 | 32 | 33 | UTF-8 34 | 35 | 36 | 37 | 38 | 39 | TMXValidatingHandler.0 40 | 41 | 42 | 43 | Reload with DTD 44 | Recargar con DTD 45 | 46 | 47 | 48 | 49 | 50 | TMXValidatingHandler.1 51 | 52 | 53 | 54 | TMX version is missing 55 | Falta versión de TMX 56 | 57 | 58 | 59 | 60 | 61 | TMXValidatingHandler.10 62 | 63 | 64 | 65 | Error validating language 66 | Error al validar idioma 67 | 68 | 69 | 70 | 71 | 72 | TMXValidatingHandler.11 73 | 74 | 75 | 76 | Invalid value for "usagecount": ''{0}'' 77 | Valor incorrecto para "usagecount": ''{0}'' 78 | 79 | 80 | 81 | 82 | 83 | TMXValidatingHandler.12 84 | 85 | 86 | 87 | Invalid date format ''{0}'' 88 | Formato de fecha no válido ''{0}'' 89 | 90 | 91 | 92 | 93 | 94 | TMXValidatingHandler.13 95 | 96 | 97 | 98 | Unbalanced number of <bpt>/<ept> elements 99 | 100 | {0} 101 | Número desequilibrado de elementos <bpt>/<ept> 102 | 103 | {0} 104 | 105 | 106 | 107 | 108 | 109 | TMXValidatingHandler.14 110 | 111 | 112 | 113 | <bpt>/<ept> element without matching <ept>/<bpt> 114 | 115 | {0} 116 | Elemento <bpt>/<ept> sin coincidencia <ept>/<bpt> 117 | 118 | {0} 119 | 120 | 121 | 122 | 123 | 124 | TMXValidatingHandler.15 125 | 126 | 127 | 128 | Incorrect "x" matching 129 | Correspondencia "x" incorrecta 130 | 131 | 132 | 133 | 134 | 135 | TMXValidatingHandler.16 136 | 137 | 138 | 139 | Incorrect "x" matching 140 | Correspondencia "x" incorrecta 141 | 142 | 143 | 144 | 145 | 146 | TMXValidatingHandler.17 147 | 148 | 149 | 150 | <tuv> without language attribute 151 | <tuv> sin atributo de idioma 152 | 153 | 154 | 155 | 156 | 157 | TMXValidatingHandler.18 158 | 159 | 160 | 161 | <tu> element lacks <tuv> with language set to ''{0}'' 162 | <tu> no tiene <tuv> con idioma establecido en ''{0}'' 163 | 164 | 165 | 166 | 167 | 168 | TMXValidatingHandler.19 169 | 170 | 171 | 172 | Invalid value for attribute 'i' in a <bpt> element 173 | 174 | {0} 175 | Valor incorrecto para para el atributo 'i' en un elemento <bpt> 176 | 177 | {0} 178 | 179 | 180 | 181 | 182 | 183 | TMXValidatingHandler.2 184 | 185 | 186 | 187 | Incorrect TMX version: {0} 188 | Versión incorrecta de TMX: {0} 189 | 190 | 191 | 192 | 193 | 194 | TMXValidatingHandler.20 195 | 196 | 197 | 198 | Duplicated value for attribute 'i' in a <bpt> element 199 | 200 | {0} 201 | Valor duplicado para para el atributo 'i' en un elemento <bpt> 202 | 203 | {0} 204 | 205 | 206 | 207 | 208 | 209 | TMXValidatingHandler.21 210 | 211 | 212 | 213 | Invalid value for attribute 'i' in a <ept> element 214 | 215 | {0} 216 | Valor incorrecto para para el atributo 'i' en un elemento <ept> 217 | 218 | {0} 219 | 220 | 221 | 222 | 223 | 224 | TMXValidatingHandler.22 225 | 226 | 227 | 228 | Mismatched value for attribute 'i' in a <bpt>/<ept> element 229 | 230 | {0} 231 | Valor no coincidente para el atributo 'i' en un elemento <bpt>/<ept> 232 | 233 | {0} 234 | 235 | 236 | 237 | 238 | 239 | TMXValidatingHandler.3 240 | 241 | 242 | 243 | Selected file is not a TMX document 244 | El archivo seleccionado no es un documento TMX 245 | 246 | 247 | 248 | 249 | 250 | TMXValidatingHandler.4 251 | 252 | 253 | 254 | Source language not declared 255 | Idioma origen no declarado 256 | 257 | 258 | 259 | 260 | 261 | TMXValidatingHandler.5 262 | 263 | 264 | 265 | Invalid source language ''{0}'' 266 | Idioma de origen no válido ''{0}'' 267 | 268 | 269 | 270 | 271 | 272 | TMXValidatingHandler.6 273 | 274 | 275 | 276 | Error validating source language 277 | Error al validar idioma origen 278 | 279 | 280 | 281 | 282 | 283 | TMXValidatingHandler.7 284 | 285 | 286 | 287 | Invalid value for "x" attribute: ''{0}'' 288 | Valor incorrecto para el atributo "x": ''{0}'' 289 | 290 | 291 | 292 | 293 | 294 | TMXValidatingHandler.8 295 | 296 | 297 | 298 | Duplicated value for "x" attribute: ''{0}'' in ''{1}'' 299 | Valor duplicado para el atributo "x": ''{0}'' en ''{1}'' 300 | 301 | 302 | 303 | 304 | 305 | TMXValidatingHandler.9 306 | 307 | 308 | 309 | Invalid language code ''{0}'' 310 | Código de idioma no válido ''{0}'' 311 | 312 | 313 | 314 | 315 | 316 | TMXValidator.0 317 | 318 | 319 | 320 | Version: {0} Build: {1} 321 | Versión: {0} Compilación: {1} 322 | 323 | 324 | 325 | 326 | 327 | TMXValidator.1 328 | 329 | 330 | 331 | Selected file is valid TMX 332 | El archivo seleccionado es TMX válido 333 | 334 | 335 | 336 | 337 | 338 | TMXValidator.2 339 | 340 | 341 | 342 | 343 | 344 | Usage: 345 | 346 | {0} [-help] [-version] -tmx tmxFile 347 | 348 | Where: 349 | 350 | -help: (optional) Display this help information and exit 351 | -version: (optional) Display version & build information and exit 352 | -tmx: TMX file to validate 353 | 354 | 355 | 356 | 357 | Uso: 358 | 359 | {0} [-help] [-version] -tmx tmxFile 360 | 361 | Donde: 362 | 363 | -help: (opcional) Mostrar esta información de ayuda y salir 364 | -version: (opcional) Mostrar información de versión y salir 365 | -tmx: Archivo TMX a validar 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | ValidationServer.0 374 | 375 | 376 | 377 | Version: {0} Build: {1} 378 | Versión: {0} Compilación: {1} 379 | 380 | 381 | 382 | 383 | 384 | ValidationServer.1 385 | 386 | 387 | 388 | Validation server started 389 | Servidor de validación iniciado 390 | 391 | 392 | 393 | 394 | 395 | ValidationServer.2 396 | 397 | 398 | 399 | Unknown command 400 | Comando desconocido 401 | 402 | 403 | 404 | 405 | 406 | ValidationServer.3 407 | 408 | 409 | 410 | Missing command 411 | Falta comando 412 | 413 | 414 | 415 | 416 | 417 | ValidationServer.4 418 | 419 | 420 | 421 | Missing 'process' parameter 422 | Falta el parámetro 'process' 423 | 424 | 425 | 426 | 427 | 428 | ValidationServer.5 429 | 430 | 431 | 432 | Null 'status' 433 | 'status' nulo 434 | 435 | 436 | 437 | 438 | 439 | ValidationServer.6 440 | 441 | 442 | 443 | Validation result not found 444 | Resultado de validación no encontrado 445 | 446 | 447 | 448 | 449 | 450 | ValidationServer.7 451 | 452 | 453 | 454 | Missing 'file' parameter 455 | Falta el parámetro 'file' 456 | 457 | 458 | 459 | 460 | 461 | ValidationServer.8 462 | 463 | 464 | 465 | Selected file is valid TMX 466 | El archivo seleccionado es TMX válido 467 | 468 | 469 | 470 | -------------------------------------------------------------------------------- /ts/about.ts: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005-2025 Maxprograms. 3 | * 4 | * This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License 1.0 6 | * which accompanies this distribution, and is available at 7 | * https://www.eclipse.org/org/documents/epl-v10.html 8 | * 9 | * Contributors: 10 | * Maxprograms - initial API and implementation 11 | *******************************************************************************/ 12 | 13 | class About { 14 | 15 | electron = require('electron'); 16 | 17 | constructor() { 18 | this.electron.ipcRenderer.send('get-version'); 19 | this.electron.ipcRenderer.on('set-version', (event: Electron.IpcRendererEvent, arg: any) => { 20 | document.getElementById('version').innerHTML = arg.version; 21 | document.getElementById('build').innerHTML = arg.build; 22 | }); 23 | } 24 | } -------------------------------------------------------------------------------- /ts/app.ts: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005-2025 Maxprograms. 3 | * 4 | * This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License 1.0 6 | * which accompanies this distribution, and is available at 7 | * https://www.eclipse.org/org/documents/epl-v10.html 8 | * 9 | * Contributors: 10 | * Maxprograms - initial API and implementation 11 | *******************************************************************************/ 12 | 13 | import { app, ipcMain, BrowserWindow, dialog } from "electron"; 14 | import { ChildProcessWithoutNullStreams, execFileSync, spawn } from "child_process"; 15 | import { ClientRequest, request } from "http"; 16 | import { IpcMainEvent } from "electron/main"; 17 | 18 | class TMXValidator { 19 | 20 | static mainWindow: BrowserWindow; 21 | javapath: string = app.getAppPath() + '/bin/java'; 22 | static ls: ChildProcessWithoutNullStreams; 23 | static killed: boolean = false; 24 | static currentStatus: any = {}; 25 | 26 | constructor() { 27 | if (!app.requestSingleInstanceLock()) { 28 | app.quit() 29 | } else { 30 | if (TMXValidator.mainWindow) { 31 | // Someone tried to run a second instance, we should focus our window. 32 | if (TMXValidator.mainWindow.isMinimized()) { 33 | TMXValidator.mainWindow.restore() 34 | } 35 | TMXValidator.mainWindow.focus(); 36 | } 37 | } 38 | if (process.platform == 'win32') { 39 | this.javapath = app.getAppPath() + '\\bin\\java.exe'; 40 | } 41 | TMXValidator.ls = spawn(this.javapath, ['--module-path', 'lib', '-m', 'tmxvalidator/com.maxprograms.tmxvalidation.ValidationServer'], { cwd: app.getAppPath() }); 42 | TMXValidator.ls.stdout.on('data', (data) => { 43 | console.log(`stdout: ${data}`); 44 | }); 45 | TMXValidator.ls.stderr.on('data', (data) => { 46 | console.error(`stderr: ${data}`); 47 | }); 48 | execFileSync('bin/java', ['--module-path', 'lib', '-m', 'tmxvalidator/com.maxprograms.server.CheckURL', 'http://localhost:8010/ValidationServer'], { cwd: app.getAppPath() }); 49 | app.on('ready', () => { 50 | TMXValidator.createWindows(); 51 | TMXValidator.mainWindow.show(); 52 | }); 53 | app.on('quit', () => { 54 | TMXValidator.stopServer(); 55 | }); 56 | app.on('window-all-closed', function () { 57 | TMXValidator.stopServer(); 58 | app.quit() 59 | }); 60 | ipcMain.on('select-file', () => { 61 | dialog.showErrorBox('Attention', 'Select TMX file'); 62 | }); 63 | ipcMain.on('select-tmx-validation', () => { 64 | this.selectFile(); 65 | }); 66 | ipcMain.on('show-about', () => { 67 | this.showAbout(); 68 | }); 69 | ipcMain.on('validate', (event: IpcMainEvent, arg: any) => { 70 | TMXValidator.validate(event, arg); 71 | }); 72 | ipcMain.on('get-version', (event: IpcMainEvent) => { 73 | TMXValidator.sendRequest({ command: 'version' }, 74 | (data: any) => { 75 | event.sender.send('set-version', data); 76 | }, 77 | (reason: string) => { 78 | dialog.showErrorBox('Error', reason); 79 | } 80 | ); 81 | }); 82 | } 83 | 84 | static stopServer(): void { 85 | if (!this.killed) { 86 | TMXValidator.ls.kill(); 87 | TMXValidator.killed = true; 88 | } 89 | } 90 | 91 | selectFile(): void { 92 | dialog.showOpenDialog({ 93 | properties: ['openFile'], 94 | filters: [ 95 | { name: 'TMX File', extensions: ['tmx'] } 96 | ] 97 | }).then((value) => { 98 | if (!value.canceled) { 99 | TMXValidator.mainWindow.webContents.send('add-tmx-validation', value.filePaths[0]); 100 | } 101 | }).catch((reason) => { 102 | dialog.showErrorBox('Error', reason); 103 | }); 104 | } 105 | 106 | static validate(event: IpcMainEvent, arg: any): void { 107 | event.sender.send('validation-started'); 108 | TMXValidator.sendRequest(arg, 109 | function success(data: any) { 110 | TMXValidator.currentStatus = data; 111 | let intervalObject = setInterval(function () { 112 | if (TMXValidator.currentStatus.status === 'Success') { 113 | // ignore status from validation request 114 | } else if (TMXValidator.currentStatus.status === 'Completed') { 115 | clearInterval(intervalObject); 116 | event.sender.send('validation-completed'); 117 | TMXValidator.getValidationStatus(data.process, event); 118 | return; 119 | } else if (TMXValidator.currentStatus.status === 'Running') { 120 | // keep waiting 121 | } else { 122 | clearInterval(intervalObject); 123 | event.sender.send('validation-completed'); 124 | dialog.showErrorBox('Error', TMXValidator.currentStatus.reason); 125 | return; 126 | } 127 | TMXValidator.getStatus(data.process); 128 | }, 500); 129 | }, 130 | function error(reason: string) { 131 | dialog.showErrorBox('Error', reason); 132 | } 133 | ); 134 | } 135 | 136 | showAbout(): void { 137 | let about = new BrowserWindow({ 138 | parent: TMXValidator.mainWindow, 139 | width: 280, 140 | height: 290, 141 | minimizable: false, 142 | maximizable: false, 143 | resizable: false, 144 | show: false, 145 | icon: 'img/tmxvalidator.png', 146 | backgroundColor: '#2d2d2e', 147 | webPreferences: { 148 | nodeIntegration: true, 149 | contextIsolation: false 150 | } 151 | }); 152 | about.loadURL('file://' + app.getAppPath() + '/html/about.html'); 153 | about.setMenu(null); 154 | about.show(); 155 | } 156 | 157 | static createWindows() { 158 | TMXValidator.mainWindow = new BrowserWindow({ 159 | width: 560, 160 | height: 180, 161 | show: false, 162 | maximizable: false, 163 | icon: 'img/tmxvalidator.png', 164 | backgroundColor: '#2d2d2e', 165 | darkTheme: true, 166 | webPreferences: { 167 | nodeIntegration: true, 168 | contextIsolation: false 169 | } 170 | }); 171 | TMXValidator.mainWindow.setMenu(null); 172 | TMXValidator.mainWindow.loadURL('file://' + app.getAppPath() + '/html/main.html'); 173 | } 174 | 175 | static sendRequest(json: any, success: any, error: any): void { 176 | let postData: string = JSON.stringify(json); 177 | let options = { 178 | hostname: '127.0.0.1', 179 | port: 8010, 180 | path: '/ValidationServer', 181 | headers: { 182 | 'Content-Type': 'application/json', 183 | 'Content-Length': Buffer.byteLength(postData) 184 | } 185 | }; 186 | // Make a request 187 | let req: ClientRequest = request(options); 188 | req.on('response', 189 | function (res: any) { 190 | res.setEncoding('utf-8'); 191 | if (res.statusCode != 200) { 192 | error('sendRequest() error: ' + res.statusMessage); 193 | } 194 | let rawData: string = ''; 195 | res.on('data', (chunk: string) => { 196 | rawData += chunk; 197 | }); 198 | res.on('end', () => { 199 | try { 200 | success(JSON.parse(rawData)); 201 | } catch (e) { 202 | error(e.message); 203 | } 204 | }); 205 | } 206 | ); 207 | req.write(postData); 208 | req.end(); 209 | } 210 | 211 | static getStatus(processId: string): void { 212 | TMXValidator.sendRequest({ command: 'status', process: processId }, 213 | (data: any) => { 214 | TMXValidator.currentStatus = data; 215 | }, 216 | (reason: string) => { 217 | dialog.showErrorBox('Error', reason); 218 | } 219 | ); 220 | } 221 | 222 | static getValidationStatus(processId: string, event: any): void { 223 | TMXValidator.sendRequest({ command: 'validationResult', process: processId }, 224 | (data: any) => { 225 | if (data.valid) { 226 | dialog.showMessageBox({ type: 'info', message: data.comment }); 227 | } else { 228 | dialog.showMessageBox({ type: 'error', message: data.reason }); 229 | } 230 | }, 231 | (reason: string) => { 232 | dialog.showErrorBox('Error', reason); 233 | } 234 | ); 235 | } 236 | 237 | } 238 | try { 239 | new TMXValidator(); 240 | } catch (e) { 241 | console.error(e); 242 | } -------------------------------------------------------------------------------- /ts/main.ts: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (c) 2005-2025 Maxprograms. 3 | * 4 | * This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License 1.0 6 | * which accompanies this distribution, and is available at 7 | * https://www.eclipse.org/org/documents/epl-v10.html 8 | * 9 | * Contributors: 10 | * Maxprograms - initial API and implementation 11 | *******************************************************************************/ 12 | 13 | class Main { 14 | 15 | electron = require('electron'); 16 | 17 | constructor() { 18 | document.getElementById('browse').addEventListener('click', () => { 19 | this.browse(); 20 | }); 21 | document.getElementById('about').addEventListener('click', () => { 22 | this.showAbout(); 23 | }); 24 | document.getElementById('validate').addEventListener('click', () => { 25 | this.validate(); 26 | }); 27 | this.electron.ipcRenderer.on('add-tmx-validation', (event: Electron.IpcRendererEvent, arg: any) => { 28 | (document.getElementById('tmxFile') as HTMLInputElement).value = arg; 29 | }); 30 | 31 | this.electron.ipcRenderer.on('validation-started', () => { 32 | document.documentElement.style.cursor = 'wait'; 33 | document.getElementById('working').style.display = 'block'; 34 | }); 35 | 36 | this.electron.ipcRenderer.on('validation-completed', () => { 37 | document.documentElement.style.cursor = 'default'; 38 | document.getElementById('working').style.display = 'none'; 39 | }); 40 | } 41 | 42 | browse() { 43 | this.electron.ipcRenderer.send('select-tmx-validation'); 44 | } 45 | 46 | validate() { 47 | let tmxfile: string = (document.getElementById('tmxFile') as HTMLInputElement).value; 48 | if (tmxfile === '') { 49 | this.electron.ipcRenderer.send('select-file'); 50 | return; 51 | } 52 | this.electron.ipcRenderer.send('validate', { command: 'validate', file: tmxfile }); 53 | } 54 | 55 | showAbout() { 56 | this.electron.ipcRenderer.send('show-about'); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "noImplicitAny": true, 5 | "sourceMap": false, 6 | "outDir": "js", 7 | "baseUrl": ".", 8 | "paths": { 9 | "*": [ 10 | "node_modules/*" 11 | ] 12 | } 13 | }, 14 | "include": [ 15 | "ts/**/*" 16 | ] 17 | } --------------------------------------------------------------------------------