Building the Formatter

   

The only thing remaining is for us to write the EDIFACT formatter.

Application Organization

Figure 5.7 shows the UML model for our application. The major classes are as follows :

  • EdifactFormatter , which implements SAX's DocumentHandler . It interprets the XML-ized document.

  • Segment , CompositeDataElement , SimpleDataElement , and EdifactElement are used by EdifactFormatter to take care of the EDIFACT syntax.

  • XML2Edifact is the application's main method.

    Figure 5.7. At the heart of our application are the XSL processor and our custom formatter.

    graphics/05fig07.gif

EdifactFormatter

Listing 5.4 is the code for EdifactFormatter . It uses a SAX parser to read the XML document.

Listing 5.4 EdifactFormatter.java
 package com.psol.xsledi; import java.io.*; import java.util.*; import org.xml.sax.*; public class EdifactFormatter    implements DocumentHandler {    protected static final int NONE = 0,                               MESSAGE = 1,                               SEGMENT = 2,                               COMPOSITE_DATA = 3,                               SEGMENT_SIMPLE_DATA = 4,                               COMPOSITE_SIMPLE_DATA = 5;    protected int state = NONE;    protected Segment segment = null;    protected CompositeData compositeData = null;    protected StringBuffer buffer = null;    protected Writer writer;    public EdifactFormatter(OutputStream out)       throws IOException    {       writer = new OutputStreamWriter(out,"ISO-8859-1");    }    public void setDocumentLocator(Locator locator)       { }    public void startDocument()       throws SAXException       { }    public void endDocument()       throws SAXException    {       try       {          writer.flush();       }       catch(IOException e)       {          throw new SAXException(e);       }    }    public void startElement(String name,AttributeList atts)       throws SAXException    {       if(name.equals("Message") && state == NONE)          state = MESSAGE;       else if(name.equals("Segment") && state == MESSAGE)       {          state = SEGMENT;          String segmentTag = atts.getValue("tag");          if(null != segmentTag)             segment = new Segment(segmentTag);          else             throw new SAXException("Empty 'tag'attribute");       }       else if(name.equals("Composite") && state == SEGMENT)       {          state = COMPOSITE_DATA;          compositeData = new CompositeData();       }       else if(name.equals("Simple") && state == SEGMENT)       {          state = SEGMENT_SIMPLE_DATA;          buffer = new StringBuffer();       }       else if(name.equals("Simple") && state == COMPOSITE_DATA)       {          state = COMPOSITE_SIMPLE_DATA;          buffer = new StringBuffer();       }    }    public void endElement(String name)       throws SAXException    {       try       {          if(name.equals("Message") && state == MESSAGE)             state = NONE;          else if(name.equals("Segment") && state == SEGMENT)          {             state = MESSAGE;             segment.toEdifact(writer);          }          else if(name.equals("Composite") &&                  state == COMPOSITE_DATA)          {             state = SEGMENT;             segment.add(compositeData);             compositeData = null;          }          else if(name.equals("Simple") &&                  state == SEGMENT_SIMPLE_DATA)          {             state = SEGMENT;             SimpleData sd = new SimpleData(buffer.toString());             segment.add(sd);          }          else if(name.equals("Simple") &&                  state == COMPOSITE_SIMPLE_DATA)          {             state = COMPOSITE_DATA;             SimpleData sd = new SimpleData(buffer.toString());             compositeData.add(sd);          }       }       catch(IOException e)       {          throw new SAXException(e);       }    }    public void characters(char[] ch,int start,int len)       throws SAXException    {       if(state == SEGMENT_SIMPLE_DATA           state == COMPOSITE_SIMPLE_DATA)          buffer.append(ch,start,len);    }    public void ignorableWhitespace(char[] ch,int start,int len)       { }    public void processingInstruction(String target,String data)       { } } 

Warning

As always, this project focuses on the XML side of things. The EdifactFormatter is limited to the most useful options in the EDIFACT syntax. It leaves out a few, seldom used, options.


