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.ByteArrayOutputStream; 042import java.io.IOException; 043import java.util.ArrayList; 044import java.util.LinkedList; 045 046import org.dcm4che3.data.Attributes; 047import org.dcm4che3.data.BulkData; 048import org.dcm4che3.data.ElementDictionary; 049import org.dcm4che3.data.Fragments; 050import org.dcm4che3.data.PersonName; 051import org.dcm4che3.data.Sequence; 052import org.dcm4che3.data.VR; 053import org.dcm4che3.util.Base64; 054import org.dcm4che3.util.ByteUtils; 055import org.dcm4che3.util.TagUtils; 056import org.xml.sax.SAXException; 057import org.xml.sax.helpers.DefaultHandler; 058 059/** 060 * @author Gunter Zeilinger <gunterze@gmail.com> 061 */ 062public class ContentHandlerAdapter extends DefaultHandler { 063 064 private Attributes fmi; 065 private final boolean bigEndian; 066 private final boolean addBulkDataReferences; 067 private final LinkedList<Attributes> items = new LinkedList<Attributes>(); 068 private final LinkedList<Sequence> seqs = new LinkedList<Sequence>(); 069 070 private final ByteArrayOutputStream bout = new ByteArrayOutputStream(64); 071 private final char[] carry = new char[4]; 072 private int carryLen; 073 private final StringBuilder sb = new StringBuilder(64); 074 private final ArrayList<String> values = new ArrayList<String>(); 075 private PersonName pn; 076 private PersonName.Group pnGroup; 077 private int tag; 078 private String privateCreator; 079 private VR vr; 080 private BulkData bulkData; 081 private Fragments dataFragments; 082 private boolean processCharacters; 083 private boolean inlineBinary; 084 085 public ContentHandlerAdapter(Attributes attrs, boolean addBulkDataReferences) { 086 if (attrs == null) 087 throw new NullPointerException(); 088 items.add(attrs); 089 bigEndian = attrs.bigEndian(); 090 this.addBulkDataReferences = addBulkDataReferences; 091 } 092 093 public ContentHandlerAdapter(Attributes attrs) { 094 this(attrs, false); 095 } 096 097 public Attributes getFileMetaInformation() { 098 return fmi; 099 } 100 101 @Override 102 public void startElement(String uri, String localName, String qName, 103 org.xml.sax.Attributes atts) throws SAXException { 104 switch (qName.charAt(0)) { 105 case 'A': 106 if (qName.equals("Alphabetic")) 107 startPNGroup(PersonName.Group.Alphabetic); 108 break; 109 case 'B': 110 if (qName.equals("BulkData")) 111 bulkData(atts.getValue("uuid"), atts.getValue("uri")); 112 break; 113 case 'D': 114 if (qName.equals("DicomAttribute")) 115 startDicomAttribute( 116 (int) Long.parseLong(atts.getValue("tag"), 16), 117 atts.getValue("privateCreator"), 118 atts.getValue("vr")); 119 else if (qName.equals("DataFragment")) 120 startDataFragment(Integer.parseInt(atts.getValue("number"))); 121 break; 122 case 'F': 123 if (qName.equals("FamilyName")) 124 startText(); 125 break; 126 case 'G': 127 if (qName.equals("GivenName")) 128 startText(); 129 break; 130 case 'I': 131 if (qName.equals("Item")) 132 startItem(Integer.parseInt(atts.getValue("number"))); 133 else if (qName.equals("InlineBinary")) 134 startInlineBinary(); 135 else if (qName.equals("Ideographic")) 136 startPNGroup(PersonName.Group.Ideographic); 137 break; 138 case 'L': 139 if (qName.equals("Length")) 140 startText(); 141 break; 142 case 'M': 143 if (qName.equals("MiddleName")) 144 startText(); 145 break; 146 case 'N': 147 if (qName.equals("NamePrefix") || qName.equals("NameSuffix")) 148 startText(); 149 break; 150 case 'O': 151 if (qName.equals("Offset")) 152 startText(); 153 break; 154 case 'P': 155 if (qName.equals("PersonName")) { 156 startPersonName(Integer.parseInt(atts.getValue("number"))); 157 } else if (qName.equals("Phonetic")) 158 startPNGroup(PersonName.Group.Phonetic); 159 break; 160 case 'T': 161 if (qName.equals("TransferSyntax")) 162 startText(); 163 break; 164 case 'U': 165 if (qName.equals("URI")) 166 startText(); 167 break; 168 case 'V': 169 if (qName.equals("Value")) { 170 startValue(Integer.parseInt(atts.getValue("number"))); 171 startText(); 172 } 173 break; 174 } 175 } 176 177 private void bulkData(String uuid, String uri) { 178 bulkData = new BulkData(uuid, uri, items.getLast().bigEndian()); 179 } 180 181 private void startInlineBinary() { 182 processCharacters = true; 183 inlineBinary = true; 184 bout.reset(); 185 } 186 187 private void startText() { 188 processCharacters = true; 189 inlineBinary = false; 190 sb.setLength(0); 191 } 192 193 private void startDicomAttribute(int tag, String privateCreator, 194 String vr) { 195 this.tag = tag; 196 this.privateCreator = privateCreator; 197 this.vr = vr != null ? VR.valueOf(vr) 198 : ElementDictionary.vrOf(tag, privateCreator); 199 if (this.vr == VR.SQ) 200 seqs.add(items.getLast().newSequence(privateCreator, tag, 10)); 201 } 202 203 private void startDataFragment(int number) { 204 if (dataFragments == null) 205 dataFragments = items.getLast() 206 .newFragments(privateCreator, tag, vr, 10); 207 while (dataFragments.size() < number-1) 208 dataFragments.add(ByteUtils.EMPTY_BYTES); 209 } 210 211 private void startItem(int number) { 212 Sequence seq = seqs.getLast(); 213 while (seq.size() < number-1) 214 seq.add(new Attributes(0)); 215 Attributes item = new Attributes(); 216 seq.add(item); 217 items.add(item); 218 } 219 220 private void startValue(int number) { 221 while (values.size() < number-1) 222 values.add(null); 223 } 224 225 private void startPersonName(int number) { 226 startValue(number); 227 pn = new PersonName(); 228 } 229 230 private void startPNGroup(PersonName.Group pnGroup) { 231 this.pnGroup = pnGroup; 232 } 233 234 @Override 235 public void characters(char[] ch, int offset, int len) 236 throws SAXException { 237 if (processCharacters) 238 if (inlineBinary) 239 try { 240 len = removeWhitespaces(ch, offset, len); 241 if (carryLen != 0) { 242 int copy = Math.min(4 - carryLen, len); 243 System.arraycopy(ch, offset, carry, carryLen, copy); 244 if ((carryLen += copy) < 4) 245 return; 246 247 Base64.decode(carry, 0, 4, bout); 248 offset += copy; 249 len -= copy; 250 } 251 if ((carryLen = len & 3) != 0) { 252 len -= carryLen; 253 System.arraycopy(ch, offset + len, carry, 0, carryLen); 254 } 255 Base64.decode(ch, offset, len, bout); 256 } catch (IOException e) { 257 throw new RuntimeException(e); 258 } 259 else 260 sb.append(ch, offset, len); 261 } 262 263 private static int removeWhitespaces(char[] ch, int offset, int len) { 264 int ws = 0; 265 int srcPos = -1; 266 int destPos = -1; 267 int copy = 0; 268 for (int i = offset, end = offset + len; i < end; i++) { 269 switch (ch[i]) { 270 case ' ': 271 case '\t': 272 case '\r': 273 case '\n': 274 if (copy > 0) { 275 System.arraycopy(ch, srcPos, ch, destPos, copy); 276 destPos += copy; 277 copy = 0; 278 } else if (destPos < 0) { 279 destPos = i; 280 } 281 srcPos = i + 1; 282 ws++; 283 break; 284 default: 285 if (ws > 0) 286 copy++; 287 } 288 } 289 if (copy > 0) 290 System.arraycopy(ch, srcPos, ch, destPos, copy); 291 return len - ws; 292 } 293 294 @Override 295 public void endElement(String uri, String localName, String qName) 296 throws SAXException { 297 switch (qName.charAt(0)) { 298 case 'D': 299 if (qName.equals("DicomAttribute")) 300 endDicomAttribute(); 301 else if (qName.equals("DataFragment")) 302 endDataFragment(); 303 break; 304 case 'F': 305 if (qName.equals("FamilyName")) 306 endPNComponent(PersonName.Component.FamilyName); 307 break; 308 case 'G': 309 if (qName.equals("GivenName")) 310 endPNComponent(PersonName.Component.GivenName); 311 break; 312 case 'I': 313 if (qName.equals("Item")) 314 endItem(); 315 else if (qName.equals("InlineBinary")) 316 endInlineBinary(); 317 break; 318 case 'M': 319 if (qName.equals("MiddleName")) 320 endPNComponent(PersonName.Component.MiddleName); 321 break; 322 case 'N': 323 if (qName.equals("NamePrefix")) 324 endPNComponent(PersonName.Component.NamePrefix); 325 else if (qName.equals("NameSuffix")) 326 endPNComponent(PersonName.Component.NameSuffix); 327 break; 328 case 'P': 329 if (qName.equals("PersonName")) 330 endPersonName(); 331 break; 332 case 'V': 333 if (qName.equals("Value")) { 334 endValue(); 335 } 336 break; 337 } 338 processCharacters = false; 339 } 340 341 @Override 342 public void endDocument() throws SAXException { 343 if (fmi != null) 344 fmi.trimToSize(); 345 items.getFirst().trimToSize(); 346 } 347 348 private void endDataFragment() { 349 dataFragments.add(getBytes()); 350 } 351 352 private void endDicomAttribute() { 353 if (vr == VR.SQ) { 354 seqs.removeLast().trimToSize(); 355 return; 356 } 357 if (dataFragments != null) { 358 dataFragments.trimToSize(); 359 dataFragments = null; 360 return; 361 } 362 Attributes attrs = items.getLast(); 363 if (TagUtils.isFileMetaInformation(tag)) { 364 if (fmi == null) 365 fmi = new Attributes(); 366 attrs = fmi; 367 } 368 if (bulkData != null) { 369 attrs.setValue(privateCreator, tag, vr, 370 bulkData.hasFragments() ? bulkData.toFragments(privateCreator, tag, vr) : bulkData); 371 if (addBulkDataReferences) 372 attrs.getRoot().addBulkDataReference(privateCreator, tag, vr, bulkData, attrs.itemPointers()); 373 bulkData = null; 374 } else if (inlineBinary) { 375 attrs.setBytes(privateCreator, tag, vr, getBytes()); 376 } else { 377 attrs.setString(privateCreator, tag, vr, getStrings()); 378 } 379 } 380 381 private void endItem() { 382 items.removeLast().trimToSize(); 383 vr = VR.SQ; 384 } 385 386 private void endInlineBinary() throws SAXException { 387 if (carryLen != 0) { 388 // Attempting to end the inline binary section while we still have leftover characters in the carry 389 throw new SAXException("Inline binary data contained invalid number of characters"); 390 } 391 } 392 393 private void endPersonName() { 394 values.add(pn.toString()); 395 pn = null; 396 } 397 398 private void endValue() { 399 values.add(getString()); 400 } 401 402 private void endPNComponent(PersonName.Component pnComp) { 403 pn.set(pnGroup, pnComp, getString()); 404 } 405 406 private String getString() { 407 return sb.toString(); 408 } 409 410 private byte[] getBytes() { 411 byte[] b = bout.toByteArray(); 412 return bigEndian ? vr.toggleEndian(b, false) : b; 413 } 414 415 private String[] getStrings() { 416 try { 417 return values.toArray(new String[values.size()]); 418 } finally {; 419 values.clear(); 420 } 421 } 422 423}