JavaCC
The most popular parser generator for use with Java applications.
This section contains some examples to get you started using JavaCC and JJTree.
Once you have tried out and understood each of these examples, you should take a look at the grammar repository, and more complex examples under the examples/ directory.
But even with just these examples, you should be able to get started on reasonably complex grammars.
Contents
JavaCC Examples
Instructions
The following instructions show you how to get started with JavaCC. The instructions below are with respect to Example1.jj, but you can build any parser using the same set of commands.
- Run javacc on the grammar input file to generate a bunch of Java files that implement the parser and lexical analyzer (or token manager):
- Now compile the resulting Java programs:
- The parser is now ready to use. To run the parser, type:
The Example1 parser and others in this directory are designed to take input from standard input. Example1 recognizes matching braces followed by zero or more line terminators and then an end of file.
Examples of legal strings in this grammar are:
Examples of illegal strings are:
Try typing various different inputs to Example1 . Remember <control-d> may be used to indicate the end of file (this is on the UNIX platform).
Output
Here are some sample runs:
- The parser processes the string « successfully.
- The parser tries to process the string
- The parser tries to process the string <>> but throws a ParseException .
Example1.jj
Example1.jj is a simple JavaCC grammar that recognizes a set of left braces followed by the same number of right braces and finally followed by zero or more line terminators and finally an end of file.
This grammar file starts with settings for all the options offered by JavaCC. In this case the option settings are their default values. Hence these option settings were really not necessary. One could as well have completely omitted the options section, or omitted one or more of the individual option settings. The details of the individual options is described in the JavaCC documentation.
Following this is a Java compilation unit enclosed between PARSER_BEGIN(name) and PARSER_END(name) . This compilation unit can be of arbitrary complexity. The only constraint on this compilation unit is that it must define a class called name — the same as the arguments to PARSER_BEGIN and PARSER_END . This is the name that is used as the prefix for the Java files generated by the parser generator. The parser code that is generated is inserted immediately before the closing brace of the class called name .
In the above example, the class in which the parser is generated contains a main program. This main program creates an instance of the parser object (an object of type Example1 ) by using a constructor that takes one argument of type java.io.InputStream ( System.in in this case).
The main program then makes a call to the non-terminal in the grammar that it would like to parse — Input in this case. All non-terminals have equal status in a JavaCC generated parser, and hence one may parse with respect to any grammar non-terminal.
Following this is a list of productions. In this example, there are two productions that define the non-terminals Input and MatchedBraces respectively. In JavaCC grammars, non-terminals are written and implemented (by JavaCC) as Java methods. When the non-terminal is used on the left-hand side of a production, it is considered to be declared and its syntax follows the Java syntax. On the right-hand side, its use is similar to a method call in Java.
Each production defines its left-hand side non-terminal followed by a colon. This is followed by a bunch of declarations and statements within braces (in both cases in the above example, there are no declarations and hence this appears as <> ) which are generated as common declarations and statements into the generated method. This is then followed by a set of expansions also enclosed within braces.
Lexical tokens (regular expressions) in a JavaCC input grammar are either simple strings ( < , >, \n , and \r in the above example), or a more complex regular expression. In our example above, there is one such regular expression <EOF> which is matched by the end of file. All complex regular expressions are enclosed within angular brackets.
The first production above says that the non-terminal Input expands to the non-terminal MethodBraces followed by zero or more line terminators ( \n or \r ) and then the end of file.
The second production above says that the non-terminal MatchedBraces expands to the token < followed by an optional nested expansion of MatchedBraces followed by the token >. Square brackets [. ] in a JavaCC input file indicate that the . is optional.
[. ] may also be written as (. )? . These two forms are equivalent. Other structures that may appear in expansions are:
Note that these may be nested within each other, so we can have something like:
Парсинг при помощи JAVA
Всем привет, данная статья является — маленьким туториалом, для примера были взяты XML данные с сайта Центр Банка.
В статье будут использованы — Spring Boot, PostgreSQL и Hibernate.
Ингредиенты:
Создание Spring Boot проект, проще всего это сделать через Spring Initializr. (в качестве системы сборки будет использоваться Gradle).
PostgreSQL (для комфортной работы, я использую — DBeaver).
Postman — для отправки запросов на сервер.
Прошу пишите в комментариях возникшие проблемы, на всякий случай — вот мой git и ТГ
Начинаем с чистки ингредиентов:
Первостепенно нужно настроить build.gradle со всеми зависимостями.
Теперь настройки application.properties
Хорошо, после настроек нашего проекта, давайте обговорим его структуру:

