Статьи

Подходы к XML — Часть 2. Как насчет SAX?

В первой части была представлена ​​идея о том, что существуют разные способы анализа XML, и было подчеркнуто, что XML НЕ является СТРОКОЙ; скорее это объектно-ориентированная модель документа, которая может быть представлена ​​в виде строки. Сегодняшний блог продолжает это обсуждение, используя сценарий «Идеальная пицца» моего Пита . Если вы помните, Пит просто просунул голову в дверь и попросил усовершенствовать систему, чтобы приемная могла отправлять заказы на несколько пицц в одном сообщении XML. Вы знаете, что ваш простой код синтаксического анализа строк имеет недостатки и не поднимет мошенничество, так что вам нужно больше гуглить на XML и придумать идею использования SAX.

SAX, или Simple API for XML, существует уже много лет и, насколько я помню, изначально был разработчиком Дэвида Меггинсона до начала тысячелетия. В те дни вам приходилось загружать Java-версию SAX с личного веб-сайта Дэвида. Это превратилось в SAX Project, прежде чем окончательно добавиться в Java Standard Edition 1.4.

SAX — это потоковый интерфейс для XML, который означает, что приложения, использующие SAX, получают уведомления о событиях, когда XML-документ обрабатывает элемент и атрибут, в одно и то же время в последовательном порядке, начиная с верхней части документа и заканчивая закрытием Корень элемент. Это означает, что он чрезвычайно эффективен при обработке XML за линейное время без чрезмерных требований к системной памяти.

Возвращаясь к Питу , вы усердно работаете и придумали следующий класс на основе SAX-парсера:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
public class PizzaParser {
 
 
 
  public List<PizzaOrder> order(InputStream xml) {
 
 
 
    PizzaContentHandler handler = new PizzaContentHandler();
 
 
 
    // do the parsing
 
    try {
 
      // Construct the parser by bolting together an XMLReader
 
      // and the ContentHandler
 
      XMLReader parser = XMLReaderFactory.createXMLReader();
 
      parser.setContentHandler(handler);
 
 
 
      // create an input source from the XML input stream
 
      InputSource source = new InputSource(xml);
 
      // Do the actual work
 
      parser.parse(source);
 
 
 
      return handler.getPizzaOrder();
 
    } catch (Exception ex) {
 
      throw new RuntimeException('Exception parsing xml message. Message: ' + ex.getMessage(), ex);
 
    }
 
  }
 
 
 
  static class PizzaOrder {
 
 
 
    private final String pizzaName;
 
    private final String base;
 
    private final String quantity;
 
 
 
    PizzaOrder(String pizzaName, String base, String quantity) {
 
      this.pizzaName = pizzaName;
 
      this.base = base;
 
      this.quantity = quantity;
 
    }
 
 
 
    public String getPizzaName() {
 
      return pizzaName;
 
    }
 
 
 
    public String getBase() {
 
      return base;
 
    }
 
 
 
    public String getQuantity() {
 
      return quantity;
 
    }
 
  }
 
 
 
  /**
 
   * Use this class the handle the SAX events
 
   */
 
  class PizzaContentHandler extends DefaultHandler {
 
 
 
    private String[] pizzaInfo;
 
    private int index;
 
    private List<PizzaOrder> outList;
 
    private boolean capture;
 
 
 
    /**
 
     * Set things up at the start of the document.
 
     */
 
    @Override
 
    public void startDocument() {
 
      outList = new ArrayList<PizzaOrder>();
 
    }
 
 
 
    /**
 
     * Handle the startElement event
 
     */
 
    @Override
 
    public void startElement(String uri, String localName, String qName, Attributes attributes) {
 
 
 
      capture = true;
 
      if ('pizzas'.equals(qName)) {
 
        capture = false;
 
      } else if ('pizza'.equals(qName)) {
 
        pizzaInfo = new String[3];
 
        capture = false;
 
      } else if ('name'.equals(qName)) {
 
        index = 0;
 
      } else if ('base'.equals(qName)) {
 
        index = 1;
 
      } else if ('quantity'.equals(qName)) {
 
        index = 2;
 
      }
 
    }
 
 
 
    /**
 
     * Handle the endElement event
 
     */
 
    @Override
 
    public void endElement(String uri, String localName, String qName) {
 
 
 
      if ('pizza'.equals(qName)) {
 
        outList.add(new PizzaOrder(pizzaInfo[0], pizzaInfo[1], pizzaInfo[2]));
 
      }
 
    }
 
 
 
    /**
 
     * Grab hold of incoming character data
 
     */
 
    @Override
 
    public void characters(char[] ch, int start, int length) {
 
 
 
      if (capture) {
 
        pizzaInfo[index] = new String(ch, start, length);
 
        capture = false;
 
      }
 
    }
 
 
 
    List<PizzaOrder> getPizzaOrder() {
 
      return outList;
 
    }
 
  }
 
 
 
}

Этот блог здесь не для того, чтобы продемонстрировать, как использовать SAX, есть много примеров, доступных, если вы посмотрите вокруг, но давайте критически рассмотрим код, и первое, на что нужно обратить внимание, — это то, что метод order (...) теперь принимает входной поток, а не строку в соответствии с API на основе потока:

