From f285c8295b02d5eb9a8ae6e476aa14315756f448 Mon Sep 17 00:00:00 2001
From: Dick Hollenbeck <dick@softplc.com>
Date: Sat, 7 Aug 2010 19:31:07 -0500
Subject: [PATCH] S-expression in xnode.cpp

---
 CHANGELOG.txt             |  13 +++
 common/richio.cpp         |  62 +++++++++------
 common/xnode.cpp          |  79 +++++++++++++------
 eeschema/netform.cpp      | 161 +++++++++++++++++++++++---------------
 eeschema/netlist.keywords |  35 +++++++++
 include/richio.h          |  85 ++++++++++++++++----
 include/xnode.h           |  50 +-----------
 pcbnew/specctra.cpp       |  85 ++++++--------------
 pcbnew/specctra.h         |  23 +-----
 9 files changed, 339 insertions(+), 254 deletions(-)
 create mode 100644 eeschema/netlist.keywords

diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index bdbe9fc813..73e0166fd0 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -4,6 +4,19 @@ KiCad ChangeLog 2010
 Please add newer entries at the top, list the date and your name with
 email address.
 
+2010-Aug-7 UPDATE Dick Hollenbeck <dick@softplc.com>
+================================================================================
+++common
+  * add xnode.cpp and xnode.h which can be used to output either an XML or
+    S-expression document file.
+  * Add class STREAM_OUTPUTFORMATTER which is a richio class which can write
+    to any of the wxOutputStream derivatives, such as file, socket, zip, tar.
+  * Added netlist.keywords
+++eeschema
+  * netform.cpp can now output S-expression OK, although I have it commented out
+    pending the addition of a UI for it.
+
+
 2010-Aug-4 UPDATE Dick Hollenbeck <dick@softplc.com>
 ================================================================================
 ++eeschema netform.cpp:
diff --git a/common/richio.cpp b/common/richio.cpp
index f8b656efc1..344da30cc3 100644
--- a/common/richio.cpp
+++ b/common/richio.cpp
@@ -147,18 +147,7 @@ const char* OUTPUTFORMATTER::GetQuoteChar( const char* wrapee, const char* quote
 }
 
 
-//-----<STRINGFORMATTER>----------------------------------------------------
-
-const char* STRINGFORMATTER::GetQuoteChar( const char* wrapee )
-{
-    // for what we are using STRINGFORMATTER for at this time, we can return
-    // the nul string always.
-
-    return "";
-//    return OUTPUTFORMATTER::GetQuoteChar( const char* wrapee, "\"" );
-}
-
-int STRINGFORMATTER::vprint( const char* fmt,  va_list ap )
+int OUTPUTFORMATTER::vprint( const char* fmt,  va_list ap )  throw( IOError )
 {
     int ret = vsnprintf( &buffer[0], buffer.size(), fmt, ap );
     if( ret >= (int) buffer.size() )
@@ -168,13 +157,13 @@ int STRINGFORMATTER::vprint( const char* fmt,  va_list ap )
     }
 
     if( ret > 0 )
-        mystring.append( (const char*) &buffer[0] );
+        write( &buffer[0], ret );
 
     return ret;
 }
 
 
-int STRINGFORMATTER::sprint( const char* fmt, ... )
+int OUTPUTFORMATTER::sprint( const char* fmt, ... )  throw( IOError )
 {
     va_list     args;
 
@@ -186,9 +175,8 @@ int STRINGFORMATTER::sprint( const char* fmt, ... )
 }
 
 
