/*
 * $Id: NodeSelector.java 220 2007-07-16 10:32:15Z sugimotokenichi $
 * Copyright (C) 2005 SUGIMOTO Ken-ichi
 * 作成日: 2005/10/29
 */
package feat2.template;

import feat2.template.impl.HTMLNodeListImpl;
import feat2.template.impl.HTMLNodeMapImpl;

/**
 * ノードを検索するユーティリティクラス。
 * @author SUGIMOTO Ken-ichi
 */
public class NodeSelector {


    /**
     * イテレータが返すノードの中で最初に見つかった要素ノードを返す。
     *
     * 渡されたイテレータは見つかったノードの位置まで進む
     * (次のnextメソッドでは、ここで見つかったノードの次のノードが返される)。
     * ノードが見つからなかったときは、イテレータをこのメソッドを実行する前の位置に戻す。
     * このメソッドはイテレータのマークを変更するので注意。
     * @param iterator
     * @return 見つかったノード。ノードが見つからないときはnullを返す
     */
    public static HTMLElement getElement(NodeIterator iterator) {

        iterator.mark();
        while(iterator.hasNext()) {
            HTMLNode node = iterator.nextNode();
            if ( node instanceof HTMLElement )
                return (HTMLElement)node;
        }
        iterator.reset();
        return null;

    }


    /**
     * イテレータが返すノードの中でn番目に見つかった要素ノードを返す。
     *
     * 渡されたイテレータは見つかったノードの位置まで進む
     * (次のnextメソッドでは、ここで見つかったノードの次のノードが返される)。
     * ノードが見つからなかったときは、イテレータをこのメソッドを実行する前の位置に戻す。
     * このメソッドはイテレータのマークを変更するので注意。
     * @param iterator
     * @param n
     * @return 見つかったノード。ノードが見つからないときはnullを返す
     */
    public static HTMLElement getElement(NodeIterator iterator, int n) {
        iterator.mark();
        int counter = 0;
        while(iterator.hasNext()) {
            HTMLNode node = iterator.nextNode();
            if ( node instanceof HTMLElement ) {
                if ( ++counter == n ) {
                    return (HTMLElement)node;
                }
            }
        }
        iterator.reset();
        return null;
    }


    /**
     * イテレータが返すノードの中で最初に見つかったテキストノードを返す。
     *
     * 渡されたイテレータは見つかったノードの位置まで進む
     * (次のnextメソッドでは、ここで見つかったノードの次のノードが返される)。
     * ノードが見つからなかったときは、イテレータをこのメソッドを実行する前の位置に戻す。
     * このメソッドはイテレータのマークを変更するので注意。
     * @param iterator
     * @return 見つかったノード。ノードが見つからないときはnullを返す
     */
    public static HTMLText getText(NodeIterator iterator) {
        iterator.mark();
        while(iterator.hasNext()) {
            HTMLNode node = iterator.nextNode();
            if ( node instanceof HTMLText ) {
                return (HTMLText)node;
            }
        }
        iterator.reset();
        return null;
    }

    /**
     * イテレータが返すノードの中でn番目に見つかったテキストノードを返す。
     *
     * 渡されたイテレータは見つかったノードの位置まで進む
     * (次のnextメソッドでは、ここで見つかったノードの次のノードが返される)。
     * ノードが見つからなかったときは、イテレータをこのメソッドを実行する前の位置に戻す。
     * このメソッドはイテレータのマークを変更するので注意。
     * @param iterator
     * @param n
     * @return 見つかったノード。ノードが見つからないときはnullを返す
     */
    public static HTMLText getText(NodeIterator iterator, int n) {
        iterator.mark();
        int counter = 0;
        while(iterator.hasNext()) {
            HTMLNode node = iterator.nextNode();
            if ( node instanceof HTMLText ) {
                if ( ++counter == n ) {
                    return (HTMLText)node;
                }
            }
        }
        iterator.reset();
        return null;
    }

