001 /*
002 * Copyright 2002 - 2008 JEuclid, http://jeuclid.sf.net
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 /* $Id: MathMLConverter.java,v ee8eb070689f 2008/06/25 14:51:09 maxberger $ */
018
019 package net.sourceforge.jeuclid.ant;
020
021 import java.io.File;
022 import java.io.IOException;
023 import java.util.Arrays;
024 import java.util.List;
025
026 import net.sourceforge.jeuclid.MutableLayoutContext;
027 import net.sourceforge.jeuclid.context.LayoutContextImpl;
028 import net.sourceforge.jeuclid.context.Parameter;
029 import net.sourceforge.jeuclid.converter.Converter;
030 import net.sourceforge.jeuclid.converter.ConverterRegistry;
031
032 import org.apache.tools.ant.BuildException;
033 import org.apache.tools.ant.DirectoryScanner;
034 import org.apache.tools.ant.Project;
035 import org.apache.tools.ant.taskdefs.MatchingTask;
036 import org.apache.tools.ant.util.FileUtils;
037
038 /**
039 * This task converts MathML files to images.
040 *
041 * @version $Revision: ee8eb070689f $
042 */
043 public class MathMLConverter extends MatchingTask {
044
045 private static final String CURRENT_DIR = "./";
046
047 private static final char EXTENSION_SEP = '.';
048
049 /**
050 *
051 */
052 private File mdestDir;
053
054 private File mbaseDir;
055
056 private File minFile;
057
058 private File moutFile;
059
060 private String moutType = "image/png";
061
062 private boolean mforce;
063
064 private final MutableLayoutContext context;
065
066 private final FileUtils fileUtils;
067
068 /**
069 * Creates a new MathMLConverter Task.
070 */
071 public MathMLConverter() {
072 this.context = new LayoutContextImpl(LayoutContextImpl
073 .getDefaultLayoutContext());
074 this.fileUtils = FileUtils.getFileUtils();
075 }
076
077 /**
078 * Executes the task.
079 *
080 */
081 @Override
082 public void execute() {
083 DirectoryScanner scanner;
084 String[] list;
085 String[] dirs;
086
087 if (this.mbaseDir == null) {
088 this.mbaseDir = this.getProject().resolveFile(
089 MathMLConverter.CURRENT_DIR);
090 this.log("Base is not sets, sets to " + this.mbaseDir,
091 Project.MSG_WARN);
092 }
093
094 // if we have an in file and out then process them
095 if ((this.minFile != null) && (this.moutFile != null)) {
096 this.log("Transforming file: " + this.minFile + " --> "
097 + this.moutFile, Project.MSG_VERBOSE);
098 try {
099 Converter.getInstance().convert(this.minFile, this.moutFile,
100 this.moutType, this.context);
101 } catch (final IOException io) {
102 throw new BuildException(io);
103 }
104 return;
105 }
106
107 /*
108 * if we get here, in and out have not been specified, we are in batch
109 * processing mode.
110 */
111
112 // -- make sure Source directory exists...
113 if (this.mdestDir == null) {
114 throw new BuildException("m_destDir attributes must be set!");
115 }
116 scanner = this.getDirectoryScanner(this.mbaseDir);
117 this.log("Transforming into " + this.mdestDir, Project.MSG_INFO);
118
119 // Process all the files marked for styling
120 list = scanner.getIncludedFiles();
121 this.log("Included files: " + Arrays.toString(list),
122 Project.MSG_VERBOSE);
123 this.process(this.mbaseDir, Arrays.asList(list), this.mdestDir);
124
125 // Process all the directories marked for styling
126 dirs = scanner.getIncludedDirectories();
127 this.log("Included directories: " + Arrays.toString(dirs),
128 Project.MSG_VERBOSE);
129 for (final String dir : dirs) {
130 list = this.fileUtils.resolveFile(this.mbaseDir, dir).list();
131 this.process(this.mbaseDir, Arrays.asList(list), this.mdestDir);
132 }
133 }
134
135 /**
136 * Sets support for anti alias (default is <i>true</i>).
137 *
138 * @param antiAlias
139 * Flag for support anti alias.
140 */
141 public void setAntiAlias(final boolean antiAlias) {
142 this.setOption(Parameter.ANTIALIAS, antiAlias);
143 }
144
145 /**
146 * Sets minimal size for turn on anti alias (default is <i>10.0</i>).
147 *
148 * @param antiAliasMinSize
149 * Minimal size in float number.
150 */
151 public void setAntiAliasMinSize(final float antiAliasMinSize) {
152 this.setOption(Parameter.ANTIALIAS_MINSIZE, antiAliasMinSize);
153 }
154
155 /**
156 * Sets background color.
157 *
158 * @param color
159 * String representation of color.
160 */
161 public void setBackgroundColor(final String color) {
162 if (this.isNullOrEmpty(color)) {
163 this.log("Attribute \"backgroundcolor\" is empty, not used",
164 Project.MSG_WARN);
165 } else {
166 this.setOption(Parameter.MATHBACKGROUND, color);
167 }
168 }
169
170 /**
171 * Sets support for debug (default is <i>false</i>).
172 *
173 * @param debug
174 * Flag for support debug.
175 */
176 public void setDebug(final boolean debug) {
177 this.setOption(Parameter.DEBUG, debug);
178 }
179
180 /**
181 * Sets display style (default is <i>BLOCK</i>.
182 *
183 * @param display
184 * String value of display style.
185 *
186 * @see net.sourceforge.jeuclid.context.Display
187 */
188 public void setDisplay(final String display) {
189 this.setOption(Parameter.DISPLAY, display);
190 }
191
192 /**
193 * Sets list of supported font families for <i>Double-Struck</i>.
194 *
195 * @param fonts
196 * List separated by comma.
197 */
198 public void setFontsDoublestruck(final String fonts) {
199 this.setOption(Parameter.FONTS_DOUBLESTRUCK, fonts);
200 }
201
202 /**
203 * Sets list of supported font families for <i>Fraktur</i>.
204 *
205 * @param fonts
206 * List separated by comma.
207 */
208 public void setFontsFraktur(final String fonts) {
209 this.setOption(Parameter.FONTS_FRAKTUR, fonts);
210 }
211
212 /**
213 * Sets font size of text.
214 *
215 * @param fontSize
216 * Font size as float value.
217 */
218 public void setFontSize(final float fontSize) {
219 this.setOption(Parameter.MATHSIZE, fontSize);
220 }
221
222 /**
223 * Sets list of supported font families for <i>Monospaced</i>.
224 *
225 * @param fonts
226 * List separated by comma.
227 */
228 public void setFontsMonospaced(final String fonts) {
229 this.setOption(Parameter.FONTS_MONOSPACED, fonts);
230 }
231
232 /**
233 * Sets list of supported font families for <i>Sans-Serif</i>.
234 *
235 * @param fonts
236 * List separated by comma.
237 */
238 public void setFontsSansSerif(final String fonts) {
239 this.setOption(Parameter.FONTS_SANSSERIF, fonts);
240 }
241
242 /**
243 * Sets list of supported font families for <i>Script</i>.
244 *
245 * @param fonts
246 * List separated by comma.
247 */
248 public void setFontsScript(final String fonts) {
249 this.setOption(Parameter.FONTS_SCRIPT, fonts);
250 }
251
252 /**
253 * Sets list of supported font families for <i>Serif</i>.
254 *
255 * @param fonts
256 * List separated by comma.
257 */
258 public void setFontsSerif(final String fonts) {
259 this.setOption(Parameter.FONTS_SERIF, fonts);
260 }
261
262 /**
263 * Sets foreground color.
264 *
265 * @param color
266 * String representation of color.
267 */
268 public void setForegroundColor(final String color) {
269 if (this.isNullOrEmpty(color)) {
270 this
271 .log(
272 "Attribute \"foregroundcolor\" is empty, use default color",
273 Project.MSG_WARN);
274 } else {
275 this.setOption(Parameter.MATHCOLOR, color);
276 }
277 }
278
279 /**
280 * Sets <mfrac> keep scriptlevel.
281 *
282 * @param keepScriptLevel
283 * if true, element will NEVER increase children's scriptlevel
284 * (in violation of the spec).
285 */
286 public void setMfracKeepScriptLevel(final boolean keepScriptLevel) {
287 this.setOption(Parameter.MFRAC_KEEP_SCRIPTLEVEL, keepScriptLevel);
288 }
289
290 /**
291 * Sets scripts level (default is <i>0</i>).
292 *
293 * @param level
294 * Script level.
295 */
296 public void setScriptLevel(final int level) {
297 this.setOption(Parameter.SCRIPTLEVEL, level);
298 }
299
300 /**
301 * Sets minimal size of smallest font size (default is <i>8.0</i>).
302 *
303 * @param minSize
304 * Size of font.
305 */
306 public void setScriptMinSize(final float minSize) {
307 this.setOption(Parameter.SCRIPTMINSIZE, minSize);
308 }
309
310 /**
311 * Sets size of multiplier (default is <i>0.71</i>).
312 *
313 * @param multSize
314 * Size of multiplier.
315 */
316 public void setScriptSizeMult(final float multSize) {
317 this.setOption(Parameter.SCRIPTSIZEMULTIPLIER, multSize);
318 }
319
320 /**
321 * Set whether to check dependencies, or always generate.
322 *
323 * @param force
324 * True, if the task should always generate the images.
325 */
326 public void setForce(final boolean force) {
327 this.logProperty("force", force);
328 this.mforce = force;
329 }
330
331 /**
332 * Set the base directory.
333 *
334 * @param dir
335 * Base directory
336 */
337 public void setBasedir(final File dir) {
338 this.logProperty("basedir", dir);
339 this.mbaseDir = dir;
340 }
341
342 /**
343 * Set the destination directory into which the result files should be
344 * copied to.
345 *
346 * @param dir
347 * Destination directory
348 */
349 public void setDestdir(final File dir) {
350 this.logProperty("destdir", dir);
351 this.mdestDir = dir;
352 }
353
354 /**
355 * Sets an out file.
356 *
357 * @param outFile
358 * Output file
359 */
360 public void setOut(final File outFile) {
361 this.logProperty("out", outFile);
362 this.moutFile = outFile;
363 }
364
365 /**
366 * Sets an input xml file to be converted.
367 *
368 * @param inFile
369 * Input file
370 */
371 public void setIn(final File inFile) {
372 this.logProperty("in", inFile);
373 this.minFile = inFile;
374 }
375
376 /**
377 * Sets output file mimetype.
378 *
379 * @param mimetype
380 * mimetype for output file.
381 */
382 public void setType(final String mimetype) {
383 this.logProperty("type", mimetype);
384 this.moutType = mimetype;
385 }
386
387 /**
388 * Processes the given input XML file and stores the result in the given
389 * resultFile.
390 *
391 * @param baseDir
392 * Base directory
393 * @param xmlFiles
394 * Source file
395 * @param destDir
396 * Destination directory
397 */
398 private void process(final File baseDir, final List<String> xmlFiles,
399 final File destDir) {
400 for (final String xmlFile : xmlFiles) {
401 File outFile = null;
402 File inFile = null;
403 final String suffix = MathMLConverter.EXTENSION_SEP
404 + ConverterRegistry.getInstance().getSuffixForMimeType(
405 this.moutType);
406 this.log("Found extension: " + suffix, Project.MSG_DEBUG);
407 try {
408 inFile = this.fileUtils.resolveFile(baseDir, xmlFile);
409 final int dotPos = xmlFile
410 .lastIndexOf(MathMLConverter.EXTENSION_SEP);
411
412 if (dotPos > 0) {
413 outFile = this.fileUtils.resolveFile(destDir, xmlFile
414 .substring(0, dotPos)
415 + suffix);
416 } else {
417 outFile = this.fileUtils.resolveFile(destDir, xmlFile
418 + suffix);
419 }
420 this.log("Input file: " + inFile, Project.MSG_DEBUG);
421 this.log("Output file: " + outFile, Project.MSG_DEBUG);
422 if (this.mforce
423 || !this.fileUtils.isUpToDate(inFile, outFile)) {
424 this.fileUtils.createNewFile(outFile, true);
425 Converter.getInstance().convert(inFile, outFile,
426 this.moutType, this.context);
427 }
428 } catch (final IOException ex) {
429 // If failed to process document, must delete target document,
430 // or it will not attempt to process it the second time
431 this.log("Failed to process " + inFile, Project.MSG_ERR);
432 FileUtils.delete(outFile);
433
434 throw new BuildException(ex);
435 }
436 }
437 }
438
439 /**
440 * Convert string value of parameter and sets to current context.
441 *
442 * @param param
443 * Type of parameter.
444 * @param value
445 * String value of parameter.
446 */
447 private void setOption(final Parameter param, final String value) {
448 this.setOption(param, param.fromString(value));
449 }
450
451 /**
452 * Sets parameter for current context.
453 *
454 * @param param
455 * Type of parameter.
456 * @param value
457 * Object with value of parameter.
458 */
459 private void setOption(final Parameter param, final Object value) {
460 this.logProperty(param.getOptionName(), value);
461 this.context.setParameter(param, value);
462 }
463
464 /**
465 * Tests if string is null or is empty.
466 *
467 * @param s
468 * Tested string.
469 *
470 * @return <code>true</code> if string is null or empty else
471 * <code>false</code>.
472 */
473 private boolean isNullOrEmpty(final String s) {
474 // TODO: use .isEmpty() when JEuclid moves to 1.6
475 return s == null || s.length() == 0;
476 }
477
478 /**
479 * Logs property, which is sets.
480 *
481 * @param param
482 * Parameter name.
483 * @param value
484 * Parameter value.
485 */
486 private void logProperty(final String param, final Object value) {
487 this.log("Sets property \"" + param + "\" with value: " + value,
488 Project.MSG_DEBUG);
489 }
490 }