Вы, наверное, встречали web-сайты небольших организаций, в которых самые свежие новости относятся к событиям полугодовой давности. Эти организации теряют прекрасный шанс привлечь внимание случайных посетителей. Может быть, так происходит потому, что люди, находящиеся в курсе происходящих в фирме событий, не общаются с web-мастером своей организации или просто страницы с новостями слишком неудобно редактировать. Мы, естественно, собираемся предложить вам решение, основанное на технологии XML, которое мы подробно обсуждаем в этой лекции.
Мы хотим предложить посетителям нашего web-сайта последние новости фирмы в компактном и удобном для чтения формате. Помните, что обычно в вашем распоряжении имеется всего лишь несколько секунд, чтобы привлечь внимание к вашему сайту случайного посетителя, который просто «прогуливается» по сети. Мы хотим, чтобы посетителю было понятно, как найти на сайте подробную информацию, если его что-то заинтересовало, как войти в систему каталога и, будем надеяться, что-нибудь приобрести.
Если вы подумаете, какими характеристиками должна обладать система показа новостей, в ваш список, вероятно, войдут следующие пункты:
гибкий способ отображения;
удобный ввод данных;
минимальная нагрузка на сервер.
Наряду с обеспечением этих свойств при разработке системы вы, в первую очередь, должны будете решить, какие элементы и атрибуты XML использовать для представления данных, затем сделать выбор между моделями DOM и SAX (механизмами отображения данных для пользователя) и, наконец, определить механизм добавления новых сообщений.
Поскольку мы решили остановиться на XML как на исходной среде хранения данных, мы можем обеспечить гибкость отображения посредством хранения данных, необходимых для всех доступных способов представления информации. Мы имеем в виду следующее: вероятно, вы замечали, что на разных коммерческих web-сайтах новости компании представлены обычно одним из трех способов:
заголовки новостей со ссылкой на полный текст;
несколько наиболее интригующих строк со ссылкой на полный текст;
полный текст сообщения.
Существуют и другие форматы сообщений: электронный информационный бюллетень, печатная копия информационного бюллетеня, корпоративные сообщения. Для этих форматов также применимы описанные выше три способа (заголовок, краткое сообщение и полный текст). Первая наша задача при разработке системы отображения новостей — создать такой документ XML, который поддерживал бы все три формата.
Хотя в области искусственного интеллекта и понимания компьютером естественных языков сделаны большие успехи, никто не рассчитывает, что компьютер напишет хороший заголовок, проанализировав текст сообщения. Поэтому приходится согласиться с тем, что нужен человек, который для каждого способа представления новостей создаст отдельный заголовок. В сообщении обычно указывается дата, а иногда рядом еще и место, где произошло то событие, о котором идет речь, например: «Остин, Техас, 1 января 2000». Эта задача тоже должна выполняться человеком.
Некоторые сообщения очень выигрывают, когда сопровождаются графикой, звуковыми клипами или ссылками на другие сайты, поэтому продумайте, как сконструировать документ XML, чтобы его можно было дополнить различными элементами, способствующими увеличению привлекательности страницы новостей. Мы решили, что было бы слишком сложно и неудобно снабжать систему показа новостей в нашем примере всеми возможными «украшениями». Поэтому мы будем хранить тексты сообщений для второго и третьего способов (краткое и полное сообщение), используя тег XML <[[CDATA...]]>.
Поскольку анализаторы XML не пытаются анализировать текст, содержащийся внутри раздела СВАТА, в этот раздел вы можете поместить любую разметку HTML, не сбивая с толку анализатор.
Элементы, содержащие дату, заголовок, краткое сообщение и полный текст, показаны в листинге 8.1.
Листинг 8.1. Дата, заголовок, короткое и полное сообщения (thenews.xml)
<date>Austin, TX, Jun 14 2000</date>
<head>Best Seller at a Great Price</head>
<short>
<![CDATA[Due to a special deal with the publisher, we can now offer
<i>Dryer Lint Art</i>
at 50% off the retail price.]]>
</short>
<long>
<![CDATA[This books starts with simple Dryer Lint projects suitable for the novice and advances through easy stages tothe (literally)
<b>monumental</b>
recreation of famous monuments in that most flexible of craft materials, dryer lint. Even though you may never attempt major constructions like the Statue of Liberty project documented in the final chapter, your projects will benefit by a study of this famous creation. Includes DHL diagrams.]]>
</long>
Другим аспектом гибкости является способность выборочно представлять сообщения в соответствии с темой, интересующей посетителя. Предполагая, что спектр возможных интересов посетителей сайта XMLGifts.com очень широк, мы хотим показать каждому посетителю те новости, которые связаны с его излюбленной темой, и в том месте сайта, куда он с наибольшей вероятностью заглянет. В такой структуре неизбежны перекрывающиеся области; например, книга о музыкальной группе может оказаться интересной как для покупателей книг, так и для покупателей музыкальных компакт-дисков. Следовательно, каждое сообщение должно быть снабжено одной или несколькими пометками, которые указывают, к каким тематическим категориям можно его отнести; а формат представления новостей должен допускать переключения между различными темами сообщений.
Для того чтобы пометить сообщение и отнести его тем самым к определенной категории, мы можем использовать элемент или атрибут. Следуя советам, приведенным в разделе «Элементы или атрибуты?» лекции 2, можно заключить, что в данном случае лучше использовать атрибуты, так как тема сообщения — это данные о содержимом элемента, и мы предполагаем, что количество тем сообщений будет ограниченным.
Расположение сообщений в зависимости от их новизны
Самые свежие новости должны располагаться первыми. Так как документ XML автоматически сохраняет порядок следования элементов, новые элементы должны добавляться к началу документа. Более того, было бы неплохо предусмотреть возможность отображения только самых свежих новостей. Следовательно, нам нужен способ представления «возраста» сообщений.
После долгих колебаний между многочисленными способами представления даты, которые позволили бы нам отображать только недавние сообщения, мы остановились на использовании простого целочисленного представления количества дней, прошедших с 1 января 1970 года. Для этого значение типа long, возвращаемое методом System.currentTimeMillisO, делится на количество миллисекунд в сутках, и полученное число становится значением атрибута timestamp тега News item. Альтернативные варианты — использование классов Java DateFormat или Calendar — были отвергнуты, так как они подразумевают создание большого количества объектов, а мы хотим, чтобы показ новостей создавал минимальную нагрузку на сервер.
Информация для управления сообщениями
Поскольку вы или ваши служащие будут обновлять страницу новостей в режиме подключения к сети, было бы полезно отслеживать, кто какое сообщение написал. (Например, чтобы знать, кто должен получать нагоняй за допущенную ошибку.) Для этого используется атрибут <author> элемента <Newsitem>. Сервлет обновления сообщений, описанный в разделе «Добавление свежих новостей» этой лекции, обладает простым механизмом контроля доступа, в котором используются имя автора и пароль; этот то самое имя автора, которое становится значением атрибута author.
Чтобы создать документ HTML, в котором заголовок содержит ссылку на полную версию текста, нужно использовать уникальный идентификатор. Мы, например, выбрали простейший вариант: при создании каждого сообщения <NewsItem> ему присваивается серийный номер, который и становится значением его атрибута id.
Мы отслеживаем некоторые параметры, используемые во всем файле, с помощью атрибутов, которые задаются в корневом элементе документа, Newsfile. Очередной атрибут id — это просто next id. Присваивание нового значения каждому следующему атрибуту nextid является обязанностью программы, добавляющей новые сообщения или, в случае редактирования в автономном режиме, автора элемента Newsitem.
Корневой элемент также является подходящим местом для хранения атрибутов, связанных с различными заданными по умолчанию параметрами отображения. В приведенном ниже примере имеется только один такой атрибут, longtemplate, который идентифицирует заданный по умолчанию файл — шаблон HTML, используемый для форматирования сообщений.
В листинге 8.2 показано, как используются все перечисленные теги.
Листинг 8.2. Элемент <Newsfile> с одним элементом <Newsitem> (thenews.xml)
<?xral version="l.О" standalone="yes" ?> <!-выходные данные NewsllpKeep -->
<Newsfile longtemplate="tmlong.html" nextid="1010"> <Newsitem timestamp="11045" topic="CDs" author="wbrogden" id="1008" > <head>Your Favorite Music Now Available</head> <date>Austin, Feb 1, 2000</date> <short>
<![CDATA[XMLGifts proudly announces the availability of the CD that has all the geeks singing, <i>It's Dot Com Enough for He.</i>]]> </short> <long>
<![CDATA[<p>
<i>It's Dot Com Enough For He.</i>
now in stock!</p>
<p>All those great songs created during breaks in all-night coding sessions - now recorded by top Silicon Valley garage bands on our private label. <i>It's Dot Com Enough for Me</i> will have you singing along - or maybe laughing till the Jolt cola spurts out your nose. Seventeen songs from geeks at Sun. Microsoft, Apple. Cisco, and other top tech outfits. </P>]]>
</long>
</Newsitem>
</Newsfile>
Поскольку мы выбрали простой формат новостей, показанный в листинге 8.2, ввод данных осуществляется тоже достаточно просто. Система, основанная на формах HTML и сервлетах, описанная в разделе «Добавление свежих новостей» этой лекции, позволяет добавлять новые сообщения через Интернет.
Тем не менее легкость ввода данных никак не связана с контролем за качеством самого текста. Для достижения лучших результатов следует убедиться, что темы, согласно которым классифицируются сообщения, и стиль текста всегда согласуются между собой. Также необходимо составить список требований и рекомендаций по составлению текстов сообщений и убедиться в том, что эти списки доступны всем служащим фирмы, уполномоченным размещать сообщения на сайте.
Минимальная нагрузка на сервер
Поскольку мы надеемся, что посещаемость нашего сайта будет достаточно велика, мы хотим, чтобы отображение главной страницы выполнялось по возможности просто, то есть чтобы страница быстро загружалась, а ее генерирование не требовало больших затрат ресурсов со стороны сервера. Рассмотрим следующие альтернативные варианты отображения новостей.
Статические страницы новостей (Static News Pages). Статические страницы быстро загружаются, и главная страница благодаря XML может формироваться заново при появлении нового сообщения. Однако использование статических страниц исключает возможность их индивидуальной настройки для постоянных посетителей.
Страницы новостей, генерируемые сервлетами (Servlet-Generated News Pages). Сервлеты Java могут генерировать все, что потребуется; при этом предполагается, что они используют файлы с шаблонами HTML для регулировки многочисленных атрибутов внешнего вида сайта.
JSP-страницы (JavaServer Pages). Преимущество JSP-страниц перед сервлетами заключается в том, что для изменения внешнего вида страницы web-дизайнеру не нужно уметь программировать на Java.
При выборе методов обработки следует сделать выбор между моделями SAX и DOM, а также между анализаторами XML, проверяющими либо не проверяющими допустимость документа. Модель SAX подразумевает работу анализатора для доступа к каждой странице. При использовании модели DOM доступ к страницам осуществляется быстрее, но зато все приходится хранить в памяти. Предполагая, что количество новостей будет не больше нескольких сотен, затраты ресурсов на хранение всей модели DOM в памяти не окажутся слишком значительными, поэтому мы считаем, что в данном случае модель DOM является безусловно оптимальным вариантом.
Мы не видим никаких причин в использовании анализатора, проверяющего допустимость документа. Так как новые сообщения будут создаваться автоматически, вероятность ошибки форматирования весьма невелика. Кроме того, вряд ли ошибки, связанные с недопустимостью документа, могут смутить пользователя.
Окончательный вариант устройства системы показа новостей изображается блок-схемой, приведенной на рис. 8.1. Обработка информации, происходящая на сервере, представлена в правой части блок-схемы, а обработка в автономном режиме — в левой части. Исходный файл XML может редактироваться как в режиме подключения к сети, так и автономно, но предпочтительным является режим подключения, так как он позволяет автоматически задавать атрибуты id и timestamp.
Рис. 8.1. Обработка сообщений
Объектная модель документа поддерживается в памяти с помощью класса DOMlibrary, который был описан в лекции 7. Обновление исходного файла XML в режиме подключения выполняется сервлетом CompanyNewsServ и классом NewsUpKeep, которые обсуждаются далее в этой лекции. Формирование web-страниц новостей на основе текстов сообщений осуществляется сервлетами или JSP-страницами, использующими класс NewsFormatter, который обсуждается в следующем разделе. Для создания информационных бюллетеней и печатной версии требуются другие форматы, которые легко написать, основываясь на приведенных ниже примерах.
Прежде чем мы углубимся в рассмотрение кода нашей системы показа новостей, давайте посмотрим, как может выглядеть разрабатываемая страница. На рис. 8.2 показана web-страница, посвященная новостям фирмы, в контексте более крупного сайта. Мы не включили в нее навигационный интерфейс и другие элементы, которые должны присутствовать в реальном сайте.
Рис. 8.2. Web-страница новостей, генерируемая с помощью JSP-страницы
JSP-страница, используя класс NewsFormatter, который подробно описывается в следующем разделе, сгенерировала показанную на рисунке страницу на основе файла XML. В левой части экрана приводятся заголовки последних новостей по всем темам. Самое свежее сообщение приводится полностью в центре страницы. Справа показаны новости в кратком изложении, но первая из них (полная версия которой расположена по центру) пропущена. При щелчке мышью на заголовке или кратком изложении сообщения появляется полная версия этого сообщения.
Ключевым классом Java для формирования новостных сообщений является класс NewsFormatter. Как показано в листинге 8.3 и следующих листингах, класс NewsFormatter включает в себя объект Fi I e, который указывает на исходный файл XML. Класс NewsFormatter использует класс DOMlibrary, описанный в лекции 7, чтобы получить объект Document, содержащий все данные из файла XML. Конструктор получает список узлов NodeList, содержащий все узлы Newsitem, и задействует его для создания массива с названием itemNodes. Этот массив требуется для решения различных задач форматирования.
Листинг 8.3. Начало кода класса NewsFormatter
package com.XmlEcomBook.Chap08;
import com.XmlEcomBook.DOMlibrary ; import java.io.*; import java.util.* ; import javax.servlet.*; import javax.servlet.http.*; import org.w3c.dom.* ;
public class NewsFormatter { static String handler ; // the servlet for single item presentation public static void setHandler(String s){handler=s; }
// instance variables File newsFile ; String newsFileName ; String newsFilePath ; String headStr, footStr ;
Node[] itemNodes ; Element docRoot ; Hashtable nodeHash ; // <Newsitem Elements keyed by tag name
int maxNitems, skipNitems; int itemsCount = 0 ;
public NewsFormatter( File f ) throws IOException { newsFile = f ; newsFileName = f.getAbsolutePath() ; int p = newsFileName.lastIndexOf( File.separatorChar ); if( p > 0 ){ newsFilePath = newsFileName.substring(0,p); } else { System.out.println("NewsFormatter path problem"); } DOMlibrary library = DOMlibrary.getLibrary(); Document doc = library.getDOM( newsFileName ); if( doc == null ){ throw new FileNotFoundException( newsFileName ); } docRoot = doc.getDocumentElement(); NodeList newsItemNodes = doc.getElementsByTagName("Newsitem"); int ct = newsItemNodes.getLength(); itemNodes = new Node[ ct ]; for( int i = 0 ; i < ct ; i++ ){ itemNodes[i] = newsItemNodes.item( i ); } }
Вы, должно быть, помните из лекции 7, что класс DOMIibrary перезагружал файл XML, если время его последней модификации изменялось. Поскольку в нашем случае объект Document не меняется в результате действия класса NewsFormatter, он может использоваться совместно любым количеством сервлетов и доступ к нему нужно синхронизировать.
У нас имеются две версии метода doNews. Версия, приведенная в листинге 8.4, используется для вывода нескольких сообщений в виде заголовков новостей, краткого и полного форматов изложения. Эта версия метода обеспечивает следующие возможности: выбор сообщений по их тематике и времени появления, пропуск указанного количества сообщений и ограничение общего количества отображаемых сообщений. Строки hs и fs — необязательные параметры, которые обеспечивают некоторые небольшие дополнительные возможности форматирования.
Метод doNews проверяет наличие параметров типа Srting, которые ограничивают выбор сообщений определенными тематическими или временными рамками. Если параметр topstr отличен от null и не пуст, вызывается метод selectNodes, который ограничивает полный список сообщений набором новостей, соответствующим заданной тематике. Аналогично, если указана строка age, вызывается метод limitAge. Если какой-либо из этих методов сокращает список сообщений до нуля, метод doNews сразу же прекращает свое выполнение. Другие параметры контролируют максимальное количество новостей на странице и относительный номер сообщения, с которого начинается их просмотр.
Листинг 8.4. Метод doNews выбирает способ представления сообщений (NewsFormatter.java)
// hs and fs are head and foot used in short and long display // you can also specify templates in the <Newsfile element // PrintWriter, hs, fs, topics, H,S or L, age, mx# // skpN is used to skip the first N items that qualify // presumably printed elsewhere on the page, use 0 to see all // returns number of news items printed public int doNews( PrintWriter out, String hs, String fs, String topstr, String sz, String age, int skpN, int mxN ){ headStr = hs ; footStr = fs ; skipNitems = skpN ; maxNitems = mxN ; itemsCount = 0 ; if( topstr != null && topstr.length() > 0 ){ if( selectNodes(topstr, out )== 0 ) return 0 ; } if( age != null && age.length() > 0 ){ if( limitAge( age, out ) == 0 ) return 0 ; } char szch ; if( sz == null || sz.length() == 0 ) szch = 'L' ; // default to long form else szch = sz.toUpperCase().charAt(0); switch( szch ) { case 'H' : doHeadlineNews( out ); break ; case 'S' : doShortNews( out ); break ; case 'L' : default : doLongNews(out ); } return itemsCount ; }
Метод doNews, показанный в листинге 8.5, отыскивает сообщение по указанному атрибуту id и форматирует полную версию сообщения. Оставшийся метод класса NewsFormatter предназначен для поддержки двух методов doNews.
Листинг 8.5. Версия doNews для одного выбранного сообщения (NewsFormatter.java)
// version to do a single item by id - always full length public int doNews( PrintWriter out, String hs, String fs, String id ){ headStr = hs ; footStr = fs ; itemsCount = 0 ; Node n = null ; // for( int i = 0 ; i < itemNodes.length ; i++ ){ n = itemNodes[i]; // <Newsitem nodes String nid = ((Element)n).getAttribute("id"); if( id.equals( nid )){ break ; } } // if not located by id, will be oldest item findNodes((Element) n ); // locates the parts of <Newsitem doNewsItemLong( out ); // with the single id return itemsCount ; }
Мы решили, что заголовки сообщений всегда будут форматироваться как маркированные списки (unordered lists) HTML. Это очень упрощает метод doHeadli - neNews, показанный в листинге 8.6.
Листинг 8.6. Метод, форматирующий список заголовков новостей (NewsFormatter.java)
// Headline always formatted as <UL> with link public void doHeadlineNews(PrintWriter out){ out.println( "<ul>" ); for( int i = skipNitems ; i < itemNodes.length ; i++ ){ if( i >= maxNitems ) break ; Node n = itemNodes[i]; // <Newsitem nodes String id = ((Element)n).getAttribute("id"); findNodes((Element) n ); // locates the parts of <Newsitem out.print("<li><a href=" + handler + "?id=" + id + "&size=L >" ); out.print( nodeHash.get("head") ); out.println("</a></li>"); } out.println("</ul>"); }
Метод doShort, показанный в листинге 8.7, проверяет наличие заданного по умолчанию шаблона форматирования короткой версии сообщения, а затем выводит эту версию на страницу. Обратите внимание на то, что из каждого элемента (сообщения) извлекается его атрибут id, прежде чем будет вызван метод doNewsItemShort. Этот идентификатор впоследствии присоединяется к каждому элементу, представляющему собой краткую версию, в качестве ссылки на полный текст сообщения.
Листинг 8.7. Метод doShortNews (NewsFormatter.java)
public void doShortNews(PrintWriter out){ NamedNodeMap attrib = docRoot.getAttributes(); Node n = attrib.getNamedItem( "shorttemplate") ; String template = null ; if( n != null ) template = n.getNodeValue(); if( headStr == null && template != null && template.length() > 2 ){ try { setFromTemplate( template ); System.out.println("Template set ok " + headStr + footStr ); }catch(IOException ie ){ System.out.println("Unable to read " + template ); } } out.println( headStr ); for( int i = skipNitems ; i < itemNodes.length ; i++ ){ if( i >= maxNitems ) break ; n = itemNodes[i]; // <Newsitem nodes String id = ((Element)n).getAttribute("id"); findNodes((Element) n ); // locates the parts of <Newsitem doNewsItemShort( out, id ); } out.println( footStr ); }
Как показано в листинге 8.8, метод doLongNews проверяет наличие заданного по умолчанию шаблона форматирования полной версии сообщения, после чего выполняет цикл по всем сообщениям в массиве itemNodes.
Листинг 8.8. Метод doLongNews выводит полный текст сообщения (NewsFormatter.java)
public void doLongNews(PrintWriter out){ NamedNodeMap attrib = docRoot.getAttributes(); Node n = attrib.getNamedItem( "longtemplate"); String template = null ; if( n != null ) template = n.getNodeValue(); if( headStr == null && template != null && template.length() > 2 ){ try { setFromTemplate( template ); System.out.println("Template set ok " + headStr + footStr ); }catch(IOException ie ){ System.out.println("Unable to read " + template ); } } out.println( headStr ); for( int i = skipNitems ; i < itemNodes.length ; i++ ){ if( i >= maxNitems ) break ; n = itemNodes[i]; findNodes((Element) n ); doNewsItemLong( out ); } out.println( footStr ); }
В листинге 8.9 показан метод limitAge, который вызывается всегда, когда в методе doNews присутствует строка, задающая максимально допустимый «возраст» новостей. После проверки корректности целочисленного значения, содержащегося в строке age, этот метод заново компонует массив itemNodes, помещая туда только выбранные сообщения.
Листинг 8.9. Метод, выбирающий сообщения по дате их создания (NewsFormatter.java)
// limit to only most recent entries - return number, may be zero private int limitAge(String age, PrintWriter out ){ int days = 100 ; try { days = Integer.parseInt( age ); if( days <= 0 ) days = 1 ; }catch(NumberFormatException nfe){ return itemNodes.length ; // no change } int today =(int)( System.currentTimeMillis() /( 24 * 60 * 60 * 1000)); int oldest = today - days ; Vector v = new Vector( itemNodes.length ); int nidate = today ; // in case of parse problem int i ; for( i = 0 ; i < itemNodes.length ; i++ ){ Node n = itemNodes[i]; // <Newsitem nodes String t = ((Element)n).getAttribute("timestamp"); try { nidate = Integer.parseInt( t ); }catch(Exception nfe){ // number format or null pointer System.out.println( "NewsFormatter.limitAge " + nfe ); } if( nidate >= oldest ){ v.addElement( n ); } } itemNodes = new Node[ v.size() ]; // may be zero for( i = 0 ; i < v.size(); i++ ){ itemNodes[i] = (Node) v.elementAt(i); } return itemNodes.length ; }
Причина сложности метода selectNodes заключается в том, что и параметр topics этого метода, задающий выбор тем сообщений, и атрибут topic каждого сообщения могут содержать как одну, так и несколько тем, разделенных запятыми. Как показано в листинге 8.10, мы строим хэш-таблицу recognize для ускорения распознавания тем.
Листинг 8.10. Метод, который выбирает сообщения по указанным темам (NewsFormatter.java)
// based on String with topics separated by commas // example attribute topics="general,books,java" // output capability only used for debugging private int selectNodes(String topics, PrintWriter out ){ Hashtable recognize = new Hashtable(); StringTokenizer st = new StringTokenizer ( topics.toUpperCase(), ","); while( st.hasMoreTokens()){ String tmp = st.nextToken().trim(); recognize.put( tmp,tmp ); } // hashtable can now be used to recognize selected topics Vector v = new Vector( itemNodes.length ); int i ; for( i = 0 ; i < itemNodes.length ; i++ ){ Node n = itemNodes[i]; // <Newsitem nodes String t = ((Element)n).getAttribute("topic"); st = new StringTokenizer(t.toUpperCase(),","); while( st.hasMoreElements()){ // we just use hashtable get to see if topic is present if( recognize.get( st.nextToken().trim() ) != null ){ v.addElement(n); break; } } // end while over topic list } // end loop over all nodes // build new array from selected nodes itemNodes = new Node[ v.size() ]; for( i = 0 ; i < v.size(); i++ ){ itemNodes[i] = (Node) v.elementAt(i); } return itemNodes.length ; }
Метод findNodes, показанный в листинге 8.11, вызывается для каждого сообщения, которое должно быть помещено на страницу. Входной элемент Element — это узел Newsltem документа XML. Метод findNodes создает переменную nodeHash, которая позволяет другим методам извлекать дочерние элементы Newsltem, например <short>, из коллекции nodeHash. Ключами элементов в этой хэш-таблице являются имена узлов.
Листинг 8.11. Метод findNodes класса NewsFormatter (NewsFormatter.java)
// locate the nodes that are Elements for text data private void findNodes( Element ne ){ NodeList nl = ne.getChildNodes(); // all nodes int ct = nl.getLength(); nodeHash = new Hashtable( 2 * ct ); for( int i = 0 ; i < ct ; i++ ){ Node n = nl.item(i); if( n instanceof Element ){ nodeHash.put( n.getNodeName(), n ); } } }
Заголовки и краткая версия сообщения всегда снабжаются ссылкой на полную версию. Эта ссылка встраивается в HTML-страницу с помощью методов doNewsItemHead и doNewsItemShort, как показано в листинге 8.12.
Листинг 8.12. Методы doNewsItemHead и doNewsItemShort (NewsFormatter.java)
// <Newsitem has been hashed, id is attribute private void doNewsItemHead( PrintWriter out, String id ){ out.print("<a href=" + handler + "?id=" + id + "&size=L >" ); out.print("<h3>"); out.print( nodeHash.get("head") ); out.println("</h3></a>"); out.println(); }
// <Newsitem has been hashed, id is attribute // output with <p>..</p> formatting private void doNewsItemShort( PrintWriter out, String id ){ // note anchor to full item display out.print("<a href=" + handler + "?id=" + id + "&size=L >" ); out.print("<h3>"); out.print( nodeHash.get("head") ); out.println("</h3></a>"); Element de = (Element)nodeHash.get("date"); out.print( de.getFirstChild() ); out.println("</p>"); Element ne = (Element)nodeHash.get("short"); String wrk = ne.getFirstChild().getNodeValue().trim() ; if( !(wrk.startsWith("<P") || wrk.startsWith("<p")) ){ out.print("<p>"); } out.print( wrk ); if( !(wrk.endsWith("/p>") || wrk.endsWith("/P>"))){ out.print("</p>"); } itemsCount++ ; out.println(); }
Как показано в листинге 8.13, метод doNewsItemLong форматирует текст заголовка с помощью тега <h3>. Было бы неплохо усовершенствовать этот метод так, чтобы он допускал возможность изменять указанный формат по мере надобности. Основной текст сообщения форматируется как абзац с помощью тега <р>. Внутри самого текста могут содержаться любые форматирующие теги HTML, но теги <р> всегда будут использоваться для полного текста сообщения.
Листинг 8.13. Метод doNewsItemLong выводит полную версию сообщения (NewsFormatter.java)
// <Newsitem elements have been hashed // output long form with <p>...</p> formatting private void doNewsItemLong( PrintWriter out ){ out.print("<h3>"); out.print( nodeHash.get("head") ); out.println("</h3>"); Element de = (Element)nodeHash.get("date"); out.print( de.getFirstChild() ); out.println("</p>"); Element ne = (Element)nodeHash.get("long"); String wrk = ne.getFirstChild().getNodeValue().trim() ; if( !(wrk.startsWith("<P") || wrk.startsWith("<p")) ){ out.print("<p>"); } out.print( wrk ); if( !(wrk.endsWith("/p>") || wrk.endsWith("/P>"))){ out.print("</p>"); } itemsCount++ ; out.println(); }
Наконец, в листинге 8.14 представлены два служебных метода. Метод setFor- matTempl ate отыскивает файл и считывает его строка за строкой. Предполагается, что в файле имеется строка, начинающаяся с текста "<!-INSERT". Она разделяет разметку HTML на два раздела, которые становятся переменными headStr и footStr. Метод toString предназначен для помощи в отладке.
Листинг 8.14. Конец исходного кода класса NewsFormatter (NewsFormatter.java)
private void setFromTemplate(String template ) throws IOException { File f = new File( newsFilePath, template ); FileReader fr = new FileReader( f ); BufferedReader br = new BufferedReader( fr ); StringBuffer hsb = new StringBuffer( 100 ); StringBuffer fsb = new StringBuffer( 100 ); String tmp = br.readLine(); // strips line terminators while( !tmp.startsWith("<!--INSERT" )){ hsb.append( tmp ); fsb.append("\r\n"); tmp = br.readLine(); } tmp = br.readLine(); while( tmp != null ){ fsb.append( tmp ); fsb.append("\r\n"); tmp = br.readLine(); } headStr = hsb.toString(); footStr = fsb.toString(); }
public String toString() { StringBuffer sb = new StringBuffer("NewsFormatter item ct= "); sb.append( Integer.toString( itemNodes.length )); return sb.toString() ; }
}
Использование класса NewsFormatter
В этом разделе рассматриваются два способа использования класса NewsFormatter: с сервлетом общего назначения TheNewsServ и с JSP-страницами.
Сервлет TheNewsServ можно использовать для отображения одного сообщения с указанным параметром id или для отображения нескольких сообщений с заданными параметрами topic и аде. В листинге 8.15 показаны инструкции импорта и статические переменные. Мы установили значения статических переменных равными заданным по умолчанию, но, разумеется, вам нужно будет заменить их на значения, отражающие ваши фактические настройки.
Листинг 8.15. Начало исходного кода сервлета TheNewsServ (TheNewsServ)
package com.XmlEcomBook.Chap08 ;
import java.io.*; import java.util.* ; import javax.servlet.*; import javax.servlet.http.*;
public class TheNewsServ extends HttpServlet { static String workDir = "E:\\scripts\\CompanyNews" ; static String newsFile = "thenews.xml" ; static String handler = "http://localhost/servlet/thenews" ; static String propfile = "conewserv.properties"; static String version = "v1.0"; static String pversion = "" ; static Properties cnProp ; static String brcrlf = "<br />\r\n" ; static String defaultHead = "<html>\r\n" + "<head><title>Company News Servlet</title></head>\r\n" + "<body>\r\n" + "<h2>Here is the news</h2>\r\n" ; static String defaultFoot = "</body></html>\r\n";
Метод init, показанный в листинге 8.16, считывает файл свойств, значения которых могут быть использованы для замены установленных по умолчанию значений статических переменных.
Листинг 8.16. Метод init класса TheNewsServ (TheNewsServ.java)
public void init(ServletConfig config) throws ServletException { super.init(config); String tmp = config.getInitParameter("workdir"); if( tmp != null ) workDir = tmp ; tmp = config.getInitParameter("propfile"); if( tmp != null ) propfile = tmp; System.out.println("Start TheNewsServ using " + workDir ); File f = new File( workDir, propfile ); try { cnProp = new Properties(); cnProp.load( new FileInputStream(f) ); tmp = cnProp.getProperty("thenewshandler"); if( tmp != null ) handler = tmp ; pversion = cnProp.getProperty("version"); if( pversion != null ){ defaultFoot = "<hr><br>News Servlet " + version + " properties: " + pversion + "<br>\r\n" + "</body>\r\n</html>\r\n" ; } NewsFormatter.setHandler( handler ); System.out.println( new Date().toString() + " Loaded properties for TheNewsServ: " + handler ); }catch(IOException e){ System.out.println("Error loading " + e ); }
}
Функциональность сервлета сконцентрирована в методе doGet, как видно из листинга 8.17. В запросе можно передать значения параметров, определяющих тему сообщений, максимальный «возраст» сообщений, требуемый способ представления и идентификатор сообщения. Заметим, что создается объект Fi I e, соответствующий файлу XML с сообщениями, и передается конструктору NewsFormatter. Использование объекта File гарантирует, что соблюдаются соглашения относительно разделителей для компонентов пути; NewsFormatter не открывает этот файл, но использует его имя при получении объектной модели документа для этого файла из DOMlibrary.
Листинг 8.17. Метод doGet (TheNewsServ.java)
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter out = new PrintWriter(resp.getOutputStream()); String topics = req.getParameter("topic"); String ageStr = req.getParameter("days"); String len = req.getParameter("size" ); // "S","H" or "L" String id = req.getParameter("id"); // a single item is requested try { File f = new File( workDir, newsFile ); NewsFormatter nf = new NewsFormatter( f ); if( id != null ){ nf.doNews( out, defaultHead,defaultFoot, id ); } else { // PrintWriter, head, foot, topics, H,S or L, age, skip#, mx# nf.doNews( out, defaultHead, defaultFoot, topics, len, ageStr,0, 10 ); } out.close(); }catch(Exception e){ System.err.println("TheNewsServ.doGet " + e ); errorMsg( out, "TheNewsServ.doGet", e ); } }
Обратите внимание, что конструкция try-catch в методе doGet направляет все исключения методу errorMsg, показанному в листинге 8.18. Разумеется, вам следует вставить свой адрес электронной почты в текст сообщения либо текст этого сообщения может состоять из специальной строки, которая задается в файле свойств. Методы header и footer просто выписывают стандартные теги HTML.
Листинг 8.18. Методы errorMsg, Header и Footer (TheNewsServ.java)
// assumes response has been set to text/html private void errorMsg( PrintWriter out, String msg, Exception ex ){ header( out ); out.println("<h2>Error: " ); out.println( msg ); out.println("</h2><br>"); if( ex != null ){ ex.printStackTrace( out ); } out.println("<br>"); out.println("<a href=\"mailto:wbrogden@bga.com\">Please mail me the error message.</a><br>"); footer( out ); }
private void header(PrintWriter out ){ out.println("<html>"); out.println("<head><title>Company News Servlet</title> </head>"); out.println("<body>"); }
private void footer(PrintWriter out ){ out.println("<hr><br>Company News Servlet " + version + " properties: <br>" ); out.println("</body>"); out.println("</html>"); out.close(); }
}
Для рассматриваемого нами примера JSP-страницы новостей ее основной формой является таблица с тремя столбцами. Чтобы уменьшить размер листинга, мы предельно сократили эту страницу; на реальной странице, разумеется, содержится гораздо больше сообщений, связанных с фирмой.
Привлекательность JSP-страниц объясняется как раз простотой включения выходных данных Java в разметку HTML. В листинге 8.19 показано начало JSP- страницы, где создается первая строка таблицы.
Листинг 8.19. Первая часть упрощенной JSP-страницы для отображения новостей (mockup.jsp)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> <html> <head> <title>The XMLGifts News </title> </head>
<body bgcolor="#FFFFFF"> <%@ page language="java" import= "com.XmlEcomBook.Chap08.NewsFormatter,java.io.*" %> <%! String newsFilePath = "e:\\scripts\\CompanyNews" ; String newsFileName = "thenews.xml" ; String newsHandler = "http://localhost:8080/XMLbook/Chap08/thenews.jsp" ; File newsFile = new File( newsFilePath, newsFileName ); public void jspInit(){ super.jspInit(); NewsFormatter.setHandler( newsHandler ); } %>
<table width="89%" border="0" align="left" cellpadding="8"> <tr align="center" bgcolor="cyan"> <td colspan="3"><font size="4"> Various Corporate Navigation Links Go Here</font> </td> </tr>
Чтобы не усложнять пример, мы жестко запрограммировали тему сообщений — музыкальные компакт-диски (листинг 8 20) Первый раз объект NewsFormatter используется для создания левого столбца таблицы, где расположены заголовки сообщений. Это делается в первую очередь, так как, когда тематика сообщений задана, объект NewsFormatter будет содержать только данные по сообщениям, соответствующим выбранной тематике
Листинг 8.20. Продолжение JSP-страницы с выходными данными NewsFormatter (mockup.java)
<!-- the nf and pw objects will be used for all three td --> <tr valign="TOP" ><font size="3"> <td><b>News Headlines</b><br> <% // topic could be set from customer records or the previous form String topic = "CDs" ; NewsFormatter nf = new NewsFormatter( newsFile ); PrintWriter pw = new PrintWriter( out );
/* Note the doNews signature doNews( PrintWriter out, String hs, String fs, String topstr, String sz, String age, int skpN, int mxN ) */ // headlines - all topics nf.doNews( pw, "","", "", "H", null, 0, 8 ); %>
</td> <td width="50%"> <% nf.doNews( pw, "","", topic, "L", null, 0, 1 ); %>
</td>
<!-- the short form column --> <td width="23%">
<%= "<b>Recent news items about " + topic + "</b><br>" %>
<%
/* Note the doNews signature doNews( PrintWriter out, String hs, String fs, String topstr, String sz, String age, int skpN, int mxN ) */
nf.doNews( pw, "","", topic, "S", null, 1, 8 ); %>
</td> </font> </tr> <tr align="center" bgcolor="cyan"> <td colspan="3"><font size="4" > Repeat the Navigation links here for convenience<br></font> </td> </tr> <tr> <td colspan="3" align='center'> <font face='arial, helvetica' size='3'> ©2000 XMLGifts.com<sup>SM</sup> <br /></font> </td> </tr> </table> </body> </html>
Важной особенностью этого приложения является возможность добавления новых сообщений без нарушения нормальной работы web-сайта Эту функцию иллюстрирует верхний правый угол рис. 8 1. Вместо того чтобы модифицировать DOM в памяти сервера, сервлет CompanyNewsServ записывает модифицированную версию исходного файла XML на диск. Этот обновленный файл сообщений будет автоматически загружен в очередной раз при вызове DOM из библиотеки DOMlibrary.
Форма HTML для обновления страницы новостей создается и управляется сер- влетом CompanyNewsServl et. Начальный вход в сервлет осуществляется с помощью HTML-страницы, в которой имеется обычная форма HTML для ввода имени автора и пароля. Пример такой страницы представлен в файле CoNewsUpdate.html, который находится на прилагаемом к курсу каталоге файлов. Сервлет отыскивает имя автора в файле свойств, проверяя таким образом, что этот человек имеет право на добавление новых сообщений.
В листинге 8.21 показан файл свойств для работы на сервере local host. Заметим, что имя автора является именем свойства, а пароль — его значением.
Листинг 8.21. Файл свойств, используемый сервлетом CompanyNewsServ (conewserv. properties)
# properties for CompanyNewsServ handler=http://localhost/servlet/conewserv thenewshandler=http://localhost/servlet/thenews newsfile=thenews.xml version=June 15, 2000 wbrogden=xmlrules
В листинге 8.22 показаны инструкции импорта, статические переменные и метод init для сервлета CompanyNewsServ.
Листинг 8.22. Начало кода метода CompanyNewsServ (ComanyNewsServ.java)
package com.XmlEcomBook.Chap08 ;
import com.XmlEcomBook.DOMlibrary ; import java.io.*; import java.util.* ; import javax.servlet.*; import javax.servlet.http.*; import org.w3c.dom.* ;
public class CompanyNewsServ extends HttpServlet { static String workDir = "E:\\scripts\\CompanyNews" ; static String propfile = "conewserv.properties" ; static String newsFile = "thenews.xml" ; static String handler = "http://localhost/servlet/conewserv" ; static String version = "v0.12"; static String pversion = "" ; static Properties cnProp ; static String brcrlf = "<br />\r\n" ;
public void init(ServletConfig config) throws ServletException { super.init(config); String tmp = config.getInitParameter("workdir"); if( tmp != null ) workDir = tmp ; tmp = config.getInitParameter("propfile"); if( tmp != null ) propfile = tmp ; System.out.println("Start CompanyNewsServ using " + workDir ); File f = new File( workDir, propfile ); try { cnProp = new Properties(); cnProp.load( new FileInputStream(f) ); tmp = cnProp.getProperty("handler"); if( tmp != null ) handler = tmp ; tmp = cnProp.getProperty("newsfile"); if( tmp != null ) newsFile = tmp ; pversion = cnProp.getProperty("version"); System.out.println("Loaded properties for CompanyNewsServ: " + handler + " file:" + newsFile ); }catch(IOException e){ System.out.println("Error loading " + e ); }
}
Метод doGet, как показано в листинге 8.23, проверяет введенные пользователем имя и пароль, сравнивая их с данными в файле свойств, загруженном при инициализации сервлета. Если обнаруживается, что имя соответствует паролю, вызывается метод generateForm, создающий форму HTML для ввода текста нового сообщения.
Листинг 8.23. Метод doGet создает форму для ввода нового сообщения (CompanyNewsServ.java)
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter out = new PrintWriter(resp.getOutputStream()); String username = req.getParameter("username"); String password = req.getParameter("password"); String action = req.getParameter("action"); String tmp = cnProp.getProperty(username); boolean userok = false ; if( tmp != null ){ userok = tmp.equals( password ); } header( out ); if( userok ){ generateForm( out, username, password ); } else { out.println("<p>User: " + username + " password: " + password + " not found.</p>" ); } footer( out ); }
Заполненная форма посылается методу doPost. Как показано в листинге 8.24, различные текстовые элементы извлекаются и передаются объекту NewsllpKeep с помощью метода addltem.
Листинг 8.24. Метод doPost собирает данные из формы (CompanyNewsServ.java)
public void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html"); PrintWriter out = new PrintWriter(resp.getOutputStream()); String username = req.getParameter("username"); String password = req.getParameter("password"); String action = req.getParameter("action"); String head = req.getParameter("head"); String date = req.getParameter("date"); String topics = req.getParameter("topics"); String shrtStr = req.getParameter("short").trim(); String longStr = req.getParameter("long").trim(); File f = new File( workDir, newsFile ); try { NewsUpkeep nup = new NewsUpkeep( f ); nup.addItem( head, date, topics, username, shrtStr, longStr ); header( out ); out.println("NewsUpkeep is " + nup + "<br />"); footer( out ); } catch( Exception e){ errorMsg( out, "CompanyNewsServ.doPost ", e ); } }
Форма HTML для ввода новых сообщений создается методом generateForm, как показано в листинге 8.25. Заметим, что имя пользователя и пароль вставлены в форму в виде скрытых значений.
Листинг 8.25. Метод generateForm создает форму для ввода (Com.panyNewsServ.java)
private void generateForm( PrintWriter out, String name, String pw ){ out.println("<h2>Enter Company News Item Data</h2>"); out.println("<form method=\"POST\" action=\"" + handler + "\" >"); out.println("Headline - 80 char max<br />"); out.println("<input type=\"text\" maxlength=\"80\" size=\"60\"" + " name=\"head\" ><br />" ); out.println("Dated <br />"); out.println("<input type=\"text\" maxlength=\"50\" size=\"40\"" + " name=\"date\" value=\"" + new Date().toString() + "\" ><br />" ); out.println("Topics separated by commas - please stick to the official list.<br />"); out.println("<input type=\"text\" maxlength=\"80\" size=\"60\"" + " name=\"topics\" ><br />" ); out.println("Short version <br />"); out.println("<textarea cols=\"60\" rows=\"3\" name=\"short\" >"); out.println("</textarea><br />"); out.println("Long version <br />"); out.println("<textarea cols=\"60\" rows=\"10\" name=\"long\" >"); out.println("</textarea><br />"); out.println("<input type=\"hidden\" name=\"username\" value=\"" + name + "\"><br>" ); out.println("<input type=\"hidden\" name=\"password\" value=\"" + pw + "\" ><br>"); out.println("<input type=\"submit\" name=\"action\" value=\"Submit\" ><br />" ); out.println("</form></center>"); }
Наконец, в сервлете имеются обычные вспомогательные методы, показанные в листинге 8.26. Естественно, вам нужно будет заменить адрес в тексте сообщения на свой собственный либо предоставить переменную типа String, которая инициализируется в файле свойств.
Листинг 8.26. Служебные методы в сервлете CompanyNewsServ (CompanyNewsServ.java)
// assumes response has been set to text/html private void errorMsg( PrintWriter out, String msg, Exception ex ){ header( out ); out.println("<h2>Error: " ); out.println( msg ); out.println("</h2><br>"); if( ex != null ){ ex.printStackTrace( out ); } out.println("<br>"); out.println("<a href=\"mailto:wbrogden@bga.com\"> Please mail me the error message.</a><br>"); footer( out ); }
private void header(PrintWriter out ){ out.println("<html>"); out.println("<head><title>Company News Servlet</title> </head>"); out.println("<body>"); }
private void footer(PrintWriter out ){ out.println("<hr><br>Company News Servlet " + version + " properties: <br>" ); out.println("</body>"); out.println("</html>"); out.close(); }
}
Классу NewsUpKeep передаются существующий объект DOM <Newsfile> и различные текстовые строки, которые составляют новый элемент <Newsitem>; он переписывает файл XML с сообщениями. Применить этот метод проще, чем создать элемент <Newsitem> и вставить его модель DOM, которая постоянно находится в памяти; кроме того, указанный метод гарантирует, что файл новостей будет обновлен правильным образом. Также при использовании этого метода исключается возможность того, что один из отображающих методов получит частично измененную модель DOM.
В листинге 8.27 показано начало кода класса NewsUpKeep. Конструктор класса использует полное имя файла для получения DOM из DOMIibrary. Поскольку эта модель DOM не модифицируется при переписывании файла XML, вам не нужно беспокоиться по поводу возможности одновременного доступа к этому объекту класса NewsFormatter.
Заметим, что в классе NewsUpKeep создается переменная rootNNM типа NamedNodeMap, которая содержит имена и значения атрибутов корневого элемента документа XML, <Newsfile>. Также конструктор помещает все узлы <Newsitem> в массив itemNodes.
Листинг 8.27. Начало класса NewsUpKeep (NewsUpKeep.java)
package com.XmlEcomBook.Chap08;
import com.XmlEcomBook.DOMlibrary ; import java.io.*; import java.util.* ; import javax.servlet.*; import javax.servlet.http.*; import org.w3c.dom.* ;
public class NewsUpkeep { File newsFile ; String newsFileName ; Node[] itemNodes ; NamedNodeMap rootNNM ; // for root attributes
public NewsUpkeep( File f) throws IOException { newsFile = f ; newsFileName = f.getAbsolutePath() ; DOMlibrary library = DOMlibrary.getLibrary(); Document doc = library.getDOM( newsFileName ); if( doc == null ){ throw new FileNotFoundException( newsFileName ); } Element re = doc.getDocumentElement(); rootNNM = re.getAttributes(); System.out.println("Root has " + rootNNM.getLength() + " attributes"); NodeList newsItemNodes = doc.getElementsByTagName("Newsitem"); int ct = newsItemNodes.getLength(); itemNodes = new Node[ ct ]; for( int i = 0 ; i < ct ; i++ ){ itemNodes[i] = newsItemNodes.item( i ); } }
В листинге 8.28 показаны некоторые вспомогательные методы, необходимые в классе NewsUpKeep. Метод formatTopics гарантирует, что строка, которая будет записана в качестве значения атрибута topic, имеет правильный формат.
Листинг 8.28. Различные вспомогательные функции класса NewsUpKeep (NewsUpKeep.java)
//ensure there are no leading or trailing spaces on the // individual topics, comma separated, general,food , etc private String formatTopics(String s ){ if( s.indexOf(',') < 0 ) return s.trim(); // only separator is comma StringTokenizer st = new StringTokenizer( s, "," ); StringBuffer sb = new StringBuffer( s.length() ); while( st.hasMoreTokens() ){ sb.append( st.nextToken().trim() ); if( st.hasMoreTokens() ) sb.append(','); } return sb.toString(); } // convert system millisecs to days since epoch private String timeInDays(){ long t = System.currentTimeMillis() ; int tid = (int)(t / ( 1000 * 60 * 60 * 24 )); return Integer.toString( tid ); }
// s expected to be decimal number used in <Newsitem id= private String incrementID(String s ){ try{ int n = Integer.parseInt( s ); return Integer.toString( n + 2 ); }catch(NumberFormatException e){ return s + "a" ; } } public String toString() { StringBuffer sb = new StringBuffer("NewsUpkeep "); sb.append(" Newsitem count: " ); sb.append( Integer.toString( itemNodes.length )); return sb.toString(); }
Теперь мы подходим к основному рабочему методу, addltem. Сначала этот метод создает новый файл с временным именем и записывает туда стандартное объявление XML и комментарии. Затем создается тег <Newsfile>, куда записываются имена атрибутов и их значения из коллекции rootNNM.
Как видно из листинга 8.29, атрибут nextid обрабатывается специальным образом. Сохраняется текущее значение, которое становится значением атрибута id нового элемента <Newsitem>, а увеличенное значение записывается в тег <Newsfile>.
Листинг 8.29. Начало метода addltem (NewsUpKeep.java)
// items are always added at the top of the file // so we have to rebuild the start of the root element public void addItem( String head, String date, String topics, String author, String shrtStr, String longStr ) throws IOException { String idVal = "" ; String tmpfile = newsFileName + "$$" ; File f = new File( tmpfile ); FileWriter fw = new FileWriter(f); PrintWriter out = new PrintWriter( new BufferedWriter( fw ) ); out.println("<?xml version=\"1.0\" standalone=\"yes\" ?>"); out.println("<!-- output by NewsUpkeep -->"); int ct = rootNNM.getLength(); if( ct == 0 ){ out.println("<Newsfile>"); } else { out.print("<Newsfile "); for( int i = 0 ; i < ct ; i++ ){ Node an = rootNNM.item(i); String name = an.getNodeName(); String val = an.getNodeValue(); out.print( name + "=\"" ); if( name.equals("nextid") ){ idVal = val ; val = incrementID( val ); } out.print( val + "\" "); } out.println(" >"); }
Затем, как показано в листинге 8.30, пишется новый тег <NewsItem>, за которым следует заголовок сообщения, указывается дата и приводятся краткая и полная версии текста сообщения. Для того чтобы записать старые элементы <NewsItan>, вызывается метод writeNewsNode. После закрытия временного файла старый файл XML удаляется, а временный файл получает имя. Следующий раз, когда этот файл будет запрошен, класс DOMIibrary по изменившейся метке даты модификации файла (timestamp) определит, что нужно считывать новый файл.
Листинг 8.30. Метод addltem, продолжение (NewsllpKeep.java)
out.print("<Newsitem timestamp=\""); out.print( timeInDays() + "\" topic=\""); out.print( formatTopics( topics ) ); out.println( "\" author=\"" + author + "\" id=\"" + idVal + "\" >"); // end of <Newsitem .. > out.println("<head>" + head.trim() + "</head>" ); out.println("<date>" + date.trim() + "</date>" ); out.println("<short><![CDATA["); out.println( shrtStr.trim() ); out.println("]]></short>"); out.println("<long><![CDATA["); out.println( longStr ); out.println("]]></long>"); out.println("</Newsitem>"); for( int i = 0 ; i < itemNodes.length ; i++ ){ writeNewsNode(out, (Element)itemNodes[i] ); } out.println("</Newsfile>"); out.flush(); out.close(); File forig = new File( newsFileName ); DOMlibrary library = DOMlibrary.getLibrary(); // to prevent overlapping XML file operations synchronized( library ){ forig.delete(); if( !f.renameTo( forig )){ System.out.println("NewsUpkeep.addItem rename failed") ; } } }
Метод writeNewsNode, который записывает отдельный элемент <Newsitem>, показан в листинге 8.31.
Листинг 8.31. Метод, который записывает отдельный элемент из DOM (NewsllpKeep.java)
// write a <Newsitem Element duplicating the attributes public void writeNewsNode(PrintWriter out, Element e) { NamedNodeMap nnm = e.getAttributes(); out.print("<Newsitem " ) ; //timestamp=\""); int i ; for( i = 0 ; i < nnm.getLength() ; i++ ){ Attr na = (Attr) nnm.item(i); // Attr extends Node String atr = na.getName(); String val = na.getValue(); out.print( atr ); out.print("=\""); out.print( val ); out.print("\" "); } out.println(">"); NodeList nl = e.getChildNodes(); int ct = nl.getLength(); for( i = 0 ; i < ct ; i++ ){ Node nde = nl.item( i ); if( nde instanceof Element ){ Element ce = (Element)nde; String name = ce.getTagName(); out.print("<" + name + ">"); NodeList chnl = ce.getChildNodes() ; if( chnl.getLength() == 0 ) continue ; Node chn = chnl.item(0); if( name.equals("long") || name.equals("short") ){ out.print("<![CDATA["); out.println( chn.getNodeValue().trim() ); out.print("]]>"); } else { out.print( chn.getNodeValue() ); } out.println("</" + name + ">"); } } // loop over <Newsitem> child nodes out.println("</Newsitem>"); } }
На основе нашего опыта добавления новых сообщений с помощью сервлета CompanyNewsServ вы можете сначала создать полный текст сообщения в текстовом редакторе, а затем при работе с формой HTML для ввода текста просто вставить его в форму с помощью команд вырезания и вставки.
Дело в том, что в его постановке и выводах произведена подмена, аналогичная подмене в школьной шуточной задачке на сообразительность, в которой спрашивается:
- Cколько яблок на березе, если на одной ветке их 5, на другой ветке - 10 и так далее
При этом внимание учеников намеренно отвлекается от того основополагающего факта, что на березе яблоки не растут, в принципе.
В эксперименте Майкельсона ставится вопрос о движении эфира относительно покоящегося в лабораторной системе интерферометра. Однако, если мы ищем эфир, как базовую материю, из которой состоит всё вещество интерферометра, лаборатории, да и Земли в целом, то, естественно, эфир тоже будет неподвижен, так как земное вещество есть всего навсего определенным образом структурированный эфир, и никак не может двигаться относительно самого себя.
Удивительно, что этот цирковой трюк овладел на 120 лет умами физиков на полном серьезе, хотя его прототипы есть в сказках-небылицах всех народов всех времен, включая барона Мюнхаузена, вытащившего себя за волосы из болота, и призванных показать детям возможные жульничества и тем защитить их во взрослой жизни. Подробнее читайте в FAQ по эфирной физике.