1

I've a big string which is a representation of XML. I'm trying to extract a node data as follows:

        String textToExtract = "<FnAnno>\r\n" + 
                "   <PropDesc F_ANNOTATEDID=\"{60431964-0000-C411-9979-E6A21CEE873F}\" F_BACKCOLOR=\"0\" F_BORDER_BACKMODE=\"2\" F_BORDER_COLOR=\"0\" F_BORDER_STYLE=\"0\" F_BORDER_WIDTH=\"1\" F_CLASSID=\"{5CF11941-018F-11D0-A87A-00A0246922A5}\" F_CLASSNAME=\"Text\" F_CREATOR=\"req92333\" F_ENTRYDATE=\"2018-06-19T13:15:43.0000000-05:00\" F_FONT_BOLD=\"true\" F_FONT_ITALIC=\"false\" F_FONT_NAME=\"arial\" F_FONT_SIZE=\"12\" F_FONT_STRIKETHROUGH=\"false\" F_FONT_UNDERLINE=\"false\" F_FORECOLOR=\"0\" F_HASBORDER=\"true\" F_HEIGHT=\"0\" F_ID=\"{60431964-0000-C411-9979-E6A21CEE873F}\" F_LEFT=\"3.430379746835443\" F_MODIFYDATE=\"2018-06-19T13:15:49.0000000-05:00\" F_MULTIPAGETIFFPAGENUMBER=\"1\" F_NAME=\"-1-1\" F_PAGENUMBER=\"1\" F_TEXT_BACKMODE=\"2\" F_TOOLTIP=\"0043007200650061007400650064002000420079003A002000720065007100390032003300330033002C0020002000430072006500610074006500640020004F006E003A002000320030003100380020004A0075006E0065002000310039002C002000310033003A00310035003A00340033002C0020005500540043002D0035\" F_TOOLTIPTRANSFERENCODING=\"hex\" F_TOP=\"1.3291139240506329\" F_WIDTH=\"0\">\r\n" + 
                "       <F_CUSTOM_BYTES/>\r\n" + 
                "       <F_POINTS/>\r\n" + 
                "       <F_TEXT Encoding=\"unicode\">005400680069007300200069007300200061002000740065007300740020000A00280041006200680069006c0061007300680020004d007500740068007500720061006a00200036002f00310039002f00320030003100380029</F_TEXT>\r\n" + 
                "   </PropDesc>\r\n" + 
                "</FnAnno>";
String      extractedString =textToExtract.substring(textToExtract.indexOf("=\"unicode\">"),textToExtract.indexOf("</F_TEXT>")).replaceFirst("=\"unicode\">", "");

Result is 005400680069007300200069007300200061002000740065007300740020000A00280041006200680069006c0061007300680020004d007500740068007500720061006a00200036002f00310039002f00320030003100380029

In order to increase efficiency, I want to use Pattern and matcher to extract the sub string. Below is the code I'm trying struggling at:

    Pattern pattern = Pattern.compile("\\bEncoding=.*?\\.*F_TEXT\\b");
    Matcher matcher = pattern.matcher(textToExtract);
    while (matcher.find()){
        extractedString = (matcher.group());
    }   

The above result is Encoding="unicode">005400680069007 which again I need to truncate.

How to get just the data between the <F_TEXT Encoding=\"unicode\"> and </F_TEXT>? I've had issues at school during regular expressions and even now have issues at work :( Guess I need practice a lot.

Thanks.

1

2 Answers 2

2

If you are always going to be retrieving data between the same XML tags, then you don't need to worry about parsing it into a data structure. You had the right idea. If speed is what you're after, just grab the string inbetween the markers you know will be there.

Your way, though, is wasting some cycles.

textToExtract.substring(textToExtract.indexOf("=\"unicode\">"),textToExtract.indexOf("</F_TEXT>")).replaceFirst("=\"unicode\">", "");

Let's break this down:

// loops through the array until "=\"unicode\">" is found
int startIndex = textToExtract.indexOf("=\"unicode\">");
// loops through the array again, until "</F_TEXT>" is found
int endIndex = textToExtract.indexOf("</F_TEXT>");
//loop through the array, copying the bytes to a new array to form a new String
String substr = textToExtract.substring(startIndex,endIndex);
//loop through the array to find and replace "=\"unicode\">" with nothing
String data = substr.replaceFirst("=\"unicode\">", "");

You're looping through the same array a lot.

Once you know where the start point is, there is no need to search from the beginning again. Instead, start looking from that start point. Then, once you have the start point and end point of your substring, you can simply get it.

// we know what precedes the substring we want
String anchor = "<F_TEXT Encoding=\"unicode\">";
// so we use it to get the start point, looping once, up to that point
int start = textToExtract.indexOf(anchor)+anchor.length();
// we know the end point won't be before the start point, so start where it left off
int end = start;
// count each character from that point until the next XML tag starts
while (textToExtract.charAt(end) != '<') { end++; }
// now we have what we need to simply get the substring
String data = textToExtract.substring(start,end);