-int STRINGFORMATTER::Print( int nestLevel, const char* fmt, ... ) throw( IOError )
+int OUTPUTFORMATTER::Print( int nestLevel, const char* fmt, ... ) throw( IOError )
 {
-
 #define NESTWIDTH           2   ///< how many spaces per nestLevel
 
     va_list     args;
@@ -200,17 +188,14 @@ int STRINGFORMATTER::Print( int nestLevel, const char* fmt, ... ) throw( IOError
 
     for( int i=0; i<nestLevel;  ++i )
     {
+        // no error checking needed, an exception indicates an error.
         result = sprint( "%*c", NESTWIDTH, ' ' );
-        if( result < 0 )
-            break;
 
         total += result;
     }
 
-    if( result<0 || (result=vprint( fmt, args ))<0 )
-    {
-        throw IOError( _("Error writing to STRINGFORMATTER") );
-    }
+    // no error checking needed, an exception indicates an error.
+    result = vprint( fmt, args );
 
     va_end( args );
 
@@ -219,6 +204,13 @@ int STRINGFORMATTER::Print( int nestLevel, const char* fmt, ... ) throw( IOError
 }
 
 
+//-----<STRINGFORMATTER>----------------------------------------------------
+
+void STRINGFORMATTER::write( const char* aOutBuf, int aCount ) throw( IOError )
+{
+    mystring.append( aOutBuf, aCount );
+}
+
 void STRINGFORMATTER::StripUseless()
 {
     std::string  copy = mystring;
@@ -234,3 +226,29 @@ void STRINGFORMATTER::StripUseless()
     }
 }
 
+
+//-----<STREAM_OUTPUTFORMATTER>--------------------------------------
+
+const char* STREAM_OUTPUTFORMATTER::GetQuoteChar( const char* wrapee )
+{
+    return OUTPUTFORMATTER::GetQuoteChar( wrapee, quoteChar );
+}
+
+
+void STREAM_OUTPUTFORMATTER::write( const char* aOutBuf, int aCount ) throw( IOError )
+{
+    int lastWrite;
+
+    // This might delay awhile if you were writing to say a socket, but for
+    // a file it should only go through the loop once.
+    for( int total = 0;  total<aCount;  total += lastWrite )
+    {
+        lastWrite = os.Write( aOutBuf, aCount ).LastWrite();
+
+        if( !os.IsOk() )
+        {
+            throw IOError( _( "OUTPUTSTREAM_OUTPUTFORMATTER write error" ) );
+        }
+    }
+}
+
diff --git a/common/xnode.cpp b/common/xnode.cpp
index 3bfb55b08d..43aa806275 100644
--- a/common/xnode.cpp
+++ b/common/xnode.cpp
@@ -24,46 +24,77 @@
 
 
 #include "xnode.h"
+#include "macros.h"
+
+
+typedef wxXmlProperty  XATTR;
 
 void XNODE::Format( OUTPUTFORMATTER* out, int nestLevel ) throw( IOError )
 {
-    // output attributes first if they exist
+    switch( GetType() )
+    {
+    case wxXML_ELEMENT_NODE:
+        out->Print( nestLevel, "(%s", CONV_TO_UTF8( GetName() ) );
+        FormatContents( out, nestLevel );
+        if( GetNext() )
+            out->Print( 0, ")\n" );
+        else
+            out->Print( 0, ")" );
+        break;
 
-    // output children if they exist.
-
-    // output "contents" if it exists.  Use quote need checker to wrap contents if needed.
-
-    // A good XML element will not have both children AND contents, usually one or the other.
-    // children != attributes in the above statement.
-
-//    for( XNODE*...
+    default:
+        FormatContents( out, nestLevel );
+    }
 }
 
 
 void XNODE::FormatContents( OUTPUTFORMATTER* out, int nestLevel ) throw( IOError )
 {
-    // overridden in ELEM_HOLDER
-}
+    std::string utf8;
+    const char* quote;
 
-
-void XATTR::Format( OUTPUTFORMATTER* out, int nestLevel ) throw( IOError )
-{
     // output attributes first if they exist
+    for( XATTR* attr = (XATTR*) GetAttributes();  attr;  attr = (XATTR*) attr->GetNext() )
+    {
+        utf8  = CONV_TO_UTF8( attr->GetValue() );   // capture the content
+        quote = out->GetQuoteChar( utf8.c_str() );
 
-    // output children if they exist.
+        out->Print( 0, " (%s %s%s%s)",
+            // attr names should never need quoting, no spaces, we designed the file.
+            CONV_TO_UTF8( attr->GetName() ),
+            quote, utf8.c_str(), quote );
+    }
 
-    // output "contents" if it exists.  Use quote need checker to wrap contents if needed.
+    // we only expect to have used one of two types here:
+    switch( GetType() )
+    {
+    case wxXML_ELEMENT_NODE:
 
-    // A good XML element will not have both children AND contents, usually one or the other.
-    // children != attributes in the above statement.
+        // output children if they exist.
+        for( XNODE* kid = (XNODE*) GetChildren();  kid;  kid = (XNODE*) kid->GetNext() )
+        {
+            if( kid->GetType() != wxXML_TEXT_NODE )
+            {
+                if( kid == GetChildren() )
+                    out->Print( 0, "\n" );
+                kid->Format( out, nestLevel+1 );
+            }
+            else
+            {
+                kid->Format( out, 0 );
+            }
+        }
+        break;
 
-//    for( XNODE*...
-}
+    case wxXML_TEXT_NODE:
+        utf8  = CONV_TO_UTF8( GetContent() );
+        quote = out->GetQuoteChar( utf8.c_str() );
+        out->Print( 0, " %s%s%s", quote, utf8.c_str(), quote );
+        break;
 
-
-void XATTR::FormatContents( OUTPUTFORMATTER* out, int nestLevel ) throw( IOError )
-{
-    // overridden in ELEM_HOLDER
+    default:
+        ;   // not supported
+    }
 }
 
 
diff --git a/eeschema/netform.cpp b/eeschema/netform.cpp
index 1df1cffe97..21bd78de13 100644
--- a/eeschema/netform.cpp
+++ b/eeschema/netform.cpp
@@ -190,46 +190,45 @@ class EXPORT_HELP
      */
     void writeListOfNetsCADSTAR( FILE* f, NETLIST_OBJECT_LIST& aObjectsList );
 
-
     /**
      * Function makeGenericRoot
      * builds the entire document tree for the generic export.  This is factored
      * out here so we can write the tree in either S-expression file format
      * or in XML if we put the tree built here into a wxXmlDocument.
      */
-    XNODE*     makeGenericRoot();
+    XNODE* makeGenericRoot();
 
     /**
      * Function makeGenericComponents
      * returns a sub-tree holding all the schematic components.
      */
-    XNODE*     makeGenericComponents();
+    XNODE* makeGenericComponents();
 
     /**
      * Function makeGenericDesignHeader
      * fills out a project "design" header into an XML node.
-     * @return XNODE*     - the design header
+     * @return XNODE* - the design header
      */
-    XNODE*     makeGenericDesignHeader();
+    XNODE* makeGenericDesignHeader();
 
     /**
      * Function makeGenericLibParts
      * fills out an XML node with the unique library parts and returns it.
      */
-    XNODE*     makeGenericLibParts();
+    XNODE* makeGenericLibParts();
 
     /**
      * Function makeGenericListOfNets
      * fills out an XML node with a list of nets and returns it.
      */
-    XNODE*     makeGenericListOfNets();
+    XNODE* makeGenericListOfNets();
 
     /**
      * Function makeGenericLibraries
      * fills out an XML node with a list of used libraries and returns it.
      * Must have called makeGenericLibParts() before this function.
      */
-    XNODE*     makeGenericLibraries();
+    XNODE* makeGenericLibraries();
 
 
 public:
@@ -392,6 +391,12 @@ static bool sortPinsByNum( NETLIST_OBJECT* aPin1, NETLIST_OBJECT* aPin2 )
     return RefDesStringCompare( aPin1->GetPinNumText(), aPin2->GetPinNumText() ) < 0;
 }
 