    /**
     * イテレータが返すノードの中で最後に見つかったテキストノードを返す。
     *
     * 渡されたイテレータは最後の位置まで進むが、
     * ノードが見つからなかったときは、イテレータをこのメソッドを実行する前の位置に戻す。
     * このメソッドはイテレータのマークを変更するので注意。
     * @param iterator
     * @return 見つかったノード。ノードが見つからないときはnullを返す
     */
    public static HTMLText getLastText(NodeIterator iterator) {
        iterator.mark();
        HTMLText ret = null;
        while(iterator.hasNext()) {
            HTMLNode node = iterator.nextNode();
            if ( node instanceof HTMLText ) {
                ret = (HTMLText)node;
            }
        }
        if ( ret == null )
            iterator.reset();
        return ret;
    }

    /**
     * イテレータが返すノードの中のテキストノードをすべて返す。
     *
     * 渡されたイテレータは最後まで進む。
     * @param iterator
     * @return 見つかったノードのリスト。ノードが見つからないときは空のリストを返す。
     */
    public static HTMLNodeList selectTextNode(NodeIterator iterator) {
        HTMLNodeListImpl list = new HTMLNodeListImpl();
        while(iterator.hasNext()) {
            HTMLNode node = iterator.nextNode();
            if ( node instanceof HTMLText ) {
                list.add(node);
            }
        }
        return list;
    }

    /**
     * 指定ノード以下のテキストノードのテキストを連結した文字列を返す。
     * 結果の文字列は以下のように整形される。<br>
     * ・改行とタブ文字はスペースに変換される<br>
     * ・連続したスペースは1つのスペースに置き換えられる<br>
     * ・文字列の最初と最後のスペースは削除される
     * @param node 検索を開始するサブツリーの頂点ノード
     * @return
     */
    public static String joinText(HTMLNode node) {
        int spaceCount = 0;
        HTMLNodeList list = selectTextNode(new NodeTreeIterator(node));
        int size = list.size();
        StringBuffer buf = new StringBuffer();
        for(int i=0; i<size; i++) {
            spaceCount = joinText(buf, (HTMLText)list.get(i), spaceCount, i == 0);
        }
        return buf.toString();
    }

    private static int joinText(StringBuffer buf, HTMLText textNode, int spaceCount, boolean first) {
        String text = textNode.getText();
        if ( text != null ) {
            char[] c = text.toCharArray();
            int mode = 0;
            for(int i=0; i<c.length; ) {
                switch(mode) {
                    // 通常の文字
                    case 0:
                        if ( c[i] == '\n' || c[i] == '\r' || c[i] == '\t' || c[i] == ' ' ) {
                            mode = 1;
                        }
                        else if ( c[i] == '&' ) {
                            mode = 2;
                        }
                        else {
                            if ( spaceCount > 0 && !first ) {
                                buf.append(' ');
                            }
                            spaceCount = 0;
                            first = false;
                            buf.append(c[i]);
                            i++;
                        }
                        break;
                    // スペース
                    case 1:
                        if ( c[i] == '\n' || c[i] == '\r' || c[i] == '\t' || c[i] == ' ' ) {
                            spaceCount++;
                            i++;
                        }
                        else {
                            mode = 0;
                        }
                        break;
                    // &nbsp
                    case 2:
                        if ( c.length - i >= 6 ) {
                            String nbsp = new String(c, i, 6);
                            if ( nbsp.equals("&nbsp") ) {
                                spaceCount++;
                                i += 6;
                            }
                            else {
                                buf.append(c[i]);
                                i++;
                            }
                        }
                        else {
                            buf.append(c[i]);
                            i++;
                        }
                        mode = 0;
                        break;
                }
            }
        }
        return spaceCount;
    }

