| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* $Id$ */ |
| |
| package org.apache.xmlgraphics.xmp; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.io.UnsupportedEncodingException; |
| |
| import javax.xml.transform.TransformerException; |
| import javax.xml.transform.stream.StreamSource; |
| |
| import org.apache.commons.io.IOUtils; |
| import org.apache.commons.io.output.ByteArrayOutputStream; |
| |
| /** |
| * This class is a parser for XMP packets. By default, it tries to locate the first XMP packet |
| * it finds and parses it. |
| * <p> |
| * Important: Before you use this class to look for an XMP packet in some random file, please read |
| * the chapter on "Scanning Files for XMP Packets" in the XMP specification! |
| */ |
| public final class XMPPacketParser { |
| |
| private XMPPacketParser() { |
| } |
| |
| private static final byte[] PACKET_HEADER; |
| private static final byte[] PACKET_HEADER_END; |
| private static final byte[] PACKET_TRAILER; |
| |
| static { |
| try { |
| PACKET_HEADER = "<?xpacket begin=".getBytes("US-ASCII"); |
| PACKET_HEADER_END = "?>".getBytes("US-ASCII"); |
| PACKET_TRAILER = "<?xpacket".getBytes("US-ASCII"); |
| } catch (UnsupportedEncodingException e) { |
| throw new RuntimeException("Incompatible JVM! US-ASCII encoding not supported."); |
| } |
| } |
| |
| /** |
| * Locates an XMP packet in a stream, parses it and returns the XMP metadata. If no |
| * XMP packet is found until the stream ends, null is returned. Note: This method |
| * only finds the first XMP packet in a stream. And it cannot determine whether it |
| * has found the right XMP packet if there are multiple packets. |
| * @param in the InputStream to search |
| * @return the parsed XMP metadata or null if no XMP packet is found |
| * @throws IOException if an I/O error occurs |
| * @throws TransformerException if an error occurs while parsing the XMP packet |
| */ |
| public static Metadata parse(InputStream in) throws IOException, TransformerException { |
| if (!in.markSupported()) { |
| in = new java.io.BufferedInputStream(in); |
| } |
| boolean foundXMP = skipAfter(in, PACKET_HEADER); |
| if (!foundXMP) { |
| return null; |
| } |
| //TODO Inspect "begin" attribute! |
| if (!skipAfter(in, PACKET_HEADER_END)) { |
| throw new IOException("Invalid XMP packet header!"); |
| } |
| ByteArrayOutputStream baout = null; |
| Metadata metadata = new Metadata(); |
| try { |
| //TODO think about not buffering this but for example, parse in another thread |
| //ex. using PipedInput/OutputStream |
| baout = new ByteArrayOutputStream(); |
| //TODO Do with TeeInputStream when Commons IO 1.4 is available |
| if (!skipAfter(in, PACKET_TRAILER, baout)) { |
| throw new IOException("XMP packet not properly terminated!"); |
| } |
| metadata = XMPParser.parseXMP( |
| new StreamSource(new ByteArrayInputStream(baout.toByteArray()))); |
| } finally { |
| IOUtils.closeQuietly(baout); |
| } |
| return metadata; |
| } |
| |
| private static boolean skipAfter(InputStream in, byte[] match) throws IOException { |
| return skipAfter(in, match, null); |
| } |
| |
| private static boolean skipAfter(InputStream in, byte[] match, OutputStream out) |
| throws IOException { |
| int found = 0; |
| int len = match.length; |
| int b; |
| while ((b = in.read()) >= 0) { |
| if (b == match[found]) { |
| found++; |
| if (found == len) { |
| return true; |
| } |
| } else { |
| if (out != null) { |
| if (found > 0) { |
| out.write(match, 0, found); |
| } |
| out.write(b); |
| } |
| found = 0; |
| } |
| } |
| return false; |
| } |
| |
| } |