+static bool sortPinsByNumber( LIB_PIN* aPin1, LIB_PIN* aPin2 )
+{
+    // return "lhs < rhs"
+    return RefDesStringCompare( aPin1->GetNumber(), aPin2->GetNumber() ) < 0;
+}
+
 
 void EXPORT_HELP::sprintPinNetName( wxString* aResult,
                         const wxString& aNetNameFormat, NETLIST_OBJECT* aPin )
@@ -577,7 +582,7 @@ static XNODE* node( const wxString& aName, const wxString& aTextualContent = wxE
 
 XNODE* EXPORT_HELP::makeGenericDesignHeader()
 {
-    XNODE*      xdesign = node( wxT("design") );
+    XNODE*  xdesign = node( wxT("design") );
     char        date[128];
 
     DateAndTime( date );
@@ -616,17 +621,17 @@ XNODE* EXPORT_HELP::makeGenericDesignHeader()
 }
 
 
-XNODE*     EXPORT_HELP::makeGenericLibraries()
+XNODE* EXPORT_HELP::makeGenericLibraries()
 {
-    XNODE*      xlibs = node( wxT( "libraries" ) );     // auto_ptr
+    XNODE*  xlibs = node( wxT( "libraries" ) );     // auto_ptr
 
     for( std::set<void*>::iterator it = m_Libraries.begin(); it!=m_Libraries.end();  ++it )
     {
         CMP_LIBRARY*    lib = (CMP_LIBRARY*) *it;
-        XNODE*          xlibrary;
+        XNODE*      xlibrary;
 
         xlibs->AddChild( xlibrary = node( wxT( "library" ) ) );
-        xlibrary->AddProperty( wxT( "logical" ), lib->GetLogicalName() );
+        xlibrary->AddAttribute( wxT( "logical" ), lib->GetLogicalName() );
         xlibrary->AddChild( node( wxT( "uri" ),  lib->GetFullFileName() ) );
 
         // @todo: add more fun stuff here
@@ -636,7 +641,7 @@ XNODE*     EXPORT_HELP::makeGenericLibraries()
 }
 
 
-XNODE*     EXPORT_HELP::makeGenericLibParts()
+XNODE* EXPORT_HELP::makeGenericLibParts()
 {
     XNODE*      xlibparts = node( wxT( "libparts" ) );   // auto_ptr
     wxString    sLibpart  = wxT( "libpart" );
@@ -651,7 +656,6 @@ XNODE*     EXPORT_HELP::makeGenericLibParts()
     wxString    sDescr    = wxT( "description" );
     wxString    sDocs     = wxT( "docs" );
 
-
     LIB_PIN_LIST    pinList;
     LIB_FIELD_LIST  fieldList;
 
@@ -666,8 +670,8 @@ XNODE*     EXPORT_HELP::makeGenericLibParts()
 
         XNODE*      xlibpart;
         xlibparts->AddChild( xlibpart = node( sLibpart ) );
-        xlibpart->AddProperty( sLib, library->GetLogicalName() );
-        xlibpart->AddProperty( sPart, lcomp->GetName()  );
+        xlibpart->AddAttribute( sLib, library->GetLogicalName() );
+        xlibpart->AddAttribute( sPart, lcomp->GetName()  );
 
         //----- show the important properties -------------------------
         if( !lcomp->GetDescription().IsEmpty() )
@@ -693,7 +697,7 @@ XNODE*     EXPORT_HELP::makeGenericLibParts()
             {
                 XNODE*     xfield;
                 xfields->AddChild( xfield = node( sField, fieldList[i].m_Text ) );
-                xfield->AddProperty( sName, fieldList[i].m_Name );
+                xfield->AddAttribute( sName, fieldList[i].m_Name );
             }
         }
 
@@ -701,7 +705,7 @@ XNODE*     EXPORT_HELP::makeGenericLibParts()
         pinList.clear();
         lcomp->GetPins( pinList, 0, 0 );
 
-        // sort the pin list here?
+        sort( pinList.begin(), pinList.end(), sortPinsByNumber );
 
         if( pinList.size() )
         {
@@ -713,7 +717,7 @@ XNODE*     EXPORT_HELP::makeGenericLibParts()
                 XNODE*     pin;
 
                 pins->AddChild( pin = node( sPin ) );
-                pin->AddProperty( sNum, pinList[i]->GetNumber() );
+                pin->AddAttribute( sNum, pinList[i]->GetNumber() );
 
                 // caution: construction work site here, drive slowly
             }
@@ -724,7 +728,7 @@ XNODE*     EXPORT_HELP::makeGenericLibParts()
 }
 
 
-XNODE*     EXPORT_HELP::makeGenericListOfNets()
+XNODE* EXPORT_HELP::makeGenericListOfNets()
 {
     XNODE*      xnets = node( wxT( "nets" ) );      // auto_ptr if exceptions ever get used.
     wxString    netCodeTxt;
@@ -796,25 +800,25 @@ XNODE*     EXPORT_HELP::makeGenericListOfNets()
         {
             xnets->AddChild( xnet = node( sNet ) );
             netCodeTxt.Printf( sFmtd, netCode );
-            xnet->AddProperty( sCode, netCodeTxt );
-            xnet->AddProperty( sName, netName );
+            xnet->AddAttribute( sCode, netCodeTxt );
+            xnet->AddAttribute( sName, netName );
         }
 
         XNODE*      xnode;
         xnet->AddChild( xnode = node( sNode ) );
-        xnode->AddProperty( sRef, ref );
-        xnode->AddProperty( sPin,  nitem->GetPinNumText() );
+        xnode->AddAttribute( sRef, ref );
+        xnode->AddAttribute( sPin,  nitem->GetPinNumText() );
     }
 
     return xnets;
 }
 
 
