XmlStacks

XmlStacks to interpreter autorskiego języka do przetwarzania plików XML, którego składnia także bazuje na XML'u.

Spis treści:

Założenia

Główne założenia przy tworzenia interpretera obejmowały:

  • Maksymalne wykorzystanie gotowych technologii dostarczanych w ramach środowiska Java SE
  • Opracowanie efektywnego mechanizmu do łatwego dodawanie oraz poszerzania konstrukcji językowych języka XmlStacks

Na podstawie tych założeń wynikły następujące decyzje techniczne:

  • Wykorzystanie wzorców CoC (Convention over Configuration) oraz DRY (Don't Repeate Yor Self) razem z mechanizmem refleksji do opisu semantyki język
  • Wykorzystanie standardowej Javowej impelmentacji modelu DOM jako składu danych
  • Sam język będzie także bazował leksykalnie na językach XML i XPath. W tym XPath będzie stosowany do prostej selekcji stosów z innych stosów, w tym z stosu środowiskowego. Język ten będzie zapewniał nam wyrażenia wykonujące operacje na wyekstrahowanych stosach oraz konstrukcje sterowania przepływem programu. Dzięki takiemu podejściu lekser oraz parser zapewnią nam standardowe implementacje pakietów org.w3c.dom oraz javax.xml.xpath
  • Wykorzystanie XML Schema do walidacji składni języka

Aplikacja interpretera

Najłatwiej rozpocząć pracę z językiem XmlStacks przy pomocy graficznego interfejsu przedstawionego poniżej. Jest od podzielony na 4 rubryki:

  • Script - w którym umieszczamy nasz skrypt napisany w XmlStacks
  • Source data - miejsce na dane źródłowe
  • Result - wynikowy pracy skryptu zwrócony przez komendy Output*
  • Messages - komunikaty zwrócone przez skrypt przy użyciu koment Print*

Po wprowadzeniu skryptu i źródła danych możemy uruchomić skrypt klikając na Execute->Execute script lub wciskając CTRL + ENTER. W aplikacji znajdują się także przykładowe skryptu razem z odpowiednimi źródłami danych w File->Examples. Natomiast w Help->View languages XML Schema znajduje się szczegółowy opis składni języka XmlStacks.

Interpreter można także uruchomić z konsoli w następujący sposób:

XmlStacks.jar -nogui script_file_path source_data_file_path

XmlStacks.jar oraz pliki źródłowe są dostępne na końcu strony.

Aplikację XmlStacks można także uruchomić przy pomocy Java Web Start klikając na ten link.

Przykłady

Źródło danych

We wszystkich przykładach po za ostatnich będziemy wykorzystywać następujące źródło danych:

<?xml version="1.0"?>
<nutrition>

    <daily-values>
        <total-fat units="g">65</total-fat>
        <saturated-fat units="g">20</saturated-fat>
        <cholesterol units="mg">300</cholesterol>
        <sodium units="mg">2400</sodium>
        <carb units="g">300</carb>
        <fiber units="g">25</fiber>
        <protein units="g">50</protein>
    </daily-values>

    <food>
        <name>Avocado Dip</name>
        <mfr>Sunnydale</mfr>
        <serving units="g">29</serving>
        <calories total="110" fat="100" />
        <total-fat>11</total-fat>
        <saturated-fat>3</saturated-fat>
        <cholesterol>5</cholesterol>
        <sodium>210</sodium>
        <carb>2</carb>
        <fiber>0</fiber>
        <protein>1</protein>
        <vitamins>
            <a>0</a>
            <c>0</c>
        </vitamins>
        <minerals>
            <ca>0</ca>
            <fe>0</fe>
        </minerals>
    </food>

    <food>
        <name>Bagels, New York Style</name>
        <mfr>Thompson</mfr>
        <serving units="g">104</serving>
        <calories total="300" fat="35" />
        <total-fat>4</total-fat>
        <saturated-fat>1</saturated-fat>
        <cholesterol>0</cholesterol>
        <sodium>510</sodium>
        <carb>54</carb>
        <fiber>3</fiber>
        <protein>11</protein>
        <vitamins>
            <a>0</a>
            <c>0</c>
        </vitamins>
        <minerals>
            <ca>8</ca>
            <fe>20</fe>
        </minerals>
    </food>

    ...

</nutrition>

Hello World

Skrypt Wynik
<?xml version="1.0"?>
<Block as="hello">
    <OutputComment text="world" />
</Block>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<hello>
  <!-- world -->
</hello>

Hello Stacks

Skrypt

<?xml version="1.0"?>
<Block as="root">
    <OutputStack><Get path="//minerals/*" as="my_mins"/></OutputStack>
    <OutputStack><Get path="//vitamins/*"/></OutputStack>
    <OutputStack><Get as="all_source" /></OutputStack>
</Block>

Wynik

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<root>
  <my_mins>
    <ca>0</ca>
    <fe>0</fe>
    <ca>8</ca>
    <fe>20</fe>
    <ca>1</ca>
    ...
  </my_mins>
  <Result>
    <a>0</a>
    <c>0</c>
    <a>0</a>
    ...
  </Result>
  <all_source>
    <nutrition>

    <daily-values>
        <total-fat units="g">65</total-fat>
        <saturated-fat units="g">20</saturated-fat>
        <cholesterol units="mg">300</cholesterol>
        <sodium units="mg">2400</sodium>
        <carb units="g">300</carb>
        <fiber units="g">25</fiber>
        <protein units="g">50</protein>
    </daily-values>

    <food>
        <name>Avocado Dip</name>
        <mfr>Sunnydale</mfr>
        <serving units="g">29</serving>
        <calories fat="100" total="110"/>
        <total-fat>11</total-fat>
        <saturated-fat>3</saturated-fat>
        <cholesterol>5</cholesterol>
        <sodium>210</sodium>
        <carb>2</carb>
        <fiber>0</fiber>
        <protein>1</protein>
        <vitamins>
            <a>0</a>
            <c>0</c>
        </vitamins>
        <minerals>
            <ca>0</ca>
            <fe>0</fe>
        </minerals>
    </food>

    <food>
        <name>Bagels, New York Style</name>
        <mfr>Thompson</mfr>
        <serving units="g">104</serving>
        <calories fat="35" total="300"/>
        <total-fat>4</total-fat>
        <saturated-fat>1</saturated-fat>
        <cholesterol>0</cholesterol>
        <sodium>510</sodium>
        <carb>54</carb>
        <fiber>3</fiber>
        <protein>11</protein>
        <vitamins>
            <a>0</a>
            <c>0</c>
        </vitamins>
        <minerals>
            <ca>8</ca>
            <fe>20</fe>
        </minerals>
    </food>

   ...

</nutrition>
  </all_source>
</root>

Pętla Foreach

Skrypt

<?xml version="1.0"?>
<Block as="root">
<Foreach var="itr">
<From><Get path="/nutrition/food" /></From>
<Do><Block>
<OutputComment text="iteration" />
<If>
<Cond><Eq>
<Get var="itr" path="/food/total-fat" as="res" />
<CreateElement name="res">
<Child>
<CreateElement name="total-fat">
<Text>10</Text>
</CreateElement>
</Child>
</CreateElement>
</Eq></Cond>

<Then><OutputStack><Get var="itr" /></OutputStack></Then>
</If>
</Block></Do>
</Foreach>
</Block>

Wynik
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<root>
  <!-- iteration -->
  <!-- iteration -->
  <!-- iteration -->
  <!-- iteration -->
  <!-- iteration -->
  <!-- iteration -->
  <!-- iteration -->
  <food>
        <name>Hazelnut Spread</name>
        <mfr>Ferreira</mfr>
        <serving units="tbsp">2</serving>
        <calories fat="90" total="200"/>
        <total-fat>10</total-fat>
        <saturated-fat>2</saturated-fat>
        <cholesterol>0</cholesterol>
        <sodium>20</sodium>
        <carb>23</carb>
        <fiber>2</fiber>
        <protein>3</protein>
        <vitamins>
            <a>0</a>
            <c>0</c>
        </vitamins>
        <minerals>
            <ca>6</ca>
            <fe>4</fe>
        </minerals>
    </food>
  <!-- iteration -->
  <food>
        <name>Potato Chips</name>
        <mfr>Lees</mfr>
        <serving units="g">28</serving>
        <calories fat="90" total="150"/>
        <total-fat>10</total-fat>
        <saturated-fat>3</saturated-fat>
        <cholesterol>0</cholesterol>
        <sodium>180</sodium>
        <carb>15</carb>
        <fiber>1</fiber>
        <protein>2</protein>
        <vitamins>
            <a>0</a>
            <c>10</c>
        </vitamins>
        <minerals>
            <ca>0</ca>
            <fe>0</fe>
        </minerals>
    </food>
  <!-- iteration -->
  <!-- iteration -->
</root>

Złączenie

Źródło danych

<?xml version="1.0"?>
<database>
<personel>
<person>
<name>Adam</name>
<age>23</age>
</person>
<person>
<name>Marcin</name>
<age>31</age>
</person>
<person>
<name>Ewa</name>
<age>25</age>
</person>
</personel>
<resource>
<car>
<name>BMW</name>
<driver>Adam</driver>
</car>
<car>
<name>Fiat</name>
<driver>Marcin</driver>
</car>
<car>
<name>Trabant</name>
<driver>Adam</driver>
</car>
</resource>
</database>

Skrypt
<?xml version="1.0"?>
<Block as="join_test">
<Set var="P"><Get path="/database/personel/person" /></Set>
<OutputStack>
<Join>
<Left on="name/text()"><Get var="P" /></Left>
<Right on="driver/text()"><Get path="//car" /></Right>
</Join>
</OutputStack>
</Block>

Wynik
<?xml version="1.0" encoding="UTF-8"?>
<join_test>
<Result>
<person>
<name>Adam</name>
<age>23</age>
<car>
<name>BMW</name>
<driver>Adam</driver>
</car>
<car>
<name>Trabant</name>
<driver>Adam</driver>
</car>
</person>
<person>
<name>Marcin</name>
<age>31</age>
<car>
<name>Fiat</name>
<driver>Marcin</driver>
</car>
</person>
</Result>
</join_test>

Opis składni języka w XML Schema

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:group name="Statement">
<xs:annotation>
<xs:documentation>
This group specifies elements that are recognized as
individual language statements
</xs:documentation>
</xs:annotation>
<xs:choice>
<xs:element ref="Block" />
<xs:element ref="OutputComment" />
<xs:element ref="OutputStack" />
<xs:element ref="PrintMessage" />
<xs:element ref="PrintStack" />
<xs:element ref="If" />
<xs:element ref="Foreach" />
<xs:element ref="Set" />
<xs:group ref="Query" />
<xs:group ref="LogicOperation" />
</xs:choice>
</xs:group>

<xs:group name="Query">
<xs:annotation>
<xs:documentation>
This group specifies language statement that are
queries and return the root XML Element of there
queries result.
</xs:documentation>
</xs:annotation>
<xs:choice>
<xs:element ref="Get" />
<xs:element ref="CreateElement" />
<xs:element ref="Join" />
</xs:choice>
</xs:group>

<xs:group name="LogicOperation">
<xs:annotation>
<xs:documentation>
This group specifies language statements that
are logic operations and return a boolean value.
</xs:documentation>
</xs:annotation>
<xs:choice>
<xs:element ref="Eq" />
<xs:element ref="Not" />
<xs:element ref="True" />
</xs:choice>
</xs:group>

<xs:element name="Block">
<xs:annotation>
<xs:documentation>
Block is used to run several statements in a sequence. It can
also add an element to the output XML using the 'as' attribute.
If this attribute is set then an element with the name specified
in 'as' will be appended to the output XML and all
Output* commands will append their results to this element.
The 'as' attribute must be given in the most outer Block.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:group ref="Statement" minOccurs="0" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="as" type="xs:string" />
</xs:complexType>
</xs:element>

<xs:element name="OutputComment">
<xs:annotation>
<xs:documentation>
Outputs a comment.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="text" type="xs:string" use="required" />
</xs:complexType>
</xs:element>

<xs:element name="OutputStack">
<xs:annotation>
<xs:documentation>
Outputs the result of the given query.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:group ref="Query" />
</xs:sequence>
</xs:complexType>
</xs:element>

<xs:element name="PrintMessage">
<xs:annotation>
<xs:documentation>
Prints a message to the message stream.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="text" type="xs:string" use="required" />
</xs:complexType>
</xs:element>

<xs:element name="PrintStack">
<xs:annotation>
<xs:documentation>
Prints the result of the given query to the
message stream.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:group ref="Query" />
</xs:sequence>
</xs:complexType>
</xs:element>

<xs:element name="Foreach">
<xs:annotation>
<xs:documentation>
The Foreach statement evaluates the statement given in
Do for each child element from the root element
return by the query given in From. The current child
element is available in the local variable named in
the 'var' attribute.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="From">
<xs:complexType>
<xs:sequence>
<xs:group ref="Query" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Do">
<xs:complexType>
<xs:sequence>
<xs:group ref="Statement" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="var" type="xs:string" use="required" />
</xs:complexType>
</xs:element>

<xs:element name="If">
<xs:annotation>
<xs:documentation>
If the logic operation given in Cond returns 'true' then
the statement given in Then will be evaluated. Optionally
you can give a statement in Else to be evaluated if the
logic operation returns 'false'.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="Cond">
<xs:complexType>
<xs:group ref="LogicOperation" />
</xs:complexType>
</xs:element>
<xs:element name="Then">
<xs:complexType>
<xs:group ref="Statement" />
</xs:complexType>
</xs:element>
<xs:element name="Else" minOccurs="0">
<xs:complexType>
<xs:group ref="Statement" />
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>

<xs:element name="Set">
<xs:annotation>
<xs:documentation>
Sets a local variable with the name 'var' with the
result of the given query. The variable is visible
to the the child statements of the current block.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:group ref="Query" />
</xs:sequence>
<xs:attribute name="var" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>

<xs:element name="Get">
<xs:annotation>
<xs:documentation>
If the 'var' attribute is set then Get will return the
local variable, otherwise Get will return the whole source
XML. If the 'path' attribute is set then the XPath expresion
from it will be evaluated on the local variable or the
source XML. The result will be appended to an element with
name from 'as' and returned as the result of Get.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="var" type="xs:string" />
<xs:attribute name="path" type="xs:string"/>
<xs:attribute name="as" type="xs:string" default="Result"/>
</xs:complexType>
</xs:element>

<xs:element name="CreateElement">
<xs:annotation>
<xs:documentation>
Creates an element with the name from the 'name' attribute,
with child elements from the results of queries from the Child
elements and with the attributes from the Attribute elements.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="Attribute" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="name" type="xs:string" />
<xs:attribute name="value" type="xs:string" />
</xs:complexType>
</xs:element>
<xs:element name="Child" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:group ref="Query" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Text" type="xs:string" minOccurs="0" maxOccurs="1" />
</xs:sequence>
<xs:attribute name="name" type="xs:string" use="required" />
</xs:complexType>
</xs:element>

<xs:element name="Join">
<xs:annotation>
<xs:documentation>
This query returns the an element with the name from the
'as' attribute with appended children of the Left query result.
The children of the Right query result are
appended to the matching children from Left. The children
match if the XPath expression from Left's 'on' attribute
evaluated on the current left child returns the same result
as the XPath expression from Right's 'on' attribute
evaluated on the current right child. If the 'type' attribute
is set to 'Equi' then the right children that didn't have any
match will not be returned.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element name="Left" type="JoinSide" />
<xs:element name="Right" type="JoinSide" />
</xs:sequence>
<xs:attribute name="type" use="optional" default="Equi">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="Equi" />
<xs:enumeration value="Left" />
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="as" type="xs:string" default="Result"/>
</xs:complexType>
</xs:element>

<xs:complexType name="JoinSide">
<xs:sequence>
<xs:group ref="Query" />
</xs:sequence>
<xs:attribute name="on" type="xs:string" />
</xs:complexType>

<xs:element name="Eq">
<xs:annotation>
<xs:documentation>
Checks in the given queries return the same results.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:group ref="Query" minOccurs="2" maxOccurs="unbounded" />
</xs:sequence>
</xs:complexType>
</xs:element>

<xs:element name="Not">
<xs:complexType>
<xs:sequence>
<xs:group ref="LogicOperation" />
</xs:sequence>
</xs:complexType>
</xs:element>

<xs:element name="True">
<xs:complexType />
</xs:element>

</xs:schema>

Pliki do ściągnięcia