package folioxml.export.html;
import folioxml.config.*;
import folioxml.core.InvalidMarkupException;
import folioxml.core.Pair;
import folioxml.core.TokenUtils;
import folioxml.export.FileNode;
import folioxml.xml.Node;
import folioxml.xml.NodeList;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RenameImages {
ExportLocations export;
public RenameImages(InfobaseSet infobase_set, ExportLocations export) {
this.infobase_set = infobase_set;
this.export = export;
signatures = AllFileSignatures();
nextAssetId = infobase_set.getInteger("asset_start_index");
if (nextAssetId == null) nextAssetId = 0;
asset_use_index_in_url = infobase_set.getBool("asset_use_index_in_url");
if (asset_use_index_in_url == null) asset_use_index_in_url = false;
}
Boolean asset_use_index_in_url;
public void process(NodeList nodes, FileNode document) throws InvalidMarkupException, IOException {
Path document_base = export.getLocalPath(document.getRelativePath(), AssetType.Html, FolderCreation.None);
//
NodeList images = nodes.filterByTagName("img|object|link", true);
for (Node n : images.list()) {
//If it's an image, convert it to an img tag, if it's a data link, convert to 'a'.
//If type="ole" - oops!
//if type="folio" - we can fix this.
//link, dataLink
//link, objectName,
//object, type="folio"
//object type="data-link"
boolean isImage = (n.matches("object") && TokenUtils.fastMatches("bitmap|metafile|picture", n.get("handler"))) || n.matches("img");
boolean isImageLink = n.matches("link|a") && TokenUtils.fastMatches("bitmap|metafile|picture", n.get("handler"));
boolean isObjectLink = n.matches("link") && (n.get("objectName") != null || n.get("dataLink") != null);
if (n.matches("link") && !isObjectLink)
continue; //We don't care about web, program, query, popup, jump, or menu links.
/*boolean isLinkToImage
if (TokenUtils.fastMatches("folio|data-link", n.get("type"))){
continue;
}
boolean isDataLink =
if (TokenUtils.fastMatches("bitmap|metafile|picture",handler)){ //Convert these three types to "img" tags immediately.
}*/
String attr = n.matches("img|object") ? "src" : "href";
//Fix path. It will be relative, since it is from a local embedded object.
String src = n.get(attr);
if (src != null) {
//Parse the file signature (cached on 'src'
BundledAsset b = getAsset(src);
if (b.success) {
String resultUri = export.getUri(asset_use_index_in_url ? Long.toString(b.assetId) : b.targetPath, AssetType.Image, document_base);
n.set(attr, resultUri);
n.set("resolved", "true");
if (isImage) {
n.setTagName("img");
n.removeAttr("type");
n.removeAttr("handler");
b.alt = n.get("name");
n.set("alt", n.get("name")); //The alt tag can use the name
n.removeAttr("name");
} else if (isImageLink) {
n.setTagName("a");
n.removeAttr("type");
n.removeAttr("handler");
b.alt = n.get("objectName");
n.set("alt", n.get("objectName")); //The alt tag can use the name
n.removeAttr("objectName");
} else if (isObjectLink) {
n.setTagName("a");
b.alt = n.get("dataLink");
n.set("alt", b.alt);
if (n.get("mime") != null) n.set("data-mime", n.get("mime"));
if (n.get("dataLink") != null) n.set("data-linkname", n.get("dataLink"));
n.removeAttr("mime");
n.removeAttr("dataLink");
}
} else {
//Unscucessfully.
}
}
//TODO: catch the rest
}
}
private ArrayList<Pair<String, byte[]>> signatures;
private InfobaseSet infobase_set;
public Integer nextAssetId;
public class BundledAsset {
public BundledAsset() {
}
public Path originalDiskLocation;
public String originalFileExtension;
public String originalPath;
public Path targetDiskLocation;
public String targetFileExtension;
public String targetPath;
public String alt;
public int assetId;
public InfobaseConfig infobase;
public boolean dataLink;
public boolean success;
public String error_message;
}
BundledAsset fail(String path, String message) {
BundledAsset b = new BundledAsset();
b.success = false;
b.error_message = path;
b.originalPath = path;
System.err.println(path);
System.err.println(message);
return b;
}
//Flags
//Drop extension in XML - assumes our uploader also drops the extension and replaces it with a content type
//Convert BMP files to PNG.
protected static Pattern object_file = Pattern.compile("\\A([^\\\\/]+)[\\\\/]FFF([0-9]+).(OB|OLE|BMP)\\Z");
protected static Pattern data_file = Pattern.compile("\\A([^\\\\/]+)[\\\\/]Data[\\\\/]([^\\\\/]+)\\Z");
private HashMap<String, BundledAsset> assets = new HashMap<String, BundledAsset>();
public void CopyConvertFiles() throws IOException {
for (Map.Entry<String, BundledAsset> pair : assets.entrySet()) {
BundledAsset target = pair.getValue();
if (!target.success) continue;
//If the destination file doesn't exist, copy
if (!Files.exists(target.targetDiskLocation)) {
if (!Files.isDirectory(target.targetDiskLocation.getParent())) {
Files.createDirectory(target.targetDiskLocation.getParent());
}
try {
if ("bmp".equals(target.originalFileExtension) && "png".equals(target.targetFileExtension)) {
ConvertToPng(target.originalDiskLocation.toFile(), target.targetDiskLocation.toFile());
} else {
Files.copy(target.originalDiskLocation, target.targetDiskLocation, StandardCopyOption.COPY_ATTRIBUTES);
}
} catch (IOException e) {
System.err.println("Failed to copy/compress to " + pair.getValue().targetDiskLocation);
e.printStackTrace(System.err);
}
}
}
}
public void ExportAssetInventory() throws IOException, InvalidMarkupException {
Path xmlPath = export.getLocalPath("AssetInventory.xml", AssetType.Xml, FolderCreation.CreateParents);
BufferedWriter out = Files.newBufferedWriter(xmlPath, Charset.forName("UTF-8"));
out.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
Node assetsNode = new Node("<assets />");
for (Map.Entry<String, BundledAsset> pair : assets.entrySet()) {
BundledAsset target = pair.getValue();
if (!target.success) continue;
Node n = new Node("<asset />");
n.set("asset_id", Long.toString(target.assetId));
if (target.alt != null) n.set("alt", target.alt);
n.set("dataLink", Boolean.toString(target.dataLink));
n.set("originalPath", target.originalPath.toString());
n.set("orginalDiskLocation", target.originalDiskLocation.toString());
if (target.originalFileExtension != null)
n.set("originalFileType", target.originalFileExtension.toString());
n.set("targetPath", target.targetPath.toString());
n.set("targetDiskLocation", target.targetDiskLocation.toString());
if (target.targetFileExtension != null) n.set("targetFileType", target.targetFileExtension.toString());
assetsNode.addChild(n);
}
out.append(assetsNode.toXmlString(true));
out.close();
}
public void ConvertToPng(File input, File output) throws IOException {
//Read the file to a BufferedImage
BufferedImage image = ImageIO.read(input);
//Write the image to the destination as a PNG
ImageIO.write(image, "png", output);
}
private BundledAsset parse(String path) throws IOException {
BundledAsset b = new BundledAsset();
b.success = true;
b.originalPath = path;
Matcher m = data_file.matcher(path);
b.dataLink = true;
if (!m.find()) {
m = object_file.matcher(path);
b.dataLink = false;
if (!m.find()) {
//Not a data link or object/ole? skip.
return fail(path, "Path is not a Data link or OB/OLE file.");
}
}
//Which infobase does it correspond with
b.infobase = infobase_set.byName(m.group(1));
if (b.infobase == null) {
return fail(path, "Failed to find corresponding InfobaseConfig for '" + m.group(1) + "'");
}
//What's the full source file path
b.originalDiskLocation = Paths.get(b.infobase.getFlatFilePath()).resolveSibling(path.replace("\\", File.separator)).toAbsolutePath();
String filename = null;
if (m.pattern() == data_file) {
b.targetPath = m.group(2).toLowerCase(Locale.ENGLISH); //Use existing filename
} else {
//object or OLE file
byte[] buffer = null;
try {
buffer = getFileSignature(b.originalDiskLocation.toFile());
} catch (IOException e) {
e.printStackTrace();
return fail(path, b.originalDiskLocation.toString() + "\n" + e.toString());
}
String ext = getExtensionForSignature(buffer);
if (ext == null) {
return fail(path, "Unknown file type; unrecognized magic byte signature.");
}
b.originalFileExtension = ext;
//Convert bmp to png
b.targetFileExtension = ext.equalsIgnoreCase("bmp") ? "png" : ext;
//m.group(3) == OB or OLE
b.targetPath = m.group(2) + "." + b.targetFileExtension;
}
b.targetPath = Paths.get(b.infobase.getId()).resolve(Paths.get(b.targetPath)).toString();
b.targetDiskLocation = export.getLocalPath(b.targetPath, AssetType.Image, FolderCreation.None);
return b;
}
public BundledAsset getAsset(String path) throws IOException {
BundledAsset b;
//If we have renamed this file before, reuse result
if (assets.containsKey(path)) {
b = assets.get(path);
} else {
b = parse(path);
b.assetId = nextAssetId;
nextAssetId++;
assets.put(path, b);
}
return b;
}
public String modifyImageUrl(String path, FileNode document_base) throws IOException {
BundledAsset b = getAsset(path);
if (!b.success) return path;
//TODO: log failure path into attributes, perhaps?
Path document = export.getLocalPath(document_base.getRelativePath(), AssetType.Html, FolderCreation.None);
return export.getUri(b.targetPath, AssetType.Image, document);
}
public static byte[] getFileSignature(File file) throws IOException {
InputStream ios = null;
try {
byte[] buffer = new byte[12];
ios = new FileInputStream(file);
int bytesRead = ios.read(buffer);
return buffer;
} finally {
if (ios != null)
ios.close();
}
}
public String getExtensionForSignature(byte[] buffer) {
for (Pair<String, byte[]> sig : signatures) {
byte[] bsig = sig.getSecond();
boolean match = true;
for (int i = 0; i < bsig.length && i < buffer.length; i++) {
if (bsig[i] != buffer[i]) {
match = false; //not a match
break;
}
}
if (match) return sig.getFirst();
}
return null;
}
//https://en.wikipedia.org/wiki/List_of_file_signatures
private static byte[] jpeg = new byte[]{(byte) 0xFF, (byte) 0xD8, (byte) 0xFF, (byte) 0xE0};
private static byte[] gif87 = new byte[]{(byte) 0x47, (byte) 0x49, (byte) 0x46, (byte) 0x38, (byte) 0x37, (byte) 0x61};
private static byte[] gif89 = new byte[]{(byte) 0x47, (byte) 0x49, (byte) 0x46, (byte) 0x38, (byte) 0x39, (byte) 0x61};
private static byte[] ico = new byte[]{(byte) 0, (byte) 0, (byte) 1, (byte) 0};
private static byte[] png = new byte[]{(byte) 0x89, (byte) 0x50, (byte) 0x4e, (byte) 0x47, (byte) 0x0D, (byte) 0x0a, (byte) 0x1A, (byte) 0x0A};
private static byte[] pdf = new byte[]{(byte) 0x25, (byte) 0x50, (byte) 0x44, (byte) 0x46};
private static byte[] tiff_little = new byte[]{(byte) 0x49, (byte) 0x49, (byte) 0x2A, (byte) 0x00};
private static byte[] tiff_big = new byte[]{(byte) 0x4D, (byte) 0x4D, (byte) 0x00, (byte) 0x2A};
private static byte[] bmp = new byte[]{(byte) 0x42, (byte) 0x4D};
private static ArrayList<Pair<String, byte[]>> AllFileSignatures() {
ArrayList<Pair<String, byte[]>> signatures = new ArrayList<Pair<String, byte[]>>();
signatures.add(new Pair<String, byte[]>("jpg", jpeg));
signatures.add(new Pair<String, byte[]>("tiff", tiff_big));
signatures.add(new Pair<String, byte[]>("tiff", tiff_little));
signatures.add(new Pair<String, byte[]>("pdf", pdf));
signatures.add(new Pair<String, byte[]>("png", png));
signatures.add(new Pair<String, byte[]>("ico", ico));
signatures.add(new Pair<String, byte[]>("gif", gif87));
signatures.add(new Pair<String, byte[]>("gif", gif89));
signatures.add(new Pair<String, byte[]>("bmp", bmp));
return signatures;
}
}