-XNODE*     EXPORT_HELP::makeGenericRoot()
+XNODE* EXPORT_HELP::makeGenericRoot()
 {
     XNODE*      xroot = node( wxT( "export" ) );
 
-    xroot->AddProperty( wxT( "version" ), wxT( "D" ) );
+    xroot->AddAttribute( wxT( "version" ), wxT( "D" ) );
 
     // add the "design" header
     xroot->AddChild( makeGenericDesignHeader() );
@@ -832,11 +836,11 @@ XNODE*     EXPORT_HELP::makeGenericRoot()
 }
 
 
-XNODE*     EXPORT_HELP::makeGenericComponents()
+XNODE* EXPORT_HELP::makeGenericComponents()
 {
-    XNODE*          xcomps = node( wxT( "components" ) );
+    XNODE*      xcomps = node( wxT( "components" ) );
 
-    wxString        timeStamp;
+    wxString    timeStamp;
 
     // some strings we need many times, but don't want to construct more
     // than once for performance.  These are used within loops so the
@@ -844,25 +848,25 @@ XNODE*     EXPORT_HELP::makeGenericComponents()
     // they were in a nested scope.
 
     // these are actually constructor invocations, not assignments as it appears:
-    wxString  sFields     = wxT( "fields" );
-    wxString  sField      = wxT( "field" );
-    wxString  sComponent  = wxT( "comp" );          // use "part" ?
-    wxString  sName       = wxT( "name" );
-    wxString  sRef        = wxT( "ref" );
-    wxString  sPins       = wxT( "pins" );
-    wxString  sPin        = wxT( "pin" );
-    wxString  sValue      = wxT( "value" );
-    wxString  sSheetPath  = wxT( "sheetpath" );
-    wxString  sFootprint  = wxT( "footprint" );
-    wxString  sDatasheet  = wxT( "datasheet" );
-    wxString  sTStamp     = wxT( "tstamp" );
-    wxString  sTStamps    = wxT( "tstamps" );
-    wxString  sTSFmt      = wxT( "%8.8lX" );        // comp->m_TimeStamp
-    wxString  sLibSource  = wxT( "libsource" );
-    wxString  sLibPart    = wxT( "libpart" );
-    wxString  sLib        = wxT( "lib" );
-    wxString  sPart       = wxT( "part" );
-    wxString  sNames      = wxT( "names" );
+    wxString    sFields     = wxT( "fields" );
+    wxString    sField      = wxT( "field" );
+    wxString    sComponent  = wxT( "comp" );          // use "part" ?
+    wxString    sName       = wxT( "name" );
+    wxString    sRef        = wxT( "ref" );
+    wxString    sPins       = wxT( "pins" );
+    wxString    sPin        = wxT( "pin" );
+    wxString    sValue      = wxT( "value" );
+    wxString    sSheetPath  = wxT( "sheetpath" );
+    wxString    sFootprint  = wxT( "footprint" );
+    wxString    sDatasheet  = wxT( "datasheet" );
+    wxString    sTStamp     = wxT( "tstamp" );
+    wxString    sTStamps    = wxT( "tstamps" );
+    wxString    sTSFmt      = wxT( "%8.8lX" );        // comp->m_TimeStamp
+    wxString    sLibSource  = wxT( "libsource" );
+    wxString    sLibPart    = wxT( "libpart" );
+    wxString    sLib        = wxT( "lib" );
+    wxString    sPart       = wxT( "part" );
+    wxString    sNames      = wxT( "names" );
 
 
     m_ReferencesAlreadyFound.Clear();
@@ -882,7 +886,7 @@ XNODE*     EXPORT_HELP::makeGenericComponents()
 
             schItem = comp;
 
-            XNODE*     xcomp;  // current component being constructed
+            XNODE* xcomp;  // current component being constructed
 
             // Output the component's elments in order of expected access frequency.
             // This may not always look best, but it will allow faster execution
@@ -890,7 +894,7 @@ XNODE*     EXPORT_HELP::makeGenericComponents()
             // an element.
 
             xcomps->AddChild( xcomp = node( sComponent ) );
-            xcomp->AddProperty( sRef, comp->GetRef( path ) );
+            xcomp->AddAttribute( sRef, comp->GetRef( path ) );
 
             xcomp->AddChild( node( sValue, comp->GetField( VALUE )->m_Text ) );
 
@@ -905,7 +909,7 @@ XNODE*     EXPORT_HELP::makeGenericComponents()
             // container element if there are any <field>s.
             if( comp->GetFieldCount() > MANDATORY_FIELDS )
             {
-                XNODE*     xfields;
+                XNODE* xfields;
                 xcomp->AddChild( xfields = node( sFields ) );
 
                 for( int fldNdx = MANDATORY_FIELDS; fldNdx < comp->GetFieldCount(); ++fldNdx )
@@ -915,14 +919,14 @@ XNODE*     EXPORT_HELP::makeGenericComponents()
                     // only output a field if non empty
                     if( !f->m_Text.IsEmpty() )
                     {
-                        XNODE*      xfield;
+                        XNODE*  xfield;
                         xfields->AddChild( xfield = node( sField, f->m_Text ) );
-                        xfield->AddProperty( sName, f->m_Name );
+                        xfield->AddAttribute( sName, f->m_Name );
                     }
                 }
             }
 
