How can we modify an existing YAML and preserve comments in it.
Is there any Java parser which does that ?
For example if i have following YAML:
#This is a test YAML
name: abcd
age: 23
#Test YAML ends here.
Is there a way I can edit this Yaml using a java parser and preserve the comments.
As of the time of writing, there is no round-tripping YAML parser for Java. There is the well-known SnakeYAML, which does not preserve comments (see the author's comment here), and a newer project named camel, which I know little of; but it definitely is not round-tripping.
What you can theoretically do is to use SnakeYaml's Yaml.parse and then iterate over the events. Each event has a start and an end mark, giving the start and end line & column of the event. This makes it possible to map the events back into the source and discover the portions of the source that were not parsed into events (presumably comments). Having this mapping, you can now modify the event list and write it back. Finally, you read the result in a second time and discover the gaps between your events where there were comments in the original YAML, but not in the modified YAML, and re-insert those comments, giving you the final YAML with your modifications and the comments.
Of course, this is very complex. I would not advice to do it unless you a) have either a solid understanding of how YAML is structured or are willing to learn it, and b) your use-case justifies this amount of work.
I wrote a groovy script to solve this. The Java version is very similar:
def key = "name"
def value = "efgh"
def yamlFile = new File("file.yaml")
def yamlFileLines = new StringBuilder()
def foundKey = false
yamlFile.text.eachLine { line ->
if (!foundKey && line.contains("$key:")) {
line = line.replaceAll(/$key:.*/, "$key: $value")
foundKey = true
}
yamlFileLines.append("$line\n")
}
if (foundKey) {
yamlFile.text = yamlFileLines.toString()
} else {
throw new StopExecutionException("Could not find key '$key' in file ${yamlFile.getAbsolutePath()}")
}
if you use snakeyaml , you should modify the ScannerImpl file.
notice: read the in-line comment as text
private Token scanPlain() {
StringBuilder chunks = new StringBuilder();
Mark startMark = reader.getMark();
Mark endMark = startMark;
int indent = this.indent + 1;
String spaces = "";
while (true) {
int c;
int length = 0;
// A comment indicates the end of the scalar.
// read the in-line comment as text
// if (reader.peek() == '#' && ) {
// break;
// }
while (true) {
c = reader.peek(length);
if (Constant.NULL_BL_T_LINEBR.has(c)
|| (c == ':' && Constant.NULL_BL_T_LINEBR.has(reader.peek(length + 1), flowLevel != 0 ? ",[]{}":""))
|| (this.flowLevel != 0 && ",?[]{}".indexOf(c) != -1)) {
break;
}
length++;
}
if (length == 0) {
break;
}
this.allowSimpleKey = false;
chunks.append(spaces);
chunks.append(reader.prefixForward(length));
endMark = reader.getMark();
spaces = scanPlainSpaces();
// System.out.printf("spaces[%s]\n", spaces);
if (spaces.length() == 0
// read the in-line comment as text
// || reader.peek() == '#'
|| (this.flowLevel == 0 && this.reader.getColumn() < indent)) {
break;
}
}
return new ScalarToken(chunks.toString(), startMark, endMark, true);
}
Related
I am new to Java and practicing parsing csv file into the object. I've tried but cannot figure it out.
The file looks like this:
[0], [1], [2], [3] , [4] , [5] , [6] , [7] , [8] , [9]
class, gender, age, bodyType, profession, pregnant, isYou ,species, isPet, role
scenario:green, , , , , , , ,
person, female, 24, average , , FALSE , , , , passenger
animal, male , 4, , , FALSE , , dog , TRUE , pedestrian
scenario:red
person, male , 16, athletic, boxer , FALSE , TRUE , , , passenger
person, female, 25, athletic, doctor , TRUE , FALSE , , , pedestrian
I need to parse it by any number of passengers and pedestrians with any scenarios. Finally, add these scenarios into an ArrayList for analyzing.
What I think is to:
loop through each line, stops when reaches to the next scenario:red, adds the passengers and the pedestrians to the Character ArrayList. (I've done adding, but don't how to stop).
Create a scenario using constructor scenario(ArrayList<Character> passenger, ArrayList<Character> pedestrians, boolean redOrGreen);
The ArrayList scenarios add the created scenarios.
What I've done is put everything together instead of separate them. Any help or hint is highly appreciated.
Thanks for this community who helped me, here is what I've got so far.
public void loadCsv() throws IOException {
String csvFile = "config.csv";
String line = "";
String csvSplit = "\\s*,\\s*";
Scenario scenario = new Scenario();
Person person = new Person();
Animal animal = new Animal();
ArrayList<Scenario> scenaios = new ArrayList<Scenario>();
ArrayList<String> csvContents = new ArrayList<String>();
ArrayList<Character> passengers = new ArrayList<Character>();
ArrayList<Character> pedestrians = new ArrayList<Character>();
try (BufferedReader csvReader = new BufferedReader(new FileReader(csvFile));) {
String headerLine = csvReader.readLine(); //get rid of the header
//add each line to the arrayList
while ((line = csvReader.readLine()) != null) {
csvContents.add(line);
}
for(String csvLine : csvContents) {
String[] data = csvLine.split(csvSplit); // split by comma and remove redundant spaces
if (data.length == NO_OF_FIELD) { //check and avoid indexOutOfBoundException
String clazz = data[0].toLowerCase();// cannot use word "class" as a variable
if (clazz.startsWith("scenario") && data.length == 1) {
scenario = new Scenario();
scenario.setLegalCrossing(clazz.endsWith("green"));
continue;
}
else if ("person".equals(clazz) && data.length ==10) {
person = loadCsvPerson(data);
addCharacter(person, data);
}
else if ("animal".equals(clazz) && data.length ==10) {
animal = loadCsvAnimal(data);
addCharacter(animal, data);
}
}
}
}
//passenger and pedestrians are in position
System.out.println("passengers: " + passengers);
System.out.println("pedestrians: " + pedestrians);
if (null != scenario) {
scenario.setPassengers(passengers);
scenario.setPedestrians(pedestrians);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
If it is possible to change the csv file format, I would add scenario type column (and scenario id or name if required), so you can work with csv file as a result set from database when you join tables (scenario + passenger + pedestrian) and return plain rows.
With this approach you will be able to delegate parsing to any csv library and do your logic (group by scenario id/name/type) separately. With surrogate rows you have (scenario:green...) you have to write your custom parser.
For example, you can use univocity to simply parse file into your model (even using annotations) and iteratively group it and handle.
Or if you need to work with existing file format do something like that:
if (clazz.startsWith("scenario") && data.length == 1) {
// collect existing scenario before starting processing new one
if (scenario != null) {
scenario.setPassengers(passengers);
scenario.setPedestrians(pedestrians);
passengers = new ArrayList();
pedestrians = new ArrayList();
scenarios.add(scenario);
}
// now start new group (scenario)
scenario = new Scenario();
scenario.setLegalCrossing(clazz.endsWith("green"));
continue;
}
Following things need to be addressed in your code:
Strive to avoid using the name of a class which is already used by the standard library (and especially when it is in the default package, java.lang) e.g. there is already a class Character in Java library and therefore you should use a different name for your custom class.
Use continue to skip the line, scenario:red
for(String csvLine : csvContents) {
if(csvLine.equals("scenario:red")){
continue;
}
String[] data = csvLine.split(csvSplit); // split by comma and remove redundant spaces
if (data.length == NO_OF_FIELD) {
//..
}
//..
}
If you have already defined final int NO_OF_FIELD = 10, you can use the same instead of using the value 10 directly i.e. you should use NO_OF_FIELD instead of 10 in the following code:
if (data.length == NO_OF_FIELD) { //check and avoid indexOutOfBoundException
String clazz = data[0].toLowerCase();// cannot use word "class" as a variable
//...
else if ("person".equals(clazz) && data.length ==10) {
However, you also need to understand that && data.length ==10 is unnecessary here as you have already checked data.length == NO_OF_FIELD in the enclosing if condition.
I couldn't understand the rest of your points. If you clarify them, I'll be able to help you further.
I need to add the previous scenario in the second round.
Since the last set of data won't be captured, I need to set another new scenario to add it in. Thanks for the art sir.
Character character = null;
try (BufferedReader csvReader = new BufferedReader(new FileReader(csvFile));) {
String headerLine = csvReader.readLine(); //get rid of the header
//add each line to the arrayList
while ((line = csvReader.readLine()) != null) {
csvContents.add(line);
}
final int NO_OF_FIELDS = 10;
for(String csvLine : csvContents) {
String[] data = csvLine.split(csvSplit); // split by comma and remove redundant spaces
String clazz = data[0].toLowerCase();// cannot use word "class" as a variable
if (clazz.startsWith("scenario") && data.length == 1) {
// adding scenario after one set of data
// i.e second round adding the first round data
if (passengers.size() != 0 && pedestrians.size() != 0) {
Scenario scenario = new Scenario();
scenario.setPassengers(passengers);
scenario.setPedestrians(pedestrians);
scenarios.add(scenario);
}
passengers = new ArrayList<Character>();
pedestrians = new ArrayList<Character>();
if (clazz.endsWith("green")) {
scenario.setLegalCrossing(true);
System.out.println("green light");
}
else if (clazz.endsWith("red")){
scenario.setLegalCrossing(false);
System.out.println("red light");
}
continue;
}
//...
Scenario scenario = new Scenario();
scenario.setPassengers(passengers);
scenario.setPedestrians(pedestrians);
scenarios.add(scenario);
scenario.setPassengers(passengers);
scenario.setPedestrians(pedestrians);
Audit audit = new Audit();
audit.setScenario(scenarios);
i'm having a problem parsin the CACM collection in java.
The collection has this format:
.I number
.T
title
.A
authors
multiple authors allowed
.W
body
multiple lines of body allowed
I'm trying to extract each of the fields with this extract method:
public static String extract(char campo, String text,Boolean allowEmpty)
{
String[] lines = text.split("\\r?\\n");
/*for(String line:lines)
System.out.println(line);*/
StringBuilder builder = new StringBuilder();
boolean start = false;
boolean end = false;
for(String l:lines)
{
System.out.println(l);
//System.out.println(line.charAt(0));
if((l.charAt(0) == '.') && (l.charAt(1) == campo))
{
System.out.println("Detectado campo "+l.charAt(1));
start = true;
builder.append(l.substring(2)).append("\n");
}
else
{
if(l.charAt(0) == '.')
{
//System.out.println(campo);
break;
}
else if(start)
builder.append(l);
}
}
return builder.toString();
}
But i do not know why, it does only extract the .I field, and i cant get it to work with any other field. I'm clueless in regard to where to correct the code, or if the approximation is logical.
Any clue in this?
Thank you in advance.
Background
We allow the user to create some text that will get converted to HTML, using a rich-text editor library (called Android-RTEditor).
The output HTML text is saved as is on the server and the device.
Because on some end cases, there is a need to show a lot of this content (multiple instances), we wish to also save a "preview" version of this content, meaning it will be much shorter in length (say 120 of normal characters, excluding the extra characters for the HTML tags, which are not counted).
What we want is a minimized version of the HTML. Some tags might optionally be removed, but we still want to see lists (numbered/bullets), no matter what we choose to do, because lists do show like text to the user (the bullet is a character, and so do the numbers with the dot).
The tag of going to next line should also be handled , as it's important to go to the next line.
The problem
As opposed to a normal string, where I can just call substring with the required number of characters, on HTML it might ruin the tags.
What I've tried
I've thought of 2 possible solutions for this:
Convert to plain text (while having some tags handled), and then truncate : Parse the HTML, and replacing some tags with Unicode alternatives, while removing the others. For example, instead of a bullet-list, put the bullet character (maybe this), and same for numbered list (put numbers instead). All the other tags would be removed. Same goes for the tag of going to the next line (""), which should be replaced with "\n". After that, I could safely truncate the normal text, because there are no more tags that could be ruined.
Truncate nicely inside the HTML : Parse the HTML, while identifying the text within it, and truncate it there and closing all tags when reaching the truncation position. This might even be harder.
I'm not sure which is easier, but I can think of possible disadvantages for each. It is just a preview though, so I don't think it matters much.
I've searched the Internet for such solutions, to see if others have made it.
I've found some links that talk about "cleaning" or "optimizing" HTML, but I don't see they can handle replacing them or truncating them. Not only that, but since it's HTML, most are not related to Android, and use PHP, C#, Angular and others as their language.
Here are some links that I've found:
Java Library to truncate html strings?
how to truncate HTML string without leaving it malformated?
The questions
Are those solutions that I've written possible? If so, is there maybe a known way to implement them? Or even a Java/Kotlin/Android library? How hard would it be to make such a solution?
Maybe other solution I haven't thought about?
EDIT:
I've also tried using an old code I've made in the past (here), which parses XML. Maybe it will work. I also try now to investigate some third party libraries for parsing HTML, such as Jsoup. I think it can help with the truncating, while supporting "faulty" HTML inputs.
OK, I think I got it, using my old code for converting XML string into an object . It would still be great to see more robust solutions, but I think what I got is good enough, at least for now.
Below code uses it (origininal XmlTag class available here) :
XmlTagTruncationHelper.kt
object XmlTagTruncationHelper {
/**#param maxLines max lines to permit. If <0, means there is no restriction
* #param maxTextCharacters max text characters to permit. If <0, means there is no restriction*/
class Restriction(val maxTextCharacters: Int, val maxLines: Int) {
var currentTextCharactersCount: Int = 0
var currentLinesCount: Int = 0
}
#JvmStatic
fun truncateXmlTag(xmlTag: XmlTag, restriction: Restriction): String {
if (restriction.maxLines == 0 || (restriction.maxTextCharacters >= 0 && restriction.currentTextCharactersCount >= restriction.maxTextCharacters))
return ""
val sb = StringBuilder()
sb.append("<").append(xmlTag.tagName)
val numberOfAttributes = if (xmlTag.tagAttributes != null) xmlTag.tagAttributes!!.size else 0
if (numberOfAttributes != 0)
for ((key, value) in xmlTag.tagAttributes!!)
sb.append(" ").append(key).append("=\"").append(value).append("\"")
val numberOfInnerContent = if (xmlTag.innerTagsAndContent != null) xmlTag.innerTagsAndContent!!.size else 0
if (numberOfInnerContent == 0)
sb.append("/>")
else {
sb.append(">")
for (innerItem in xmlTag.innerTagsAndContent!!) {
if (restriction.maxTextCharacters >= 0 && restriction.currentTextCharactersCount >= restriction.maxTextCharacters)
break
if (innerItem is XmlTag) {
if (restriction.maxLines < 0)
sb.append(truncateXmlTag(innerItem, restriction))
else {
// Log.d("AppLog", "xmlTag:" + innerItem.tagName + " " + innerItem.innerTagsAndContent?.size)
var needToBreak = false
when {
innerItem.tagName == "br" -> {
++restriction.currentLinesCount
needToBreak = restriction.currentLinesCount >= restriction.maxLines
}
innerItem.tagName == "li" -> {
++restriction.currentLinesCount
needToBreak = restriction.currentLinesCount >= restriction.maxLines
}
}
if (needToBreak)
break
sb.append(truncateXmlTag(innerItem, restriction))
}
} else if (innerItem is String) {
if (restriction.maxTextCharacters < 0)
sb.append(innerItem)
else
if (restriction.currentTextCharactersCount < restriction.maxTextCharacters) {
val str = innerItem
val extraCharactersAllowedToAdd = restriction.maxTextCharacters - restriction.currentTextCharactersCount
val strToAdd = str.substring(0, Math.min(str.length, extraCharactersAllowedToAdd))
if (strToAdd.isNotEmpty()) {
sb.append(strToAdd)
restriction.currentTextCharactersCount += strToAdd.length
}
}
}
}
sb.append("</").append(xmlTag.tagName).append(">")
}
return sb.toString()
}
}
XmlTag.kt
//based on https://stackoverflow.com/a/19115036/878126
/**
* an xml tag , includes its name, value and attributes
* #param tagName the name of the xml tag . for example : <a>b</a> . the name of the tag is "a"
*/
class XmlTag(val tagName: String) {
/** a hashmap of all of the tag attributes. example: <a c="d" e="f">b</a> . attributes: {{"c"="d"},{"e"="f"}} */
#JvmField
var tagAttributes: HashMap<String, String>? = null
/**list of inner text and xml tags*/
#JvmField
var innerTagsAndContent: ArrayList<Any>? = null
companion object {
#JvmStatic
fun getXmlFromString(input: String): XmlTag? {
val factory = XmlPullParserFactory.newInstance()
factory.isNamespaceAware = true
val xpp = factory.newPullParser()
xpp.setInput(StringReader(input))
return getXmlRootTagOfXmlPullParser(xpp)
}
#JvmStatic
fun getXmlRootTagOfXmlPullParser(xmlParser: XmlPullParser): XmlTag? {
var currentTag: XmlTag? = null
var rootTag: XmlTag? = null
val tagsStack = Stack<XmlTag>()
xmlParser.next()
var eventType = xmlParser.eventType
var doneParsing = false
while (eventType != XmlPullParser.END_DOCUMENT && !doneParsing) {
when (eventType) {
XmlPullParser.START_DOCUMENT -> {
}
XmlPullParser.START_TAG -> {
val xmlTagName = xmlParser.name
currentTag = XmlTag(xmlTagName)
if (tagsStack.isEmpty())
rootTag = currentTag
tagsStack.push(currentTag)
val numberOfAttributes = xmlParser.attributeCount
if (numberOfAttributes > 0) {
val attributes = HashMap<String, String>(numberOfAttributes)
for (i in 0 until numberOfAttributes) {
val attrName = xmlParser.getAttributeName(i)
val attrValue = xmlParser.getAttributeValue(i)
attributes[attrName] = attrValue
}
currentTag.tagAttributes = attributes
}
}
XmlPullParser.END_TAG -> {
currentTag = tagsStack.pop()
if (!tagsStack.isEmpty()) {
val parentTag = tagsStack.peek()
parentTag.addInnerXmlTag(currentTag)
currentTag = parentTag
} else
doneParsing = true
}
XmlPullParser.TEXT -> {
val innerText = xmlParser.text
if (currentTag != null)
currentTag.addInnerText(innerText)
}
}
eventType = xmlParser.next()
}
return rootTag
}
/**returns the root xml tag of the given xml resourceId , or null if not succeeded . */
fun getXmlRootTagOfXmlFileResourceId(context: Context, xmlFileResourceId: Int): XmlTag? {
val res = context.resources
val xmlParser = res.getXml(xmlFileResourceId)
return getXmlRootTagOfXmlPullParser(xmlParser)
}
}
private fun addInnerXmlTag(tag: XmlTag) {
if (innerTagsAndContent == null)
innerTagsAndContent = ArrayList()
innerTagsAndContent!!.add(tag)
}
private fun addInnerText(str: String) {
if (innerTagsAndContent == null)
innerTagsAndContent = ArrayList()
innerTagsAndContent!!.add(str)
}
/**formats the xmlTag back to its string format,including its inner tags */
override fun toString(): String {
val sb = StringBuilder()
sb.append("<").append(tagName)
val numberOfAttributes = if (tagAttributes != null) tagAttributes!!.size else 0
if (numberOfAttributes != 0)
for ((key, value) in tagAttributes!!)
sb.append(" ").append(key).append("=\"").append(value).append("\"")
val numberOfInnerContent = if (innerTagsAndContent != null) innerTagsAndContent!!.size else 0
if (numberOfInnerContent == 0)
sb.append("/>")
else {
sb.append(">")
for (innerItem in innerTagsAndContent!!)
sb.append(innerItem.toString())
sb.append("</").append(tagName).append(">")
}
return sb.toString()
}
}
Sample usage:
build.grade
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
...
dependencies{
implementation 'com.1gravity:android-rteditor:1.6.7'
...
}
...
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// val inputXmlString = "<zz>Zhshs<br/>ABC</zz>"
val inputXmlString = "Aaa<br/><b>Bbb<br/></b>Ccc<br/><ul><li>Ddd</li><li>eee</li></ul>fff<br/><ol><li>ggg</li><li>hhh</li></ol>"
// XML must have a root tag
val xmlString = if (!inputXmlString.startsWith("<"))
"<html>$inputXmlString</html>" else inputXmlString
val rtApi = RTApi(this, RTProxyImpl(this), RTMediaFactoryImpl(this, true))
val mRTManager = RTManager(rtApi, savedInstanceState)
mRTManager.registerEditor(beforeTruncationTextView, true)
mRTManager.registerEditor(afterTruncationTextView, true)
beforeTruncationTextView.setRichTextEditing(true, inputXmlString)
val xmlTag = XmlTag.getXmlFromString(xmlString)
Log.d("AppLog", "xml parsed: " + xmlTag.toString())
val maxTextCharacters = 10
val maxLines = 20
val output = XmlTagTruncationHelper.truncateXmlTag(xmlTag!!, XmlTagTruncationHelper.Restriction(maxTextCharacters, maxLines))
afterTruncationTextView.setRichTextEditing(true, output)
Log.d("AppLog", "xml with truncation : maxTextCharacters: $maxTextCharacters , maxLines: $maxLines output: " + output)
}
}
activity_main.xml
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:gravity="center" android:orientation="vertical"
tools:context=".MainActivity">
<com.onegravity.rteditor.RTEditText
android:id="#+id/beforeTruncationTextView" android:layout_width="match_parent"
android:layout_height="wrap_content" android:background="#11ff0000" tools:text="beforeTruncationTextView"/>
<com.onegravity.rteditor.RTEditText
android:id="#+id/afterTruncationTextView" android:layout_width="match_parent"
android:layout_height="wrap_content" android:background="#1100ff00" tools:text="afterTruncationTextView"/>
</LinearLayout>
And the result:
I have some grammar, that ignores whitespaces in following way
WS : [ \r\t\n]+ -> channel(HIDDEN) ;
It's ok, 'cos whitespace isn't part of my grammar. But in parser I need to know where whitespaces was. For now I unable to find any straight way to do this.
I use last version of ANTLR4
Thanks in advance.
in v3 you would do something like that if you're looking for a token while parsing the tree:
getPreviousTokenInHiddenChannel(retval, input);
public String getPreviousTokenInHiddenChannel(TreeRuleReturnScope retval, TreeNodeStream input) {
try {
TokenStream tstream = input.getTokenStream();
CommonTree node = (CommonTree) retval.start;
int boundary = node.getTokenStopIndex();
if (boundary <= 0) { // fix for antlr 3.3 bug, from 3.5 getTokenStartIndex should itself resolve parent's boundaries if <= 0
while (node.getTokenStartIndex() == -1) { // if node is imaginary
node = (CommonTree) node.getParent();
if (node == null) return ""; // means we are root
boundary = node.getTokenStopIndex();
if (boundary > 0) break;
}
}
int i = boundary;
while (true) {
i--;
Token tok = tstream.get(i);
if (tok.getChannel() == HIDDEN) {
// do what you want to do https://www.youtube.com/watch?v=JgRBkjgXHro
}
}
} catch (Exception e) {
// handle e
}
}
You can easily adapt that piece of code for v4 with something like that (pseudocode):
BufferedTokenStream bts;
// retrieve bts
List<Token> hiddenTokens = bts.getHiddenTokensToLeft(bts.index(), HIDDEN);
// loop backwards over the list
for (int i = hiddenTokens.size(); i--; i >= 0) {
Token t = hiddenTokens.get(i)
// process your hidden token
}
See Token stream API
You must get used to looking at the API and source code. You can also buy the book cheaply. Page 206: Accessing Hidden Channels.
I'm looking for a way to translate an EMV response with Java like with this online option:
http://www.emvlab.org/tlvutils/
where you put something like this EMV response:
6f3a8407a0000000031010a52f500b56495341204352454449548701015f2d086573656e707466729f12074352454449544f9f1101019f38039f1a02
and it will show you everything perfectly, I started doing something by myself but then I realize that maybe we could have two 9F38(PDOL) Strings not neccesary two same tags cuz I know it's impossible but maybe the value of a tag end in 9F and the start of the next tag would be 38 and that would give me an error... Now that I mention it, is that possible? cuz that was one of the main reasons why I stopped doing my own function..
Does any of you have written a function to do this already?
Thanks!
https://github.com/binaryfoo/emv-bertlv should do the trick.
Using your example, the following code:
List<DecodedData> decoded = new RootDecoder().decode("6f3a8407a0000000031010a52f500b56495341204352454449548701015f2d086573656e707466729f12074352454449544f9f1101019f38039f1a02", "EMV", "constructed");
new DecodedWriter(System.out).write(decoded, "");
Will output:
[6F (FCI template)] 8407A0000000031010A52F500B56495341204352454449548701015F...1A02
[84 (dedicated file name)] A0000000031010
[A5 (FCI proprietary template)] 500B56495341204352454449548701015F2D086573656E707466729F...1A02
[50 (application label)] VISA CREDIT
[87 (application priority indicator)] 01
[5F2D (language preference)] esenptfr
[9F12 (application preferred name)] CREDITO
[9F11 (issuer code table index)] 01
[9F38 (PDOL - Processing data object list)] 9F1A02
9F1A (terminal country code) 2 bytes
This project has code to deal with EMV data http://code.google.com/p/javaemvreader/
You are on the right track. You can easily build your own EMV parser using the technique call TLV (Tag Length Value). Your raw data always comes back with a Tag, then after the tag is the length, using the length can get you the value.
So create three methods
method 1: Contains all the short tags
method 2: Contains all the long tags
method 3: Contains all the proprietary tags
So when you pass in your raw emv tag:
6f3a8407a0000000031010a52f500b56495341204352454449548701015f2d086573656e707466729f12074352454449544f9f1101019f38039f1a02
Loop through all those three methods, it will give you all the nice information that you need.
Use below function which will gives you hashmap of TLV value
public LinkedHashMap parseBERTLVTag(String tlv) throws DecoderException
{
if(tlv==null || "".equalsIgnoreCase(tlv)){
return null;
}
System.out.println("============= START ["+tlv+"]==================");
boolean inTagRead= true;
Map<String,String> tags= new HashMap<>();
StringBuilder _tmp = new StringBuilder();
String lastTag = "";
int old_index = 0;
boolean isFirstTagByte = true;
int len = 0;
boolean more=true;
String data = "";
while (more)
{
len = 0;
String hByte = tlv.substring(old_index,(old_index = old_index+2));
if(inTagRead)
{
if(isLastTagByte(hByte, isFirstTagByte))
{
inTagRead=false;
_tmp.append(hByte);
lastTag = _tmp.toString();
System.out.println("Tag["+lastTag+"]");
tags.put(lastTag, null);
_tmp= new StringBuilder();
}else
{
_tmp.append(hByte);
}
isFirstTagByte = false;
}else//Length
{
isFirstTagByte = true;
if(isLastLengthByte(hByte)) {
inTagRead=true;
_tmp.append(hByte);
len = Integer.parseInt(_tmp.toString(), 16 );
//read len*2
System.out.println(" Length ["+len+"]");
data = tlv.substring(old_index, (old_index = old_index+len*2));
String tmpData= lastTag+":"+_tmp.toString()+":h"+data;
System.out.println(" Data ["+tmpData+"]");
_tmp = new StringBuilder();
tags.put(lastTag, tmpData);
}else
{
_tmp.append(hByte);
}
}
more= tlv.length()<=old_index?false:true;
System.out.println("tag "+lastTag+" value "+data+" length "+len);
if(lastTag.length() > 0 && data.length() > 0 && len > 0){
if(!map.containsKey(lastTag)){
map.put(lastTag,new TLVModel().setTag(lastTag).setLength(len).setValue(data));
}
}
}//END OF WHILE
System.out.println("------------ as MAP ---------------------");
System.out.println("size "+map.size());
for (Map.Entry mp:map.entrySet()){
System.out.println("key "+mp.getKey()+" value "+mp.getValue());
}
return map.size() > 0 ? map : null;
}