package com.door43.translationstudio.spannables;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.ImageSpan;
import android.text.style.StyleSpan;
import android.util.Xml;
import com.door43.tools.reporting.Logger;
import com.door43.translationstudio.R;
import com.door43.translationstudio.AppContext;
import org.apache.commons.io.input.CharSequenceReader;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
/**
* Class to create NoteSpans from USX format text
*/
public class USXNoteSpan extends NoteSpan {
private final CharSequence mNotes;
private final CharSequence mPassage;
private final String mCaller;
private List<USXChar> mChars;
private static final String DEFAULT_CALLER = "+";
private String mStyle;
private SpannableStringBuilder mSpannable;
public static final String PATTERN = "<note ((?!>).)*>((?!</note>).)*</note>";
/**
* @param style the note style
* @param caller the note caller
* @param chars a list of char elements that make up the note
*/
public USXNoteSpan(String style, String caller, List<USXChar> chars) {
super();
CharSequence spanTitle = "";
CharSequence note = "";
CharSequence quotation = "";
CharSequence altQuotation = "";
CharSequence passageText = "";
for(USXChar c:chars) {
if(c.style.equals(USXChar.STYLE_PASSAGE_TEXT)) {
passageText = c.value;
} else if(c.style.equals(USXChar.STYLE_FOOTNOTE_QUOTATION)) {
quotation = c.value;
} else if(c.style.equals(USXChar.STYLE_FOOTNOTE_ALT_QUOTATION)) {
altQuotation = c.value;
} else {
// TODO: implement better. We may need to format the values
note = TextUtils.concat(note, c.value);
}
}
// set the span title
if(!TextUtils.isEmpty(passageText)) {
spanTitle = passageText;
} else if(!TextUtils.isEmpty(quotation)) {
spanTitle = quotation;
}
mChars = chars;
init(spanTitle, generateTag(style, caller, spanTitle, chars));
mCaller = caller;
mPassage = spanTitle;
mNotes = TextUtils.concat(note, " ", altQuotation);
mStyle = style;
}
@Override
public SpannableStringBuilder render() {
if(mSpannable == null) {
mSpannable = super.render();
// apply custom styles
if(getHumanReadable().toString().isEmpty()) {
Bitmap image = BitmapFactory.decodeResource(AppContext.context().getResources(), R.drawable.ic_description_black_24dp);
BitmapDrawable background = new BitmapDrawable(AppContext.context().getResources(), image);
background.setBounds(0, 0, background.getMinimumWidth(), background.getMinimumHeight());
mSpannable.setSpan(new ImageSpan(background), 0, mSpannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
} else {
mSpannable.setSpan(new BackgroundColorSpan(AppContext.context().getResources().getColor(R.color.footnote_yellow)), 0, mSpannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
mSpannable.setSpan(new StyleSpan(Typeface.ITALIC), 0, mSpannable.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mSpannable.setSpan(new ForegroundColorSpan(AppContext.context().getResources().getColor(R.color.dark_gray)), 0, mSpannable.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
return mSpannable;
}
/**
* Generates the passage note tag with additional attributes
* @param style
* @param caller
* @param title
* @param chars
* @return
*/
public static CharSequence generateTag(String style, String caller, CharSequence title, List<USXChar> chars) {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
try {
db = dbf.newDocumentBuilder();
} catch (ParserConfigurationException e) {
return title;
}
Document document = db.newDocument();
// build root
Element rootElement = document.createElement("note");
rootElement.setAttribute("style", style);
rootElement.setAttribute("caller", caller);
document.appendChild(rootElement);
// add chars
for(USXChar c:chars) {
Element element = document.createElement("char");
element.setAttribute("style", c.style);
element.setTextContent(c.value.toString().replace("\n", "\\n"));
rootElement.appendChild(element);
}
// generate
DOMSource domSource = new DOMSource(document.getDocumentElement());
OutputStream output = new ByteArrayOutputStream();
StreamResult result = new StreamResult(output);
TransformerFactory factory = TransformerFactory.newInstance();
Transformer transformer;
try {
transformer = factory.newTransformer();
Properties outFormat = new Properties();
outFormat.setProperty(OutputKeys.INDENT, "no");
outFormat.setProperty(OutputKeys.METHOD, "xml");
outFormat.setProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
outFormat.setProperty(OutputKeys.VERSION, "1.0");
outFormat.setProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperties(outFormat);
transformer.transform(domSource, result);
} catch (TransformerConfigurationException e) {
return title;
} catch (TransformerException e) {
Logger.e(USXNoteSpan.class.getName(), "failed to transform the the span text", e);
}
String tag = output.toString();
return tag;
}
/**
* Generates a custom Doku Wiki footnote tag.
* TODO: I think this will just be used for footnotes, however if footnotes are to be treated normally we won't have the span text.
* @return
*/
public String generateDokuWikiTag() {
return "((ref:\""+ mPassage +"\",note:\""+ mNotes +"\"))";
}
/**
* returns the caller
* @return
*/
public String getCaller() {
return mCaller;
}
/**
* Returns the type of note this is
* @return
*/
public String getStyle() {
return mStyle;
}
/**
* Returns the notes regarding the passage
* @return
*/
public CharSequence getNotes() {
return mNotes;
}
/**
* Returns the text upon which the notes are made
* @return
*/
public CharSequence getPassage() {
return mPassage;
}
/**
* Generates a footnote span
* @param note the note
* @return
*/
public static USXNoteSpan generateFootnote(CharSequence note) {
List<USXChar> chars = new ArrayList<>();
chars.add(new USXChar(USXChar.STYLE_FOOTNOTE_TEXT, note));
return new USXNoteSpan("f", DEFAULT_CALLER, chars);
}
/**
* Generates a new note span from the supplied xml and returns it.
* Don't forget to set the click listener!
* we are using usx for footnotes and our own variant for user notes
* http://dbl.ubs-icap.org:8090/display/DBLDOCS/USX#USX-note(Footnote)
* @param usx
* @return
*/
public static USXNoteSpan parseNote(CharSequence usx) {
XmlPullParser parser = Xml.newPullParser();
try {
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
parser.setInput(new CharSequenceReader(usx));
parser.nextTag();
return readXML(parser);
} catch (XmlPullParserException e) {
Logger.e(USXNoteSpan.class.getName(), "Failed to parse note", e);
return null;
} catch(IOException e) {
return null;
}
}
/**
* Reads some xml to produce a new note span
* @param parser
* @return
* @throws XmlPullParserException
*/
private static USXNoteSpan readXML(XmlPullParser parser) throws XmlPullParserException, IOException {
// NoteType noteType;
// String notes = "";
// String passageText = "";
parser.require(XmlPullParser.START_TAG, null, "note");
// load attributes
String style = parser.getAttributeValue("","style");
String caller = parser.getAttributeValue("", "caller");
if(caller == null) {
caller = DEFAULT_CALLER;
}
int eventType = parser.getEventType();
parser.nextTag();
// load char's
// CharSequence note = "";
List<USXChar> chars = new ArrayList<>();
while(eventType != XmlPullParser.END_DOCUMENT) {
if(eventType == XmlPullParser.START_TAG){
parser.require(XmlPullParser.START_TAG, null, "char");
String charStyle = parser.getAttributeValue("", "style");
String charText = parser.nextText();
chars.add(new USXChar(charStyle, charText));
}
eventType = parser.next();
}
return new USXNoteSpan(style, caller.trim(), chars);
}
public List<USXChar> getChars() {
return mChars;
}
}