-            XNODE*      xlibsource;
+            XNODE*  xlibsource;
             xcomp->AddChild( xlibsource = node( sLibSource ) );
 
             // "logical" library name, which is in anticipation of a better search
@@ -930,13 +934,13 @@ XNODE*     EXPORT_HELP::makeGenericComponents()
             // is merely the library name minus path and extension.
             LIB_COMPONENT* entry = CMP_LIBRARY::FindLibraryComponent( comp->m_ChipName );
             if( entry )
-                xlibsource->AddProperty( sLib, entry->GetLibrary()->GetLogicalName() );
-            xlibsource->AddProperty( sPart, comp->m_ChipName );
+                xlibsource->AddAttribute( sLib, entry->GetLibrary()->GetLogicalName() );
+            xlibsource->AddAttribute( sPart, comp->m_ChipName );
 
-            XNODE*     xsheetpath;
+            XNODE* xsheetpath;
             xcomp->AddChild( xsheetpath = node( sSheetPath ) );
-            xsheetpath->AddProperty( sNames, path->PathHumanReadable() );
-            xsheetpath->AddProperty( sTStamps, path->Path() );
+            xsheetpath->AddAttribute( sNames, path->PathHumanReadable() );
+            xsheetpath->AddAttribute( sTStamps, path->Path() );
 
             timeStamp.Printf( sTSFmt, comp->m_TimeStamp );
             xcomp->AddChild( node( sTStamp, timeStamp ) );
@@ -946,10 +950,45 @@ XNODE*     EXPORT_HELP::makeGenericComponents()
     return xcomps;
 }
 