Структура проекта
controller — обрабатывает запрос пользователя;
model — описывает модель данных;
repository — логика работы с БД;
service — основная бизнес логика проекта.
Нарезаем овощи и смешиваем:
Перед описанием моделей, посмотрим на фрагмент данных:
— Сначала опишем общую модель курса валют, так как по схеме XML видно, что у нас должен быть общий список элементов валют (ValCurs), внутри которого элементы (Valute)
Теперь для работы с БД, напишем repository
— Создадим интерфейс, который наследует от JpaRepository методы, для работы с записями в БД.
Переходим к созданию бизнес логики приложения
— CourseClient — работа с внешним ресурсом, обработка и выдача результата.
— CourseService — бизнес логика проекта.
Теперь создадим путь, для обращения к сервису из вне
— Пишем контроллер, который используется, для получения списка сохраненных в БД записей
В конце класс, который собственно и запускает все наше приложение
Мы сделали салат, теперь заправляем его
Логи запуска сервиса
— Запускаем DBeaver, после запуска приложения, создастся таблица с полями:

Автоматически создаваемая таблица
— Запускаем Postman, прописываем в поле для url — http://localhost:8080/getCourse:

GETзапрос на url — http://localhost:8080/getCourse
— Нажимаем синию кнопку «Send» и получаем в ответ данные о курсе валют, на 23/01/2022:

Курс валют на 23/01/2022
— Так же курсами валют заполнилась и БД:

Заполненная курсами валют БД Логи сервиса, после отправки запроса
Вот и все, надеюсь, что у всех получилось повторить туториал с первого
раза, в будущем будет еще много интересного, всем спасибо.
Пишем парсер на Java + MySQL
Недавно пробегал на Хабре пост про базу доменных имен с электронной почтой. Решил написать парсер, чтоб благополучно слить всю эту базу. Но так как очень быстро сервис загнулся в силу хабраэффекта (а может админы пофиксили, черт его знает), я пошел дальше и нашел просто базу доменов в plaintext’е в зоне .RU. Решил ее пропарсить с помощью whois на nic.ru. Но на последнем действует скрипт, благополучно притормаживая слив базы с одного ip адреса. Выход — использование proxy листа. И, будучи благополучно задушенным жабой покупать прокси листы, я решил написать на Java два скрипта:
1. Парсит samair.ru/proxy и сливает в mysql прокси лист.
2. Проходит по базе и проверяет timeout полученных проксей.
База данных
Скрин структуры базы из PhpMyAdmin
Итак, сначала парсер.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class ProxyHunter <
/**
* @param args
* @throws ClassNotFoundException
* @throws SQLException
* @throws IOException
*/
public static void main ( String [] args ) throws ClassNotFoundException, SQLException, IOException <
// TODO Auto-generated method stub
Class.forName ( «com.mysql.jdbc.Driver» ) ;
//подключаем драйвер MySQL
Connection conn = DriverManager.getConnection ( «jdbc:mysql://192.168.1.7:3306/database» , «username» , «password» ) ;
//192.168.1.7 — имя хоста, можно подставить хоть localhost
//database — имя базы данных для экспорта
//username, password — имя пользователя и пароль для MySQL
Statement st = conn.createStatement () ;
URL connection = null ;
String [] replacements = new String [ 10 ] ;
String host = null ;
String port = null ;
String anon_level = null ;
String country = null ;
int cursor = 0 ;
HttpURLConnection urlconn = null ;
int n = 1 ;
while ( n <= 50 )
<
if ( n < 10 )
<
connection = new URL ( «www.samair.ru/proxy/ip-address-0»; +n+ «.htm» ) ;
>
else
<
connection = new URL ( «www.samair.ru/proxy/ip-address-«; +n+ «.htm» ) ;
>
System.out.println ( «Starting page: » +Integer.toString ( n )) ;
urlconn = ( HttpURLConnection ) connection.openConnection () ;
urlconn.setRequestMethod ( «GET» ) ;
urlconn.connect () ;
//посылаем GET запрос на список проксей samair’а
java.io.InputStream in = urlconn.getInputStream () ;
BufferedReader reader = new BufferedReader ( new InputStreamReader ( in )) ;
String text = null ;
String line = null ;
while (( line = reader.readLine ()) != null )
<
text += line;
>
//парсим текст страницы
replacements = text.substring ( text.indexOf ( «<script src=\»http://samair.ru:81/js/m.js» type=»text/javascript»> ) + «<script src=\»http://samair.ru:81/js/m.js» type=»text/javascript»> .length () , text.indexOf ( «</script></head>» )) .split ( «;» ) ;
//на самаире, возможно, в целях защиты от парсеров порты в списке выводятся javascript’ом
//в начале страницы рандомом задаются 10 переменных для каждой цыфры, затем они скриптом же и выводятся в таблицу
//replacements — как раз массив этих переменных
cursor = text.indexOf ( «<tr><td>» ) ;
while ( cursor != — 1 )
<
cursor += «<tr><td>» .length () ;
host = text.substring ( cursor, text.indexOf ( «<script type=\»text/javascript\»>» , cursor )) ;
//host — адрес прокси сервера
port = text.substring ( text.indexOf ( «>document.write(\»:\»+» , cursor ) + «>document.write(\»:\»+» .length () , text.indexOf ( «)</script>» , cursor )) ;
port = removeChar ( port, ‘+’ ) ;
for ( int i = 0 ; i< 10 ; i++ )
<
port = port.replaceAll ( replacements [ i ] .split ( » #000000″>)[ 0 ] , replacements [ i ] .split ( » #000000″>)[ 1 ]) ;
//подставляем вместо букв циферки
>
//port — порт сервера
cursor = text.indexOf ( «</td><td>» , cursor ) + «</td><td>» .length () ;
anon_level = text.substring ( cursor, text.indexOf ( «</td><td>» , cursor )) ;
cursor = text.indexOf ( «</td><td>» , cursor ) + «</td><td>» .length () ;
cursor = text.indexOf ( «</td><td>» , cursor ) + «</td><td>» .length () ;
country = text.substring ( cursor, text.indexOf ( «</td></tr>» , cursor )) ;
//получаем остальную лабуду — тип сервера и страна, не пропадать же траффику зря) хотя они и вряд ли понадобятся
ResultSet rs = st.executeQuery ( «select host, port from proxies where host = ‘» +host+ «‘ and port = ‘» +port+ «‘» ) ;
if ( !rs.next ())
<
st.executeUpdate ( «INSERT INTO proxies (host, port, anon_level, country) VALUES (‘» +host+ «‘, ‘» +port+ «‘, ‘» +anon_level+ «‘, ‘» +country+ «‘)» ) ;
System.out.println ( «Added: » +host+ «:» +port ) ;
//Если такого хоста и порта в базе еще нету, то вносим его туда
>
cursor = text.indexOf ( «<tr><td>» , cursor ) ;
>
n++;
>
st.close () ;
conn.close () ;
>
public static String removeChar ( String s, char c ) <
String r = «» ;
for ( int i = 0 ; i < s.length () ; i ++ ) <
if ( s.charAt ( i ) != c ) r += s.charAt ( i ) ;
>
return r;
>
И непосредственно сам чекер
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class ProxyHunter <
/**
* @param args
* @throws ClassNotFoundException
* @throws SQLException
* @throws IOException
*/
public static void main ( String [] args ) throws ClassNotFoundException, SQLException, IOException <
// TODO Auto-generated method stub
Class.forName ( «com.mysql.jdbc.Driver» ) ;
//подключаем драйвер MySQL
Connection conn = DriverManager.getConnection ( «jdbc:mysql://192.168.1.7:3306/database» , «username» , «password» ) ;
//192.168.1.7 — имя хоста, можно подставить хоть localhost
//database — имя базы данных для экспорта
//username, password — имя пользователя и пароль для MySQL
Statement st = conn.createStatement () ;
URL connection = null ;
String [] replacements = new String [ 10 ] ;
String host = null ;
String port = null ;
String anon_level = null ;
String country = null ;
int cursor = 0 ;
HttpURLConnection urlconn = null ;
int n = 1 ;
while ( n <= 100 )
<
if ( n < 10 )
<
connection = new URL ( «www.samair.ru/proxy/ip-address-0»; +n+ «.htm» ) ;
>
else
<
connection = new URL ( «www.samair.ru/proxy/ip-address-«; +n+ «.htm» ) ;
>
System.out.println ( «Starting page: » +Integer.toString ( n )) ;
urlconn = ( HttpURLConnection ) connection.openConnection () ;
urlconn.setRequestMethod ( «GET» ) ;
urlconn.connect () ;
//посылаем GET запрос на список проксей samair’а
java.io.InputStream in = urlconn.getInputStream () ;
BufferedReader reader = new BufferedReader ( new InputStreamReader ( in )) ;
String text = null ;
String line = null ;
while (( line = reader.readLine ()) != null )
<
text += line;
>
//парсим текст страницы
replacements = text.substring ( text.indexOf ( «<script src=\»http://samair.ru:81/js/m.js» type=»text/javascript»> ) + «<script src=\»http://samair.ru:81/js/m.js» type=»text/javascript»> .length () , text.indexOf ( «</script></head>» )) .split ( «;» ) ;
//на самаире, возможно, в целях защиты от парсеров порты в списке выводятся javascript’ом
//в начале страницы рандомом задаются 10 переменных для каждой цыфры, затем они скриптом же и выводятся в таблицу
//replacements — как раз массив этих переменных
cursor = text.indexOf ( «<tr><td>» ) ;
while ( cursor != — 1 )
<
cursor += «<tr><td>» .length () ;
//host — адрес прокси сервера
//каким-то непонятным китайским рандомом у самаира порт проксей выводится либо javascript’ом
//либо plaintext’ом
//обрабатываем
if ( text.indexOf ( «>document.write(\»:\»+» , cursor ) != — 1 )
<
//это для javascript
host = text.substring ( cursor, text.indexOf ( «<script type=\»text/javascript\»>» , cursor )) ;
port = text.substring ( text.indexOf ( «>document.write(\»:\»+» , cursor ) + «>document.write(\»:\»+» .length () , text.indexOf ( «)</script>» , cursor )) ;
port = removeChar ( port, ‘+’ ) ;
for ( int i = 0 ; i< 10 ; i++ )
<
port = port.replaceAll ( replacements [ i ] .split ( » #000000″>)[ 0 ] , replacements [ i ] .split ( » #000000″>)[ 1 ]) ;
//подставляем вместо букв циферки
>
>
else
<
//это plaintext
host = text.substring ( cursor, text.indexOf ( «:» , cursor )) ;
port = text.substring ( text.indexOf ( «:» , cursor ) + 1 , text.indexOf ( «</td><td>» , cursor )) ;
>
//port — порт сервера
cursor = text.indexOf ( «</td><td>» , cursor ) + «</td><td>» .length () ;
anon_level = text.substring ( cursor, text.indexOf ( «</td><td>» , cursor )) ;
cursor = text.indexOf ( «</td><td>» , cursor ) + «</td><td>» .length () ;
cursor = text.indexOf ( «</td><td>» , cursor ) + «</td><td>» .length () ;
country = text.substring ( cursor, text.indexOf ( «</td></tr>» , cursor )) ;
//получаем остальную лабуду — тип сервера и страна, не пропадать же траффику зря) хотя они и вряд ли понадобятся
ResultSet rs = st.executeQuery ( «select host, port from proxies where host = ‘» +host+ «‘ and port = ‘» +port+ «‘» ) ;
if ( !rs.next ())
<
st.executeUpdate ( «INSERT INTO proxies (host, port, anon_level, country) VALUES (‘» +host+ «‘, ‘» +port+ «‘, ‘» +anon_level+ «‘, ‘» +country+ «‘)» ) ;
System.out.println ( «Added: » +host+ «:» +port ) ;
//Если такого хоста и порта в базе еще нету, то вносим его туда
>
cursor = text.indexOf ( «<tr><td>» , cursor ) ;
>
n++;
>
st.close () ;
conn.close () ;
>
public static String removeChar ( String s, char c ) <
String r = «» ;
for ( int i = 0 ; i < s.length () ; i ++ ) <
if ( s.charAt ( i ) != c ) r += s.charAt ( i ) ;
>
return r;
>
Вообще, меня терзают сомнения по поводу правильности реализации массива с потоками. Думается, что в Java есть что-то специальное для таких целей, но я реализовал первое что пришло в голову — массив потоков.
И не ругайте за изобретение велосипеда. Тут цель была just for fun плюс с MySQL’ем поработать. Для красоты вывода этого добра в консоль можно закоментировать все PrintStackTrace()’ы.
Результат
Кусок проверенной базы. -1 в latency означает дохлую проксю.
P.S.
Драйвер MySQL можно скачать здесь: www.mysql.com/products/connector
Выбрать там JDBC Driver for MySQL (Connector/J). Архив распаковать, выдрать оттуда файлик mysql-connector-java-5.1.10-bin.jar и закинуть его в папку с проектом. Потом в Eclipse правой кнопкой по проекту -> Properties -> Java Build Path -> Libraries -> Add JARs и подцепить его там.
Вот то, что должно получится.
Efficient XML Parsing with Java
For a language that likes XML so much, it was rather difficult to find an efficient and elegant way to parse it.
There’s more than one way to parse XML?
There are 2 ways to work with XML documents, read the full document and build a full DOM object representing the document or read the document as stream extracting what you need from the stream. The DOM method is fine for small documents like configuration files but completely falls apart when working with documents of any real size.
When it comes to stream parsing in Java the SAX parser seems to be the most common choice. Most Stack Overflow Answers and Tutorials about parsing large XML files in Java point to the SAX parser. The only problem with the SAX parser is it’s event driven API is very awkward to work with. Defining an event listener (Handler) and waiting for it to be called removes your control of the stream. I like to have control of the execution of my program and have always preferred a pull based API when working with streams.
Fortunately, like most things in programming, I’m not the first person to experience these pain points. The StAX API was created to address this pain, by providing pull based API for a XML stream.
The advantages of the approach are best described by the JavaDocs:
Pull parsing provides several advantages over push parsing when working with XML streams:
With pull parsing, the client controls the application thread, and can call methods on the parser when needed. By contrast, with push processing, the parser controls the application thread, and the client can only accept invocations from the parser.
Pull parsing libraries can be much smaller and the client code to interact with those libraries much simpler than with push libraries, even for more complex documents.
Pull clients can read multiple documents at one time with a single thread.
A StAX pull parser can filter XML documents such that elements unnecessary to the client can be ignored, and it can support XML views of non-XML data.
Show me this magic!
So let’s use the wikipedia dataset as an example, below is a sample of the data:
And here’s the code to extract the name and id of each page:
Wait! What just happened?
You can see the full project here, the first little bit extracts the xml file from a zip archive stored in the resources folder.
The next part uses the XMLInputFactory to create a StAX reader from the stream and starts reading the file. The reader provides a pull API over the stream to pull XML elements from the stream. This first loop goes through the document looking for <page> start element’s and then passing over control to the parsePage function to handle the processing of the page element.
The parsePage function continues to loop through the events in the stream. First we check if we’ve reach the end of the page element and return, as we’ve read the page and completed the scope of this function.
Next the start element is searched for and action taken based off which one has been reached. This allows us to quickly iterate over all the elements contained within the page element, extracting what is needed and ignoring the rest. An additional case could easily be added to the switch statement to handle the revision , calling an additional function to handle the details of parsing that element.
Why was that so simple?
Writing a SAXParser feels like instructing a clown how to juggle. It’s awkward and makes what should be a relatively simple task of reading an XML file unnecessarily complicated. The StAX parser flips this on it’s head, instead been thrown elements with no control, control remains with the caller. Instead of surrendering control and providing functions to be called when events happen. Events are requested and handled as necessary, leaving control with the consumer.
Java is a great language, when used well, unfortunately that is not always the case. The SAXParser is one of those examples, while it does serve a purpose, that purpose is niche. It should not be the default answer for efficient XML parsing. Why it still is when a much better solution exists in the standard library boggles my mind.