Выбор заголовков
Теперь мы подошли к рассмотрению методов сервлета, которые отвечают на запросы пользователя о поиске определенных заголовков сообщений. Для нашего примера мы ограничились двумя методами выбора заголовков: вы можете либо просмотреть всю коллекцию в поисках заголовков, которые содержат определенную последовательность символов, либо извлечь все заголовки, входящие в конкретную категорию.
При вызове метода articlesByKeyword, показанного в листинге 9.13, ему передается строка, содержащая одну или более последовательностей символов, разделенных запятыми. Первый шаг заключается в преобразовании этой строки keystring в массив типа String. Этот шаг выполняется методом prepKeys, который, кроме того, переводит все символы в верхний регистр. Далее метод searchArticle осуществляет поиск по всем элементам article и все совпадения возвращаются в виде массива ссылок на соответствующие элементы.
Листинг 9.13. Метод articlesByKeyWord вызывается сервлетом (NewsModel.java)
// articles by keyword appearance in headline // keys may be word or phrase, one or more, sep by comma // just use original order public Element[] articlesByKeyWord( String keystring ){ String[] keys = prepKeys( keystring ); // upper case and separated Vector v = new Vector(); int i ; int ct = articleNodeList.getLength(); for( i = 0 ; i < ct ; i++ ){ Element aE = (Element) articleNodeList.item( i ) ; if( searchArticle( aE, keys )){ v.addElement(aE); } } Element[] ret = new Element[ v.size() ]; for( i = 0 ; i < ret.length ; i++ ){ ret[i] = (Element) v.elementAt(i); } return ret ; } // convert to upper case and separate at commas private String[] prepKeys( String s ){ StringTokenizer st = new StringTokenizer( s.toUpperCase(), ","); String[] ret = new String[ st.countTokens() ]; int i = 0 ; while( st.hasMoreTokens() ){ ret[i++] = st.nextToken().trim(); } return ret ; }
Метод searchArticle, как показано в листинге 9.14, сложнее, чем вы, возможно, ожидали. Это объясняется некоторыми особенностями анализатора XML. Рассмотрим содержимое элемента <headline_text>, куда включена сущность (например, &атр;):
<headline_text>Q&A: Will Sony I
Rule the Digital World</ I headline_text>
Эта строка будет разделена анализатором на три объекта Node: два текстовых узла, разделенных узлом EntityReference. Так как нам нужен полный текст заголовка, для получения соответствующей строки вызывается метод getFullText, выдающий текст заголовка целиком.
Метод getFullText, который также показан в листинге 9.14, объединяет текст всех частей заголовка. Текст, представляющий узел EntityReference, должен быть построен как объединение с символами ; и & имени узла.
Листинг 9.14. Методы, которые поддерживают поиск заголовков по ключевым словам (NewsModel.java)
// return true if one of the keys appears //in the headline_text element private boolean searchArticle( Element aE, String[] keys ){ NodeList htNL = aE.getElementsByTagName("headline_text"); if( htNL.getLength() == 0 ) return false ; // there is only one headline_text Element htE = (Element)htNL.item(0); String str = getFullText( htE ).toUpperCase() ; for( int i = 0 ; i < keys.length ; i++ ){ if( str.indexOf( keys[i] ) >= 0 ) return true ; } return false ; } // this is needed to cope with headline text that has entities private String getFullText( Node nd ){ NodeList nl = nd.getChildNodes(); int ct = nl.getLength(); if( ct == 0 ) return ""; if( ct == 1 ) return nd.getFirstChild().getNodeValue(); StringBuffer sb = new StringBuffer(); for( int i = 0 ; i < ct ; i++ ){ Node n = nl.item(i); if( n instanceof EntityReference ){ // reconstruct & notation sb.append( '&' ); sb.append( n.getNodeName()); sb.append( ';' ); } else { sb.append( n.getNodeValue() ); } } return sb.toString(); }
Метод locateCategories (см. листинг 9.12) создал коллекцию clusterHash, в которой элементы (заголовки сообщений) хранятся в соответствии со своими тегами <cluster>. Мы не совсем понимаем, почему в Moreover.com этот элемент называется cluster (кластер) — нам кажется, что более подходящим названием для этого тега было бы topic (тема); поэтому метод, приведенный в листинге 9.15, называется articlesByTopic (статьи по темам). В этом листинге также показан метод getAllTopics, который просто превращает весь список узлов articleNodeList в массив типа Element.
Листинг 9.15. Этот метод возвращает все элементы с заданным значением элемента cluster (NewsModel.java)
// return array of Element for this topic // or null if none available public Element[] articlesByTopic( String topic ){ Vector v = (Vector) clusterHash.get( topic ); if( v == null ) return null ; Element[] ret = new Element[ v.size() ]; for( int i = 0 ; i < ret.length ; i++ ){ ret[i] = (Element) v.elementAt( i ); } return ret ; }
public Element[] getAllTopics(){ int ct = articleNodeList.getLength(); Element[] ret = new Element[ ct ]; for( int i = 0 ; i < ct ; i++ ){ ret[i] = (Element)articleNodeList.item( i ); } return ret ; }
Чтобы пользователь мог выбирать темы сообщений, нам нужно иметь список всех тем, имеющихся в текущей модели DOM. Для создания списка тематических категорий применяется класс NetNewsBean, который мы и будем рассматривать ближе к концу этой главы. В этом классе используется массив типа String, который создается в методе getTopics, приведенном в листинге 9.16. Обратите внимание на то, что для сортировки объектов Srting в данном массиве необходим метод shell Sort, потому что порядок следования ключей в хэш-таблице непредсказуем.
Листинг 9.16. Метод getTopics (NewsModel.java)
// return exact names of all topics available public String[] getTopics(){ Enumeration keys = clusterHash.keys(); String[] ret = new String[ clusterHash.size() ]; int i = 0; while( keys.hasMoreElements() ){ ret[i++] = (String)keys.nextElement(); } shellSort( ret ); return ret ; }
Метод formatElement, показанный в листинге 9.17, предлагает простой способ вставить текст из объекта art (который соответствует некоторому заголовку) в строку, обычно содержащую информацию относительно разметки HTML. Ниже приводится пример такой форматирующей строки, в которой содержатся теги, показывающие, куда нужно вставить элементы url, headline_text и source:
<tr><td><a href="<%url>"x%headline_text> </a> " from <%source></ tdx/tr>
Работа этого метода заключается в отыскании символов <%, выделении имени элемента и вызове метода getContent для извлечения текста элемента из соответствующего объекта Element. Заметим, что метод getContent вызывает метод getFullText для получения полного текста выбранного элемента.
Листинг 9.17. Метод formatElement (NewsModel.java)
// Element known to be an article, formatting string public String formatElement( Element art, String fmt ){ StringBuffer sb = new StringBuffer( 3 * fmt.length() ); int p0 = 0 ; int p1 = fmt.indexOf("<%"); int p2 = fmt.indexOf('>', p1); while( p1 > p0 && p2 > p1 ){ sb.append( fmt.substring( p0, p1 )); sb.append( getContent( art, fmt.substring(p1 + 2, p2) )); p0 = p2 + 1 ; p1 = fmt.indexOf("<%", p0); if( p1 > p0 ){ p2 = fmt.indexOf('>', p1); } } sb.append( fmt.substring( p0 )); return sb.toString(); } // element known to be an article private String getContent( Element art, String key ){ NodeList nl = art.getElementsByTagName( key ); if( nl.getLength() == 0 ) return ""; Element kE = (Element)nl.item(0); return getFullText( kE ) ; }
Последняя часть кода класса NewsModel, приведенная в листинге 9.18, содержит метод she! 1 Sort и некоторые другие служебные методы.
Листинг 9.18. Метод для сортировки и другие служебные методы (NewsModel.java)
public void shellSort (String[] srted ) { // h is the separation between items we compare. int h = 1; while ( h < srted.length ) { h = 3 * h + 1; } // now h is optimum while ( h > 0 ) { h = (h - 1)/3; for ( int i = h; i < srted.length; ++i ) { String item = srted[i]; int j=0; for ( j = i - h; j >= 0 && compare( srted[j], item ) < 0; j -= h ) { srted[j+h] = srted[j]; } // end inner for srted[j+h] = item; } // end outer for } // end while } // end sort
// return -1 if a < b , 0 if equal, +1 if a > b int compare(String a, String b ){ String aa = a.toUpperCase() ; String bb = b.toUpperCase() ; return bb.compareTo( aa ) ; }
public String toString() { StringBuffer sb = new StringBuffer( "NewsModel " ); if( !usable ){ sb.append("is not usable due to "); sb.append( lastErr ); return sb.toString(); } sb.append("count of articles "); sb.append( Integer.toString( articleNodeList.getLength()) ); sb.append("Unique clusters " + clusterHash.size() ); sb.append("\n"); Enumeration keys = clusterHash.keys(); while( keys.hasMoreElements() ){ String key = (String)keys.nextElement(); Vector v = (Vector)clusterHash.get( key ); sb.append(" Topic: " ); sb.append( key ) ; sb.append(" has " ) ; sb.append(Integer.toString( v.size())); sb.append("\n"); } return sb.toString(); } }