+#include <wx/wfstream.h>        // wxFFileOutputStream
 
 bool EXPORT_HELP::WriteGENERICNetList( WinEDA_SchematicFrame* frame, const wxString& aOutFileName )
 {
-#if 1
+#if 0
+
+    // this code seems to work now, for S-expression support.
+
+    bool rc = false;
+    wxFFileOutputStream os( aOutFileName, wxT( "wt" ) );
+
+    if( !os.IsOk() )
+    {
+    L_error:
+        wxString msg = _( "Failed to create file " ) + aOutFileName;
+        DisplayError( frame, msg );
+    }
+    else
+    {
+        XNODE*  xroot = makeGenericRoot();
+
+        try
+        {
+            STREAM_OUTPUTFORMATTER  outputFormatter( os );
+            xroot->Format( &outputFormatter, 0 );
+        }
+        catch( IOError ioe )
+        {
+            delete xroot;
+            goto L_error;
+        }
+
+        delete xroot;
+        rc = true;
+    }
+
+    return rc;
+
+#elif 1
     // output the XML format netlist.
     wxXmlDocument   xdoc;
 
diff --git a/eeschema/netlist.keywords b/eeschema/netlist.keywords
new file mode 100644
index 0000000000..3067b27ea6
--- /dev/null
+++ b/eeschema/netlist.keywords
@@ -0,0 +1,35 @@
+code
+comp
+components
+datasheet
+date
+description
+design
+docs
+export
+field
+fields
+footprint
+lib
+libpart
+libraries
+library
+libsource
+name
+names
+net
+nets
+node
+num
+part
+pin
+pins
+ref
+sheetpath
+source
+tool
+tstamp
+tstamps
+uri
+value
+version
diff --git a/include/richio.h b/include/richio.h
index 56d73fa34b..345f740f96 100644
--- a/include/richio.h
+++ b/include/richio.h
@@ -148,14 +148,14 @@ public:
     /**
      * Function Rewind
      * a wrapper to the standard function rewind.
-     * also clear the current line number 
+     * also clear the current line number
      */
     void Rewind()
     {
         rewind( fp );
         lineNum = 0;
     }
-    
+
 };
 
 
