├── .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 | 
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 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | Validate TMX
30 |
31 |
32 |
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("" + qName + ">");
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 |
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 | }
--------------------------------------------------------------------------------