Tag: java

Parse key value separated by ;

Parse key value separated by ;

Introduction

Let us use as a sample the following string (the 3 dots mean the same pattern to the infinity):

key0=value0;key1=value1;key2=value2;key3=value3;...

While doing some code review the previous string was parsed using the split method from String class using the “;” character, after that operation another operation was performed on the token “key=value” string (yes, by the “=” sign) and checking that the key was present with an optional value.

I will not type that code here. The code we are interested here is how to parse this pattern using a unique Regular Expression, in Java that regular expression (and other programming language) is as follows:

((\w+)=(\w*)(?=;)?)

That would read (without the back references):

  • Match a word character between one and unlimited times
  • Followed by the “=” character
  • Followed by another word character between zero and unlimited times (optional)
  • This is the interesting part. We will use for this regex a positive lookahead (see my other post related to this topic). This means we are interested to match “;” without making “;” part of the match, it is only there to assert that the match of type “key=value” is true.
  • The last “?” at the end of the regex mean that it is optional, this will help to match a string that does not have a “;” at the end of the string, e.g.
key1=value1;key2=vaalue2;key3=vaalue4;key8=vaalue9;key843=vaalue854;kdkkd=jfdjfjsd;fjdsjk=jfdsj

Code

As you can see the implementation is straight, if the string matches each captured token is in the group 2 and 3.

public final class KeyValueParser {

private static final ThreadLocal<Pattern> PATTERN_THREAD_LOCAL = ThreadLocal.withInitial(() -> Pattern.compile("((\\w+)=(\\w*)(?=;)?)"));

/**
 * Example on how to use the PATTERN_THREAD_LOCAL declared above.
 *
 * @param paramsAttrValue a String in the form: key0=value0;key1=value1;key2=value2;keyN=valueN......
 *
 * @return a List with Something key value pair
 */
 public static List<Something> getRequestParams(String paramsAttrValue) {
 List<Something> somethings = new ArrayList<>();
 Matcher regexMatcher = PATTERN_THREAD_LOCAL.get().matcher(paramsAttrValue);
 while (regexMatcher.find()) {
    final String key = regexMatcher.group(2) 
    final String value = regexMatcher.group(3);
    //Do something with key and value
    //Probably Something is a holder for the key and the value.
 }
  return somethings;
}



/**
 * Forbidden to create instances of this class.
 */
 private KeyValueParser() {
   //Forbidden to create instances of this class.
 }
}

Note

ThreadLocal for the Pattern seems not needed as Pattern is already thread safe.

Advertisements
Google Parser to (now vCard), version 3.0

Google Parser to (now vCard), version 3.0

 

I have not dedicated time to the Google Address Book converter, only the Alpine part was done, but today I decided to also include the converter for vCard (v2.1), seems working fine in Mozilla Thunderbird when I import the generated file.

Shortly the code (only of the converter)

package at.mavila.gcontactsalp.converters;

import at.mavila.gcontactsalp.pojos.AddressBook;
import org.apache.commons.lang3.StringUtils;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * Converts the List of Address book to a CharSequence.
 *
 * Created by mavila on 4/16/17.
 */
public class VisitCardConverter implements Converter<List<AddressBook>, CharSequence> {


 private static final Pattern NAME_SPLIT = Pattern.compile("(.+)(\\s+)(.+)");


 @Override
 public CharSequence convert(final List<AddressBook> addressBooks) {

 if (addressBooks == null || addressBooks.isEmpty()) {
 return StringUtils.EMPTY;
 }


 final StringBuilder stringBuilder = new StringBuilder();


 addressBooks.stream().filter(addressBook -> addressBook != null).forEach(addressBook -> {

 stringBuilder.append("begin:vcard\r\n");
 stringBuilder.append("fn:").append(addressBook.getFullname()).append("\r\n");


 stringBuilder.append("n:").append(createN(addressBook.getFullname())).append("\r\n");

 final AtomicInteger atomicInteger = new AtomicInteger(1);
 addressBook.getAddress().forEach(emailAddress -> stringBuilder.append("email;internet;" + (atomicInteger.getAndIncrement() == 1 ? "HOME:" : "WORK:")).append(emailAddress).append("\r\n"));

 if (!addressBook.getComment().contains("Google")) {
 stringBuilder.append("note:").append(addressBook.getComment()).append("\r\n");
 }

 stringBuilder.append("version:2.1\r\n");
 stringBuilder.append("end:vcard\r\n");
 stringBuilder.append("\r\n");
 });


 return stringBuilder.toString();
 }


 private String createN(final String fullName) {

 if (StringUtils.isEmpty(fullName)) {
 return StringUtils.EMPTY;
 }


 final Matcher matcher = NAME_SPLIT.matcher(fullName);

 if (matcher.find()) {
 return matcher.group(3) + ";" + matcher.group(1);
 }


 return fullName+";";


 }
}

The list of Address book is already parsed by the SAX parser, so we just deal with the conversion in

VisitCardConverter

To call it as a web service:

curl localhost:8080/importVCard -d @mutt.xml --header "Content-Type:text/xml" > vas.vcf

Where mutt.xml is produced by the Google dump command, see my other post related.

Grab the code from:

https://github.com/totopoloco/GoogleContactsParserToAlpine