    /**
     * イテレータが返すノードの中で最初に見つかった指定の名前の要素を返す。
     *
     * 渡されたイテレータは見つかったノードの位置まで進む
     * (次のnextメソッドでは、ここで見つかったノードの次のノードが返される)。
     * ノードが見つからなかったときは、イテレータをこのメソッドを実行する前の位置に戻す。
     * このメソッドはイテレータのマークを変更するので注意。
     * @param iterator
     * @param tagName 検索する要素の名前。"*"を指定するとすべての要素にヒットする。
     * @return 見つかったノード。ノードが見つからないときはnullを返す
     */
    public static HTMLElement getTag(NodeIterator iterator, String tagName) {
        iterator.mark();
        while(iterator.hasNext()) {
            HTMLNode node = iterator.nextNode();
            if ( node instanceof HTMLElement ) {
                HTMLElement element = (HTMLElement)node;
                String name = element.getTagName();
                if ( tagName.equals("*") || (name != null && name.equalsIgnoreCase(tagName)) ) {
                    return element;
                }
            }
        }
        iterator.reset();
        return null;
    }

    /**
     * イテレータが返すノードの中でn番目に見つかった指定の名前の要素を返す。
     *
     * 渡されたイテレータは見つかったノードの位置まで進む
     * (次のnextメソッドでは、ここで見つかったノードの次のノードが返される)。
     * ノードが見つからなかったときは、イテレータをこのメソッドを実行する前の位置に戻す。
     * このメソッドはイテレータのマークを変更するので注意。
     * @param iterator
     * @param tagName 検索する要素の名前。"*"を指定するとすべての要素にヒットする。
     * @param n
     * @return 見つかったノード。ノードが見つからないときはnullを返す
     */
    public static HTMLElement getTag(NodeIterator iterator, String tagName, int n) {
        iterator.mark();
        int counter = 0;
        while(iterator.hasNext()) {
            HTMLNode node = iterator.nextNode();
            if ( node instanceof HTMLElement ) {
                HTMLElement element = (HTMLElement)node;
                if ( tagName.equals("*") || element.getTagName().equalsIgnoreCase(tagName) ) {
                    if ( ++counter == n ) {
                        return element;
                    }
                }
            }
        }
        iterator.reset();
        return null;
    }

    /**
     * イテレータが返すノードの中で最後に見つかった指定の名前の要素を返す。
     *
     * 渡されたイテレータは最後の位置まで進むが、
     * ノードが見つからなかったときは、イテレータをこのメソッドを実行する前の位置に戻す。
     * このメソッドはイテレータのマークを変更するので注意。
     * @param iterator
     * @param tagName 検索する要素の名前。"*"を指定するとすべての要素にヒットする。
     * @return 見つかったノード。ノードが見つからないときはnullを返す
     */
    public static HTMLElement getLastTag(NodeIterator iterator, String tagName) {
        iterator.mark();
        HTMLElement ret = null;
        while(iterator.hasNext()) {
            HTMLNode node = iterator.nextNode();
            if ( node instanceof HTMLElement ) {
                HTMLElement element = (HTMLElement)node;
                String name = element.getTagName();
                if ( tagName.equals("*") || (name != null && name.equalsIgnoreCase(tagName)) ) {
                    ret = element;
                }
            }
        }
        if ( ret == null )
            iterator.reset();
        return ret;
    }

    /**
     * 指定のタグ名の要素をすべて返す。
     * タグ名は大文字/小文字を区別しない。
     * 渡されたイテレータは最後まで進む。
     * @param iterator
     * @param tagName 検索するタグの名前。"*"が指定されたときはすべてのタグ名が該当する
     * @return 結果のリスト。該当するノードが見つからなかったときはサイズ0のリスト
     */
    public static HTMLNodeList selectTags(NodeIterator iterator, String tagName) {
        HTMLNodeListImpl list = new HTMLNodeListImpl();
        while(iterator.hasNext()) {
            HTMLNode node = iterator.nextNode();
            if ( node instanceof HTMLElement ) {
                HTMLElement element = (HTMLElement)node;
                String name = element.getTagName();
                if ( tagName.equals("*") || (name != null && name.equalsIgnoreCase(tagName)) ) {
                    list.add(node);
                }
            }
        }
        return list;
    }

