package org.jabref.logic.xmp;
import java.io.IOException;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.xml.transform.TransformerException;
import org.jabref.model.database.BibDatabase;
import org.jabref.model.entry.Author;
import org.jabref.model.entry.AuthorList;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.FieldName;
import org.jabref.model.entry.FieldProperty;
import org.jabref.model.entry.InternalBibtexFields;
import org.apache.jempbox.xmp.XMPMetadata;
import org.apache.jempbox.xmp.XMPSchema;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class XMPSchemaBibtex extends XMPSchema {
/**
* The namespace of this schema.
*/
public static final String NAMESPACE = "http://jabref.sourceforge.net/bibteXMP/";
private static final String KEY = "bibtex";
private static final Set<String> PRESERVE_WHITE_SPACE = new HashSet<>();
static {
XMPSchemaBibtex.PRESERVE_WHITE_SPACE.add(FieldName.ABSTRACT);
XMPSchemaBibtex.PRESERVE_WHITE_SPACE.add(FieldName.NOTE);
XMPSchemaBibtex.PRESERVE_WHITE_SPACE.add(FieldName.REVIEW);
}
/**
* Create a new empty XMPSchemaBibtex as a child in the given XMPMetadata.
*
* @param parent
*/
public XMPSchemaBibtex(XMPMetadata parent) {
super(parent, XMPSchemaBibtex.KEY, XMPSchemaBibtex.NAMESPACE);
}
/**
* Create schema from an existing XML element.
*
* @param e
* The existing XML element.
* @param namespace
* The name space considered. Must currently be there for compatibility reasons despite being unused.
*/
public XMPSchemaBibtex(Element e, @SuppressWarnings("unused") String namespace) {
super(e, XMPSchemaBibtex.KEY);
}
private static String makeProperty(String propertyName) {
return XMPSchemaBibtex.KEY + ':' + propertyName;
}
/**
* Uses XMPSchema methods
*
* @param field
* @return
*/
public List<String> getPersonList(String field) {
return getSequenceList(field);
}
/**
* Uses XMPSchema methods
*
* @param field
* @param value
*/
public void setPersonList(String field, String value) {
AuthorList list = AuthorList.parse(value);
for (Author author : list.getAuthors()) {
addSequenceValue(field, author.getFirstLast(false));
}
}
@Override
public String getTextProperty(String field) {
return super.getTextProperty(makeProperty(field));
}
@Override
public void setTextProperty(String field, String value) {
super.setTextProperty(makeProperty(field), value);
}
@Override
public List<String> getBagList(String bagName) {
return super.getBagList(makeProperty(bagName));
}
@Override
public void removeBagValue(String bagName, String value) {
super.removeBagValue(makeProperty(bagName), value);
}
@Override
public void addBagValue(String bagName, String value) {
super.addBagValue(makeProperty(bagName), value);
}
@Override
public List<String> getSequenceList(String seqName) {
return super.getSequenceList(makeProperty(seqName));
}
@Override
public void removeSequenceValue(String seqName, String value) {
super.removeSequenceValue(makeProperty(seqName), value);
}
@Override
public void addSequenceValue(String seqName, String value) {
super.addSequenceValue(makeProperty(seqName), value);
}
@Override
public List<Calendar> getSequenceDateList(String seqName) throws IOException {
return super.getSequenceDateList(makeProperty(seqName));
}
@Override
public void removeSequenceDateValue(String seqName, Calendar date) {
super.removeSequenceDateValue(makeProperty(seqName), date);
}
@Override
public void addSequenceDateValue(String field, Calendar date) {
super.addSequenceDateValue(makeProperty(field), date);
}
private static String getContents(NodeList seqList) {
Element seqNode = (Element) seqList.item(0);
StringBuffer seq = null;
NodeList items = seqNode.getElementsByTagName("rdf:li");
for (int j = 0; j < items.getLength(); j++) {
Element li = (Element) items.item(j);
if (seq == null) {
seq = new StringBuffer();
} else {
seq.append(" and ");
}
seq.append(XMPSchemaBibtex.getTextContent(li));
}
if (seq != null) {
return seq.toString();
}
return null;
}
/**
* Returns a map of all properties and their values. LIs and bags in seqs
* are concatenated using " and ".
*
* @return Map from name of textproperty (String) to value (String). For
* instance: "year" => "2005". Empty map if none found.
* @throws TransformerException
*/
public static Map<String, String> getAllProperties(XMPSchema schema, String namespaceName) {
NodeList nodes = schema.getElement().getChildNodes();
Map<String, String> result = new HashMap<>();
if (nodes == null) {
return result;
}
// Check child-nodes first
int n = nodes.getLength();
for (int i = 0; i < n; i++) {
Node node = nodes.item(i);
if ((node.getNodeType() != Node.ATTRIBUTE_NODE)
&& (node.getNodeType() != Node.ELEMENT_NODE)) {
continue;
}
String nodeName = node.getNodeName();
String[] split = nodeName.split(":");
if ((split.length == 2) && split[0].equals(namespaceName)) {
NodeList seqList = ((Element) node).getElementsByTagName("rdf:Seq");
if (seqList.getLength() > 0) {
String seq = XMPSchemaBibtex.getContents(seqList);
if (seq != null) {
result.put(split[1], seq);
}
} else {
NodeList bagList = ((Element) node).getElementsByTagName("rdf:Bag");
if (bagList.getLength() > 0) {
String seq = XMPSchemaBibtex.getContents(bagList);
if (seq != null) {
result.put(split[1], seq);
}
} else {
result.put(split[1], XMPSchemaBibtex.getTextContent(node));
}
}
}
}
// Then check Attributes
NamedNodeMap attrs = schema.getElement().getAttributes();
int m = attrs.getLength();
for (int j = 0; j < m; j++) {
Node attr = attrs.item(j);
String nodeName = attr.getNodeName();
String[] split = nodeName.split(":");
if ((split.length == 2) && split[0].equals(namespaceName)) {
result.put(split[1], attr.getNodeValue());
}
}
/*
* Collapse Whitespace
*
* Quoting from
* http://www.gerg.ca/software/btOOL/doc/bt_postprocess.html: <cite>
* "The exact rules for collapsing whitespace are simple: non-space
* whitespace characters (tabs and newlines mainly) are converted to
* space, any strings of more than one space within are collapsed to a
* single space, and any leading or trailing spaces are deleted."
* </cite>
*/
for (Map.Entry<String, String> entry : result.entrySet()) {
String key = entry.getKey();
if (XMPSchemaBibtex.PRESERVE_WHITE_SPACE.contains(key)) {
continue;
}
entry.setValue(entry.getValue().replaceAll("\\s+", " ").trim());
}
return result;
}
public void setBibtexEntry(BibEntry entry, XMPPreferences xmpPreferences) {
setBibtexEntry(entry, null, xmpPreferences);
}
/**
*
* @param entry
* @param database maybenull
*/
public void setBibtexEntry(BibEntry entry, BibDatabase database, XMPPreferences xmpPreferences) {
// Set all the values including key and entryType
Set<String> fields = entry.getFieldNames();
if ((xmpPreferences != null) && xmpPreferences.isUseXMPPrivacyFilter()) {
Set<String> filters = new TreeSet<>(xmpPreferences.getXmpPrivacyFilter());
fields.removeAll(filters);
}
for (String field : fields) {
String value = entry.getResolvedFieldOrAlias(field, database).orElse("");
if (InternalBibtexFields.getFieldProperties(field).contains(FieldProperty.PERSON_NAMES)) {
setPersonList(field, value);
} else {
setTextProperty(field, value);
}
}
setTextProperty(BibEntry.TYPE_HEADER, entry.getType());
}
public BibEntry getBibtexEntry() {
String type = getTextProperty(BibEntry.TYPE_HEADER);
BibEntry e = new BibEntry(type);
// Get Text Properties
Map<String, String> text = XMPSchemaBibtex.getAllProperties(this, "bibtex");
text.remove(BibEntry.TYPE_HEADER);
e.setField(text);
return e;
}
/**
* Taken from DOM2Utils.java:
*
* JBoss, the OpenSource EJB server
*
* Distributable under LGPL license. See terms of license at gnu.org.
*/
public static String getTextContent(Node node) {
boolean hasTextContent = false;
StringBuilder buffer = new StringBuilder();
NodeList nlist = node.getChildNodes();
for (int i = 0; i < nlist.getLength(); i++) {
Node child = nlist.item(i);
if (child.getNodeType() == Node.TEXT_NODE) {
buffer.append(child.getNodeValue());
hasTextContent = true;
}
}
return hasTextContent ? buffer.toString() : "";
}
}