1

I've got following problem:

Wrong sorted ListView

As you can see on the picture, the CITROEN group header is not above the all Citroen car models, like e.g DACIA. The weird thing is that there is around 20 Car Brands like BMW,AUDI...and every group header is above its children items, but not the CITROEN.

This listview is populated from the html file, which has the following structure:

<optgroup label="BMW">    
<option value="225" >BMW X3 3.0si</option>
    <option value="226" >BMW X5 3.0d A/T</option>
    <option value="227" >BMW X5 4.8i A/T</option>
</optgroup>
<optgroup label="CITROËN">
    <option value="67" >CITROËN C1 1.0i</option>
    <option value="68" >CITROËN C1 1.4 HDi</option>
    <option value="69" >CITROËN C2 1.1i</option>

I'm using custom adapter. Here is the source code of the comparing method:

 @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
        progressDialog.dismiss();

        adapter = new ModelsAdaper(CarsList.this, generateData());

        /*Should sort the ListView alphabetically*/
        adapter.sort(new Comparator<Model>() {
            @Override
            public int compare(Model lhs, Model rhs) {
                return lhs.getTitle().compareTo(rhs.getTitle());
            }
        });
        setListAdapter(adapter);

The generateData() method:

private ArrayList<Model> generateData() {
    models = new ArrayList<Model>();

    /*This loop adds car brands to the listview*/
    for(String s: brands){
       models.add(new Model(R.drawable.alfa_romeo_icon_52,s));
    }

    /*This loop inserts car models into the listview*/
    int key;
    for(int i = 0; i < hashMap.size(); i++) {
        key = hashMap.keyAt(i);
        models.add(new Model(hashMap.get(key)));
    }
    return models;
}

And finally, the Model class