    /**
     * イテレータが返すノードの中で最初に見つかった指定属性値の要素を返す。
     *
     * 渡されたイテレータは見つかったノードの位置まで進む
     * (次のnextメソッドでは、ここで見つかったノードの次のノードが返される)。
     * ノードが見つからなかったときは、イテレータをこのメソッドを実行する前の位置に戻す。
     * このメソッドはイテレータのマークを変更するので注意。
     * @param iterator
     * @param attrName 属性名
     * @param attrVal 属性値
     * @return 見つかったノード。ノードが見つからないときはnullを返す
     */
    public static HTMLElement getElementByAttribute(NodeIterator iterator, String attrName, String attrVal) {
        iterator.mark();
        while(iterator.hasNext()) {
            HTMLNode node = iterator.nextNode();
            if ( node instanceof HTMLElement ) {
                HTMLElement element = (HTMLElement)node;
                String val = element.getAttribute(attrName);
                if ( val != null && val.equals(attrVal) ) {
                    return element;
                }
            }
        }
        iterator.reset();
        return null;
    }

    /**
     * 指定属性値の要素をすべて返す。
     * 渡されたイテレータは最後まで進む。
     * @param iterator
     * @param attrName 属性名
     * @param attrVal 属性値
     * @return 結果のリスト。該当するノードが見つからなかったときはサイズ0のリスト
     */
    public static HTMLNodeList selectElementsByAttribute(NodeIterator iterator, String attrName, String attrVal) {
        HTMLNodeListImpl list = new HTMLNodeListImpl();
        while(iterator.hasNext()) {
            HTMLNode node = iterator.nextNode();
            if ( node instanceof HTMLElement ) {
                HTMLElement element = (HTMLElement)node;
                String val = element.getAttribute(attrName);
                if ( val != null && val.equals(attrVal) ) {
                    list.add(node);
                }
            }
        }
        return list;
    }

    /**
     * イテレータが返すノードの中で最初に見つかった指定class属性名の要素を返す。
     *
     * 渡されたイテレータは見つかったノードの位置まで進む
     * (次のnextメソッドでは、ここで見つかったノードの次のノードが返される)。
     * ノードが見つからなかったときは、イテレータをこのメソッドを実行する前の位置に戻す。
     * このメソッドはイテレータのマークを変更するので注意。
     * @param iterator
     * @param className class属性名
     * @return 見つかったノード。ノードが見つからないときはnullを返す
     */
    public static HTMLElement getElementByClass(NodeIterator iterator, String className) {
        iterator.mark();
        while(iterator.hasNext()) {
            HTMLNode node = iterator.nextNode();
            if ( node instanceof HTMLElement ) {
                HTMLElement element = (HTMLElement)node;
                if ( element.containsClassValue(className) ) {
                    return element;
                }
            }
        }
        iterator.reset();
        return null;
    }

    /**
     * 指定属性値の要素をすべて返す。
     * 渡されたイテレータは最後まで進む。
     * @param iterator
     * @param className class属性名
     * @return 結果のリスト。該当するノードが見つからなかったときはサイズ0のリスト
     */
    public static HTMLNodeList selectElementsByClass(NodeIterator iterator, String className) {
        HTMLNodeListImpl list = new HTMLNodeListImpl();
        while(iterator.hasNext()) {
            HTMLNode node = iterator.nextNode();
            if ( node instanceof HTMLElement ) {
                HTMLElement element = (HTMLElement)node;
                if ( element.containsClassValue(className) ) {
                    list.add(node);
                }
            }
        }
        return list;
    }

    /**
     * id属性を持った要素をすべて選択する。
     * 同じid値を持つノードが複数見つかったときは最後に見つかったものが結果に格納される。
     * @return id属性値にマッピングされたノードのコレクション
     */
    public static HTMLNodeMap selectIdentifiableElements(NodeIterator iterator) {
        HTMLNodeMapImpl map = new HTMLNodeMapImpl();
        while(iterator.hasNext()) {
            HTMLNode node = iterator.nextNode();
            if ( node instanceof HTMLElement ) {
                HTMLElement element = (HTMLElement)node;
                if ( element.getId()!= null ) {
                    map.put(element.getId(), element);
                }
            }
        }
        return map;
    }

}
