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) 2012
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.data;
040
041import java.io.File;
042import java.io.FileNotFoundException;
043import java.io.IOException;
044import java.io.Serializable;
045import java.util.ArrayList;
046import java.util.Arrays;
047import java.util.HashMap;
048import java.util.LinkedList;
049import java.util.List;
050import java.util.Map;
051
052import javax.xml.parsers.ParserConfigurationException;
053import javax.xml.parsers.SAXParser;
054import javax.xml.parsers.SAXParserFactory;
055
056import org.dcm4che3.util.ByteUtils;
057import org.dcm4che3.util.ResourceLocator;
058import org.dcm4che3.util.StringUtils;
059import org.xml.sax.Locator;
060import org.xml.sax.SAXException;
061import org.xml.sax.helpers.DefaultHandler;
062
063/**
064 * IOD stands for Information Object Definition.
065 *
066 * @author Gunter Zeilinger <gunterze@gmail.com>
067 *
068 */
069public class IOD extends ArrayList<IOD.DataElement> {
070
071    private static final long serialVersionUID = -5065822488885801576L;
072
073    public enum DataElementType {
074        TYPE_0, TYPE_1, TYPE_2, TYPE_3
075    }
076
077    public static class DataElement implements Serializable {
078
079        private static final long serialVersionUID = -7460474415381086525L;
080
081        public final int tag;
082        public final VR vr;
083        public final DataElementType type;
084        public final int minVM;
085        public final int maxVM;
086        public final int valueNumber;
087        private Condition condition;
088        private Object values;
089        private int lineNumber = -1;
090
091        public DataElement(int tag, VR vr, DataElementType type,
092                int minVM, int maxVM, int valueNumber) {
093            this.tag = tag;
094            this.vr = vr;
095            this.type = type;
096            this.minVM = minVM;
097            this.maxVM = maxVM;
098            this.valueNumber = valueNumber;
099        }
100
101        public DataElement setCondition(Condition condition) {
102            this.condition = condition;
103            return this;
104        }
105
106        public Condition getCondition() {
107            return condition;
108        }
109
110        public int getValueNumber() {
111            return valueNumber;
112        }
113
114        public DataElement setValues(String... values) {
115            if (vr == VR.SQ)
116                throw new IllegalStateException("vr=SQ");
117            this.values = values;
118            return this;
119        }
120
121        public DataElement setValues(int... values) {
122            if (!vr.isIntType())
123                throw new IllegalStateException("vr=" + vr);
124            this.values = values;
125            return this;
126        }
127
128        public DataElement setValues(Code... values) {
129            if (vr != VR.SQ)
130                throw new IllegalStateException("vr=" + vr);
131            this.values = values;
132            return this;
133        }
134
135        public DataElement addItemIOD(IOD iod) {
136            if (this.values == null) {
137                this.values = new IOD[] { iod };
138            } else {
139                IOD[] iods = (IOD[]) this.values;
140                iods = Arrays.copyOf(iods, iods.length+1);
141                iods[iods.length - 1] = iod;
142                this.values = iods;
143            }
144            return this;
145        }
146
147        public Object getValues() {
148            return values;
149        }
150
151        public int getLineNumber() {
152            return lineNumber;
153        }
154
155        public DataElement setLineNumber(int lineNumber) {
156            this.lineNumber = lineNumber;
157            return this;
158        }
159
160   }
161
162    public abstract static class Condition {
163         protected String id;
164         protected boolean not;
165
166         public Condition id(String id) {
167             this.id = id;
168             return this;
169         }
170
171         public final String id() {
172             return id;
173         }
174
175         public final Condition not() {
176             this.not = !not;
177             return this;
178         }
179
180         public abstract boolean match(Attributes attrs);
181
182         public void addChild(Condition child) {
183             throw new UnsupportedOperationException();
184         }
185
186         public Condition trim() {
187             return this;
188         }
189
190         public boolean isEmpty() {
191             return false;
192         }
193
194    }
195
196    abstract static class CompositeCondition extends Condition  {
197        protected final ArrayList<Condition> childs = new ArrayList<Condition>();
198
199        public abstract boolean match(Attributes attrs);
200
201        @Override
202        public void addChild(Condition child) {
203            childs.add(child);
204        }
205
206        @Override
207        public Condition trim() {
208            int size = childs.size();
209            if (size == 1) {
210                Condition child = childs.get(0).id(id);
211                return not ? child.not() : child;
212            }
213            childs.trimToSize();
214            return this;
215        }
216
217        @Override
218        public boolean isEmpty() {
219            return childs.isEmpty();
220        }
221    }
222
223    public static class And extends CompositeCondition {
224
225        public boolean match(Attributes attrs) {
226            for (Condition child : childs) {
227                if (!child.match(attrs))
228                    return not;
229            }
230            return !not;
231        }
232   }
233
234    public static class Or extends CompositeCondition {
235
236        public boolean match(Attributes attrs) {
237            for (Condition child : childs) {
238                if (child.match(attrs))
239                    return !not;
240            }
241            return not;
242        }
243    }
244
245    public static class Present extends Condition {
246        protected final int tag;
247        protected final int[] itemPath;
248
249        public Present(int tag, int... itemPath) {
250            this.tag = tag;
251            this.itemPath = itemPath;
252        }
253
254        public boolean match(Attributes attrs) {
255            return not ? !item(attrs).containsValue(tag)
256                        : item(attrs).containsValue(tag);
257        }
258
259        protected Attributes item(Attributes attrs) {
260            for (int sqtag : itemPath) {
261                if (sqtag == -1)
262                attrs = (sqtag == -1)
263                        ? attrs.getParent()
264                        : attrs.getNestedDataset(sqtag);
265            }
266            return attrs;
267        }
268    }
269
270    public static class MemberOf extends Present {
271        private final VR vr;
272        private final int valueIndex;
273        private final boolean matchNotPresent;
274        private Object values;
275
276        public MemberOf(int tag, VR vr, int valueIndex,
277                boolean matchNotPresent, int... itemPath) {
278            super(tag, itemPath);
279            this.vr = vr;
280            this.valueIndex = valueIndex;
281            this.matchNotPresent = matchNotPresent;
282        }
283
284        public VR vr() {
285            return vr;
286        }
287
288        public MemberOf setValues(String... values) {
289            if (vr == VR.SQ)
290                throw new IllegalStateException("vr=SQ");
291            this.values = values;
292            return this;
293        }
294
295        public MemberOf setValues(int... values) {
296            if (!vr.isIntType())
297                throw new IllegalStateException("vr=" + vr);
298            this.values = values;
299            return this;
300        }
301
302        public MemberOf setValues(Code... values) {
303            if (vr != VR.SQ)
304                throw new IllegalStateException("vr=" + vr);
305            this.values = values;
306            return this;
307        }
308
309        public boolean match(Attributes attrs) {
310            if (values == null)
311                throw new IllegalStateException("values not initialized");
312            Attributes item = item(attrs);
313            if (item == null)
314                return matchNotPresent;
315
316            if (values instanceof int[])
317                return not ? !match(item, ((int[]) values))
318                           : match(item, ((int[]) values));
319            else if (values instanceof Code[])
320                return not ? !match(item, ((Code[]) values))
321                           : match(item, ((Code[]) values));
322            else
323                return not ? !match(item, ((String[]) values))
324                           : match(item, ((String[]) values));
325        }
326
327        private boolean match(Attributes item, String[] ss) {
328            String val = item.getString(tag, valueIndex);
329            if (val == null)
330                return not ? !matchNotPresent : matchNotPresent;
331            for (String s : ss) {
332                if (s.equals(val))
333                    return !not;
334            }
335            return not;
336        }
337
338        private boolean match(Attributes item, Code[] codes) {
339            Sequence seq = item.getSequence(tag);
340            if (seq != null)
341                for (Attributes codeItem : seq) {
342                    try {
343                        Code val = new Code(codeItem);
344                        for (Code code : codes) {
345                            if (code.equals(val))
346                                return !not;
347                        }
348                    } catch (NullPointerException npe) {}
349                }
350            return not;
351        }
352
353        private boolean match(Attributes item, int[] is) {
354            int val = item.getInt(tag, valueIndex, Integer.MIN_VALUE);
355            if (val == Integer.MIN_VALUE)
356                return matchNotPresent;
357            for (int i : is) {
358                if (i == val)
359                    return true;
360            }
361            return false;
362        }
363    }
364
365    private DataElementType type;
366    private Condition condition;
367    private int lineNumber = -1;
368
369    public void setType(DataElementType type) {
370        this.type = type;
371    }
372
373    public DataElementType getType() {
374        return type;
375    }
376
377    public void setCondition(Condition condition) {
378        this.condition = condition;
379    }
380
381    public Condition getCondition() {
382        return condition;
383    }
384
385    public int getLineNumber() {
386        return lineNumber;
387    }
388
389    public void setLineNumber(int lineNumber) {
390        this.lineNumber = lineNumber;
391    }
392
393    public void parse(String uri) throws IOException {
394        try {
395            SAXParserFactory f = SAXParserFactory.newInstance();
396            SAXParser parser = f.newSAXParser();
397            parser.parse(uri, new SAXHandler(this));
398        } catch (SAXException e) {
399            throw new IOException("Failed to parse " + uri, e);
400        } catch (ParserConfigurationException e) {
401            throw new RuntimeException(e);
402        }
403    }
404
405    private static class SAXHandler extends DefaultHandler {
406
407        private StringBuilder sb = new StringBuilder();
408        private boolean processCharacters;
409        private boolean elementConditions;
410        private boolean itemConditions;
411        private String idref;
412        private List<String> values = new ArrayList<String>();
413        private List<Code> codes = new ArrayList<Code>();
414        private LinkedList<IOD> iodStack = new LinkedList<IOD>();
415        private LinkedList<Condition> conditionStack = new LinkedList<Condition>();
416        private Map<String, IOD> id2iod = new HashMap<String, IOD>();
417        private Map<String, Condition> id2cond = new HashMap<String, Condition>();
418        private Locator locator;
419
420        public SAXHandler(IOD iod) {
421            iodStack.add(iod);
422        }
423
424        @Override
425        public void setDocumentLocator(Locator locator) {
426            this.locator = locator;
427        }
428
429        @Override
430        public void startElement(String uri, String localName, String qName,
431                org.xml.sax.Attributes atts) throws SAXException {
432            switch (qName.charAt(0)) {
433            case 'A':
434                if (qName.equals("And"))
435                    startCondition(qName, new And());
436                break;
437            case 'C':
438                if (qName.equals("Code"))
439                    startCode(
440                            atts.getValue("codeValue"),
441                            atts.getValue("codingSchemeDesignator"),
442                            atts.getValue("codingSchemeVersion"),
443                            atts.getValue("codeMeaning"));
444            case 'D':
445                if (qName.equals("DataElement"))
446                    startDataElement(
447                            atts.getValue("tag"),
448                            atts.getValue("vr"),
449                            atts.getValue("type"),
450                            atts.getValue("vm"),
451                            atts.getValue("items"),
452                            atts.getValue("valueNumber"));
453                break;
454            case 'I':
455                if (qName.equals("If"))
456                    startIf(atts.getValue("id"), atts.getValue("idref"));
457                else if (qName.equals("Item"))
458                    startItem(atts.getValue("id"),
459                            atts.getValue("idref"),
460                            atts.getValue("type"));
461                break;
462            case 'M':
463                if (qName.equals("MemberOf"))
464                    startCondition(qName, memberOf(atts));
465                break;
466            case 'N':
467                if (qName.equals("NotAnd"))
468                    startCondition(qName, new And().not());
469                else if (qName.equals("NotMemberOf"))
470                    startCondition(qName, memberOf(atts).not());
471                else if (qName.equals("NotOr"))
472                    startCondition(qName, new Or().not());
473                else if (qName.equals("NotPresent"))
474                    startCondition(qName, present(atts).not());
475                break;
476            case 'O':
477                if (qName.equals("Or"))
478                    startCondition(qName, new Or());
479                break;
480            case 'P':
481                if (qName.equals("Present"))
482                    startCondition(qName, present(atts));
483                break;
484            case 'V':
485                if (qName.equals("Value"))
486                    startValue();
487                break;
488            }
489        }
490
491        private Present present(org.xml.sax.Attributes atts)
492                throws SAXException {
493            int[] tagPath = tagPathOf(atts.getValue("tag"));
494            int lastIndex = tagPath.length-1;
495            return new Present(tagPath[lastIndex],
496                    lastIndex > 0 ? Arrays.copyOf(tagPath, lastIndex)
497                            : ByteUtils.EMPTY_INTS);
498        }
499
500        private MemberOf memberOf(org.xml.sax.Attributes atts)
501                throws SAXException {
502            int[] tagPath = tagPathOf(atts.getValue("tag"));
503            int lastIndex = tagPath.length-1;
504            return new MemberOf(
505                    tagPath[lastIndex],
506                    vrOf(atts.getValue("vr")),
507                    valueNumberOf(atts.getValue("valueNumber"), 1) - 1,
508                    matchNotPresentOf(atts.getValue("matchNotPresent")),
509                    lastIndex > 0 ? Arrays.copyOf(tagPath, lastIndex)
510                                  : ByteUtils.EMPTY_INTS);
511        }
512
513        private void startCode(String codeValue, 
514                String codingSchemeDesignator,
515                String codingSchemeVersion,
516                String codeMeaning) throws SAXException {
517            if (codeValue == null)
518                throw new SAXException("missing codeValue attribute");
519            if (codingSchemeDesignator == null)
520                throw new SAXException("missing codingSchemeDesignator attribute");
521            if (codeMeaning == null)
522                throw new SAXException("missing codeMeaning attribute");
523            codes.add(new Code(codeValue, codingSchemeDesignator, 
524                    codingSchemeVersion, codeMeaning));
525        }
526
527        @Override
528        public void endElement(String uri, String localName, String qName)
529                throws SAXException {
530            switch (qName.charAt(0)) {
531            case 'A':
532                if (qName.equals("And"))
533                    endCondition(qName);
534                break;
535            case 'D':
536                if (qName.equals("DataElement"))
537                    endDataElement();
538                break;
539            case 'I':
540                if (qName.equals("If"))
541                    endCondition(qName);
542                else if (qName.equals("Item"))
543                    endItem();
544                break;
545            case 'M':
546                if (qName.equals("MemberOf"))
547                    endCondition(qName);
548                break;
549            case 'N':
550                if (qName.equals("NotAnd"))
551                    endCondition(qName);
552                else if (qName.equals("NotMemberOf"))
553                    endCondition(qName);
554                else if (qName.equals("NotOr"))
555                    endCondition(qName);
556                else if (qName.equals("NotPresent"))
557                    endCondition(qName);
558                break;
559            case 'O':
560                if (qName.equals("Or"))
561                    endCondition(qName);
562                break;
563            case 'P':
564                if (qName.equals("Present"))
565                    endCondition(qName);
566                break;
567            case 'V':
568                if (qName.equals("Value"))
569                    endValue();
570                break;
571            }
572            processCharacters = false;
573            idref = null;
574        }
575
576        @Override
577        public void characters(char[] ch, int start, int length)
578                throws SAXException {
579            if (processCharacters)
580                sb.append(ch, start, length);
581        }
582
583        private void startDataElement(String tagStr, String vrStr,
584                String typeStr, String vmStr, String items,
585                String valueNumberStr) throws SAXException {
586            if (idref != null)
587                throw new SAXException("<Item> with idref must be empty");
588
589            IOD iod = iodStack.getLast();
590            int tag = tagOf(tagStr);
591            VR vr = vrOf(vrStr);
592            DataElementType type = typeOf(typeStr);
593            
594            int minVM = -1;
595            int maxVM = -1;
596            String vm = vr == VR.SQ ? items : vmStr;
597            if (vm != null) {
598                try {
599                    String[] ss = StringUtils.split(vm, '-');
600                    if (ss[0].charAt(0) != 'n') {
601                        minVM = Integer.parseInt(ss[0]);
602                        if (ss.length > 1) {
603                            if (ss[1].charAt(0) != 'n')
604                                maxVM = Integer.parseInt(ss[1]);
605                        } else {
606                            maxVM = minVM;
607                        }
608                    }
609                } catch (IllegalArgumentException e) {
610                    throw new SAXException(
611                            (vr == VR.SQ ? "invalid items=\"" 
612                                         : "invalid vm=\"")
613                            + vm + '"');
614                }
615            }
616            DataElement el = new DataElement(tag, vr, type, minVM, maxVM,
617                    valueNumberOf(valueNumberStr, 0));
618            if (locator != null)
619                el.setLineNumber(locator.getLineNumber());
620            iod.add(el);
621            elementConditions = true;
622            itemConditions = false;
623        }
624
625        private DataElementType typeOf(String s) throws SAXException {
626            if (s == null)
627                throw new SAXException("missing type attribute");
628            try {
629                return DataElementType.valueOf("TYPE_" + s);
630            } catch (IllegalArgumentException e) {
631                throw new SAXException("unrecognized type=\"" + s + '"');
632            }
633        }
634
635        private VR vrOf(String s) throws SAXException {
636            try {
637                return VR.valueOf(s);
638            } catch (NullPointerException e) {
639                throw new SAXException("missing vr attribute");
640            } catch (IllegalArgumentException e) {
641                throw new SAXException("unrecognized vr=\"" + s + '"');
642            }
643        }
644
645        private int tagOf(String s) throws SAXException {
646            try {
647               return (int) Long.parseLong(s, 16);
648            } catch (NullPointerException e) {
649                throw new SAXException("missing tag attribute");
650            } catch (IllegalArgumentException e) {
651                throw new SAXException("invalid tag=\"" + s + '"');
652            }
653        }
654
655        private int[] tagPathOf(String s) throws SAXException {
656            String[] ss = StringUtils.split(s, '/');
657            if (ss.length == 0)
658                throw new SAXException("missing tag attribute");
659            
660            try {
661                int[] tagPath = new int[ss.length];
662                for (int i = 0; i < tagPath.length; i++)
663                    tagPath[i] = ss[i].equals("..") 
664                                ? -1
665                                : (int) Long.parseLong(s, 16);
666                return tagPath;
667            } catch (IllegalArgumentException e) {
668                throw new SAXException("invalid tag=\"" + s + '"');
669            }
670        }
671
672
673        private int valueNumberOf(String s, int def) throws SAXException {
674            try {
675               return s != null ? Integer.parseInt(s) : def;
676            } catch (IllegalArgumentException e) {
677                throw new SAXException("invalid valueNumber=\"" + s + '"');
678            }
679        }
680
681        private boolean matchNotPresentOf(String s) {
682            return s != null && s.equalsIgnoreCase("true");
683        }
684
685
686        private DataElement getLastDataElement() {
687            IOD iod = iodStack.getLast();
688            return iod.get(iod.size()-1);
689        }
690
691        private void endDataElement() throws SAXException {
692            DataElement el = getLastDataElement();
693            if (!values.isEmpty()) {
694                try {
695                    if (el.vr.isIntType())
696                        el.setValues(parseInts(values));
697                    else
698                        el.setValues(values.toArray(new String[values.size()]));
699                } catch (IllegalStateException e) {
700                    throw new SAXException("unexpected <Value>");
701                }
702                values.clear();
703            }
704            if (!codes.isEmpty()) {
705                try {
706                    el.setValues(codes.toArray(new Code[codes.size()]));
707                } catch (IllegalStateException e) {
708                    throw new SAXException("unexpected <Code>");
709                }
710                codes.clear();
711            }
712            elementConditions = false;
713        }
714
715        private int[] parseInts(List<String> list) {
716            int[] is = new int[list.size()];
717            for (int i = 0; i < is.length; i++)
718                is[i] = Integer.parseInt(list.get(i));
719            return is;
720        }
721
722        private void startValue() {
723            sb.setLength(0);
724            processCharacters = true;
725        }
726
727        private void endValue() {
728            values.add(sb.toString());
729        }
730
731        private void startItem(String id, String idref, String type) throws SAXException {
732            IOD iod;
733            if (idref != null) {
734                if (type != null)
735                    throw new SAXException("<Item> with idref must not specify type");
736                    
737                iod = id2iod.get(idref);
738                if (iod == null)
739                    throw new SAXException(
740                            "could not resolve <Item idref:\"" + idref + "\"/>");
741            } else { 
742                iod = new IOD();
743                if (type != null)
744                    iod.setType(typeOf(type));
745                if (locator != null)
746                    iod.setLineNumber(locator.getLineNumber());
747            }
748            getLastDataElement().addItemIOD(iod);
749            iodStack.add(iod);
750            if (id != null)
751                id2iod.put(id, iod);
752
753            this.idref = idref;
754            itemConditions = true;
755            elementConditions = false;
756        }
757
758        private void endItem() {
759            iodStack.removeLast().trimToSize();
760            itemConditions = false;
761        }
762
763        private void startIf(String id, String idref) throws SAXException {
764            if (!conditionStack.isEmpty())
765                throw new SAXException("unexpected <If>");
766
767            Condition cond;
768            if (idref != null) {
769                cond = id2cond.get(idref);
770                if (cond == null)
771                    throw new SAXException(
772                            "could not resolve <If idref:\"" + idref + "\"/>");
773            } else { 
774                cond = new And().id(id);
775            }
776            conditionStack.add(cond);
777            if (id != null)
778                id2cond.put(id, cond);
779            this.idref = idref;
780        }
781
782       private void startCondition(String name, Condition cond)
783               throws SAXException {
784            if (!(elementConditions || itemConditions))
785               throw new SAXException("unexpected <" + name + '>');
786
787            conditionStack.add(cond);
788        }
789
790        private void endCondition(String name) throws SAXException {
791            Condition cond = conditionStack.removeLast();
792            if (cond.isEmpty())
793                throw new SAXException('<' + name + "> must not be empty");
794
795            if (!values.isEmpty()) {
796                try {
797                    MemberOf memberOf = (MemberOf) cond;
798                    if (memberOf.vr.isIntType())
799                        memberOf.setValues(parseInts(values));
800                    else
801                        memberOf.setValues(values.toArray(new String[values.size()]));
802                } catch (Exception e) {
803                    throw new SAXException("unexpected <Value> contained by <"
804                            + name + ">");
805                }
806                values.clear();
807            }
808
809            if (!codes.isEmpty()) {
810                try {
811                    ((MemberOf) cond).setValues(codes.toArray(new Code[codes.size()]));
812                } catch (Exception e) {
813                    throw new SAXException("unexpected <Code> contained by <"
814                            + name + ">");
815                }
816                codes.clear();
817            }
818
819            if (conditionStack.isEmpty()) {
820                if (elementConditions)
821                    getLastDataElement().setCondition(cond.trim());
822                else 
823                    iodStack.getLast().setCondition(cond.trim());
824                elementConditions = false;
825                itemConditions = false;
826            } else
827                conditionStack.getLast().addChild(cond.trim());
828        }
829    }
830
831    public static IOD load(String uri) throws IOException {
832        if (uri.startsWith("resource:")) {
833            try {
834                uri = ResourceLocator.getResource(uri.substring(9), IOD.class);
835            } catch (NullPointerException npe) {
836                throw new FileNotFoundException(uri);
837            }
838        } else if (uri.indexOf(':') < 2) {
839            uri = new File(uri).toURI().toString();
840        }
841        IOD iod = new IOD();
842        iod.parse(uri);
843        iod.trimToSize();
844        return iod;
845    }
846
847    public static IOD valueOf(Code code) {
848        IOD iod = new IOD();
849        iod.add(new DataElement(
850                Tag.CodeValue, VR.SH, DataElementType.TYPE_1, 1, 1, 0)
851                .setValues(code.getCodeValue()));
852        iod.add(new DataElement(
853                Tag.CodingSchemeDesignator, VR.SH, DataElementType.TYPE_1, 1, 1, 0)
854                .setValues(code.getCodingSchemeDesignator()));
855        String codingSchemeVersion = code.getCodingSchemeVersion();
856        if (codingSchemeVersion == null)
857            iod.add(new DataElement(
858                    Tag.CodingSchemeVersion, VR.SH, DataElementType.TYPE_0, -1, -1, 0));
859        else
860            iod.add(new DataElement(
861                    Tag.CodingSchemeVersion, VR.SH, DataElementType.TYPE_1, 1, 1, 0));
862            
863        return iod;
864    }
865}