public class Model {
private String title;
private boolean isGroupHeader = false;
private int icon;

/**
 * This constructor will be used for creating instance od target_item
  * @param title is content of the item
 */
public Model(String title){
     this.title = title;
 }

/**
 * This constructor will be used for group headers
 * @param icon is icon of the group
 * @param title is name of the group
 */
public Model(int icon, String title){
    this.icon = icon;
    this.title = title;
    isGroupHeader = true;
}

EDIT As requested, here is the HTMLParser class source code. Its constructor is called from CarsList Activity, which extends ListActivity

public class HTMLParser {
private String value;
private InputStream is = null;
private Context context=null;
private org.jsoup.nodes.Document document = null;
SparseArray<String> hashMap =  new SparseArray<String>();
private ArrayList<String> modelsList = new ArrayList<String>();
private ArrayList<String> brandsList = new ArrayList<String>();

/**
 * Constructor is used to pass instance of CarsList Context to get html asset
 * @param context instance of the CarsList activity context
 */
public HTMLParser(Context context) throws IOException {
    this.context = context;
    is = context.getAssets().open("modely aut.html");
    document = Jsoup.parse(is,"UTF-8","http://example.com");
}

/**
 * The purpose of this method is to parse car brands from html asset
 * @return ArrayList of car brands
 */
public ArrayList<String> parseCarBrands(){
    Elements models = document.select("optgroup");
    for (Element e: models){
        brandsList.add(e.attr("label"));
    }
    return brandsList;
}

/**
 * Method parses all car models from html asset. For IO safety operations, it is recommended to call this method
 * after parseCarBrands() method, because parseCarModels() method closes inputStream.
 * @return SparseArray consisting of key: carID and value: car model
 */
public SparseArray<String> parseCarModels(){
    try {
        Elements models = document.select("option");
        for (Element e: models){
            int res = new Scanner(e.toString()).useDelimiter("\\D+").nextInt();
            value = e.html();
            modelsList.add(value);
            hashMap.put(res,value);
        }
    }   finally {
        if(is!=null){
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return hashMap;
}

}

EDIT 2 Possible source of the problem I have made some tests with the same code, but only in simple java project. It looks like some encoding issue. when using

Elements models2 = doc.select("option");

        for (Element e: models2){
           int key =  Integer.parseInt(e.attr("value"));
            String modelName = e.html();
            modelsList.add(value);
        }

the output from System.out.println(modelName) looks like this:

CITRO&Euml;N C4 1.6i 16V EP turbo

but when parsing just a brand names using String s = e.attr("label"); the output is as it should be. Do you have any idea, where the problem can be? I will post other parts of the code, if it will be necessary. I would like to thank you for all your time and effort you give to my question

Community
  • 1
  • 1
user2151486
  • 778
  • 2
  • 13
  • 25
  • Your code has no connection to the presented html source so how would we know? But take out the sort() for a short test. And we see no icons so why post code with icons? – greenapps Sep 03 '14 at 15:16
  • Sorry for inconvenience. I've updated my question with the HTMLParser class. There are no icons yet because I'm working on them. The reason, why the icons are missing is that I've temporarily commented the setImageResource()method – user2151486 Sep 03 '14 at 15:37
  • Well did you do a run without the sort() and witout the ë in Citroën ? – greenapps Sep 03 '14 at 15:39
  • I've run the app without sorting. As expected, the first on the list are all group headers together and all car models are below them. I've tried changing ë to e, but nothing changed. Result is the same. – user2151486 Sep 03 '14 at 15:50
  • do the citroën car models have a space prior to their name? We don't see that, but in case it's the first brand, it could be that. – Sergi Juanola Sep 03 '14 at 15:52
  • Your approach is completely wrong. What you do is putting all options in one list. Putting all brands in one list. Add them together and then sort. You trow away valuable information and all depends on the assumption that the car brand is mentioned at the start of the model description. Better: in `parseCarBrands()` collect the 'option's labels for that brand too. – greenapps Sep 03 '14 at 16:00
  • @greenapps You're right, but in that specific case, well, there may always be the brand in front. I've used wheel and tyre databases, and the base structure tends to be always the same: Manufacturer (or Brand) - Model - Version. What makes sense is to store instead of just a String, a String and a reference to that brand. This way you can sort by two: First by brand and then by name. – Sergi Juanola Sep 03 '14 at 16:07
  • Thank you people for your valuable advices and tips. it seems that the HTML parser class must be definitely changed. – user2151486 Sep 03 '14 at 16:15
  • Just a curiosity, @user2151486, are you, let's say, crawling an external website for that data? In that case, it may be better to first parse the data and convert it to a structure which is more pleasant to work for Android. – Sergi Juanola Sep 03 '14 at 16:20
  • @Korcholis yes, there is a website which provides few details about every single car in the mentioned list. I'm trying to parse these information from the webpage source code and save it to the database. Because I would like to have my app offline oriented as much as possible, I've downloaded this website's source code and deleted everything unnecessary. – user2151486 Sep 03 '14 at 16:36
  • Fine. I've done it a couple times. Python is really quick to web scrap this kind of stuff. – Sergi Juanola Sep 03 '14 at 16:38
  • `it seems that the HTML parser class must be definitely changed`. Why? What is wrong with it? – greenapps Sep 03 '14 at 16:42
  • Sorry i thougth you wanted to change the JSoup class. `int res = new Scanner(e.toString()).useDelimiter("\\D+").nextInt();`. Can you explain what this code does? – greenapps Sep 03 '14 at 16:49
  • Sure, this part of code parses the IDs of the cars stored in the "value" labels. in my opinion, `parseCarBrands()` uses more elegant solution but in the time of writing this "crazy" stuff with Scanner, I didn't know about the `attr()` method of Jsoup :) – user2151486 Sep 03 '14 at 16:57
  • Please check the Edit 2. – user2151486 Sep 03 '14 at 19:01
  • Have you tried `Collator` with a `Locale` instead of `Comparator` ? http://stackoverflow.com/questions/9261305/java-array-sort-utf-8 However for my taste keeping everyting in a single list, destroying the tree and trying to sort in this way is wrong. You should consider re-designing how you accumulate the data for adapter. – emrahgunduz Sep 03 '14 at 19:06

1 Answers1

1

I've made it, but it's such a stupid thing. I've changed few things in the parseCarModels() First of all, I've changed the return type to LinkedHashMap<Integer,String>. Parsing the html now seems to be faster. Then I've changed the value variable from String to CharSequence. This allowed me to use Html.fromHtml(e.html), so the final code looks like this:

public LinkedHashMap<Integer,String> parseCarModels(){
    Elements models = document.select("option");
    int key;
    CharSequence value;
    for(Element e: models){
        key = Integer.parseInt(e.attr("value"));
        value = Html.fromHtml(e.html());
        hashMap.put(key,value.toString());
    }
    if(is!=null){
        try {
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return hashMap;
}

Thank you for all your help. I really appreciate that. I hope that this code isn't very ineffective

user2151486
  • 778
  • 2
  • 13
  • 25