Most of EdifactFormatter should be familiar, but let's review endElement() in more detail. Note that startElement() is typical and is used to track the current state in the XML document.

endElement() also tracks state and create instances of simple data elements, composite data elements, and segments. As soon as it reaches the end of a segment, endElement() calls the toEdifact() method to write the segment in EDIFACT. Then, it discards the segment:

 public void endElement(String name)    throws SAXException {    try    {       if(name.equals("Message") && state == MESSAGE)          state = NONE;       else if(name.equals("Segment") && state == SEGMENT)       {          state = MESSAGE;          segment.toEdifact(writer);       }       else if(name.equals("Composite") &&               state == COMPOSITE_DATA)       {          state = SEGMENT;          segment.add(compositeData);          compositeData = null;       }       else if(name.equals("Simple") &&               state == SEGMENT_SIMPLE_DATA)       {          state = SEGMENT;          SimpleData sd = new SimpleData(buffer.toString());          segment.add(sd);       }       else if(name.equals("Simple") &&               state == COMPOSITE_SIMPLE_DATA)       {          state = COMPOSITE_DATA;          SimpleData sd = new SimpleData(buffer.toString());          compositeData.add(sd);       }    }    catch(IOException e)    {       throw new SAXException(e);    } } 

Segment

Listing 5.5 is Segment.java . The Segment takes a tag in its constructor. It also maintains a list of EdifactElement , which represents simple and composite data elements.