@@ -201,17 +201,43 @@ public:
 
 /**
  * Class OUTPUTFORMATTER
- * is an interface (abstract class) used to output ASCII text in a convenient
- * way.  The primary interface is printf() like but with support for indentation
+ * is an important interface (abstract) class used to output UTF8 text in a convenient
+ * way. The primary interface is "printf() - like" but with support for indentation
  * control.  The destination of the 8 bit wide text is up to the implementer.
+ * <p>
+ * The implementer only has to implement the write() function, but can also optionaly
+ * re-implement GetQuoteChar().
+ * <p>
  * If you want to output a wxString, then use CONV_TO_UTF8() on it before passing
  * it as an argument to Print().
  * <p>
  * Since this is an abstract interface, only classes derived from this one
- * will be the implementations.
+ * may actually be used.
  */
 class OUTPUTFORMATTER
 {
+    std::vector<char>       buffer;
+
+    int sprint( const char* fmt, ... )  throw( IOError );
+    int vprint( const char* fmt,  va_list ap )  throw( IOError );
+
+
+protected:
+    OUTPUTFORMATTER( int aReserve = 300 ) :
+            buffer( aReserve, '\0' )
+    {
+    }
+
+
+    /**
+     * Function write
+     * should be coded in the interface implementation (derived) classes.
+     *
+     * @param aOutBuf is the start of a byte buffer to write.
+     * @param aCount  tells how many bytes to write.
+     * @throw IOError, if there is a problem outputting, such as a full disk.
+     */
+    virtual void write( const char* aOutBuf, int aCount ) throw( IOError ) = 0;
 
 #if defined(__GNUG__)   // The GNU C++ compiler defines this
 
@@ -238,7 +264,7 @@ public:
      * @return int - the number of characters output.
      * @throw IOError, if there is a problem outputting, such as a full disk.
      */
-    virtual int PRINTF_FUNC Print( int nestLevel, const char* fmt, ... ) throw( IOError ) = 0;
+    int PRINTF_FUNC Print( int nestLevel, const char* fmt, ... ) throw( IOError );
 
     /**
      * Function GetQuoteChar
@@ -256,7 +282,10 @@ public:
      * @return const char* - the quote_char as a single character string, or ""
      *   if the wrapee does not need to be wrapped.
      */
-    virtual const char* GetQuoteChar( const char* wrapee ) = 0;
+    virtual const char* GetQuoteChar( const char* wrapee )
+    {
+        return GetQuoteChar( wrapee, "\"" );
+    }
 
     virtual ~OUTPUTFORMATTER() {}
 
@@ -283,12 +312,8 @@ public:
 */
 class STRINGFORMATTER : public OUTPUTFORMATTER
 {
-    std::vector<char>       buffer;
     std::string             mystring;
 
-    int sprint( const char* fmt, ... );
-    int vprint( const char* fmt,  va_list ap );
-
 public:
 
     /**
@@ -296,11 +321,10 @@ public:
      * reserves space in the buffer
      */
     STRINGFORMATTER( int aReserve = 300 ) :
-        buffer( aReserve, '\0' )
+        OUTPUTFORMATTER( aReserve )
     {
     }
 
-
     /**
      * Function Clear
      * clears the buffer and empties the internal string.
@@ -316,16 +340,47 @@ public:
      */
     void StripUseless();
 
-
     std::string GetString()
     {
         return mystring;
     }
 
+    //-----<OUTPUTFORMATTER>------------------------------------------------
+protected:
+    void write( const char* aOutBuf, int aCount ) throw( IOError );
+    //-----</OUTPUTFORMATTER>-----------------------------------------------
+};
+
+
+/**
+ * Class STREAM_OUTPUTFORMATTER
+ * implements OUTPUTFORMATTER to a wxWidgets wxOutputStream.  The stream is
+ * neither opened nor closed by this class.
+ */
+class STREAM_OUTPUTFORMATTER : public OUTPUTFORMATTER
+{
+    wxOutputStream& os;
+    char            quoteChar[2];
+
+public:
+
+    /**
+     * Constructor STREAM_OUTPUTFORMATTER
+     * can take any number of wxOutputStream derivations, so it can write
+     * to a file, socket, or zip file.
+     */
+    STREAM_OUTPUTFORMATTER( wxOutputStream& aStream, char aQuoteChar = '"' ) :
+        os( aStream )
+    {
+        quoteChar[0] = aQuoteChar;
+        quoteChar[1] = 0;
+    }
 
     //-----<OUTPUTFORMATTER>------------------------------------------------
-    int PRINTF_FUNC Print( int nestLevel, const char* fmt, ... ) throw( IOError );
     const char* GetQuoteChar( const char* wrapee );
+
+protected:
+    void write( const char* aOutBuf, int aCount ) throw( IOError );
     //-----</OUTPUTFORMATTER>-----------------------------------------------
 };
 
diff --git a/include/xnode.h b/include/xnode.h
index b77a71fae3..b93d00b58e 100644
--- a/include/xnode.h
+++ b/include/xnode.h
@@ -25,55 +25,10 @@
  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
  */
 
-
 #include "richio.h"
-
+// #define WXUSINGDLL
 #include <wx/xml/xml.h>
 
-// These are classes for eXporting document trees, and thus have names
-// starting with X.  They can export either in XML or S-expression format.
-
-
-/**
- * Class XATTR
- * holds an XML or S-expression attribute/child value.  It is used for eXporting
- * a document tree in EITHER XML or S-expression.
- */
-class XATTR : public wxXmlProperty      // use wxXmlAttribute for wx >= 2.9
-{
-public:
-    XATTR() :
-        wxXmlProperty()
-    {
-    }
-
-    XATTR( const wxString& aName, const wxString& aValue ) :
-        wxXmlProperty( aName, aValue )
-    {
-    }
-
-
-    /**
-     * Function Format
-     * writes this object as UTF8 out to an OUTPUTFORMATTER as an S-expression
-     * @param out The formatter to write to.
-     * @param nestLevel A multiple of the number of spaces to preceed the output with.
-     * @throw IOError if a system error writing the output, such as a full disk.
-     */
-    virtual void Format( OUTPUTFORMATTER* out, int nestLevel ) throw( IOError );
-
-
-    /**
-     * Function FormatContents
-     * writes the contents of object as UTF8 out to an OUTPUTFORMATTER as an S-expression
-     * This is the same as Format() except that the outer wrapper is not included.
-     * @param out The formatter to write to.
-     * @param nestLevel A multiple of the number of spaces to preceed the output with.
-     * @throw IOError if a system error writing the output, such as a full disk.
-     */
-    virtual void FormatContents( OUTPUTFORMATTER* out, int nestLevel ) throw( IOError );
-};
-
 
 /**
  * Class XNODE
@@ -88,13 +43,11 @@ public:
     {
     }
 
-
     XNODE( wxXmlNodeType aType, const wxString& aName, const wxString& aContent = wxEmptyString ) :
         wxXmlNode( NULL, aType, aName, aContent )
     {
     }
 
-
     /**
      * Function Format
      * writes this object as UTF8 out to an OUTPUTFORMATTER as an S-expression
@@ -104,7 +57,6 @@ public:
      */
     virtual void Format( OUTPUTFORMATTER* out, int nestLevel ) throw( IOError );
 
-
     /**
      * Function FormatContents
      * writes the contents of object as UTF8 out to an OUTPUTFORMATTER as an S-expression
diff --git a/pcbnew/specctra.cpp b/pcbnew/specctra.cpp
index 13b52b618e..73644d8257 100644
--- a/pcbnew/specctra.cpp
+++ b/pcbnew/specctra.cpp
@@ -52,7 +52,9 @@
 
 #include "specctra.h"
 
-#include <wx/ffile.h>
+//#include <wx/ffile.h>
+#include <wx/wfstream.h>        // wxFFileOutputStream
+
 
 #include "build_version.h"
 
@@ -3850,81 +3852,42 @@ void SPECCTRA_DB::doSUPPLY_PIN( SUPPLY_PIN* growth ) throw( IOError )
 }
 
 
-int SPECCTRA_DB::Print( int nestLevel, const char* fmt, ... ) throw( IOError )
-{
-    va_list     args;
-
-    va_start( args, fmt );
-
-    int result = 0;
-    int total  = 0;
-
-    for( int i=0; i<nestLevel;  ++i )
-    {
-        result = fprintf( fp, "%*c", NESTWIDTH, ' ' );
-        if( result < 0 )
-            break;
-
-        total += result;
-    }
-
-    if( result<0 || (result=vfprintf( fp, fmt, args ))<0 )
-    {
-        ThrowIOError( _("System file error writing to file \"%s\""), GetChars(filename) );
-    }
-
-    va_end( args );
-
-    total += result;
-    return total;
-}
-
-
-const char* SPECCTRA_DB::GetQuoteChar( const char* wrapee )
-{
-    return OUTPUTFORMATTER::GetQuoteChar( wrapee, quote_char.c_str() );
-}
-
-
 void SPECCTRA_DB::ExportPCB( wxString filename, bool aNameChange ) throw( IOError )
 {
-    fp = wxFopen( filename, wxT("w") );
-
-    if( !fp )
-    {
-        ThrowIOError( _("Unable to open file \"%s\""), GetChars(filename) );
-    }
-
     if( pcb )
     {
+        wxFFileOutputStream os( filename, wxT( "wt" ) );
+
+        if( !os.IsOk() )
+        {
+            ThrowIOError( _("Unable to open file \"%s\""), GetChars(filename) );
+        }
+
+        STREAM_OUTPUTFORMATTER  outputFormatter( os, quote_char[0] );
+
         if( aNameChange )
             pcb->pcbname = CONV_TO_UTF8(filename);
 
-        pcb->Format( this, 0 );
+        pcb->Format( &outputFormatter, 0 );
     }
-
-    // if an exception is thrown by Format, then ~SPECCTRA_DB() will close
-    // the file.
-
-    fclose( fp );
-    fp = 0;
 }
 
 
 void SPECCTRA_DB::ExportSESSION( wxString filename )
 {
-    fp = wxFopen( filename, wxT("w") );
-
-    if( !fp )
-    {
-        ThrowIOError( _("Unable to open file \"%s\""), GetChars(filename) );
-    }
-
     if( session )
-        session->Format( this, 0 );
+    {
+        wxFFileOutputStream os( filename, wxT( "wt" ) );
 
-    fclose( fp );
-    fp = 0;
+        if( !os.IsOk() )
+        {
+            ThrowIOError( _("Unable to open file \"%s\""), GetChars(filename) );
+        }
+
+        STREAM_OUTPUTFORMATTER  outputFormatter( os, quote_char[0] );
+
+        session->Format( &outputFormatter, 0 );
+    }
 }
 
 
diff --git a/pcbnew/specctra.h b/pcbnew/specctra.h
index 4b27d03988..8d7ddcf6bd 100644
--- a/pcbnew/specctra.h
+++ b/pcbnew/specctra.h
@@ -3970,7 +3970,7 @@ typedef boost::ptr_set<PADSTACK>    PADSTACKSET;
  * Class SPECCTRA_DB
  * holds a DSN data tree, usually coming from a DSN file.
  */
-class SPECCTRA_DB : public OUTPUTFORMATTER
+class SPECCTRA_DB
 {
     /// specctra DSN keywords
     static const KEYWORD keywords[];
@@ -3979,13 +3979,8 @@ class SPECCTRA_DB : public OUTPUTFORMATTER
     DSNLEXER*       lexer;
 
     PCB*            pcb;
-
     SESSION*        session;
-
-    FILE*           fp;
-
     wxString        filename;
-
     std::string     quote_char;
 
     bool            modulesAreFlipped;
@@ -4304,7 +4299,6 @@ public:
         lexer = 0;
         pcb   = 0;
         session = 0;
-        fp    = 0;
         quote_char += '"';
         modulesAreFlipped = false;
     }
@@ -4316,18 +4310,8 @@ public:
         delete session;
 
         deleteNETs();
-
-        if( fp )
-            fclose( fp );
     }
 
-
-    //-----<OUTPUTFORMATTER>-------------------------------------------------
-    int PRINTF_FUNC Print( int nestLevel, const char* fmt, ... ) throw( IOError );
-
-    const char* GetQuoteChar( const char* wrapee );
-    //-----</OUTPUTFORMATTER>------------------------------------------------
-
     static const char* TokenName( int aToken );
 
 
@@ -4354,11 +4338,6 @@ public:
     }
     PCB*  GetPCB()  { return pcb; }
 
-    void SetFILE( FILE* aFile )
-    {
-        fp = aFile;
-    }
-
     /**
      * Function SetSESSION
      * deletes any existing SESSION and replaces it with the given one.