qucslib.h
-----------
begin : Thur Jan 30 2014
copyright : (C) 2014 by Richard Crozier
email : richard DOT crozier AT yahoo DOT co DOT uk
***************************************************************************/
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef _QUCSLIB_COMMON_H_
#define _QUCSLIB_COMMON_H_
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <QDebug>
#include "../qucs/extsimkernels/spicecompat.h"
enum LIB_PARSE_RESULT { QUCS_COMP_LIB_OK,
QUCS_COMP_LIB_IO_ERROR,
QUCS_COMP_LIB_CORRUPT,
QUCS_COMP_LIB_EMPTY };
enum LIB_PARSE_WHAT { QUCS_COMP_LIB_HEADER_ONLY,
QUCS_COMP_LIB_FULL };
struct ComponentLibraryItem
{
QString name;
QString definition;
QString symbol;
QString modelString;
} ;
struct ComponentLibrary
{
QString name;
QString defaultSymbol;
QList<ComponentLibraryItem> components;
} ;
inline QString getLibAbsPath(QString libPath)
{
QDir libdir(QucsSettings.LibDir);
QString libAbsPath = libdir.absoluteFilePath(libPath + ".lib");
return libAbsPath;
}
inline bool getSection(QString section, QString &list, QString &content)
{
int Start, End;
Start = list.indexOf("<"+section+">");
content = "";
if(Start > 0)
{
Start += section.length()+2;
End = list.indexOf("</"+section+">", Start);
if(End < 0)
{
return false;
}
content = list.mid(Start, End-Start);
content.replace(QRegularExpression("\\n\\x20+"), "\n").remove(0, 1);
}
return true;
}
inline bool getCompLineIntegers(const QString& s,
int *i1=0, int *i2=0, int *i3=0, int *i4=0, int *i5=0, int *i6=0)
{
bool ok;
QString n;
if(!i1) return true;
n = s.section(' ',1,1);
*i1 = n.toInt(&ok);
if(!ok) return false;
if(!i2) return true;
n = s.section(' ',2,2);
*i2 = n.toInt(&ok);
if(!ok) return false;
if(!i3) return true;
n = s.section(' ',3,3);
*i3 = n.toInt(&ok);
if(!ok) return false;
if(!i4) return true;
n = s.section(' ',4,4);
*i4 = n.toInt(&ok);
if(!ok) return false;
if(!i5) return true;
n = s.section(' ',5,5);
*i5 = n.toInt(&ok);
if(!ok) return false;
if(!i6) return true;
n = s.section(' ',6,6);
*i6 = n.toInt(&ok);
if(!ok) return false;
return true;
}
inline int makeModelString (QString libPath, QString compname, QString compstring, QString &modelstring, QString default_sym)
{
if (!getSection("Model", compstring, modelstring))
{
return QUCS_COMP_LIB_CORRUPT;
}
if(!modelstring.isEmpty())
{
if(modelstring.count('\n') < 2)
{
modelstring = modelstring.remove('\n');
return QUCS_COMP_LIB_OK;
}
}
QString symbolSection;
if (!getSection("Symbol", compstring, symbolSection))
{
return QUCS_COMP_LIB_CORRUPT;
}
if(symbolSection.isEmpty())
{
if (default_sym.isEmpty())
{
return QUCS_COMP_LIB_CORRUPT;
}
symbolSection = default_sym;
}
QStringList symbolstringLines = symbolSection.split ("\n");
QString Prefix = "";
int Text_x = 0;
int Text_y = 0;
for (int i = 0; i < symbolstringLines.count (); i++)
{
symbolstringLines[i] = symbolstringLines[i].trimmed ();
if(symbolstringLines[i].isEmpty()) continue;
if(symbolstringLines[i].at(0) != '<')
{
return QUCS_COMP_LIB_CORRUPT;
}
if(symbolstringLines[i].at(symbolstringLines[i].length()-1) != '>')
{
return QUCS_COMP_LIB_CORRUPT;
}
symbolstringLines[i] = symbolstringLines[i].mid(1, symbolstringLines[i].length()-2);
QString s = symbolstringLines[i].section(' ',0,0);
if(s == ".ID")
{
if (!getCompLineIntegers(symbolstringLines[i], &Text_x, &Text_y)) return QUCS_COMP_LIB_OK;
Prefix = symbolstringLines[i].section(' ',3,3);
if(Prefix.isEmpty())
{
Prefix = "X";
}
break;
}
}
modelstring = "<Lib " + Prefix + " 1 0 0 " +
QString::number(Text_x) + " " +
QString::number(Text_y) + " 0 0 \"" +
libPath + "\" 0 \"" + compname + "\" 0>";
return QUCS_COMP_LIB_OK;
}
inline int parseQucsComponentLibrary (QString libPath, ComponentLibrary &library,
LIB_PARSE_WHAT what = QUCS_COMP_LIB_FULL, bool relpath = false)
{
int Start, End, NameStart, NameEnd;
QString filename = getLibAbsPath(libPath);
QFileInfo inf(filename);
QString relName = inf.baseName();
QFile file (filename);
if(!file.open(QIODevice::ReadOnly))
{
return QUCS_COMP_LIB_IO_ERROR;
}
QTextStream ReadWhole(&file);
QString LibraryString = ReadWhole.readAll();
file.close();
LibraryString.replace(QRegularExpression("\\r\\n"), "\n");
Start = LibraryString.indexOf("<Qucs Library ");
if(Start < 0)
{
return QUCS_COMP_LIB_CORRUPT;
}
End = LibraryString.indexOf('>', Start);
if(End < 0)
{
return QUCS_COMP_LIB_CORRUPT;
}
library.name = LibraryString.mid(Start, End-Start).section('"', 1, 1);
Start = LibraryString.indexOf("\n<", End);
if(Start < 0)
{
return QUCS_COMP_LIB_EMPTY;
}
if(LibraryString.mid(Start+2, 14) == "DefaultSymbol>")
{
End = LibraryString.indexOf("\n</DefaultSymbol>");
if(End < 0)
{
return QUCS_COMP_LIB_CORRUPT;
}
library.defaultSymbol = LibraryString.mid(Start+16, End-Start-16);
Start = End + 3;
}
if (what == QUCS_COMP_LIB_HEADER_ONLY)
{
return QUCS_COMP_LIB_OK;
}
while((Start=LibraryString.indexOf("\n<Component ", Start)) > 0)
{
Start++;
NameStart = Start + 11;
NameEnd = LibraryString.indexOf('>', NameStart);
if(NameEnd < 0) continue;
End = LibraryString.indexOf("\n</Component>", NameEnd);
if(End < 0) continue;
End += 13;
ComponentLibraryItem component;
component.name = LibraryString.mid(NameStart, NameEnd-NameStart);
component.definition = LibraryString.mid(Start, End-Start);
QString libName = libPath;
if (relpath) {
libName = relName;
}
int result = makeModelString (libName, component.name, component.definition, component.modelString, library.defaultSymbol);
if (result != QUCS_COMP_LIB_OK) return result;
library.components.append (component);
Start = End;
}
return QUCS_COMP_LIB_OK;
}
* \brief parseSPICEComponentLibrary Parse SPICE component library as set of subcircuits.
* \param filename[in]
* \param library[out]
* \return
*/
inline int parseSPICEComponentLibrary (QString libPath, ComponentLibrary &library)
{
QString filename = getLibAbsPath(libPath);
QFile file (filename);
if(!file.open(QIODevice::ReadOnly))
{
return QUCS_COMP_LIB_IO_ERROR;
}
QTextStream ReadWhole(&file);
QString LibraryString = ReadWhole.readAll();
file.close();
QFileInfo inf(filename);
library.name = inf.baseName();
library.defaultSymbol = "";
QString defsym_filename = inf.canonicalPath() + QDir::separator()
+ inf.baseName() + QDir::separator() + library.name + ".sym";
QFile defsym_file(defsym_filename);
if (defsym_file.open(QIODevice::ReadOnly)) {
QTextStream ts(&defsym_file);
library.defaultSymbol = ts.readAll();
defsym_file.close();
} else {
library.defaultSymbol = "<Symbol>\n"
"<Line -40 20 80 0 #000080 2 1>\n"
"<Line -40 -20 80 0 #000080 2 1>\n"
"<Line -40 20 0 -40 #000080 2 1>\n"
"<Line 40 20 0 -40 #000080 2 1>\n"
"<Text -30 -10 14 #ff0000 0 \"SPICE\">\n"
"<Line -50 -10 10 0 #000000 2 1>\n"
"<Line -50 10 10 0 #000000 2 1>\n"
"<Line -30 -20 0 -10 #000000 2 1>\n"
"<Line -10 -20 0 -10 #000000 2 1>\n"
"<Line 10 -20 0 -10 #000000 2 1>\n"
"<Line 30 -20 0 -10 #000000 2 1>\n"
"<Line 50 -10 -10 0 #000000 2 1>\n"
"<Line 50 10 -10 0 #000000 2 1>\n"
"<Line 30 20 0 10 #000000 2 1>\n"
"<.ID -40 39 SUB>\n"
"<Line -30 20 0 10 #000000 2 1>\n"
"<Line -10 20 0 10 #000000 2 1>\n"
"<Line 10 20 0 10 #000000 2 1>\n"
"</Symbol>\n";
}
QTextStream content(&LibraryString);
while(!content.atEnd()) {
QString lin = content.readLine();
lin = lin.trimmed();
if (lin.toLower().startsWith(".subckt ")) {
QString pars;
int idx = lin.indexOf('=');
if (idx>0) {
if (lin.at(idx-1).isSpace()) {
idx--;
while (lin.at(idx).isSpace()) idx--;
while (lin.at(idx).isLetterOrNumber()) idx--;
} else idx = lin.lastIndexOf(QRegularExpression("[ \t]"),idx);
pars = lin.mid(idx);
} else pars = "";
ComponentLibraryItem comp;
comp.name = lin.section(" ",1,1,QString::SectionSkipEmpty);
comp.modelString = QStringLiteral("<SpLib X1 1 280 260 -29 -164 0 0 \"%1\" 0 \"%2\" 1 \"auto\" 1 \"%3\" 1>")
.arg(filename).arg(comp.name).arg(pars);
comp.definition += QStringLiteral("<Component %1>\n").arg(comp.name);
comp.definition += "<Description>\n";
comp.definition += QStringLiteral("%1 device from %2 library").arg(comp.name).arg(library.name);
comp.definition += "</Description>\n";
comp.definition += "<Spice>\n";
comp.definition += lin + "\n.ends\n";
comp.definition += "</Spice>\n";
comp.definition += "<Model>";
comp.definition += "<"+comp.modelString;
comp.definition += "\n\n";
comp.definition += "</Model>\n";
QString sym_filename = inf.canonicalPath() + QDir::separator()
+ inf.baseName() + QDir::separator() + comp.name + ".sym";
QFile sym_file(sym_filename);
if (sym_file.open(QIODevice::ReadOnly)) {
QTextStream ts(&sym_file);
QString sym_content =ts.readAll();
comp.definition += sym_content;
comp.symbol += sym_content;
sym_file.close();
}
comp.definition += "</Component>\n";
library.components.append(comp);
} else if (lin.toLower().startsWith(".model")) {
QStringList mod_lines;
mod_lines.append(lin);
QString clin = content.readLine();
int pos = content.pos();
while (clin.startsWith('+')) {
pos = content.pos();
mod_lines.append(clin);
clin = content.readLine();
}
content.seek(pos);
ComponentLibraryItem comp;
comp.name = lin.section(" ",1,1,QString::SectionSkipEmpty);
QString m_str = QStringLiteral("M_%1_1").arg(comp.name);
comp.modelString = QStringLiteral("<SpiceModel %1 1 250 290 -29 17 0 0").arg(m_str);
int lin_cnt = 0;
QString visible = "1";
for (const auto &p: mod_lines) {
if (lin_cnt>3) comp.modelString += QStringLiteral(" \"Line_%1=%2\" %3")
.arg(lin_cnt+1).arg(p).arg(visible);
else comp.modelString += QStringLiteral(" \"%1\" %2").arg(p).arg(visible);
lin_cnt++;
visible = "0";
}
comp.modelString += ">";
comp.definition += QStringLiteral("<Component %1>\n").arg(comp.name);
comp.definition += "<Description>\n";
comp.definition += QStringLiteral("%1 model from %2 library\n"
"This component is model-only (.MODEL).\n"
"No subcircuit definition!\n"
"Use appropriate device to attach this model.").arg(comp.name).arg(library.name);
comp.definition += "</Description>\n";
comp.definition += "<Spice>\n";
comp.definition += mod_lines.join("\n");
comp.definition += "</Spice>\n";
comp.definition += "<Model>";
comp.definition += "<"+comp.modelString;
comp.definition += "\n\n";
comp.definition += "</Model>\n";
QString symstr = "<Symbol>\n"
"<Line -40 20 80 0 #000080 2 1>\n"
"<Line -40 -20 80 0 #000080 2 1>\n"
"<Line -40 20 0 -40 #000080 2 1>\n"
"<Line 40 20 0 -40 #000080 2 1>\n"
"<Text -35 -10 14 #ff0000 0 \".MODEL\">\n"
"<.ID -40 39 SUB>\n"
"</Symbol>\n";
comp.definition +=symstr;
comp.symbol += symstr;
comp.definition += "</Component>\n";
library.components.append(comp);
}
}
return QUCS_COMP_LIB_OK;
}
inline int parseComponentLibrary (QString filename, ComponentLibrary &library,
LIB_PARSE_WHAT what = QUCS_COMP_LIB_FULL, bool relpath = false)
{
int r = parseQucsComponentLibrary(filename,library,what,relpath);
if (r!=QUCS_COMP_LIB_OK) {
r = parseSPICEComponentLibrary(filename,library);
}
return r;
}
inline QStringList getBlacklistedLibraries(QString dir)
{
QString filename;
QStringList blacklisted_libs;
blacklisted_libs.clear();
switch (QucsSettings.DefaultSimulator) {
case spicecompat::simQucsator : filename = dir + QDir::separator()+ "qucs.blacklist";
break;
case spicecompat::simXyce: filename = dir + QDir::separator()+ "xyce.blacklist";
break;
case spicecompat::simNgspice:
case spicecompat::simSpiceOpus: filename = dir + QDir::separator() + "ngspice.blacklist";
break;
default:break;
}
QFile f_blist(filename);
if (!f_blist.open(QIODevice::ReadOnly)) return blacklisted_libs;
QTextStream ts(&f_blist);
while (!ts.atEnd()) {
QString lib = ts.readLine();
if (!lib.isEmpty()) blacklisted_libs.append(lib);
}
f_blist.close();
return blacklisted_libs;
}
#endif