001/* ***** BEGIN LICENSE BLOCK *****
002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
003 *
004 * The contents of this file are subject to the Mozilla Public License Version
005 * 1.1 (the "License"); you may not use this file except in compliance with
006 * the License. You may obtain a copy of the License at
007 * http://www.mozilla.org/MPL/
008 *
009 * Software distributed under the License is distributed on an "AS IS" basis,
010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
011 * for the specific language governing rights and limitations under the
012 * License.
013 *
014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in
015 * Java(TM), hosted at https://github.com/gunterze/dcm4che.
016 *
017 * The Initial Developer of the Original Code is
018 * Agfa Healthcare.
019 * Portions created by the Initial Developer are Copyright (C) 2011
020 * the Initial Developer. All Rights Reserved.
021 *
022 * Contributor(s):
023 * See @authors listed below
024 *
025 * Alternatively, the contents of this file may be used under the terms of
026 * either the GNU General Public License Version 2 or later (the "GPL"), or
027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
028 * in which case the provisions of the GPL or the LGPL are applicable instead
029 * of those above. If you wish to allow use of your version of this file only
030 * under the terms of either the GPL or the LGPL, and not to allow others to
031 * use your version of this file under the terms of the MPL, indicate your
032 * decision by deleting the provisions above and replace them with the notice
033 * and other provisions required by the GPL or the LGPL. If you do not delete
034 * the provisions above, a recipient may use your version of this file under
035 * the terms of any one of the MPL, the GPL or the LGPL.
036 *
037 * ***** END LICENSE BLOCK ***** */
038
039package org.dcm4che3.tool.xml2dcm;
040
041import org.apache.commons.cli.CommandLine;
042import org.apache.commons.cli.OptionBuilder;
043import org.apache.commons.cli.OptionGroup;
044import org.apache.commons.cli.Options;
045import org.apache.commons.cli.ParseException;
046import org.dcm4che3.data.Attributes;
047import org.dcm4che3.data.Tag;
048import org.dcm4che3.data.UID;
049import org.dcm4che3.io.BulkDataDescriptor;
050import org.dcm4che3.io.ContentHandlerAdapter;
051import org.dcm4che3.io.DicomEncodingOptions;
052import org.dcm4che3.io.DicomInputStream;
053import org.dcm4che3.io.DicomInputStream.IncludeBulkData;
054import org.dcm4che3.io.DicomOutputStream;
055import org.dcm4che3.tool.common.CLIUtils;
056
057import javax.xml.parsers.SAXParser;
058import javax.xml.parsers.SAXParserFactory;
059
060import java.io.BufferedOutputStream;
061import java.io.File;
062import java.io.FileDescriptor;
063import java.io.FileInputStream;
064import java.io.FileOutputStream;
065import java.io.IOException;
066import java.io.InputStream;
067import java.io.OutputStream;
068import java.util.List;
069import java.util.ResourceBundle;
070
071/**
072 * Tool to convert XML to DICOM.
073 * 
074 * @author Gunter Zeilinger <gunterze@gmail.com>
075 */
076public class Xml2Dcm {
077
078    private static ResourceBundle rb =
079        ResourceBundle.getBundle("org.dcm4che3.tool.xml2dcm.messages");
080
081    private IncludeBulkData includeBulkData = IncludeBulkData.URI;
082    private boolean catBlkFiles = false;
083    private String blkFilePrefix = "blk";
084    private String blkFileSuffix;
085    private File blkDirectory;
086    private Attributes blkAttrs;
087    private String tsuid;
088    private boolean withfmi;
089    private boolean nofmi;
090    private DicomEncodingOptions encOpts = DicomEncodingOptions.DEFAULT;
091    private List<File> bulkDataFiles;
092    private Attributes fmi;
093    private Attributes dataset;
094
095    public final void setIncludeBulkData(IncludeBulkData includeBulkData) {
096        this.includeBulkData = includeBulkData;
097    }
098
099    public final void setConcatenateBulkDataFiles(boolean catBlkFiles) {
100        this.catBlkFiles = catBlkFiles;
101    }
102
103    public final void setBulkDataFilePrefix(String blkFilePrefix) {
104        this.blkFilePrefix = blkFilePrefix;
105    }
106
107    public final void setBulkDataFileSuffix(String blkFileSuffix) {
108        this.blkFileSuffix = blkFileSuffix;
109    }
110
111    public final void setBulkDataDirectory(File blkDirectory) {
112        this.blkDirectory = blkDirectory;
113    }
114
115    public final void setBulkDataAttributes(Attributes blkAttrs) {
116        this.blkAttrs = blkAttrs;
117    }
118
119    public final void setTransferSyntax(String uid) {
120        this.tsuid = uid;
121    }
122
123    public final void setWithFileMetaInformation(boolean withfmi) {
124        this.withfmi = withfmi;
125    }
126
127    public final void setNoFileMetaInformation(boolean nofmi) {
128        this.nofmi = nofmi;
129    }
130
131    public final void setEncodingOptions(DicomEncodingOptions encOpts) {
132        this.encOpts = encOpts;
133    }
134
135     private static CommandLine parseComandLine(String[] args)
136            throws ParseException{
137        Options opts = new Options();
138        CLIUtils.addCommonOptions(opts);
139        addIOFileNameOptions(opts);
140        addBulkdataOptions(opts);
141        addFileEncodingOptions(opts);
142        CommandLine cl = CLIUtils.parseComandLine(args, opts, rb, Xml2Dcm.class);
143        if (!(cl.hasOption("x") || cl.hasOption("i")))
144            throw new ParseException(rb.getString("missing-i-x"));
145        return cl;
146    }
147
148     @SuppressWarnings("static-access")
149     private static void addIOFileNameOptions(Options opts) {
150         opts.addOption(OptionBuilder
151                 .hasArg()
152                 .withArgName("xml-file")
153                 .withDescription(rb.getString("x-file"))
154                 .create("x"));
155         opts.addOption(OptionBuilder
156                 .hasArg()
157                 .withArgName("dicom-file")
158                 .withDescription(rb.getString("i-file"))
159                 .create("i"));
160         opts.addOption(OptionBuilder
161                 .hasArg()
162                 .withArgName("dicom-file")
163                 .withDescription(rb.getString("o-file"))
164                 .create("o"));
165      }
166
167
168     @SuppressWarnings("static-access")
169     private static void addBulkdataOptions(Options opts) {
170         OptionGroup blkGroup = new OptionGroup();
171         blkGroup.addOption(OptionBuilder
172                 .withLongOpt("no-bulkdata")
173                 .withDescription(rb.getString("no-bulkdata"))
174                 .create("B"));
175         blkGroup.addOption(OptionBuilder
176                 .withLongOpt("alloc-bulkdata")
177                 .withDescription(rb.getString("alloc-bulkdata"))
178                 .create("b"));
179         opts.addOptionGroup(blkGroup);
180         opts.addOption(OptionBuilder
181                 .withLongOpt("blk-file-dir")
182                 .hasArg()
183                 .withArgName("directory")
184                 .withDescription(rb.getString("blk-file-dir"))
185                 .create("d"));
186         opts.addOption(OptionBuilder
187                 .withLongOpt("blk-file-prefix")
188                 .hasArg()
189                 .withArgName("prefix")
190                 .withDescription(rb.getString("blk-file-prefix"))
191                 .create());
192         opts.addOption(OptionBuilder
193                 .withLongOpt("blk-file-suffix")
194                 .hasArg()
195                 .withArgName("suffix")
196                 .withDescription(rb.getString("blk-file-suffix"))
197                 .create());
198         opts.addOption("c", "cat-blk-files", false,
199                  rb.getString("cat-blk-files"));
200         opts.addOption(null, "keep-blk-files", false,
201                 rb.getString("keep-blk-files"));
202         opts.addOption(OptionBuilder
203                 .withLongOpt("blk-spec")
204                 .hasArg()
205                 .withArgName("xml-file")
206                 .withDescription(rb.getString("blk-spec"))
207                 .create("X"));
208     }
209
210     @SuppressWarnings("static-access")
211     private static void addFileEncodingOptions(Options opts) {
212        opts.addOption(OptionBuilder
213                .withLongOpt("transfer-syntax")
214                .hasArg()
215                .withArgName("uid")
216                .withDescription(rb.getString("transfer-syntax"))
217                .create("t"));
218        OptionGroup fmiGroup = new OptionGroup();
219        fmiGroup.addOption(OptionBuilder
220                .withLongOpt("no-fmi")
221                .withDescription(rb.getString("no-fmi"))
222                .create("F"));
223        fmiGroup.addOption(OptionBuilder
224                .withLongOpt("fmi")
225                .withDescription(rb.getString("fmi"))
226                .create("f"));
227        opts.addOptionGroup(fmiGroup);
228        CLIUtils.addEncodingOptions(opts);
229    }
230
231    public static void main(String[] args) {
232        try {
233            CommandLine cl = parseComandLine(args);
234            Xml2Dcm main = new Xml2Dcm();
235            configureBulkdata(main, cl);
236            if (cl.hasOption("t")) {
237                main.setTransferSyntax(cl.getOptionValue("t"));
238            }
239            main.setWithFileMetaInformation(cl.hasOption("f"));
240            main.setNoFileMetaInformation(cl.hasOption("F"));
241            main.setEncodingOptions(CLIUtils.encodingOptionsOf(cl));
242            try {
243                if (cl.hasOption("i")) {
244                    String fname = cl.getOptionValue("i");
245                    if (fname.equals("-")) {
246                        main.parse(new DicomInputStream(System.in));
247                    } else {
248                        DicomInputStream dis = 
249                                new DicomInputStream(new File(fname));
250                        try {
251                            main.parse(dis);
252                        } finally {
253                            dis.close();
254                        }
255                    }
256                }
257
258                if (cl.hasOption("x"))
259                    main.mergeXML(cl.getOptionValue("x"));
260
261                OutputStream out = cl.hasOption("o") 
262                        ? new FileOutputStream(cl.getOptionValue("o"))
263                        : new FileOutputStream(FileDescriptor.out);
264                try {
265                    main.writeTo(out);
266                } finally {
267                    out.close();
268                }
269            } finally {
270                if (!cl.hasOption("keep-blk-files"))
271                    main.delBulkDataFiles();
272            }
273        } catch (ParseException e) {
274            System.err.println("xml2dcm: " + e.getMessage());
275            System.err.println(rb.getString("try"));
276            System.exit(2);
277        } catch (Exception e) {
278            System.err.println("xml2dcm: " + e.getMessage());
279            e.printStackTrace();
280            System.exit(2);
281        }
282    }
283
284    private static void configureBulkdata(Xml2Dcm xml2dcm, CommandLine cl)
285            throws Exception {
286        if (cl.hasOption("b")) {
287            xml2dcm.setIncludeBulkData(IncludeBulkData.YES);
288        }
289        if (cl.hasOption("B")) {
290            xml2dcm.setIncludeBulkData(IncludeBulkData.NO);
291        }
292        if (cl.hasOption("blk-file-prefix")) {
293            xml2dcm.setBulkDataFilePrefix(
294                    cl.getOptionValue("blk-file-prefix"));
295        }
296        if (cl.hasOption("blk-file-suffix")) {
297            xml2dcm.setBulkDataFileSuffix(
298                    cl.getOptionValue("blk-file-suffix"));
299        }
300        if (cl.hasOption("d")) {
301            File tempDir = new File(cl.getOptionValue("d"));
302            xml2dcm.setBulkDataDirectory(tempDir);
303        }
304        xml2dcm.setConcatenateBulkDataFiles(cl.hasOption("c"));
305        if (cl.hasOption("X")) {
306            xml2dcm.setBulkDataAttributes(
307                    parseXML(cl.getOptionValue("X")));
308        }
309    }
310
311    public void writeTo(OutputStream out) throws IOException {
312        if (nofmi)
313            fmi = null;
314        else if (fmi == null
315                ? withfmi
316                : tsuid != null && !tsuid.equals(
317                        fmi.getString(Tag.TransferSyntaxUID, null))) {
318            fmi = dataset.createFileMetaInformation(tsuid);
319        }
320        DicomOutputStream dos = new DicomOutputStream(
321                new BufferedOutputStream(out),
322                fmi != null
323                        ? UID.ExplicitVRLittleEndian
324                        : tsuid != null 
325                                ? tsuid
326                                : UID.ImplicitVRLittleEndian);
327        dos.setEncodingOptions(encOpts);
328        dos.writeDataset(fmi, dataset);
329        dos.finish();
330        dos.flush();
331    }
332
333    public void delBulkDataFiles() {
334        if (bulkDataFiles != null)
335            for (File f : bulkDataFiles)
336                f.delete();
337    }
338
339    public void parse(DicomInputStream dis) throws IOException {
340        dis.setIncludeBulkData(includeBulkData);
341        if (blkAttrs != null)
342            dis.setBulkDataDescriptor(BulkDataDescriptor.valueOf(blkAttrs));
343        dis.setBulkDataDirectory(blkDirectory);
344        dis.setBulkDataFilePrefix(blkFilePrefix);
345        dis.setBulkDataFileSuffix(blkFileSuffix);
346        dis.setConcatenateBulkDataFiles(catBlkFiles);
347        dataset = dis.readDataset(-1, -1);
348        fmi = dis.getFileMetaInformation();
349        bulkDataFiles = dis.getBulkDataFiles();
350    }
351
352    public void mergeXML(String fname) throws Exception {
353        if (dataset == null)
354            dataset = new Attributes();
355        ContentHandlerAdapter ch = new ContentHandlerAdapter(dataset);
356        parseXML(fname, ch);
357        Attributes fmi2 = ch.getFileMetaInformation();
358        if (fmi2 != null)
359            fmi = fmi2;
360    }
361
362
363    public void mergeXML(InputStream inputStream) throws Exception {
364        if (dataset == null) {
365            dataset = new Attributes();
366        }
367        ContentHandlerAdapter ch = new ContentHandlerAdapter(dataset);
368        parseXML(inputStream, ch);
369        Attributes fmi2 = ch.getFileMetaInformation();
370        if (fmi2 != null) {
371            fmi = fmi2;
372        }
373    }
374
375    public static Attributes parseXML(String fname) throws Exception {
376        Attributes attrs = new Attributes();
377        ContentHandlerAdapter ch = new ContentHandlerAdapter(attrs);
378        parseXML(fname, ch);
379        return attrs;
380    }
381
382    private static void parseXML(String fname, ContentHandlerAdapter ch)
383            throws Exception {
384        if (fname.equals("-")) {
385            parseXML(System.in, ch);
386        } else {
387            parseXML(new FileInputStream(fname), ch);
388        }
389    }
390
391    private static void parseXML(InputStream inputStream, ContentHandlerAdapter ch)
392            throws Exception {
393        SAXParserFactory f = SAXParserFactory.newInstance();
394        SAXParser p = f.newSAXParser();
395        p.parse(inputStream, ch);
396    }
397
398}