Listing 5.5 Segment.java
 package com.psol.xsledi; import java.io.*; import java.util.*; class Segment {    protected String tag;    protected Vector elements = new Vector();    protected boolean empty = true;    public Segment(String tag)    {       this.tag = tag;    }    public void add(EdifactElement element)    {       if(!element.isEmpty())          empty = false;       elements.addElement(element);    }    public EdifactElement elementAt(int i)    {       return (EdifactElement)elements.elementAt(i);    }    public int getSize()    {       return elements.size();    }    public void toEdifact(Writer writer)       throws IOException    {       if(empty)          return;       writer.write(tag);       int plus = 1;       for(int i = 0;i < getSize();i++)       {          EdifactElement el = elementAt(i);          if(el.isEmpty())             plus++;          else          {             for(int j = 0;j < plus;j++)                writer.write('+');             plus = 1;             el.toEdifact(writer);          }       }       writer.write('\ '');    } } 

The most interesting part of Segment.java is probably toEdifact() , the method that takes care of the EDIFACT syntax. As you can see, it loops over the EdifactElement and calls the toEdifact() method:

 public void toEdifact(Writer writer)    throws IOException {    if(empty)       return;    writer.write(tag);    int plus = 1;    for(int i = 0;i < getSize();i++)    {       EdifactElement el = elementAt(i);       if(el.isEmpty())          plus++;       else       {          for(int j = 0;j < plus;j++)             writer.write('+');          plus = 1;          el.toEdifact(writer);       }    }    writer.write('\ ''); } 

The tricky part is to handle compression. For example, if the following is read

 <Segment tag="NUL"><Simple/><Simple/><Simple>1</Simple></Segment> 

toEdifact() must insert three plus signs before the third field:

 NUL+++1' 

However, simply printing a plus sign before each field is dangerous. Consider the following:

 <Segment tag="NUL"><Simple>1</Simple><Simple/><Simple/></Segment> 

Because the last two fields are empty, the compression rule dictates no plus sign should exist for them. Therefore, it would read

 NUL+1' 

If toEdifact() printed a plus sign before each field, it would need to backtrack at the end of most segments and erase unnecessary trailing plus signs. Instead, toEdifact() delays writing the plus signs until it reaches a non-empty field.

The plus variable tracks how many empty fields were written, which is equal to the number of plus signs to insert before the next non-empty field.

Note

We are not using the visitor pattern, as we did in Chapter 1, "Lightweight Data Storage," because we don't expect to change the EDIFACT syntax any time soon. Changes in the structure of the message (such as the order of segments or fields) are being dealt with in the style sheet.


CompositeData and SimpleData

Listing 5.6 is the EdifactElement interface . It defines two methods : isEmpty() , which is used for compression, and toEdifact() , which writes the field. Thanks to EdifactElement , Segment doesn't need to worry about the differences between simple and composite data elements.

Listing 5.6 EdifactElement.java
 package com.psol.xsledi; import java.io.*; public interface EdifactElement {    public boolean isEmpty();    public void toEdifact(Writer writer)       throws IOException; } 

Listing 5.7 is CompositeData . It maintains a list of SimpleData . Note that its toEdifact() method counts colons like Segment 's toEdifact() counts plus signs.

Listing 5.7 CompositeData.java
 package com.psol.xsledi; import java.io.*; import java.util.*; class CompositeData    implements EdifactElement {    protected Vector simples = new Vector();    protected boolean empty = true;    public void add(SimpleData simple)    {       if(!simple.isEmpty())          empty = false;       simples.addElement(simple);    }    public int getSize()    {       return simples.size();    }    public SimpleData simpleDataAt(int i)    {       return (SimpleData)simples.elementAt(i);    }    public boolean isEmpty()    {       return empty;    }    public void toEdifact(Writer writer)       throws IOException    {       if(isEmpty())          return;       // the empty simple data we have encountered so far       // in other words, the number of colons to write       // before the next non-empty simple data       int colons = 0;       for(int i = 0;i < getSize();i++)       {          SimpleData sd = simpleDataAt(i);          if(sd.isEmpty())             colons++;          else          {             for(int j = 0;j < colons;j++)                writer.write(':');             colons = 1;             sd.toEdifact(writer);          }       }    } } 

Listing 5.8 is the SimpleData class . As you can see, it's trivial: It stores a string. The most involving part is in the toEdifact() method, which implements a simple algorithm to escape special characters. Note that it also converts the text to uppercase; by default EDIFACT does not use lowercase characters.

Listing 5.8 SimpleData.java
 package com.psol.xsledi; import java.io.*; public class SimpleData    implements EdifactElement {    protected String data;    public SimpleData(String data)    {       this.data = data;    }    public String getData()    {       return data;    }    public boolean isEmpty()    {       return data.length() == 0;    }    public void toEdifact(Writer writer)       throws IOException    {       if(isEmpty())          return;       for(int i = 0;i < data.length();i++)       {          char c = data.charAt(i);          if(c == '\ '')             writer.write("?'");          else if(c == '+')             writer.write("?+");          else if(c == ':')             writer.write("?:");          else if(c == '?')             writer.write("??");          else             writer.write(Character.toUpperCase(c));       }    } } 

XML2Edifact

The main() method is in class XML2Edifact , which is reproduced in Listing 5.9. As you can see, it creates an EDIFACT formatter and an XSL processor and links the two together.

The Xalan XSL processor can generate its output as SAX events so that the EDIFACT formatter plugs directly into it. With other processors, you might need to explicitly write the XML file and parse.

Listing 5.9 XML2Edifact.java
 package com.psol.xsledi; import java.io.*; import java.util.*; import org.xml.sax.*; import org.apache.xalan.xslt.*; public class XML2Edifact {    public static void main(String args[])       throws SAXException, IOException    {       if(args.length < 3)          throw new IllegalArgumentException(             "Usage is XML2Edifact in.xml xsl.xsl out.edi");       InputStream sin = new FileInputStream(args[0]),                   sxsl = new FileInputStream(args[1]);       OutputStream sout = new FileOutputStream(args[2]);       EdifactFormatter formatter = new EdifactFormatter(sout);       XSLTProcessor processor =          XSLTProcessorFactory.getProcessor();       XSLTInputSource in = new XSLTInputSource(sin),                       xsl = new XSLTInputSource(sxsl);       XSLTResultTarget out = new XSLTResultTarget(formatter);       processor.process(in,xsl,out);    } } 
   


Applied XML Solutions
Applied XML Solutions
ISBN: 0672320541
EAN: 2147483647
Year: 1999
Pages: 142

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net