All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.syncthemall.enml4j.ENMLProcessor Maven / Gradle / Ivy

package com.syncthemall.enml4j;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.logging.Logger;

import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;


import com.evernote.edam.type.Note;
import com.evernote.edam.type.Resource;
import com.syncthemall.enml4j.converter.BaseConverter;
import com.syncthemall.enml4j.converter.Converter;
import com.syncthemall.enml4j.converter.MediaConverter;
import com.syncthemall.enml4j.impl.DefaultCryptTagConverter;
import com.syncthemall.enml4j.impl.DefaultInlineMediaTagConverter;
import com.syncthemall.enml4j.impl.DefaultMediaTagConverter;
import com.syncthemall.enml4j.impl.DefaultNoteTagConverter;
import com.syncthemall.enml4j.impl.DefaultTodoTagConverter;
import com.syncthemall.enml4j.util.Elements;
import com.syncthemall.enml4j.util.Utils;

/**
 * The entry point of ENML4j.
 * 
 * This class should be instantiated and kept in reference (as a static for example) for better performances. When
 * converting a {@code Note} to HTML the Evernote DTD has to be parsed the first time, then stays in memory. Parsing the
 * DTD the first time is time-consuming.
 * 

* This class rely on stAX to convert ENML to HTML. ENML4j will uses the default stAX implementation on the platform. * But implementation can be easily chosen : StAX Factory Classes *

* This class is thread-safe as long as the stAX implementation of {@link XMLInputFactory}, {@link XMLOutputFactory}, * {@link XMLEventFactory} are thread-safe. Almost all implementation of this classes are thread-safe. *

* ENML4j rely on {@link Converter}s classes to convert specifics ENML tags to an HTML equivalent. Default * {@code Converter} are provided and instantiated by default. *

    *
  • {@link DefaultNoteTagConverter}
  • *
  • {@link DefaultInlineMediaTagConverter}
  • *
  • {@link DefaultTodoTagConverter}
  • *
  • {@link DefaultCryptTagConverter}
  • *
  • {@link DefaultInlineMediaTagConverter}
  • *
*

* For specifics needs {@link BaseConverter} and {@link MediaConverter} can be implemented and set with * {@link ENMLProcessor#setConverters(BaseConverter, MediaConverter, BaseConverter, BaseConverter)} and * {@link ENMLProcessor#setInlineConverters(BaseConverter, MediaConverter, BaseConverter, BaseConverter)}. * * @see Understanding the Evernote Markup Language * @see Streaming API for XML */ public class ENMLProcessor { private Logger log = Logger.getLogger("com.syncthemall.ENMLProcessor"); /** * The Attribute {@code }. See Understanding * the Evernote Markup Language */ public static final String NOTE = "en-note"; /** * The Attribute {@code }. See Understanding * the Evernote Markup Language */ public static final String MEDIA = "en-media"; /** * The Attribute {@code }. See Understanding * the Evernote Markup Language */ public static final String TODO = "en-todo"; /** * The Attribute {@code }. See Understanding * the Evernote Markup Language */ public static final String CRYPT = "en-crypt"; /** Version of ENML4j. Written in the header of the generated HTML. */ public static final String VERSION = "ENML4J 0.1.0"; private static final Map CONVERTERS = new HashMap(); private static final Map INLINE_CONVERTERS = new HashMap(); /** An instance of {@code XMLEventFactory} used to creates new {@link XMLEvent}s. */ private static final XMLEventFactory eventFactory = XMLEventFactory.newInstance(); private static final XMLInputFactory inputFactory = XMLInputFactory.newInstance(); private static final XMLOutputFactory outputFactory = XMLOutputFactory.newInstance(); /** * Construct an {@code ENMLProcessor} with default {@code Converter}s. *

* For in-line HTML conversion the default {@code Converter} are : *

    *
  • {@link DefaultNoteTagConverter}
  • *
  • {@link DefaultMediaTagConverter}
  • *
  • {@link DefaultTodoTagConverter}
  • *
  • {@link DefaultCryptTagConverter}
  • *
*

* For HTML conversion with resource reference, the default {@code Converter} are : *

    *
  • {@link DefaultNoteTagConverter}
  • *
  • {@link DefaultInlineMediaTagConverter}
  • *
  • {@link DefaultTodoTagConverter}
  • *
  • {@link DefaultCryptTagConverter}
  • *
*/ public ENMLProcessor() { CONVERTERS.put(NOTE, new DefaultNoteTagConverter().setEventFactory(eventFactory)); CONVERTERS.put(MEDIA, new DefaultMediaTagConverter().setEventFactory(eventFactory)); CONVERTERS.put(TODO, new DefaultTodoTagConverter().setEventFactory(eventFactory)); CONVERTERS.put(CRYPT, new DefaultCryptTagConverter().setEventFactory(eventFactory)); INLINE_CONVERTERS.put(NOTE, new DefaultNoteTagConverter().setEventFactory(eventFactory)); INLINE_CONVERTERS.put(MEDIA, new DefaultInlineMediaTagConverter().setEventFactory(eventFactory)); INLINE_CONVERTERS.put(TODO, new DefaultTodoTagConverter().setEventFactory(eventFactory)); INLINE_CONVERTERS.put(CRYPT, new DefaultCryptTagConverter().setEventFactory(eventFactory)); } /** * Set the {@code Converter}s for HTML conversion with resource reference. If one the parameter is null the default * {@code Converter} will be used. *

* For HTML conversion with resource reference, the default {@code Converter} are : *

    *
  • {@link DefaultNoteTagConverter}
  • *
  • {@link DefaultMediaTagConverter}
  • *
  • {@link DefaultTodoTagConverter}
  • *
  • {@link DefaultCryptTagConverter}
  • *
* * @param noteConverter the {@code Converter} used to convert the ENML tag {@code } * @param mediaConverter the {@code Converter} used to convert the ENML tag {@code } * @param todoConverter the {@code Converter} used to convert the ENML tag {@code } * @param cryptConverter the {@code Converter} used to convert the ENML tag {@code } */ public final void setConverters(final BaseConverter noteConverter, final MediaConverter mediaConverter, final BaseConverter todoConverter, final BaseConverter cryptConverter) { if (noteConverter != null) { CONVERTERS.put(NOTE, noteConverter.setEventFactory(eventFactory)); } else { CONVERTERS.put(NOTE, new DefaultNoteTagConverter().setEventFactory(eventFactory)); } if (mediaConverter != null) { CONVERTERS.put(MEDIA, mediaConverter.setEventFactory(eventFactory)); } else { CONVERTERS.put(MEDIA, new DefaultMediaTagConverter().setEventFactory(eventFactory)); } if (todoConverter != null) { CONVERTERS.put(TODO, todoConverter.setEventFactory(eventFactory)); } else { CONVERTERS.put(TODO, new DefaultTodoTagConverter().setEventFactory(eventFactory)); } if (cryptConverter != null) { CONVERTERS.put(CRYPT, cryptConverter.setEventFactory(eventFactory)); } else { CONVERTERS.put(CRYPT, new DefaultCryptTagConverter().setEventFactory(eventFactory)); } } /** * Set the {@code Converter}s for in-line HTML conversion. If one the parameter is null the default * {@code Converter} will be used. *

* For in-line HTML conversion the default {@code Converter} are : *

    *
  • {@link DefaultNoteTagConverter}
  • *
  • {@link DefaultMediaTagConverter}
  • *
  • {@link DefaultTodoTagConverter}
  • *
  • {@link DefaultCryptTagConverter}
  • *
* * @param noteConverter the {@code Converter} used to convert the ENML tag {@code } * @param mediaConverter the {@code Converter} used to convert the ENML tag {@code } * @param todoConverter the {@code Converter} used to convert the ENML tag {@code } * @param cryptConverter the {@code Converter} used to convert the ENML tag {@code } */ public final void setInlineConverters(final BaseConverter noteConverter, final MediaConverter mediaConverter, final BaseConverter todoConverter, final BaseConverter cryptConverter) { if (noteConverter != null) { INLINE_CONVERTERS.put(NOTE, noteConverter.setEventFactory(eventFactory)); } else { INLINE_CONVERTERS.put(NOTE, new DefaultNoteTagConverter().setEventFactory(eventFactory)); } if (mediaConverter != null) { INLINE_CONVERTERS.put(MEDIA, mediaConverter.setEventFactory(eventFactory)); } else { INLINE_CONVERTERS.put(MEDIA, new DefaultInlineMediaTagConverter().setEventFactory(eventFactory)); } if (todoConverter != null) { INLINE_CONVERTERS.put(TODO, todoConverter); } else { INLINE_CONVERTERS.put(TODO, new DefaultTodoTagConverter().setEventFactory(eventFactory)); } if (cryptConverter != null) { INLINE_CONVERTERS.put(CRYPT, cryptConverter.setEventFactory(eventFactory)); } else { INLINE_CONVERTERS.put(CRYPT, new DefaultCryptTagConverter().setEventFactory(eventFactory)); } } /** * Creates an HTML version of the ENML content of a {@code Note}. *

* The HTML is generated based on the {@link Converter}s defined by * {@code ENMLProcessor#setConverters(Converter, Converter, Converter, Converter)}.
* The methods assumes the {@code Note} contains a valid ENML content and that it's {@code Resource}s objects * contains their date.
* The {@code Resource}s of the {@code Note} will be generated directly in the generated HTML using Data URI scheme. * See Data_URI_scheme
* The generated HTML page will be viewable in a browser without requiring an access to the physical file associated * to the {@code Resource} * * @param note the Note to creates the HTML from. It has to contain its list of {@code Resource}s with data and an * ENML content * @return a {@code String} containing the resulting HTML file * @throws XMLStreamException if there is an unexpected processing error, like a malformed ENML content in the Note */ public final String noteToInlineHTMLString(final Note note) throws XMLStreamException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); noteToInlineHTML(note, baos); return new String(baos.toByteArray(), Charset.forName("UTF-8")); } /** * Creates an HTML version of the ENML content of a {@code Note}. *

* The HTML is generated based on the {@link Converter}s defined by * {@code ENMLProcessor#setConverters(Converter, Converter, Converter, Converter)}.
* The methods assumes the {@code Note} contains a valid ENML content and that it's {@code Resource}s objects * contains their date.
* The {@code Resource}s of the {@code Note} will be generated directly in the generated HTML using Data URI scheme. * See Data_URI_scheme
* The generated HTML page will be viewable in a browser without requiring an access to the physical file associated * to the {@code Resource} * * @param note the Note to creates the HTML from. It has to contain its list of {@code Resource}s with data and an * ENML content * @return an {@code InputStream} containing the resulting HTML file * @throws XMLStreamException if there is an unexpected processing error, like a malformed ENML content in the Note * @throws IOException if an I/O error occurs during the {@code InputStream} creation */ public final InputStream noteToInlineHTMLInputStream(final Note note) throws XMLStreamException, IOException { PipedInputStream pis = new PipedInputStream(); PipedOutputStream pos = new PipedOutputStream(pis); noteToInlineHTML(note, pos); return pis; } /** * Creates an HTML version of the ENML content of a {@code Note}. *

* The HTML is generated based on the {@link Converter}s defined by * {@code ENMLProcessor#setConverters(Converter, Converter, Converter, Converter)}.
* The methods assumes the {@code Note} contains a valid ENML content and that it's {@code Resource}s objects * contains their date.
* The {@code Resource}s of the {@code Note} will be generated directly in the generated HTML using Data URI scheme. * See Data_URI_scheme
* The generated HTML page will be viewable in a browser without requiring an access to the physical file associated * to the {@code Resource} * * @param note the Note to creates the HTML from. It has to contain its list of {@code Resource}s with data and an * ENML content * @param out an {@code OutputStream} in which to write the resulting HTML file * @throws XMLStreamException if there is an unexpected processing error, like a malformed ENML content in the Note */ public final void noteToInlineHTML(final Note note, final OutputStream out) throws XMLStreamException { noteToHTML(note, null, out, true); } /** * Creates an HTML version of the ENML content of a {@code Note}. *

* The HTML is generated based on the {@link Converter}s defined by * {@code ENMLProcessor#setConverters(Converter, Converter, Converter, Converter)}.
* The methods assumes the {@code Note} contains a valid ENML content and that it's {@code Resource}s objects * contains their date.
* The {@code Resource}s of the {@code Note} will be referenced in the generated HTML according to the {@code Map} * in parameter. This {@code Map} has to contain for every {@code Resource} in the {@code Note} an entry with : *

    *
  • the GUID of the {@code Resource}
  • *
  • The {@code URL} of the actual resource to reference in the generated HTML. This {@code URL} will typically be * used as the value of the 'href' attribute for file resources and 'src' attribute for image resources.
  • *
*
* In order to view the HTML page generated in a browser all the resources (files, images, ...) has to be accessible * by the browser at the {@code URL} given in the {@code Map}. The resources (the actual files and images) doesn't * need to be accessible by this methods though. * * @param note the Note to creates the HTML from. It has to contain its list of {@code Resource}s with data and an * ENML content * @param mapGUIDURL the mapping of {@code Resource}s GUID with their corresponding physical files {@code URL} * @return a {@code String} containing the resulting HTML file * @throws XMLStreamException if there is an unexpected processing error, like a malformed ENML content in the Note */ public final String noteToHTMLString(final Note note, final Map mapGUIDURL) throws XMLStreamException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); Map hashURLMap = new HashMap(); if (mapGUIDURL != null) { for (String guid : mapGUIDURL.keySet()) { for (Resource resource : note.getResources()) { if (resource.getGuid().equals(guid)) { hashURLMap.put(Utils.bytesToHex(resource.getData().getBodyHash()), mapGUIDURL.get(guid)); } } } } noteToHTML(note, hashURLMap, baos, false); return new String(baos.toByteArray(), Charset.forName("UTF-8")); } /** * Creates an HTML version of the ENML content of a {@code Note}. *

* The HTML is generated based on the {@link Converter}s defined by * {@code ENMLProcessor#setConverters(Converter, Converter, Converter, Converter)}.
* The methods assumes the {@code Note} contains a valid ENML content and that it's {@code Resource}s objects * contains their date.
* The {@code Resource}s of the {@code Note} will be referenced in the generated HTML according to the {@code Map} * in parameter. This {@code Map} has to contain for every {@code Resource} in the {@code Note} an entry with : *

    *
  • the GUID of the {@code Resource}
  • *
  • The {@code URL} of the actual resource to reference in the generated HTML. This {@code URL} will typically be * used as the value of the 'href' attribute for file resources and 'src' attribute for image resources.
  • *
*
* In order to view the HTML page generated in a browser all the resources (files, images, ...) has to be accessible * by the browser at the {@code URL} given in the {@code Map}. The resources (the actual files and images) doesn't * need to be accessible by this methods though. * * @param note the Note to creates the HTML from. It has to contain its list of {@code Resource}s with data and an * ENML content * @param mapGUIDURL the mapping of {@code Resource}s GUID with their corresponding physical files {@code URL} * @return an {@code InputStream} containing the resulting HTML file * @throws XMLStreamException if there is an unexpected processing error, like a malformed ENML content in the Note * @throws IOException if an I/O error occurs during the {@code InputStream} creation */ public final InputStream noteToHTMLInputStream(final Note note, final Map mapGUIDURL) throws XMLStreamException, IOException { Map hashURLMap = new HashMap(); if (mapGUIDURL != null) { for (String guid : mapGUIDURL.keySet()) { for (Resource resource : note.getResources()) { if (resource.getGuid().equals(guid)) { hashURLMap.put(Utils.bytesToHex(resource.getData().getBodyHash()), mapGUIDURL.get(guid)); } } } } PipedInputStream pis = new PipedInputStream(); PipedOutputStream pos = new PipedOutputStream(pis); noteToHTML(note, hashURLMap, pos, false); return pis; } /** * Creates an HTML version of the ENML content of a {@code Note}. *

* The HTML is generated based on the {@link Converter}s defined by * {@code ENMLProcessor#setConverters(Converter, Converter, Converter, Converter)}.
* The methods assumes the {@code Note} contains a valid ENML content and that it's {@code Resource}s objects * contains their date.
* The {@code Resource}s of the {@code Note} will be referenced in the generated HTML according to the {@code Map} * in parameter. This {@code Map} has to contain for every {@code Resource} in the {@code Note} an entry with : *

    *
  • the GUID of the {@code Resource}
  • *
  • The {@code URL} of the actual resource to reference in the generated HTML. This {@code URL} will typically be * used as the value of the 'href' attribute for file resources and 'src' attribute for image resources.
  • *
*
* In order to view the HTML page generated in a browser all the resources (files, images, ...) has to be accessible * by the browser at the {@code URL} given in the {@code Map}. The resources (the actual files and images) doesn't * need to be accessible by this methods though. * * @param note the Note to creates the HTML from. It has to contain its list of {@code Resource}s with data and an * ENML content * @param mapGUIDURL the mapping of {@code Resource}s GUID with their corresponding physical files {@code URL} * @param out an {@code OutputStream} in which to write the resulting HTML file * @throws XMLStreamException if there is an unexpected processing error, like a malformed ENML content in the Note */ public final void noteToHTML(final Note note, final Map mapGUIDURL, final OutputStream out) throws XMLStreamException { Map hashURLMap = new HashMap(); if (mapGUIDURL != null) { for (String guid : mapGUIDURL.keySet()) { for (Resource resource : note.getResources()) { if (resource.getGuid().equals(guid)) { hashURLMap.put(Utils.bytesToHex(resource.getData().getBodyHash()), mapGUIDURL.get(guid)); } } } } noteToHTML(note, hashURLMap, out, false); } private void noteToHTML(final Note note, final Map mapHashURL, final OutputStream out, final boolean inline) throws XMLStreamException { long start = System.currentTimeMillis(); log.finer("Converting Note " + note.getGuid() + " to HTML"); Map currentConverter; if (inline) { currentConverter = INLINE_CONVERTERS; } else { currentConverter = CONVERTERS; } ArrayDeque stack = new ArrayDeque(); Map> toInsertAfter = new HashMap>(); XMLEventReader reader = inputFactory.createXMLEventReader(new ByteArrayInputStream(note.getContent().getBytes( Charset.forName("UTF-8")))); XMLEventWriter writer = outputFactory.createXMLEventWriter(out); XMLEvent lastEvent = null; while (reader.hasNext()) { XMLEvent event = (XMLEvent) reader.next(); if (event.getEventType() == XMLEvent.DTD) { writer.add(eventFactory .createDTD("")); StartElement newElement = eventFactory.createStartElement("", "http://www.w3.org/1999/xhtml", "html"); writer.add(newElement); } else if (event.getEventType() == XMLEvent.START_ELEMENT) { StartElement startElement = event.asStartElement(); if (currentConverter.containsKey(startElement.getName().getLocalPart())) { Converter converter = currentConverter.get(startElement.getName().getLocalPart()); List elementsToInsert = converter.insertBefore(startElement, note, mapHashURL); if (elementsToInsert != null) { for (XMLEvent element : elementsToInsert) { writer.add(element); } } Elements convertedElements = converter.convertElement(startElement, note, mapHashURL); writer.add(convertedElements.getStartElement()); stack.push(convertedElements.getEndElement()); elementsToInsert = converter.insertAfter(startElement, note, mapHashURL); toInsertAfter.put(convertedElements.getEndElement(), elementsToInsert); elementsToInsert = converter.insertIn(startElement, note, mapHashURL); if (elementsToInsert != null) { for (XMLEvent element : elementsToInsert) { writer.add(element); } } } else { writer.add(event); } } else if (event.getEventType() == XMLEvent.CHARACTERS) { Characters characters = event.asCharacters(); if (lastEvent != null && lastEvent.isStartElement()) { StartElement lastStartElement = lastEvent.asStartElement(); if (currentConverter.containsKey(lastStartElement.asStartElement().getName().getLocalPart())) { Converter converter = currentConverter.get(lastStartElement.getName().getLocalPart()); Characters convertedCharacter = converter.convertCharacter(characters, lastStartElement, note, mapHashURL); if (convertedCharacter != null) { writer.add(convertedCharacter); } else { writer.add(characters); } } else { writer.add(event); } } else { writer.add(event); } } else if (event.getEventType() == XMLEvent.END_ELEMENT) { if (currentConverter.containsKey(event.asEndElement().getName().getLocalPart())) { EndElement endElement = stack.pop(); writer.add(endElement); if (toInsertAfter.containsKey(endElement)) { if (toInsertAfter.get(endElement) != null) { for (XMLEvent element : toInsertAfter.get(endElement)) { writer.add(element); } } } } else { writer.add(event); } } else { writer.add(event); } lastEvent = event; } writer.flush(); log.fine("Note " + note.getGuid() + " has been converted in " + Utils.getDurationBreakdown(System.currentTimeMillis() - start)); return; } /** * Updates the {@code Note} content with the information from it's {@code Resource}s. *

* Using this methods supposes that {@link Note} in parameter contain an updated list of {@link Resource}s and it's * ENML content contain the corresponding {@code } tags. The methods parse the ENML content of the * {@code Note} and fill the hash attribute of the {@code } by a new hash created from the {@code Note}'s * list of {@code Resource} .
* The {@code Resource}s and the {@code } tag are processed in order. So the first {@code } hash * attribute will be updated with the hash of the first {@code Resource} in the list. * * @param note the Note to update. It has to contain the list of {@code Resource}s to update and a ENML content * @return the number of {@code } hash attribute left to update. This number should be 0. If not that * means there is more {@code Resource}s objects returns by {@code Note#getResourcesSize()} than * {@code } tags in the {@code Note} ENML content. * @throws XMLStreamException if there is an unexpected processing error, like a malformed ENML content in the Note * @throws NoSuchElementException if there is more {@code } tags in the {@code Note} ENML content than * number {@code Resource}s objects returns by {@code Note#getResourcesSize()} */ public final int updateNoteContentWithRessources(final Note note) throws XMLStreamException { long start = System.currentTimeMillis(); log.finer("Update ENML content of Note " + note.getGuid()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); XMLEventReader reader = inputFactory.createXMLEventReader(new ByteArrayInputStream(note.getContent().getBytes( Charset.forName("UTF-8")))); XMLEventWriter writer = outputFactory.createXMLEventWriter(baos); Iterator resourceIterator = note.getResources().iterator(); int toUpdateCount = note.getResourcesSize(); while (reader.hasNext()) { XMLEvent event = (XMLEvent) reader.next(); if (event.getEventType() == XMLEvent.START_DOCUMENT) { StartElement startElement = event.asStartElement(); if (startElement.getName().getLocalPart().equals(MEDIA)) { for (@SuppressWarnings("unchecked") Iterator iterator = startElement.getAttributes(); iterator.hasNext();) { Attribute attr = (Attribute) iterator.next(); if (attr.getName().getLocalPart().equals("hash")) { writer.add(eventFactory.createAttribute("hash", Utils.bytesToHex(resourceIterator.next().getData().getBodyHash()))); toUpdateCount--; } else { writer.add(attr); } } } else { writer.add(event); } } else { writer.add(event); } } note.setContent(new String(baos.toByteArray(), Charset.forName("UTF-8"))); log.fine("Note ENML content " + note.getGuid() + " has been updated in " + Utils.getDurationBreakdown(System.currentTimeMillis() - start)); return toUpdateCount; } /** * Updates the {@code Note} content with the information of new {@code Resource}s. *

* The update consist in replacing the 'hash' attributes of tags {@code } in the ENML content of the * {@code Note}. The attributes are updated based on the mapping represented by {@code Map} in * parameter.
* For every {@code Resource} that has to be updated this {@code Map} has to contain an entry with: *

    *
  • * the {@code Resource} to update (the old Resource)
  • *
  • the {@code Resource} to update with (the new Resource)
  • *
*
* The methods assumes the {@code Note} has an ENML content and the {@code Resource} in the map has data (to be able * to compute the hash).
* If a {@code Resource} is present in the {@code Note} but not in the {@code Map} it will stays untouched both in * the ENML content and in the {@code Resource} list of the {@code Note}.
* If a {@code Resource} is present in the {@code Map} as a new one, but is not referenced in the {@code Note} ENML * content no update will be performed for the {@code Map} entry.
* The methods will take care of removing the old {@code Resource} objects in the {@code Note} list after they have * been updated. * * @param note the Note to update. It has to contain an ENML content. * @param oldNewResourcesMap the mapping of old and new {@code Resource}s * @throws XMLStreamException if there is an unexpected processing error, like a malformed ENML content in the Note */ public final void updateNoteResources(final Note note, final Map oldNewResourcesMap) throws XMLStreamException { Map hashResourceMap = new HashMap(); for (Resource oldResource : oldNewResourcesMap.keySet()) { hashResourceMap.put(Utils.bytesToHex(oldResource.getData().getBodyHash()), oldNewResourcesMap.get(oldResource)); } updateNoteResourcesByHash(note, hashResourceMap); } /** * Updates the {@code Note} content with the information of new {@code Resource}s. *

* The update consist in replacing the 'hash' attributes of tags {@code } in the ENML content of the * {@code Note}. The attributes are updated based on the mapping represented by {@code Map} in * parameter.
* The methods assumes the {@code Note} has an ENML content and it's {@code Resource}s has data (to be able to * compute the hash).
* For every {@code Resource} that has to be updated this {@code Map} has to contain an entry with: *

    *
  • the GUID of the {@code Resource} to update (the old Resource)
  • *
  • the GUID of the {@code Resource} to update with (the new Resource)
  • *
*
* If a {@code Resource}s referenced by its GUID in the Map is not in the {@code Resource} list of the {@code Note} * no update will be performed for the {@code Map} entry.
* If a {@code Resource} is present in the {@code Note} but not in the {@code Map} it will stays untouched both in * the ENML content and in the {@code Resource} list of the {@code Note}.
* The methods will take care of removing the old {@code Resource} objects in the {@code Note} list after they have * been updated. * * @param note the Note to update. It has to contain an ENML content. * @param oldNewResourcesMap the mapping of old and new {@code Resource}s * @throws XMLStreamException if there is an unexpected processing error, like a malformed ENML content in the Note */ public final void updateNoteResourcesByGUID(final Note note, final Map oldNewResourcesMap) throws XMLStreamException { Map hashResourceMap = new HashMap(); for (String oldGuid : oldNewResourcesMap.keySet()) { Resource oldResource = null; Resource newResource = null; for (Resource resource : note.getResources()) { if (resource.getGuid().equals(oldGuid)) { oldResource = resource; } else if (resource.getGuid().equals(oldNewResourcesMap.get(oldGuid))) { newResource = resource; } } if (oldResource != null & newResource != null) { hashResourceMap.put(Utils.bytesToHex(oldResource.getData().getBodyHash()), newResource); } } updateNoteResourcesByHash(note, hashResourceMap); } private void updateNoteResourcesByHash(final Note note, final Map oldNewResourcesMap) throws XMLStreamException { long start = System.currentTimeMillis(); log.finer("Update ENML content with Resource mapping of Note " + note.getGuid()); ByteArrayOutputStream baos = new ByteArrayOutputStream(); XMLEventReader reader = inputFactory.createXMLEventReader(new ByteArrayInputStream(note.getContent().getBytes( Charset.forName("UTF-8")))); XMLEventWriter writer = outputFactory.createXMLEventWriter(baos); List hashToDelete = new ArrayList(); while (reader.hasNext()) { XMLEvent event = (XMLEvent) reader.next(); if (event.getEventType() == XMLEvent.START_DOCUMENT) { StartElement startElement = event.asStartElement(); if (startElement.getName().getLocalPart().equals(MEDIA)) { for (@SuppressWarnings("unchecked") Iterator iterator = startElement.getAttributes(); iterator.hasNext();) { Attribute attr = (Attribute) iterator.next(); if (attr.getName().getLocalPart().equals("hash")) { // If the resource has to be updated (is in the map) if (oldNewResourcesMap.containsKey(attr.getValue())) { Resource toUpdate = oldNewResourcesMap.get(attr.getValue()); writer.add(eventFactory.createAttribute("hash", Utils.bytesToHex(toUpdate.getData().getBodyHash()))); // if the ressource to update is not in the Note, add it // This way the result Note will be consistent no matter if the Resource to update has // already been added to the Note if (!note.getResources().contains(toUpdate)) { hashToDelete.add(attr.getValue()); note.addToResources(toUpdate); } } else { writer.add(attr); } } else { writer.add(attr); } } } else { writer.add(event); } } else { writer.add(event); } } // Remove the original resources after they have been updated for (Resource resource : note.getResources()) { if (hashToDelete.contains(Utils.bytesToHex(resource.getData().getBodyHash()))) { note.getResources().remove(resource); } } note.setContent(new String(baos.toByteArray(), Charset.forName("UTF-8"))); log.fine("Note ENML content of " + note.getGuid() + " has been updated with resource mapping in " + Utils.getDurationBreakdown(System.currentTimeMillis() - start)); } /** * @return the {@code XMLInputFactory} used to creates the {@code XMLEventWriter} used to write output HTML. */ public final XMLInputFactory getInputFactory() { return inputFactory; } /** * @return the {@code XMLOutputFactory} used to creates the {@code XMLEventReader} used to read input ENML. */ public final XMLOutputFactory getOutputFactory() { return outputFactory; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy