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.io; 040 041import java.io.IOException; 042 043import org.dcm4che3.data.Attributes; 044import org.dcm4che3.data.BulkData; 045import org.dcm4che3.data.ElementDictionary; 046import org.dcm4che3.data.Fragments; 047import org.dcm4che3.data.PersonName; 048import org.dcm4che3.data.Sequence; 049import org.dcm4che3.data.SpecificCharacterSet; 050import org.dcm4che3.data.Tag; 051import org.dcm4che3.data.VR; 052import org.dcm4che3.data.Value; 053import org.dcm4che3.io.DicomInputStream.IncludeBulkData; 054import org.dcm4che3.util.Base64; 055import org.dcm4che3.util.ByteUtils; 056import org.dcm4che3.util.TagUtils; 057import org.xml.sax.ContentHandler; 058import org.xml.sax.SAXException; 059import org.xml.sax.helpers.AttributesImpl; 060 061/** 062 * @author Gunter Zeilinger <gunterze@gmail.com> 063 */ 064public class SAXWriter implements DicomInputHandler { 065 066 private static final String NAMESPACE = "http://dicom.nema.org/PS3.19/models/NativeDICOM"; 067 private static final int BASE64_CHUNK_LENGTH = 256 * 3; 068 private static final int BUFFER_LENGTH = 256 * 4; 069 070 private boolean includeKeyword = true; 071 private String namespace = ""; 072 073 private final ContentHandler ch; 074 private final AttributesImpl atts = new AttributesImpl(); 075 private final char[] buffer = new char[BUFFER_LENGTH]; 076 077 public SAXWriter(ContentHandler ch) { 078 this.ch = ch; 079 } 080 081 public final boolean isIncludeKeyword() { 082 return includeKeyword; 083 } 084 085 public final void setIncludeKeyword(boolean includeKeyword) { 086 this.includeKeyword = includeKeyword; 087 } 088 089 public final boolean isIncludeNamespaceDeclaration() { 090 return namespace == NAMESPACE; 091 } 092 093 public final void setIncludeNamespaceDeclaration(boolean includeNameSpaceDeclaration) { 094 this.namespace = includeNameSpaceDeclaration ? NAMESPACE : ""; 095 } 096 097 public void write(Attributes attrs) throws SAXException { 098 startDocument(); 099 writeItem(attrs); 100 endDocument(); 101 } 102 103 private void writeItem(final Attributes item) throws SAXException { 104 final SpecificCharacterSet cs = item.getSpecificCharacterSet(); 105 try { 106 item.accept(new Attributes.Visitor(){ 107 108 @Override 109 public boolean visit(Attributes attrs, int tag, VR vr, Object value) 110 throws Exception { 111 writeAttribute(tag, vr, value, cs, item); 112 return true; 113 }}, 114 false); 115 } catch (SAXException e) { 116 throw e; 117 } catch (Exception e) { 118 throw new RuntimeException(e); 119 } 120 } 121 122 123 @Override 124 public void startDataset(DicomInputStream dis) throws IOException { 125 try { 126 startDocument(); 127 } catch (SAXException e) { 128 throw new IOException(e); 129 } 130 } 131 132 @Override 133 public void endDataset(DicomInputStream dis) throws IOException { 134 try { 135 endDocument(); 136 } catch (SAXException e) { 137 throw new IOException(e); 138 } 139 } 140 141 private void startDocument() throws SAXException { 142 ch.startDocument(); 143 startElement("NativeDicomModel", "xml:space", "preserve"); 144 } 145 146 private void endDocument() throws SAXException { 147 endElement("NativeDicomModel"); 148 ch.endDocument(); 149 } 150 151 private void startElement(String name, String attrName, int attrValue) 152 throws SAXException { 153 startElement(name, attrName, Integer.toString(attrValue)); 154 } 155 156 private void startElement(String name, String attrName, String attrValue) 157 throws SAXException { 158 addAttribute(attrName, attrValue); 159 startElement(name); 160 } 161 162 private void startElement(String name) throws SAXException { 163 ch.startElement(namespace, name, name, atts); 164 atts.clear(); 165 } 166 167 private void endElement(String name) throws SAXException { 168 ch.endElement(namespace, name, name); 169 } 170 171 private void addAttribute(String name, String value) { 172 atts.addAttribute(namespace, name, name, "NMTOKEN", value); 173 } 174 175 private void writeAttribute(int tag, VR vr, Object value, 176 SpecificCharacterSet cs, Attributes attrs) throws SAXException { 177 if (TagUtils.isGroupLength(tag) || TagUtils.isPrivateCreator(tag)) 178 return; 179 180 String privateCreator = attrs.getPrivateCreator(tag); 181 addAttributes(tag, vr, privateCreator); 182 startElement("DicomAttribute"); 183 if (value instanceof Value) 184 writeAttribute((Value) value, attrs.bigEndian()); 185 else if (!vr.isInlineBinary()) { 186 writeValues(vr, value, attrs.bigEndian(), 187 attrs.getSpecificCharacterSet(vr)); 188 } else if (value instanceof byte[]) { 189 writeInlineBinary(attrs.bigEndian() 190 ? vr.toggleEndian((byte[]) value, true) 191 : (byte[]) value); 192 } else 193 throw new IllegalArgumentException("vr: " + vr + ", value class: " 194 + value.getClass()); 195 endElement("DicomAttribute"); 196 } 197 198 private void writeAttribute(Value value, boolean bigEndian) 199 throws SAXException { 200 if (value.isEmpty()) 201 return; 202 203 if (value instanceof Sequence) { 204 Sequence seq = (Sequence) value; 205 int number = 0; 206 for (Attributes item : seq) { 207 startElement("Item", "number", ++number); 208 writeItem(item); 209 endElement("Item"); 210 } 211 } else if (value instanceof Fragments) { 212 Fragments frags = (Fragments) value; 213 if (frags.size() > 1 && frags.get(1) instanceof BulkData) 214 writeBulkData(BulkData.fromFragments(frags)); 215 else { 216 int number = 0; 217 for (Object frag : frags) { 218 ++number; 219 if (frag instanceof Value && ((Value) frag).isEmpty()) 220 continue; 221 startElement("DataFragment", "number", number); 222 byte[] b = (byte[]) frag; 223 if (bigEndian) 224 frags.vr().toggleEndian(b, true); 225 writeInlineBinary(b); 226 endElement("DataFragment"); 227 } 228 } 229 } else if (value instanceof BulkData) { 230 writeBulkData((BulkData) value); 231 } 232 } 233 234 @Override 235 public void readValue(DicomInputStream dis, Attributes attrs) 236 throws IOException { 237 int tag = dis.tag(); 238 VR vr = dis.vr(); 239 int len = dis.length(); 240 if (TagUtils.isGroupLength(tag) || TagUtils.isPrivateCreator(tag)) { 241 dis.readValue(dis, attrs); 242 } else if (dis.getIncludeBulkData() == IncludeBulkData.NO 243 && dis.isBulkData(attrs)) { 244 if (len == -1) 245 dis.readValue(dis, attrs); 246 else 247 dis.skipFully(len); 248 } else try { 249 String privateCreator = attrs.getPrivateCreator(tag); 250 addAttributes(tag, vr, privateCreator); 251 startElement("DicomAttribute"); 252 if (vr == VR.SQ || len == -1) { 253 dis.readValue(dis, attrs); 254 if (vr != VR.SQ && dis.getIncludeFragmentBulkData() == IncludeBulkData.URI) { 255 writeBulkData(BulkData.fromFragments((Fragments) attrs.remove(privateCreator, tag))); 256 } 257 } else if (len > 0) { 258 if (dis.getIncludeBulkData() == IncludeBulkData.URI 259 && dis.isBulkData(attrs)) { 260 writeBulkData(dis.createBulkData()); 261 } else { 262 byte[] b = dis.readValue(); 263 if (tag == Tag.TransferSyntaxUID 264 || tag == Tag.SpecificCharacterSet) 265 attrs.setBytes(tag, vr, b); 266 if (vr.isInlineBinary()) 267 writeInlineBinary(dis.bigEndian() 268 ? vr.toggleEndian(b, false) 269 : b); 270 else 271 writeValues(vr, b, dis.bigEndian(), 272 attrs.getSpecificCharacterSet(vr)); 273 } 274 } 275 endElement("DicomAttribute"); 276 } catch (SAXException e) { 277 throw new IOException(e); 278 } 279 } 280 281 private void addAttributes(int tag, VR vr, String privateCreator) { 282 if (privateCreator != null) 283 tag &= 0xffff00ff; 284 if (includeKeyword) { 285 String keyword = ElementDictionary.keywordOf(tag, privateCreator); 286 if (keyword != null && !keyword.isEmpty()) 287 addAttribute("keyword", keyword); 288 } 289 addAttribute("tag", TagUtils.toHexString(tag)); 290 if (privateCreator != null) 291 addAttribute("privateCreator", privateCreator); 292 addAttribute("vr", vr.name()); 293 } 294 295 @Override 296 public void readValue(DicomInputStream dis, Sequence seq) 297 throws IOException { 298 try { 299 startElement("Item", "number", seq.size() + 1); 300 dis.readValue(dis, seq); 301 endElement("Item"); 302 } catch (SAXException e) { 303 throw new IOException(e); 304 } 305 } 306 307 @Override 308 public void readValue(DicomInputStream dis, Fragments frags) 309 throws IOException { 310 int len = dis.length(); 311 Object frag = ByteUtils.EMPTY_BYTES; 312 if (len > 0) 313 switch (dis.getIncludeFragmentBulkData()) { 314 case NO: 315 dis.skipFully(len); 316 return; 317 case URI: 318 frag = dis.createBulkData(); 319 break; 320 case YES: 321 byte[] b = dis.readValue(); 322 if (dis.bigEndian()) 323 frags.vr().toggleEndian(b, false); 324 try { 325 startElement("DataFragment", "number", frags.size() + 1); 326 writeInlineBinary(b); 327 endElement("DataFragment"); 328 } catch (SAXException e) { 329 throw new IOException(e); 330 } 331 break; 332 } 333 frags.add(frag); // increment size 334 } 335 336 private void writeValues(VR vr, Object val, boolean bigEndian, 337 SpecificCharacterSet cs) throws SAXException { 338 if (vr.isStringType()) 339 val = vr.toStrings(val, bigEndian, cs); 340 int vm = vr.vmOf(val); 341 for (int i = 0; i < vm; i++) { 342 String s = vr.toString(val, bigEndian, i, ""); 343 addAttribute("number", Integer.toString(i + 1)); 344 if (vr == VR.PN) { 345 PersonName pn = new PersonName(s, true); 346 startElement("PersonName"); 347 writePNGroup("Alphabetic", pn, PersonName.Group.Alphabetic); 348 writePNGroup("Ideographic", pn, PersonName.Group.Ideographic); 349 writePNGroup("Phonetic", pn, PersonName.Group.Phonetic); 350 endElement("PersonName"); 351 } else { 352 writeElement("Value", s); 353 } 354 } 355 } 356 357 private void writeInlineBinary(byte[] b) throws SAXException { 358 startElement("InlineBinary"); 359 char[] buf = buffer; 360 for (int off = 0; off < b.length;) { 361 int len = Math.min(b.length - off, BASE64_CHUNK_LENGTH); 362 Base64.encode(b, off, len, buf, 0); 363 ch.characters(buf, 0, (len * 4 / 3 + 3) & ~3); 364 off += len; 365 } 366 endElement("InlineBinary"); 367 } 368 369 private void writeBulkData(BulkData bulkData) 370 throws SAXException { 371 if (bulkData.uuid != null) 372 addAttribute("uuid", bulkData.uuid); 373 if (bulkData.uri != null) 374 addAttribute("uri", bulkData.uri); 375 startElement("BulkData"); 376 endElement("BulkData"); 377 } 378 379 private void writeElement(String qname, String s) throws SAXException { 380 if (s != null) { 381 startElement(qname); 382 char[] buf = buffer; 383 for (int off = 0, totlen = s.length(); off < totlen;) { 384 int len = Math.min(totlen - off, buf.length); 385 s.getChars(off, off += len, buf, 0); 386 ch.characters(buf, 0, len); 387 } 388 endElement(qname); 389 } 390 } 391 392 private void writePNGroup(String qname, PersonName pn, 393 PersonName.Group group) throws SAXException { 394 if (pn.contains(group)) { 395 startElement(qname); 396 writeElement("FamilyName", 397 pn.get(group, PersonName.Component.FamilyName)); 398 writeElement("GivenName", 399 pn.get(group, PersonName.Component.GivenName)); 400 writeElement("MiddleName", 401 pn.get(group, PersonName.Component.MiddleName)); 402 writeElement("NamePrefix", 403 pn.get(group, PersonName.Component.NamePrefix)); 404 writeElement("NameSuffix", 405 pn.get(group, PersonName.Component.NameSuffix)); 406 endElement(qname); 407 } 408 } 409 410}