1
public List<PizzaOrder> order(InputStream xml)

Следующее, что следует отметить, это то, что PizzaParser использует вложенный класс PizzaContentHandler, который расширяет вспомогательный класс SAX DefaultHandler . Класс PizzaContentHandler захватывает список bean- компонентов PizzaOrder и передает их обратно во вмещающий класс для возврата вызывающей стороне. Это означает, что все, что вам нужно сделать, чтобы заполучить события SAX, это переопределить методы-обработчики, такие как startElement (...) , endElement (...) и т. Д.
Если вы присмотритесь к коду, вы поймете, что он довольно сложный. Все, что нужно сделать, — это создать список вывода, но есть несколько операторов if () , временных массивов и логических переключателей, которые используются для захвата правого бита информации из правой точки документа. Это недостаток SAX: его сложность возлагает большую нагрузку на программиста и делает ваш код более подверженным ошибкам.

Однако он более устойчив, чем предыдущая попытка на основе строк, как показывают приведенные ниже модульные тесты:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
public class PizzaParserTest {
 
 
 
  private static final String ORDER_XML = //
 
  '<?xml version=\'1.0\' encoding=\'UTF-8\'?>\n' + //
 
      '<pizza>\n' + // 8
 
      '    <name>Capricciosa</name>\n' + //
 
      '    <base>thin</base>\n' + //
 
      '    <quantity>2</quantity>\n' + //
 
      '</pizza>\n';
 
 
 
  private static final String ORDER_XML_2 = //
 
  '<?xml version=\'1.0\' encoding=\'UTF-8\'?><pizza><name>Capricciosa</name><base>thin</base><quantity>2</quantity></pizza>';
 
 
 
  private static final String ORDER_XML_3 = //
 
  '<?xml version=\'1.0\' encoding=\'UTF-8\'?>\n' + //
 
      '<pizzas>\n' + //
 
      '    <pizza>\n' + //
 
      '        <name>Capricciosa</name>\n' + //
 
      '        <base>thin</base>\n' + //
 
      '        <quantity>2</quantity>\n' + //
 
      '    </pizza>\n' + //
 
      '    <pizza>\n' + //
 
      '        <name>Margherita</name>\n' + //
 
      '        <base>thin</base>\n' + //
 
      '        <quantity>1</quantity>\n' + //
 
      '    </pizza>\n' + //
 
      '</pizzas>';
 
 
 
  private PizzaParser instance;
 
 
 
  @Before
 
  public void setUp() {
 
    instance = new PizzaParser();
 
  }
 
 
 
  @Test
 
  public void readOrderFromXML() {
 
 
 
    List<PizzaOrder> results = instance.order(new ByteArrayInputStream(ORDER_XML.getBytes()));
 
 
 
    assertEquals(1, results.size());
 
 
 
    PizzaOrder result = results.get(0);
 
    assertEquals('Capricciosa', result.getPizzaName());
 
    assertEquals('thin', result.getBase());
 
    assertEquals('2', result.getQuantity());
 
  }
 
 
 
  @Test
 
  public void readOrderFromModifiedXML() {
 
 
 
    List<PizzaOrder> results = instance.order(new ByteArrayInputStream(ORDER_XML_2.getBytes()));
 
 
 
    assertEquals(1, results.size());
 
 
 
    PizzaOrder result = results.get(0);
 
    assertEquals('Capricciosa', result.getPizzaName());
 
    assertEquals('thin', result.getBase());
 
    assertEquals('2', result.getQuantity());
 
  }
 
 
 
  @Test
 
  public void readOrderForMultiplePizza() {
 
 
 
    List<PizzaOrder> results = instance.order(new ByteArrayInputStream(ORDER_XML_3.getBytes()));
 
 
 
    PizzaOrder result = results.get(0);
 
    assertEquals('Capricciosa', result.getPizzaName());
 
    assertEquals('thin', result.getBase());
 
    assertEquals('2', result.getQuantity());
 
 
 
    result = results.get(1);
 
    assertEquals('Margherita', result.getPizzaName());
 
    assertEquals('thin', result.getBase());
 
    assertEquals('1', result.getQuantity());
 
  }
 
}

Эти тесты демонстрируют сценарии обработки сообщений XML с пробелами и без них (исправление вчерашней проблемы) вместе с сообщением, которое включает заказ на несколько пицц.

Все работает очень хорошо, но большие идеи Пита осуществляются. В настоящее время он расширяет свою деятельность по всему миру, предлагая несколько кухонь по всему миру и присутствие в Интернете. Пит нанимает несколько дерзких бизнес-консультантов, которые создают новую XML-схему заказа пиццы и объединяют ее с существующей клиентской схемой. Это попадает в ваш почтовый ящик, и вы задаетесь вопросом, что делать дальше …

1 Другие поисковые системы доступны.

Исходный код доступен на GitHub по адресу:

мерзавец: //github.com/roghughe/captaindebug.git

Перейдите к части 3 серии.

Ссылка: Подходы к XML — Часть 2. Как насчет SAX? от нашего партнера по JCG Роджера Хьюза в блоге « Капитан отладка» .