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 &lt;mfrac&gt; 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    }