This will yield around a 60% performance increase.

Edit: For the sake of completion, let's address regex

Regex is amazing, and a lot of fun in scripts, but very inefficient for something like this. If you can avoid regex, do so. I tend to use it only to be "quick and dirty" - quick in terms of coding time rather than execution time. Have a read of how regex engines work. It's really interesting, but you'll see why it's a last resort.

    /* this pattern will look for the XML tag.
    ** then, it will match [^>]+
    ** [...] will match a single character that matches SOMETHING inside the "character class."
    ** [^...] will match a single character that is NOT something inside the character class.
    ** [^>]+ will match as many characters as it can that do not match '>'
    ** putting this expression inside brackets tells the engine we want to capture it to be referenced later.
    ** '<' at the end just ensures we capture up until that point.
    */
    // create the pattern
    Pattern pattern = Pattern.compile("<F_TEXT Encoding=\"unicode\">([^>]+)<");
    // get a matcher for it
    Matcher matcher = pattern.matcher(textToExtract);
    // if we find a match
    if (matcher.find()) {
        // we can use group(1) to refer to our first capture group
        // group(0) will always return the full string matched, but we don't want the tags.
        String data= matcher.group(1);

    }
Sign up to request clarification or add additional context in comments.

2 Comments

Breaking down with the substring is very creative, I see a point with regex for smaller input. Cheers!
After running a nano test comparision with string match and SAX parse Andreas provided: Here are the results, I ran the test 10 times String Match - getAnnotIdFromF_TEXT - Time to convert :331956 SAX parser - getAnnotIdFromF_TEXT_XML - Time to convert :23137415
1

Don't use regex to parse XML. Use an XML parser.

To "increase efficiency", use SAX, e.g. like this:

String textToExtract = "<FnAnno>\r\n" + 
                       "   <PropDesc F_ANNOTATEDID=\"{60431964-0000-C411-9979-E6A21CEE873F}\" F_BACKCOLOR=\"0\" F_BORDER_BACKMODE=\"2\" F_BORDER_COLOR=\"0\" F_BORDER_STYLE=\"0\" F_BORDER_WIDTH=\"1\" F_CLASSID=\"{5CF11941-018F-11D0-A87A-00A0246922A5}\" F_CLASSNAME=\"Text\" F_CREATOR=\"req92333\" F_ENTRYDATE=\"2018-06-19T13:15:43.0000000-05:00\" F_FONT_BOLD=\"true\" F_FONT_ITALIC=\"false\" F_FONT_NAME=\"arial\" F_FONT_SIZE=\"12\" F_FONT_STRIKETHROUGH=\"false\" F_FONT_UNDERLINE=\"false\" F_FORECOLOR=\"0\" F_HASBORDER=\"true\" F_HEIGHT=\"0\" F_ID=\"{60431964-0000-C411-9979-E6A21CEE873F}\" F_LEFT=\"3.430379746835443\" F_MODIFYDATE=\"2018-06-19T13:15:49.0000000-05:00\" F_MULTIPAGETIFFPAGENUMBER=\"1\" F_NAME=\"-1-1\" F_PAGENUMBER=\"1\" F_TEXT_BACKMODE=\"2\" F_TOOLTIP=\"0043007200650061007400650064002000420079003A002000720065007100390032003300330033002C0020002000430072006500610074006500640020004F006E003A002000320030003100380020004A0075006E0065002000310039002C002000310033003A00310035003A00340033002C0020005500540043002D0035\" F_TOOLTIPTRANSFERENCODING=\"hex\" F_TOP=\"1.3291139240506329\" F_WIDTH=\"0\">\r\n" + 
                       "       <F_CUSTOM_BYTES/>\r\n" + 
                       "       <F_POINTS/>\r\n" + 
                       "       <F_TEXT Encoding=\"unicode\">005400680069007300200069007300200061002000740065007300740020000A00280041006200680069006c0061007300680020004d007500740068007500720061006a00200036002f00310039002f00320030003100380029</F_TEXT>\r\n" + 
                       "   </PropDesc>\r\n" + 
                       "</FnAnno>";

StringBuilder buf = new StringBuilder();

SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser parser = factory.newSAXParser();
parser.parse(new InputSource(new StringReader(textToExtract)), new DefaultHandler() {
    private boolean captureText;
    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        this.captureText = qName.equals("F_TEXT");
    }
    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
        this.captureText = false;
    }
    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        if (this.captureText)
            buf.append(ch, start, length);
    }
});

System.out.println(buf.toString());

Output

005400680069007300200069007300200061002000740065007300740020000A00280041006200680069006c0061007300680020004d007500740068007500720061006a00200036002f00310039002f00320030003100380029

1 Comment

I can't wait until we get raw String literals so that things like this will look better :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.