#include "lob.h"
#include "lchange.h"
#include "limits.h"
#include <QRegExp>
#include <QUrl>
#include <QDebug>
#include <unistd.h>
#ifdef MEMORY_LEAK_TEST
#include "leakwatcher.h"
#endif
// Miscellany comes first, then class implementations below.
// comment out this line if you want to assume/require IDs, nicknames, and their references
// to be in the main tree, not in any attributes off that tree.
#define PROCESS_REFS_IN_ATTRIBUTES
QString nameOfOmType ( OmType type )
{
switch ( type )
{
case OmIntegerType: return QString( "Integer" );
case OmBigIntegerType: return QString( "BigInteger" );
case OmFloatType: return QString( "Float" );
case OmByteArrayType: return QString( "ByteArray" );
case OmVariableType: return QString( "Variable" );
case OmStringType: return QString( "String" );
case OmWStringType: return QString( "WString" );
case OmSymbolType: return QString( "Symbol" );
case OmPInstructionType: return QString( "PInstruction" );
case OmCommentType: return QString( "Comment" );
case OmApplicationType: return QString( "Application" );
case OmEndApplicationType: return QString( "EndApplication" );
case OmAttributeType: return QString( "Attribute" );
case OmEndAttributeType: return QString( "EndAttribute" );
case OmAttributeParameterType: return QString( "AttributeParameter" );
case OmEndAttributeParameterType: return QString( "EndAttributeParameter" );
case OmErrorType: return QString( "Error" );
case OmEndErrorType: return QString( "EndError" );
case OmObjectType: return QString( "Object" );
case OmEndObjectType: return QString( "EndObject" );
case OmBindingType: return QString( "Binding" );
case OmEndBindingType: return QString( "EndBinding" );
case OmBindingVariableType: return QString( "BindingVariable" );
case OmEndBindingVariableType: return QString( "EndBindingVariable" );
case OmUnknownType: return QString( "Unknown" );
default: return QString( "type????" );
}
}
#define HRDEBUG(lob,om,verb,context) qDebug() << "In" << context << ":" \
<< ( unsigned long int )(lob) << verb << ( unsigned long int )(om) << ":" \
<< ( om ? nameOfOmType( om->type() ) : QString( "[no type]" ) ) \
<< (lob)->toString().trimmed().left(20) << "from count" \
<< ( referenceCounts.available() ? referenceCounts.data()[om] : -999 );
#define HDEBUG(lob,om,con) HRDEBUG(lob,om,"HELD",con)
#define RDEBUG(lob,om,con) HRDEBUG(lob,om,"RELEASED",con)
#define CDEBUG(lob,om,con) qDebug() << "In" << con << ":" \
<< ( unsigned long int )(lob) << "HELD" << ( unsigned long int )(om) << ":" \
<< ( om ? nameOfOmType( om->type() ) : QString( "[no type]" ) ) \
<< "[no string repr yet; constructing]";
//#define HRDEBUG_ON
QString incrementID ( QString id, unsigned int index )
{
if ( index == 0 )
return incrementID( id, id.length() - 1 );
if ( index == 1 )
return "id1" + id.mid( 2 );
if ( id[index] == '9' ) {
id[index] = '0';
return incrementID( id, index-1 );
}
id[index] = (char)( id[index].toLatin1() + 1 );
return id;
}
bool lessThanID ( const QString& idA, const QString& idB )
{
if ( idA.length() < idB.length() )
return true;
if ( idA.length() > idB.length() )
return false;
for ( int i = 2 ; i < idA.length() ; i++ ) {
if ( idA[i] < idB[i] )
return true;
if ( idA[i] > idB[i] )
return false;
}
return false; // strict comparison
}
QString zeroID ()
{
return "id0";
}
QString escapeXML ( QString notSafeForXML )
{
QString goodStart = notSafeForXML.replace( "&", "&" ).replace( "<", "<" )
.replace( ">", ">" ).replace( "\"", """ ).replace( "'", "'" );
QString result;
for ( int i = 0 ; i < goodStart.length() ; i++ ) {
const QChar& c = goodStart[i];
if ( c.toLatin1() && ( c.cell() < 0x80 ) )
result += c;
else
result += QString( "&#x%1%2;" ).arg( QString::number( c.row(), 16 ), 2, '0' )
.arg( QString::number( c.cell(), 16 ), 2, '0' );
}
return result;
}
QString unescapeXML ( QString escaped )
{
QString result;
QRegExp re( "&#x([0-9a-fA-F]{2})([0-9a-fA-F]{2});" );
int i = re.indexIn( escaped );
int nextblock = 0;
while ( i != -1 ) {
result += escaped.mid( nextblock, i - nextblock );
int row = re.cap( 1 ).toInt( 0, 16 );
int cell = re.cap( 2 ).toInt( 0, 16 );
result += QChar( cell, row );
nextblock = i + 8;
i = re.indexIn( escaped, nextblock );
}
result += escaped.mid( nextblock );
return result.replace( ">", ">" ).replace( "<", "<" )
.replace( "'", "'" ).replace( """, "\"" ).replace( "&", "&" );
}
// Lob implementation first, other classes later
Lob::Lob ( OmNode* wrapThis, bool editable )
: QObject(), omobj( wrapThis ), editable( editable )
{
#ifdef HRDEBUG_ON
CDEBUG(this,omobj,"Lob(OmNode*)");
#endif
hold( omobj );
#ifdef MEMORY_LEAK_TEST
LeakWatcher<Lob>::created( this );
#endif
}
Lob::Lob ( const Lob& copyThis )
: QObject(), omobj( copyThis.omobj ), editable( copyThis.editable )
{
#ifdef HRDEBUG_ON
CDEBUG(this,omobj,"Lob(const Lob&)");
#endif
hold( omobj );
#ifdef MEMORY_LEAK_TEST
LeakWatcher<Lob>::created( this );
#endif
}
Lob::~Lob ()
{
#ifdef HRDEBUG_ON
RDEBUG(this,omobj,"~Lob()");
#endif
release( omobj );
#ifdef MEMORY_LEAK_TEST
LeakWatcher<Lob>::deleted( this );
#endif
if ( watchForModifications.available() )
watchForModifications.data().removeAll( this );
}
LobAddress address_aux ( OmNode* descendant, OmNode* ancestor )
{
if ( ( descendant == ancestor ) || ( descendant == NULL ) )
return LobAddress();
LobAddress result = address_aux( descendant->getOwner(), ancestor );
result.addStep( descendant->ownerIndex(), descendant->ownershipType() );
return result;
}
LobAddress Lob::address ( Lob inThis ) const
{
return address_aux( omobj, inThis.omobj );
}
Lob Lob::index ( LobAddress address, unsigned int offset )
{
if ( isEmpty() || ( offset == address.numSteps() ) )
return *this;
if ( address.stepType( offset ) == OmNode::AsChild )
return child( address.stepIndex( offset ) ).index( address, offset + 1 );
if ( address.stepIndex( offset ) >= omobj->countAttributes() )
return Lob();
OmSymbolNode* key;
OmNode* value;
omobj->consultAttribute( address.stepIndex( offset ), key, value );
return Lob( address.stepType( offset ) == OmNode::AsKey ? key : value )
.index( address, offset + 1 );
}
Lob Lob::fromXML ( QFile& in, QString* message )
{
if ( !in.exists() ) {
if ( message )
*message = "Could not find this file: " + in.fileName();
return Lob();
}
bool wasOpen = in.isOpen();
if ( !wasOpen && !in.open( QIODevice::ReadOnly ) ) {
if ( message )
*message = "Could not open " + in.fileName() + " for reading";
return Lob();
}
OmDocumentNode* result = new OmDocumentNode;
try {
OmInputFileStream inputStream( fdopen( dup( in.handle() ), "r" ) );
OmInputDevice inputDevice( inputStream, OmXmlEncoding, false );
result->load( inputDevice );
if ( message )
*message = QString();
} catch ( OmException& e ) {
if ( message )
*message = "When reading " + in.fileName() + ": " + e.translate();
delete result;
result = NULL;
} catch ( ... ) {
if ( message )
*message = "When reading " + in.fileName() + ": unknown error";
delete result;
result = NULL;
}
if ( !wasOpen )
in.close();
return Lob( result );
}
Lob Lob::fromXML ( QString xml )
{
OmDocumentNode* result = new OmDocumentNode;
try {
OmInputStringStream inputStream( xml.toLatin1().constData() );
OmInputDevice inputDevice( inputStream, OmXmlEncoding, false );
result->load( inputDevice );
/*
} catch ( OmException& e ) {
qDebug() << "in Lob::fromXML(): " << e.translate();
delete result;
result = NULL;
*/
} catch ( ... ) {
delete result;
result = NULL;
}
return Lob( result );
}
QString Lob::toString () const
{
if ( !omobj )
return QString();
OmOutputStringStream outputStream;
OmDocumentNode* tosave = dynamic_cast<OmDocumentNode*>( omobj );
QString result;
try {
OmOutputDevice outputDevice( outputStream, OmXmlEncoding, false );
if ( !tosave ) {
tosave = new OmDocumentNode;
tosave->append( omobj->clone() );
}
tosave->save( outputDevice );
const char* b = outputStream.getBuffer();
int len = outputStream.getBufferLength();
char* copy = new char[len + 1];
memcpy( copy, b, len );
copy[len] = '\0';
result = QString( copy );
} catch ( ... ) {
result = QString();
}
if ( tosave != omobj )
delete tosave;
return result;
}
bool Lob::save ( QFile& out, QString* message )
{
if ( isEmpty() ) {
if ( message )
*message = "Cannot save empty Lurch Object";
return false;
}
bool wasOpen = out.isOpen();
if ( !wasOpen && !out.open( QIODevice::WriteOnly ) ) {
if ( message )
*message = "Could not open " + out.fileName() + " for writing";
return false;
}
OmDocumentNode* tosave = dynamic_cast<OmDocumentNode*>( omobj );
bool result = true;
try {
OmOutputFileStream outputStream( fdopen( dup( out.handle() ), "w" ) );
OmOutputDevice outputDevice( outputStream, OmXmlEncoding, false );
if ( !tosave ) {
tosave = new OmDocumentNode;
tosave->append( omobj->clone() );
}
tosave->save( outputDevice );
} catch ( OmException& e ) {
if ( message )
*message = "When writing " + out.fileName() + ": " + e.translate();
result = false;
} catch ( ... ) {
if ( message )
*message = "When writing " + out.fileName() + ": unknown error";
result = false;
}
if ( !wasOpen )
out.close();
if ( tosave != omobj )
delete tosave;
return result;
}
bool Lob::isEmpty () const
{
return !omobj;
}
bool Lob::isEditable () const
{
return editable;
}
void Lob::setEditable ( bool on )
{
editable = on;
}
Lob Lob::copy ( bool changeIDs ) const
{
Lob result = Lob( omobj ? omobj->clone() : NULL );
if ( changeIDs )
result.changeIDs( *this );
return result;
}
bool operator== ( const Lob& a, const Lob& b )
{
return ( a.omobj == b.omobj );
}
bool operator!= ( const Lob& a, const Lob& b )
{
return ( a.omobj != b.omobj );
}
bool operator< ( const Lob& a, const Lob& b )
{
return ( a.omobj < b.omobj );
}
bool Lob::equivalentTo ( const Lob& other, bool compareAttributes,
QStringList exclusions ) const
{
return equivalent( omobj, other.omobj, compareAttributes, exclusions );
}
Lob& Lob::operator= ( const Lob& rhs )
{
#ifdef HRDEBUG_ON
RDEBUG(this,omobj,"operator=()");
#endif
release( omobj );
omobj = rhs.omobj;
editable = rhs.editable;
#ifdef HRDEBUG_ON
HDEBUG(this,omobj,"operator=()");
#endif
hold( omobj );
return *this;
}
OmType Lob::nodeType () const
{
return omobj ? omobj->type() : OmUnknownType;
}
int Lob::scriptNodeType () const
{
return ( int )nodeType();
}
unsigned int Lob::numChildren () const
{
return omobj ? omobj->count() : 0;
}
Lob Lob::child ( unsigned int index ) const
{
if ( index >= numChildren() )
return Lob();
OmNode::Iterator it = omobj->iterate();
for ( unsigned int i = 0 ; !it.done() && ( i < index ) ; i++ )
it.next();
return Lob( it.get(), editable );
}
Lob Lob::parent () const
{
if ( hasParent() )
return Lob( omobj->getOwner(), editable );
else
return Lob();
}
Lob Lob::container () const
{
if ( hasContainer() )
return Lob( omobj->getOwner(), editable );
else
return Lob();
}
bool Lob::hasParent () const
{
return omobj && ( omobj->ownershipType() == OmNode::AsChild )
&& (bool)( omobj->getOwner() );
}
bool Lob::hasContainer () const
{
return omobj && ( ( omobj->ownershipType() == OmNode::AsKey )
|| ( omobj->ownershipType() == OmNode::AsValue ) )
&& (bool)( omobj->getOwner() );
}
Lob Lob::nextSibling () const
{
if ( !hasNextSibling() )
return Lob();
Lob result = parent().child( omobj->ownerIndex() + 1 );
result.editable = editable;
return result;
}
bool Lob::hasNextSibling () const
{
return omobj && ( omobj->ownerIndex() + 1 < parent().numChildren() );
}
Lob Lob::previousSibling () const
{
if ( !hasPreviousSibling() )
return Lob();
Lob result = parent().child( omobj->ownerIndex() - 1 );
result.editable = editable;
return result;
}
bool Lob::hasPreviousSibling () const
{
return omobj && ( omobj->ownershipType() == OmNode::AsChild )
&& ( omobj->ownerIndex() > 0 );
}
bool Lob::hasAttribute ( QString symbolName, QString symbolCD ) const
{
return !attribute( symbolName, symbolCD ).isEmpty();
}
Lob Lob::attribute ( QString symbolName, QString symbolCD ) const
{
if ( !omobj )
return Lob();
OmSymbolNode* key;
OmNode* value;
for ( unsigned int i = 0 ; i < omobj->countAttributes() ; i++ ) {
omobj->consultAttribute( i, key, value );
if ( ( key->getName() == symbolName ) && ( key->getCD() == symbolCD ) )
return Lob( value, editable );
}
return Lob();
}
QList<Lob> Lob::attributeKeys () const
{
QList<Lob> result;
if ( isEmpty() )
return result;
for ( unsigned int i = 0 ; i < omobj->countAttributes() ; i++ ) {
OmSymbolNode* key;
OmNode* value;
omobj->consultAttribute( i, key, value );
result << Lob( new OmSymbolNode( key->getCD(), key->getName() ) );
}
return result;
}
QVariantList Lob::scriptAttributeKeys () const
{
QVariantList result;
foreach ( const Lob& L, attributeKeys() )
result << QVariant::fromValue<Lob>( L );
return result;
}
bool Lob::hasAttribute ( Lob symbolLob ) const
{
if ( symbolLob.nodeType() != OmSymbolType )
return false;
OmSymbolNode* sym = dynamic_cast<OmSymbolNode*>( symbolLob.omobj );
return hasAttribute( sym->getName(), sym->getCD() );
}
Lob Lob::attribute ( const Lob& symbolLob ) const
{
if ( symbolLob.nodeType() != OmSymbolType )
return Lob();
OmSymbolNode* sym = dynamic_cast<OmSymbolNode*>( symbolLob.omobj );
return attribute( sym->getName(), sym->getCD() );
}
void Lob::setAttribute ( Lob key, Lob value )
{
if ( key.nodeType() != OmSymbolType )
return;
OmSymbolNode* sym = dynamic_cast<OmSymbolNode*>( key.omobj );
return setAttribute( sym->getName(), sym->getCD(), value );
}
void Lob::removeAttribute ( Lob symbolLob )
{
if ( symbolLob.nodeType() != OmSymbolType )
return;
OmSymbolNode* sym = dynamic_cast<OmSymbolNode*>( symbolLob.omobj );
return removeAttribute( sym->getName(), sym->getCD() );
}
unsigned int Lob::numAttributes () const
{
return omobj ? omobj->countAttributes() : 0;
}
bool Lob::isBasicType () const
{
if ( !omobj )
return false;
OmType t = omobj->type();
return ( t == OmIntegerType ) || ( t == OmBigIntegerType )
|| ( t == OmFloatType ) || ( t == OmByteArrayType )
|| ( t == OmVariableType ) || ( t == OmStringType )
|| ( t == OmWStringType ) || ( t == OmSymbolType );
}
QVariant Lob::toVariant () const
{
if ( !isBasicType() )
return QVariant();
switch ( omobj->type() )
{
case OmIntegerType:
return dynamic_cast<OmIntegerNode*>( omobj )->getValue();
case OmBigIntegerType:
{
OmBigIntegerNode* bin = dynamic_cast<OmBigIntegerNode*>( omobj );
return QString( "%1%2" ).arg( ( bin->getSign() > 0 ) ? "" : "-" )
.arg( bin->getDigits() );
}
case OmFloatType:
return dynamic_cast<OmFloatNode*>( omobj )->getValue();
case OmByteArrayType:
{
OmByteArrayNode* ban = dynamic_cast<OmByteArrayNode*>( omobj );
const char* buffer;
unsigned int bufsize;
ban->getBuffer( buffer, bufsize );
return QByteArray( buffer, bufsize );
}
case OmVariableType:
return unescapeXML( QString( dynamic_cast<OmVariableNode*>( omobj )->getName() ) );
case OmStringType:
return unescapeXML(
QString( dynamic_cast<OmStringNode*>( omobj )->getBuffer() ) );
case OmWStringType:
return unescapeXML( QString::fromWCharArray(
dynamic_cast<OmWStringNode*>( omobj )->getBuffer() ) );
case OmSymbolType:
{
OmSymbolNode* sym = dynamic_cast<OmSymbolNode*>( omobj );
return QVariant( QStringList() << QString( sym->getName() )
<< QString( sym->getCD() ) );
}
default:
return QVariant(); // this should never happen; it's here for compiler happiness
}
}
Lob Lob::fromVariant ( QVariant v )
{
switch ( v.type() )
{
case QVariant::ByteArray:
{
QByteArray ba = v.toByteArray();
OmByteArrayNode* ban = new OmByteArrayNode;
ban->setBufferLength( ba.data(), ba.length() );
return Lob( ban );
}
case QVariant::Double:
return Lob( new OmFloatNode( v.toDouble() ) );
case QVariant::Int:
return Lob( new OmIntegerNode( v.toInt() ) );
case QVariant::LongLong:
{
qlonglong lli = v.toLongLong();
if ( ( lli <= (qlonglong)INT_MAX ) && ( (qlonglong)INT_MIN <= lli ) )
return Lob( new OmIntegerNode( (int)lli ) );
else
return Lob( new OmBigIntegerNode(
QString::number( abs( lli ) ).toLatin1().constData(),
( lli > 0 ) ? 1 : -1 ) );
}
case QVariant::String:
return Lob( new OmStringNode(
escapeXML( v.toString() ).toLatin1().constData() ) );
case QVariant::UInt:
{
unsigned int ui = v.toUInt();
if ( ui <= (unsigned int)INT_MAX )
return Lob( new OmIntegerNode( (int)ui ) );
else
return Lob( new OmBigIntegerNode(
QString::number( ui ).toLatin1().constData() ) );
}
case QVariant::ULongLong:
{
qulonglong ulli = v.toUInt();
if ( ulli <= (qulonglong)INT_MAX )
return Lob( new OmIntegerNode( (int)ulli ) );
else
return Lob( new OmBigIntegerNode(
QString::number( ulli ).toLatin1().constData() ) );
}
default:
return Lob();
}
}
QString Lob::getStringAttribute ( QString symbolName, QString symbolCD ) const
{
Lob tmp = attribute( symbolName, symbolCD );
if ( tmp.nodeType() != OmStringType )
return QString();
QString result = tmp.toVariant().toString();
return unescapeXML( result );
}
void Lob::setStringAttribute ( QString symbolName, QString symbolCD, QString value )
{
setAttribute( symbolName, symbolCD, Lob::fromVariant( escapeXML( value ) ) );
}
bool Lob::isDocument () const
{
// must be nonempty and have no parent
if ( isEmpty() || hasParent() )
return false;
// must be an application of document:LurchCore to zero or more things
if ( nodeType() != OmApplicationType )
return false;
if ( child().toVariant().toStringList()
!= ( QStringList() << "document" << "LurchCore" ) )
return false;
// must have (author,LurchCore) attribute containing nonempty string
QString author = getStringAttribute( "author", "LurchCore" );
if ( !author.length() )
return false;
// must have (title,LurchCore) attribute containing nonempty string
if ( !getStringAttribute( "title", "LurchCore" ).length() )
return false;
// must have (dependencies,LurchCore) attribute containing space-separated list of URNs
Lob tmp = attribute( "dependencies", "LurchCore" );
if ( tmp.nodeType() != OmStringType )
return false;
QString dependencies = tmp.toVariant().toString();
if ( dependencies.length() > 0 ) // otherwise split gives one-element array containing ""
foreach ( const QString& dependency, dependencies.split( ' ' ) )
if ( !isLurchURN( dependency ) )
return false;
// all passed, so return true
return true;
}
QString Lob::getAuthor () const
{
// rather than calling the expensive isDocument() function, I just make sure the author
// attribute is valid
return getStringAttribute( "author", "LurchCore" );
}
void Lob::setAuthor ( QString author )
{
setAttribute( "author", "LurchCore", Lob::fromVariant( author ) );
}
QString Lob::getTitle () const
{
// rather than calling the expensive isDocument() function, I just make sure the title
// attribute is valid
return getStringAttribute( "title", "LurchCore" );
}
void Lob::setTitle ( QString title )
{
setAttribute( "title", "LurchCore", Lob::fromVariant( title ) );
}
QString Lob::getEncodingLanguage () const
{
// rather than calling the expensive isDocument() function, I just make sure the language
// attribute is valid
return getStringAttribute( "language", "LurchCore" );
}
void Lob::setEncodingLanguage ( QString lang )
{
setAttribute( "language", "LurchCore", Lob::fromVariant( lang ) );
}
QString Lob::getVersion () const
{
// rather than calling the expensive isDocument() function, I just make sure the version
// attribute is valid
QString result = getStringAttribute( "version", "LurchCore" );
return QRegExp( "[A-Za-z0-9.]+" ).exactMatch( result ) ? result : QString();
}
void Lob::setVersion ( QString version )
{
if ( !QRegExp( "[A-Za-z0-9.]+" ).exactMatch( version ) )
return;
setAttribute( "version", "LurchCore", Lob::fromVariant( version ) );
}
QStringList Lob::getDependencies () const
{
// rather than calling the expensive isDocument() function, I just make sure the
// dependencies attribute is valid
Lob tmp = attribute( "dependencies", "LurchCore" );
if ( tmp.nodeType() != OmStringType )
return QStringList();
QString dependencies = tmp.toVariant().toString();
if ( dependencies.length() == 0 )
return QStringList(); // otherwise split gives one-element array containing ""
QStringList splitUp = dependencies.split( ' ' );
foreach ( const QString& dependency, splitUp )
if ( !isLurchURN( dependency ) )
return QStringList();
return splitUp;
}
void Lob::setDependencies ( QStringList dependencies )
{
foreach ( const QString& dependency, dependencies )
if ( !isLurchURN( dependency ) )
return;
setAttribute( "dependencies", "LurchCore", Lob::fromVariant( dependencies.join( " " ) ) );
}
QString Lob::getURN () const
{
QString t = getTitle();
if ( !t.length() )
return QString();
QString a = getAuthor();
if ( !a.length() )
return QString();
return makeLurchURN( t, a, getEncodingLanguage(), getVersion() );
}
void Lob::setURN ( QString urn )
{
if ( !isLurchURN( urn ) )
return;
QStringList bits = splitLurchURN( urn );
setTitle( bits[0] );
setAuthor( bits[1] );
setEncodingLanguage( bits[2] );
setVersion( bits[3] );
}
bool Lob::isLurchURN ( QString testThis )
{
return splitLurchURN( testThis ).count() > 0;
}
QStringList Lob::splitLurchURN ( QString splitThis )
{
// it must start with the following initial segment
static const char* initialSegment = "urn:publicid:-:lurch.sourceforge.net:";
QRegExp identifierRE( "[a-zA-Z][A-Za-z0-9_]*" );
int pos = splitThis.indexOf( initialSegment );
QString ident;
if ( pos == -1 )
return QStringList();
if ( pos > 0 ) {
ident = splitThis.left( pos - 1 );
if ( ( splitThis.mid( pos - 1, 1 ) != "=" ) || !identifierRE.exactMatch( ident ) )
return QStringList();
}
// then comes a colon-separated sequence of three items
QStringList bits = splitThis.mid( pos + strlen( initialSegment ) ).split( ':' );
if ( bits.count() != 3 )
return QStringList();
// the first in that sequence is title+by+author
QStringList authorAndTitle = bits[0].split( "+by+" );
if ( authorAndTitle.count() != 2 )
return QStringList();
QRegExp authorTitleRE( "[A-Za-z0-9._~%-]+" );
if ( !authorTitleRE.exactMatch( authorAndTitle[0] ) )
return QStringList();
if ( !authorTitleRE.exactMatch( authorAndTitle[1] ) )
return QStringList();
// the next is language
if ( ( bits[1].count() > 0 ) && !QRegExp( "[A-Za-z]{2,}" ).exactMatch( bits[1] ) )
return QStringList();
// the last is version
if ( !QRegExp( "[A-Za-z0-9.]*" ).exactMatch( bits[2] ) )
return QStringList();
// all passed, so return the split-up parts
QStringList result;
result << QUrl::fromPercentEncoding( authorAndTitle[0].toLatin1() )
<< QUrl::fromPercentEncoding( authorAndTitle[1].toLatin1() ) << bits[1] << bits[2];
if ( !ident.isEmpty() )
result << ident;
return result;
}
QString Lob::makeLurchURN ( QString title, QString author, QString language, QString version,
QString identifier )
{
return ( identifier.isEmpty() ? QString() : ( identifier + "=" ) )
+ "urn:publicid:-:lurch.sourceforge.net:"
+ QString( QUrl::toPercentEncoding( title ) ) + "+by+"
+ QString( QUrl::toPercentEncoding( author ) ) + ":"
+ language + ":" + version;
}
QString Lob::basicLurchURN ( QString urn )
{
QStringList bits = splitLurchURN( urn );
return makeLurchURN( bits[0], bits[1], bits[2], bits[3] );
}
QString Lob::identifierOfURN ( QString urn )
{
QStringList bits = splitLurchURN( urn );
return ( bits.count() == 5 ) ? bits[4] : QString();
}
Lob Lob::newDocument ( QString urn, QStringList dependencies )
{
Lob result = Lob::fromXML( "<OMA><OMS cd='LurchCore' name='document'/></OMA>\n" )
.child().copy();
result.setURN( Lob::isLurchURN( urn ) ?
urn : Lob::makeLurchURN( "New Document", "unknown", "", "0" ) );
result.setDependencies( dependencies );
return result;
}
bool Lob::isScript () const
{
// must be a string node
if ( nodeType() != OmStringType )
return false;
// and must have a nonempty script language attribute
// (do not make call to scriptLanguage() here, because infinite recursion results)
return getStringAttribute( "script language", "LurchCore" ).length() > 0;
}
bool Lob::containsScript ( QString type ) const
{
if ( isScript() && ( type.isEmpty() || ( scriptType() == type ) ) )
return true;
foreach ( const Lob& key, attributeKeys() )
if ( attribute( key ).containsScript( type ) )
return true;
for ( unsigned int i = 0 ; i < numChildren() ; i++ )
if ( child( i ).containsScript( type ) )
return true;
return false;
}
QString Lob::scriptCode () const
{
return !isScript() ? QString() :
( scriptLanguage() == "CoffeeScript" ) ? ( "# CoffeeScript\n" + toVariant().toString() ) :
toVariant().toString();
}
void Lob::setScriptCode ( QString code )
{
if ( !isEditable() || ( nodeType() != OmStringType ) )
return;
Lob before = copy( false );
OmStringNode* str = dynamic_cast<OmStringNode*>( omobj );
str->setBuffer( escapeXML( code ).toLatin1().constData() );
//maybeEmitModified( before, *this );
maybeEmitModified( LobChange::newChange( before, *this ) );
}
QString Lob::scriptLanguage () const
{
return isScript() ? getStringAttribute( "script language", "LurchCore" ) : QString();
}
void Lob::setScriptLanguage ( QString language )
{
setAttribute( "script language", "LurchCore", Lob::fromVariant( language ) );
}
QString Lob::scriptType () const
{
return isScript() ? getStringAttribute( "script type", "LurchCore" ) : QString();
}
void Lob::setScriptType ( QString type )
{
setAttribute( "script type", "LurchCore", Lob::fromVariant( type ) );
}
bool Lob::isIDReference () const
{
// ensure it is of the right type and labeled as an ID reference
if ( nodeType() != OmStringType )
return false;
if ( getStringAttribute( "reference", "LurchCore" ) != "ID" )
return false;
return QRegExp( "id\\d+" ).exactMatch( toVariant().toString() );
}
bool Lob::isNicknameReference () const
{
// ensure it is of the right type and labeled as a nickname reference
if ( nodeType() != OmStringType )
return false;
if ( getStringAttribute( "reference", "LurchCore" ) != "nickname" )
return false;
return toVariant().toString().length() > 0;
}
QString Lob::getIDReference () const
{
return isIDReference() ? toVariant().toString() : QString();
}
void Lob::setIDReference ( QString id )
{
if ( !isEditable() || ( nodeType() != OmStringType ) )
return;
if ( !QRegExp( "id\\d+" ).exactMatch( id ) )
return;
Lob before = copy( false );
OmStringNode* str = dynamic_cast<OmStringNode*>( omobj );
str->setBuffer( escapeXML( id ).toLatin1().constData() );
//maybeEmitModified( before, *this );
maybeEmitModified( LobChange::newChange( before, *this ) );
}
QString Lob::getNicknameReference () const
{
return isNicknameReference() ? toVariant().toString() : QString();
}
void Lob::setNicknameReference ( QString nickname )
{
if ( !isEditable() || ( nodeType() != OmStringType ) )
return;
Lob before = copy( false );
OmStringNode* str = dynamic_cast<OmStringNode*>( omobj );
str->setBuffer( escapeXML( nickname ).toLatin1().constData() );
//maybeEmitModified( before, *this );
maybeEmitModified( LobChange::newChange( before, *this ) );
}
QStringList Lob::listAllLabels ( QString nicknameOrID ) const
{
QStringList result;
if ( isEmpty() )
return result;
QString thisOne = getStringAttribute( nicknameOrID, "LurchCore" );
if ( thisOne.length() > 0 )
result << thisOne;
#ifdef PROCESS_REFS_IN_ATTRIBUTES
for ( unsigned int i = 0 ; i < omobj->countAttributes() ; i++ ) {
OmSymbolNode* key;
OmNode* value;
omobj->consultAttribute( i, key, value );
result << Lob( value ).listAllLabels( nicknameOrID );
}
#endif
for ( OmNode::Iterator it = omobj->iterate() ; !it.done() ; it.next() )
result << Lob( it.get() ).listAllLabels( nicknameOrID );
return result;
}
bool Lob::checkUnique ( QString nicknameOrID ) const
{
QStringList allWithDuplicates = listAllLabels( nicknameOrID );
while ( allWithDuplicates.count() > 0 ) {
QString first = allWithDuplicates.takeFirst();
if ( allWithDuplicates.contains( first ) )
return false;
}
return true;
}
bool Lob::checkUniqueIDs () const
{
return checkUnique( "ID" );
}
bool Lob::checkUniqueNicknames () const
{
return checkUnique( "nickname" );
}
void Lob::buildIDConversion ( QString& nextID, Lob context,
QMap<QString,QString>& conversion )
{
if ( isEmpty() )
return;
QString oldID = getID();
if ( !oldID.isEmpty() ) {
QString newID;
// how to compute the new ID is based on whether it's a caching situation:
if ( nextID.isEmpty() ) {
// this is only true if it's caching, so we must inform/ask the context
newID = context.getNewID();
} else {
// otherwise, we've got to keep nextID counting upwards on our own
newID = nextID;
nextID = incrementID( nextID );
}
conversion[oldID] = newID;
setID( newID );
}
#ifdef PROCESS_REFS_IN_ATTRIBUTES
for ( unsigned int i = 0 ; i < omobj->countAttributes() ; i++ ) {
OmSymbolNode* key;
OmNode* value;
omobj->consultAttribute( i, key, value );
Lob( value ).buildIDConversion( nextID, context, conversion );
}
#endif
for ( OmNode::Iterator it = omobj->iterate() ; !it.done() ; it.next() )
Lob( it.get() ).buildIDConversion( nextID, context, conversion );
}
void Lob::convertIDReferences ( QMap<QString,QString> conversion )
{
if ( isEmpty() )
return;
if ( isIDReference() ) {
QString ref = getIDReference();
if ( conversion.contains( ref ) )
setIDReference( conversion[ref] );
}
#ifdef PROCESS_REFS_IN_ATTRIBUTES
for ( unsigned int i = 0 ; i < omobj->countAttributes() ; i++ ) {
OmSymbolNode* key;
OmNode* value;
omobj->consultAttribute( i, key, value );
Lob( value ).convertIDReferences( conversion );
}
#endif
for ( OmNode::Iterator it = omobj->iterate() ; !it.done() ; it.next() )
Lob( it.get() ).convertIDReferences( conversion );
}
void Lob::changeIDs ( Lob context )
{
if ( context.isEmpty() )
context = findIDContext();
QString nextID;
if ( !context.isIDContext() )
nextID = context.getNewID();
QMap<QString,QString> conversion;
buildIDConversion( nextID, context, conversion );
convertIDReferences( conversion );
}
void Lob::findReferencedIDs ( QStringList& result )
{
if ( isIDReference() )
result << getIDReference();
#ifdef PROCESS_REFS_IN_ATTRIBUTES
for ( unsigned int i = 0 ; i < omobj->countAttributes() ; i++ ) {
OmSymbolNode* key;
OmNode* value;
omobj->consultAttribute( i, key, value );
Lob( value ).findReferencedIDs( result );
}
#endif
for ( OmNode::Iterator it = omobj->iterate() ; !it.done() ; it.next() )
Lob( it.get() ).findReferencedIDs( result );
}
void Lob::removeUnreferencedIDs ( const QStringList referenced )
{
if ( !referenced.contains( getID() ) )
removeAttribute( "ID", "LurchCore" );
#ifdef PROCESS_REFS_IN_ATTRIBUTES
for ( unsigned int i = 0 ; i < omobj->countAttributes() ; i++ ) {
OmSymbolNode* key;
OmNode* value;
omobj->consultAttribute( i, key, value );
Lob( value ).removeUnreferencedIDs( referenced );
}
#endif
for ( OmNode::Iterator it = omobj->iterate() ; !it.done() ; it.next() )
Lob( it.get() ).removeUnreferencedIDs( referenced );
}
void Lob::removeExtraIDs ()
{
QStringList referenced;
findReferencedIDs( referenced );
removeUnreferencedIDs( referenced );
}
QString Lob::getNickname () const
{
return getStringAttribute( "nickname", "LurchCore" );
}
void Lob::setNickname ( QString nickname )
{
setAttribute( "nickname", "LurchCore", Lob::fromVariant( nickname ) );
}
void Lob::set ( OmNode* s )
{
if ( !isEditable() )
return;
// this ensures that s doesn't accidentally get deleted by the Lob Memory Management
// system during the course of this function, in case I'm ever not so careful with
// creating Lob( s ) inside a block that then dies at the end of the block.
Lob sLob( s );
// now ensure that the assignment makes sense; this involves checking three situations:
// 1) you can't change an attribute key to a non-symbol node
if ( omobj && ( omobj->ownershipType() == OmNode::AsKey )
&& ( s != NULL ) && ( s->type() != OmSymbolType ) )
return;
// 2) you can't make a null node or one of type OmUnknownType a child (or attribute)
// of anything
if ( omobj && ( omobj->ownershipType() != OmNode::None )
&& ( ( s == NULL ) || ( s->type() == OmUnknownType ) ) )
return;
// 3) no cyclicity is introduced in the parenting relationship (omobj's ownership chain
// does not include s)
for ( OmNode* walk = omobj ; walk != NULL ; walk = walk->getOwner() )
if ( walk == s )
return;
// now that we know it makes sense to act, begin by deparenting s if needed
if ( s && ( s->ownershipType() == OmNode::AsChild ) ) {
Lob ownerOwner( s->getOwner() ); // ensure owner stays owned by Lob mem mgt system
unsigned int index = s->ownerIndex();
s->getOwner()->extract( s->getOwner()->iteratorToIndex( index ), s );
ownerOwner.maybeEmitModified( LobChange::newRemove( sLob ), OmNode::AsChild, index );
} else if ( s && ( ( s->ownershipType() == OmNode::AsKey )
|| ( s->ownershipType() == OmNode::AsValue ) ) ) {
OmSymbolNode* key;
OmNode* value;
Lob ownerOwner( s->getOwner() ); // ensure owner stays owned by Lob mem mgt system
bool iskey = ( s->ownershipType() == OmNode::AsKey );
unsigned int index = s->ownerIndex();
s->getOwner()->extractAttribute( index, key, value );
// hand the other half of the pair over to the Lob memory management system:
Lob owner( iskey ? value : ( OmNode* )key );
ownerOwner.maybeEmitModified(
LobChange::newRemovePair( key->getName(), key->getCD(), sLob ),
OmNode::AsValue, index );
}
// for a moment now s has no owner, but that changes below
// now we have to deparent this, if needed, and place s there instead, if it's non-null:
if ( omobj && ( omobj->ownershipType() == OmNode::AsChild ) ) {
OmNode* o = omobj->getOwner();
Lob ownerOwner( o ); // ensure owner stays owned by Lob mem mgt system
OmNode::Iterator next = o->iteratorToIndex( omobj->ownerIndex() + 1 );
o->extract( o->iteratorToIndex( omobj->ownerIndex() ), omobj );
o->insert( next, s );
} else if ( omobj && ( ( omobj->ownershipType() == OmNode::AsKey )
|| ( omobj->ownershipType() == OmNode::AsValue ) ) ) {
OmSymbolNode* key;
OmNode* value;
OmNode* o = omobj->getOwner();
Lob ownerOwner( o ); // ensure owner stays owned by Lob mem mgt system
bool iskey = ( omobj->ownershipType() == OmNode::AsKey );
unsigned int index = omobj->ownerIndex();
o->extractAttribute( index, key, value );
if ( iskey ) {
o->insertAttribute( index, dynamic_cast<OmSymbolNode*>( s ), value );
Lob owner( key );
} else {
o->insertAttribute( index, key, s );
Lob owner( value );
}
}
// release ownership of omobj and take ownership of s
Lob before = copy( false );
*this = sLob;
maybeEmitModified( LobChange::newChange( before, sLob ) );
}
void Lob::set ( Lob s )
{
set( s.omobj );
}
void Lob::remove ()
{
if ( isEmpty() )
return;
if ( hasParent() ) {
Lob p = parent();
unsigned int index = omobj->ownerIndex();
p.omobj->extract( p.omobj->iteratorToIndex( index ), omobj );
p.maybeEmitModified( LobChange::newRemove( *this ), OmNode::AsChild, index );
} else if ( hasContainer() ) {
Lob c = container();
OmSymbolNode* key;
OmNode* value;
unsigned int index = omobj->ownerIndex();
c.omobj->extractAttribute( index, key, value );
c.maybeEmitModified( LobChange::newRemovePair( key->getName(), key->getCD(), *this ),
OmNode::AsValue, index );
// ensure the other half of the pair is cared for by the Lob memory management system
Lob owner( ( omobj == key ) ? value : key );
}
}
void Lob::insertChild ( uint index, Lob newChild )
{
if ( isEmpty() || isBasicType() || ( nodeType() == OmUnknownType )
|| newChild.isEmpty() || ( newChild.nodeType() == OmUnknownType )
|| ( index > numChildren() ) || !isEditable() || hasAsAncestor( newChild ) )
return;
OmNode::Iterator it = omobj->iterate();
for ( uint i = 0 ; !it.done() && ( i < index ) ; i++ )
it.next();
Lob tmp = Lob::fromVariant( 0 );
tmp.set( newChild ); // just to deparent smoothly
omobj->insert( it, tmp.omobj );
//maybeEmitModified( Lob(), newChild );
newChild.maybeEmitModified( LobChange::newInsert( newChild ) );
}
void Lob::addChild ( Lob newChild )
{
if ( isEmpty() || isBasicType() || ( nodeType() == OmUnknownType )
|| newChild.isEmpty() || ( newChild.nodeType() == OmUnknownType )
|| !isEditable() || hasAsAncestor( newChild ) )
return;
Lob tmp = Lob::fromVariant( 0 );
tmp.set( newChild ); // just to deparent smoothly
omobj->append( tmp.omobj );
//maybeEmitModified( Lob(), newChild );
newChild.maybeEmitModified( LobChange::newInsert( newChild ) );
}
void Lob::removeChild ( uint index )
{
if ( isEmpty() || !isEditable() )
return;
OmNode::Iterator it = omobj->iterate();
for ( uint i = 0 ; !it.done() && ( i < index ) ; i++ )
it.next();
if ( it.done() )
return;
OmNode* out;
omobj->extract( it, out );
Lob newOwner( out );
//maybeEmitModified( newOwner, omobj );
maybeEmitModified( LobChange::newRemove( newOwner ), OmNode::AsChild, index );
}
void Lob::insertNextSibling ( Lob newSibling )
{
if ( !hasParent() || !isEditable() || ( parent().nodeType() == OmUnknownType )
|| newSibling.isEmpty() || hasAsAncestor( newSibling ) )
return;
OmNode::Iterator it = omobj->getOwner()->iteratorToIndex( omobj->ownerIndex() );
it.next();
Lob tmp = Lob::fromVariant( 0 );
tmp.set( newSibling ); // just to deparent smoothly
omobj->getOwner()->insert( it, tmp.omobj );
//maybeEmitModified( Lob(), newSibling );
newSibling.maybeEmitModified( LobChange::newInsert( newSibling ) );
}
void Lob::insertPreviousSibling ( Lob newSibling )
{
if ( !hasParent() || !isEditable() || ( parent().nodeType() == OmUnknownType )
|| newSibling.isEmpty() || hasAsAncestor( newSibling ) )
return;
OmNode::Iterator it = omobj->getOwner()->iteratorToIndex( omobj->ownerIndex() );
Lob tmp = Lob::fromVariant( 0 );
tmp.set( newSibling ); // just to deparent smoothly
omobj->getOwner()->insert( it, tmp.omobj );
//maybeEmitModified( Lob(), newSibling );
newSibling.maybeEmitModified( LobChange::newInsert( newSibling ) );
}
void Lob::removeNextSibling ()
{
if ( !hasParent() || !isEditable() || !hasNextSibling() )
return;
unsigned int index = omobj->ownerIndex() + 1;
OmNode::Iterator it = omobj->getOwner()->iteratorToIndex( index );
OmNode* out;
omobj->getOwner()->extract( it, out );
Lob newOwner( out );
//maybeEmitModified( newOwner, omobj->getOwner() );
parent().maybeEmitModified( LobChange::newRemove( newOwner ), OmNode::AsChild, index );
}
void Lob::removePreviousSibling ()
{
if ( !hasParent() || !isEditable() || !hasPreviousSibling() )
return;
unsigned int index = omobj->ownerIndex() - 1;
OmNode::Iterator it = omobj->getOwner()->iteratorToIndex( index );
OmNode* out;
omobj->getOwner()->extract( it, out );
Lob newOwner( out );
//maybeEmitModified( newOwner, omobj->getOwner() );
parent().maybeEmitModified( LobChange::newRemove( newOwner ), OmNode::AsChild, index );
}
unsigned int Lob::numComments ()
{
if ( isEmpty() )
return 0;
return omobj->countComments();
}
QString Lob::comment ( unsigned int i )
{
if ( i >= numComments() )
return QString();
OmComment* c;
omobj->consultComment( i, c );
return unescapeXML( QString( c->getBuffer() ) ).trimmed();
}
void Lob::removeComment ( unsigned int i )
{
if ( i >= numComments() )
return;
omobj->killComment( i );
}
void Lob::changeComment ( unsigned int i, QString text )
{
if ( i >= numComments() )
return;
OmComment* c;
omobj->consultComment( i, c );
c->setBuffer( escapeXML( " " + text + " " ).toLatin1().constData() );
}
void Lob::insertComment ( unsigned int i, QString text )
{
if ( i > numComments() )
return;
omobj->insertComment( i,
new OmComment( escapeXML( " " + text + " " ).toLatin1().constData() ) );
}
void Lob::appendComment ( QString text )
{
if ( isEmpty() )
return;
omobj->appendComment(
new OmComment( escapeXML( " " + text + " " ).toLatin1().constData() ) );
}
QStringList Lob::comments ()
{
if ( isEmpty() )
return QStringList();
QStringList result;
for ( unsigned int i = 0 ; i < omobj->countComments() ; i++ )
result << comment( i );
return result;
}
void Lob::setComments ( QStringList all )
{
if ( isEmpty() )
return;
while ( omobj->countComments() > 0 )
omobj->killComment( 0 );
foreach ( const QString& comment, all )
appendComment( comment );
}
void Lob::setAttribute ( QString keyName, QString keyCD, Lob newValue )
{
if ( isEmpty() || ( nodeType() == OmUnknownType )
|| newValue.isEmpty() || ( newValue.nodeType() == OmUnknownType )
|| !isEditable() )
return;
Lob findAttribute = attribute( keyName, keyCD );
if ( !findAttribute.isEmpty() ) {
findAttribute.set( newValue );
} else {
Lob tmp = Lob::fromVariant( 0 );
tmp.set( newValue );
OmSymbolNode* sym = new OmSymbolNode( keyCD.toLatin1().constData(),
keyName.toLatin1().constData() );
omobj->appendAttribute( sym, tmp.omobj );
maybeEmitModified( LobChange::newInsertPair( keyName, keyCD, newValue ),
OmNode::AsValue, omobj->countAttributes() - 1 );
}
}
void Lob::insertAttribute ( uint index, QString keyName, QString keyCD, Lob value )
{
if ( isEmpty() || ( nodeType() == OmUnknownType )
|| value.isEmpty() || ( value.nodeType() == OmUnknownType )
|| !isEditable() )
return;
if ( index > omobj->countAttributes() )
return;
Lob tmp = Lob::fromVariant( 0 );
tmp.set( value );
OmSymbolNode* sym = new OmSymbolNode( keyCD.toLatin1().constData(),
keyName.toLatin1().constData() );
omobj->insertAttribute( index, sym, tmp.omobj );
//maybeEmitModified( Lob(), Lob( sym ) );
//maybeEmitModified( Lob(), value );
maybeEmitModified( LobChange::newInsertPair( keyName, keyCD, value ),
OmNode::AsValue, index );
}
void Lob::removeAttribute ( QString keyName, QString keyCD )
{
if ( !isEditable() )
return;
Lob findAttribute = attribute( keyName, keyCD );
Lob tmp = Lob::fromVariant( 0 );;
if ( !findAttribute.isEmpty() )
tmp.set( findAttribute );
}
void Lob::removeAttribute ( uint index )
{
if ( isEmpty() || !isEditable() )
return;
if ( index >= omobj->countAttributes() )
return;
OmSymbolNode* key;
OmNode* value;
omobj->consultAttribute( index, key, value );
Lob tmp = Lob::fromVariant( 0 );
tmp.set( value );
}
bool excludedAttribute ( QString name, QString cd, QStringList exclusions )
{
// check to see if (name,cd) is excluded in the list exclusions, of the form
// [ name1, cd1, name2, cd2, ... , nameN, cdN ] (with the last cdN optional)
for ( int i = 0 ; i < exclusions.count() ; i += 2 )
if ( ( name == exclusions[i] )
&& ( ( i == exclusions.count() - 1 ) || ( cd == exclusions[i+1] ) ) )
return true;
return false;
}
bool equivalentAttributes ( const OmNode* a, const OmNode* b, QStringList exclusions )
{
// for every attribute of a, be sure it's equivalent() to the same attribute of b
for ( unsigned int i = 0 ; i < a->countAttributes() ; i++ ) {
OmSymbolNode* akeyi;
OmNode* avaluei;
a->consultAttribute( i, akeyi, avaluei );
// if this one is excluded, skip it
if ( excludedAttribute( akeyi->getName(), akeyi->getCD(), exclusions ) )
continue;
// if the attribute appeared already in a's list, ignore this duplicate
bool alreadyDidThisOne = false;
for ( unsigned int j = 0 ; j < i ; j++ ) {
OmSymbolNode* akeyj;
OmNode* avaluej;
a->consultAttribute( j, akeyj, avaluej );
if ( !strcmp( akeyj->getName(), akeyi->getName() )
&& !strcmp( akeyj->getCD(), akeyi->getCD() ) ) {
alreadyDidThisOne = true;
break;
}
}
if ( alreadyDidThisOne )
continue;
// this attribute counts, so look the same one up in b
OmSymbolNode* bkeyj;
OmNode* bvaluej;
bool found = false;
for ( unsigned int j = 0 ; j < b->countAttributes() ; j++ ) {
b->consultAttribute( j, bkeyj, bvaluej );
if ( !strcmp( bkeyj->getName(), akeyi->getName() )
&& !strcmp( bkeyj->getCD(), akeyi->getCD() ) ) {
// found the same key in b, so return false if their values don't match
found = true;
if ( !equivalent( avaluei, bvaluej, true, exclusions ) )
return false;
break;
}
}
if ( !found )
return false; // couldn't find the same key in b, so return false
}
// for every attribute of b, just verify that it's in a
for ( unsigned int j = 0 ; j < b->countAttributes() ; j++ ) {
OmSymbolNode* bkeyj;
OmNode* bvaluej;
b->consultAttribute( j, bkeyj, bvaluej );
// if this one excluded, skip it
if ( excludedAttribute( bkeyj->getName(), bkeyj->getCD(), exclusions ) )
continue;
// see if a contains a like-keyed attribute
bool foundInA = false;
for ( unsigned int i = 0 ; i < a->countAttributes() ; i++ ) {
OmSymbolNode* akeyi;
OmNode* avaluei;
a->consultAttribute( i, akeyi, avaluei );
if ( !strcmp( bkeyj->getName(), akeyi->getName() )
&& !strcmp( bkeyj->getCD(), akeyi->getCD() ) ) {
foundInA = true;
break;
}
}
if ( !foundInA )
return false;
}
// found no reason to return false, so return true
return true;
}
bool equivalent ( const OmNode* a, const OmNode* b,
bool compareAttributes, QStringList exclusions )
{
if ( !a && !b )
return true;
if ( !a || !b )
return false;
if ( a->type() != b->type() )
return false;
if ( compareAttributes && !equivalentAttributes( a, b, exclusions ) )
return false;
switch ( a->type() )
{
case OmIntegerType:
return ((OmIntegerNode*)a)->getValue() == ((OmIntegerNode*)b)->getValue();
case OmBigIntegerType:
{
OmBigIntegerNode* bia = (OmBigIntegerNode*)a;
OmBigIntegerNode* bib = (OmBigIntegerNode*)b;
QString digsa = QString( bia->getDigits() ).trimmed();
QString digsb = QString( bib->getDigits() ).trimmed();
if ( ( digsa.count( '0' ) == digsa.length() ) // a is the big integer zero
&& ( digsb.count( '0' ) == digsb.length() ) ) // so is b
return true;
if ( bia->getSign() * bib->getSign() < 0 )
return false;
int i = 0;
while ( ( i < digsa.length() ) && ( digsa[i] == '0' ) ) i++;
int j = 0;
while ( ( j < digsb.length() ) && ( digsb[j] == '0' ) ) j++;
return digsa.mid( i ) == digsb.mid( j );
}
case OmFloatType:
return ((OmFloatNode*)a)->getValue() == ((OmFloatNode*)b)->getValue();
case OmByteArrayType:
{
const char* bufa;
const char* bufb;
unsigned int lena;
unsigned int lenb;
((OmByteArrayNode*)a)->getBuffer( bufa, lena );
((OmByteArrayNode*)b)->getBuffer( bufb, lenb );
if ( lena != lenb )
return false;
for ( unsigned int i = 0 ; i < lena ; i++ )
if ( bufa[i] != bufb[i] )
return false;
return true;
}
case OmStringType:
return !strcmp( ((OmStringNode*)a)->getBuffer(),
((OmStringNode*)b)->getBuffer() );
case OmWStringType:
return QString::fromWCharArray( ((OmWStringNode*)a)->getBuffer() )
== QString::fromWCharArray( ((OmWStringNode*)b)->getBuffer() );
case OmSymbolType:
return !strcmp( ((OmSymbolNode*)a)->getName(), ((OmSymbolNode*)b)->getName() )
&& !strcmp( ((OmSymbolNode*)a)->getCD(), ((OmSymbolNode*)b)->getCD() );
case OmVariableType:
return !strcmp( ((OmVariableNode*)a)->getName(),
((OmVariableNode*)b)->getName() );
case OmPInstructionType:
return !strcmp( ((OmPInstructionNode*)a)->getBuffer(),
((OmPInstructionNode*)b)->getBuffer() );
default:
{
if ( a->count() != b->count() )
return false;
OmNode::ConstIterator ita = a->iterate();
OmNode::ConstIterator itb = b->iterate();
while ( !ita.done() ) {
if ( !equivalent( ita.get(), itb.get(), compareAttributes, exclusions ) )
return false;
ita.next();
itb.next();
}
return true;
}
}
}
void deleteOmNode ( OmNode* node )
{
if ( node->type() == OmUnknownType ) {
OmDocumentNode* maybeDoc = dynamic_cast<OmDocumentNode*>( node );
if ( maybeDoc )
delete maybeDoc; // otherwise we are not authorized to delete it
} else {
OmDocumentNode killer;
killer.append( node ); // this deletes node; it's legal because type != unknown
}
}
SafeSingleton<QMap<OmNode*, unsigned long int> > Lob::referenceCounts;
bool Lob::descendentsUnreferenced ( OmNode* node )
{
if ( ( node == NULL ) || !referenceCounts.available() )
return false;
if ( referenceCounts.data().contains( node ) && ( referenceCounts.data()[node] > 0 ) )
return false;
for ( OmNode::Iterator it = node->iterate() ; !it.done() ; it.next() )
if ( !descendentsUnreferenced( it.get() ) )
return false;
for ( unsigned int i = 0 ; i < node->countAttributes() ; i++ ) {
OmSymbolNode* key;
OmNode* value;
node->consultAttribute( i, key, value );
if ( !descendentsUnreferenced( key ) || !descendentsUnreferenced( value ) )
return false;
}
return true;
}
OmNode* topLevelOwner ( OmNode* node )
{
while ( ( node != NULL ) && ( node->getOwner() != NULL ) )
node = node->getOwner();
return node;
}
void Lob::hold ( OmNode* node )
{
if ( ( node == NULL ) || !referenceCounts.available() )
return;
if ( !referenceCounts.data().contains( node ) )
referenceCounts.data()[node] = 0;
++referenceCounts.data()[node];
}
void Lob::release ( OmNode* node )
{
if ( ( node == NULL ) || !referenceCounts.available() )
return;
if ( referenceCounts.data().contains( node ) ) {
unsigned long int& c = referenceCounts.data()[node];
if ( c == 0 ) {
OmOutputStringStream outputStream;
OmDocumentNode* tosave = dynamic_cast<OmDocumentNode*>( node );
try {
OmOutputDevice outputDevice( outputStream, OmXmlEncoding, false );
if ( !tosave ) {
tosave = new OmDocumentNode;
tosave->append( node->clone() );
}
tosave->save( outputDevice );
const char* b = outputStream.getBuffer();
int len = outputStream.getBufferLength();
char* copy = new char[len + 1];
memcpy( copy, b, len );
copy[len] = '\0';
qDebug( "%s", copy );
} catch ( ... ) {
qDebug( "Lob::release(): c==0 and error computing representation" );
}
if ( tosave != node )
delete tosave;
}
Q_ASSERT_X( c > 0, "Lob::release", "reference counts never become negative" );
c--;
}
node = topLevelOwner( node );
if ( descendentsUnreferenced( node ) )
deleteOmNode( node );
}
SafeSingleton<QList<Lob*> > Lob::watchForModifications;
void Lob::prepareModifiedSignal ()
{
if ( omobj && !watchForModifications.data().contains( this ) )
watchForModifications.data() << this;
}
void Lob::maybeEmitModified ( const LobChange& change,
OmNode::OwnershipType fromHere, unsigned int fromIndex ) const
{
// first, if it was a change that wasn't really a change, don't emit anything
if ( ( change.type() == LobChange::NoChange )
|| ( ( change.type() == LobChange::Change )
&& change.oldData().equivalentTo( change.newData(), true ) ) )
return;
// okay, it was a real change, so emit something
for ( OmNode* o = omobj ; o != NULL ; o = o->getOwner() ) {
foreach ( Lob* Lptr, watchForModifications.data() ) {
if ( Lptr->omobj == o ) {
LobChange toEmit = change;
toEmit.root = *Lptr;
toEmit.addr = address( *Lptr );
toEmit.addr.addStep( fromIndex, fromHere ); // default does nothing
emit Lptr->modified( toEmit );
}
}
}
}
QMap<Lob,QString> Lob::highestIDs;
QString Lob::getID () const
{
QString result = getStringAttribute( "ID", "LurchCore" );
const QRegExp re( "id\\d+" );
return re.exactMatch( result ) ? result : QString();
}
void Lob::setID ( QString id )
{
if ( isEmpty() || !isEditable() )
return;
const QRegExp re( "id\\d+" );
if ( re.exactMatch( id ) )
setAttribute( "ID", "LurchCore", Lob::fromVariant( id ) );
}
void Lob::setIDContext ( bool on )
{
if ( isEmpty() )
return;
if ( on && !isIDContext() ) {
updateHighestID();
connect( this, SIGNAL(modified(const LobChange&)),
this, SLOT(updateHighestID(const LobChange&)) );
}
if ( !on ) {
highestIDs.remove( *this );
disconnect( this, SIGNAL(modified(const LobChange&)),
this, SLOT(updateHighestID(const LobChange&)) );
}
}
bool Lob::isIDContext () const
{
return highestIDs.contains( *this );
}
void Lob::updateHighestID () const
{
QStringList allIDs = listAllLabels( "ID" );
QString max;
foreach ( const QString& id, allIDs )
if ( max.isEmpty() || lessThanID( max, id ) )
max = id;
highestIDs[*this] = max;
}
void Lob::updateHighestID ( const LobChange& change ) const
{
Q_ASSERT_X( isIDContext(), "Lob::updateHighestID",
"Only ID Contexts should have the updateHighestID slot fired." );
if ( !isIDContext() )
return;
// process the change; did new data come in that requires increasing our max id?
if ( ( change.type() == LobChange::Insert ) || ( change.type() == LobChange::InsertPair )
|| ( change.type() == LobChange::Change ) ) {
QString max = highestIDs[*this];
QStringList allIDs = change.result().listAllLabels( "ID" );
foreach ( const QString& id, allIDs )
if ( max.isEmpty() || lessThanID( max, id ) )
max = id;
highestIDs[*this] = max;
}
}
bool Lob::hasAsAncestor ( const Lob maybeAncestor ) const
{
OmNode* walker = omobj;
while ( walker != NULL ) {
if ( walker == maybeAncestor.omobj )
return true;
walker = walker->getOwner();
}
return false;
}
Lob Lob::findIDContext () const
{
return ( isIDContext() || !omobj || !omobj->getOwner() ) ?
*this : Lob( omobj->getOwner() ).findIDContext();
}
QString Lob::getNewID () const
{
Lob context = findIDContext();
// if my context is the efficient kind, do a lookup and keep the table up-to-date
if ( context.isIDContext() ) {
QString highest = highestIDs[context];
highest = highest.isEmpty() ? zeroID() : incrementID( highest );
highestIDs[context] = highest;
return highest;
}
// my context is the inefficient kind, so I better just search
QStringList allIDs = context.listAllLabels( "ID" );
QString max;
foreach ( const QString& id, allIDs )
if ( max.isEmpty() || lessThanID( max, id ) )
max = id;
return max.isEmpty() ? zeroID() : incrementID( max );
}
Lob Lob::reference ()
{
// first ensure we have an ID
if ( getID().isEmpty() )
setID( getNewID() );
// if there was some problem with ensuring that, bomb out with an empty Lob
QString id = getID();
if ( id.isEmpty() )
return Lob();
// create a reference to this
Lob result = Lob::fromVariant( id );
result.setAttribute( "reference", "LurchCore", Lob::fromVariant( "ID" ) );
return result;
}
Lob Lob::referent () const
{
if ( !isIDReference() )
return Lob();
return recursiveReferent( getIDReference() );
}
Lob Lob::recursiveReferent ( QString id, OmNode* skip ) const
{
if ( isEmpty() )
return Lob();
// qDebug() << "recursive dereferencing in " << (unsigned long long int)this
// << " w/skip " << (unsigned long long int)skip;
// have I found it already?
if ( getID() == id ) {
// qDebug() << "found id \"" << id << "\" in " << (unsigned long long int)this;
return *this;
}
#ifdef PROCESS_REFS_IN_ATTRIBUTES
// search in all of my attribute values except skip; if it's there, return it
for ( unsigned int i = 0 ; i < omobj->countAttributes() ; i++ ) {
OmSymbolNode* key;
OmNode* value;
omobj->consultAttribute( i, key, value );
if ( value != skip ) {
// qDebug() << "recurring on attribute " << i
// << ", " << (unsigned long long int)value
// << ", which is not " << (unsigned long long int)skip;
Lob maybe = Lob( value ).recursiveReferent( id, omobj );
if ( !maybe.isEmpty() )
return maybe;
}
}
#endif
// search in all of my children except skip; if it's there, return it
for ( OmNode::Iterator it = omobj->iterate() ; !it.done() ; it.next() ) {
if ( it.get() != skip ) {
// qDebug() << "recurring on child " << (unsigned long long int)it.get()
// << ", which is not " << (unsigned long long int)skip;
Lob maybe = Lob( it.get() ).recursiveReferent( id, omobj );
if ( !maybe.isEmpty() )
return maybe;
}
}
// move the search up one level to my owner
OmNode* owner = omobj->getOwner();
if ( owner != skip ) {
// qDebug() << "up to parent/container...";
return Lob( owner ).recursiveReferent( id, omobj );
}
// if that's not possible, then I've searched my whole tree; return failure
// qDebug() << "giving up: empty result";
return Lob();
}
bool Lob::compareVariables ( Lob var1, Lob var2 ) const
{
return var1.toVariant() == var2.toVariant();
// can be extended later if Ken wants the "equals" parameter in script functions
// related to binding implemented, say like this:
// emit compare(Lob,Lob,bool&) with the last param being the result
// such a signal can be connected by a setVariableComparison() function,
// called by routines in the Lob prototype object in script.
}
bool Lob::variableOnList ( Lob var, QVariantList list ) const
{
foreach ( const QVariant& v, list )
if ( compareVariables( var, v.value<Lob>() ) )
return true;
return false;
}
QVariantList Lob::mergeVariableLists ( QVariantList a, QVariantList b ) const
{
QVariantList result = a;
foreach ( const QVariant& v, b )
if ( !variableOnList( v.value<Lob>(), result ) )
result << QVariant::fromValue( v );
return result;
}
bool Lob::isBinding () const
{
return nodeType() == OmBindingType;
}
bool Lob::binds ( QString var ) const
{
return binds( Lob::fromXML( QString( "<OMV name='%1'/>" ).arg( var ) ).child() );
}
bool Lob::binds ( Lob var ) const
{
if ( !isBinding() )
return false;
for ( unsigned int i = 1 ; i + 1 < numChildren() ; i++ ) {
Lob ch = child( i );
if ( ( ch.nodeType() == OmVariableType ) && compareVariables( ch, var ) )
return true;
}
return false;
}
QVariantList Lob::boundVariables ( Lob context ) const
{
QVariantList result;
if ( isEmpty() )
return result;
// if we haven't recurred up to the context yet, keep going up and getting all its bounds
if ( context != *this )
result = Lob( omobj->getOwner() ).boundVariables( context );
// now add to the list any variables that this particular Lob binds
if ( isBinding() ) {
for ( unsigned int i = 1 ; i + 1 < numChildren() ; i++ ) {
Lob ch = child( i );
if ( !variableOnList( ch, result ) )
result << QVariant::fromValue( ch );
}
}
return result;
}
QStringList Lob::boundVariableNames ( Lob context ) const
{
QVariantList result = boundVariables( context );
QStringList newResult;
foreach ( const QVariant& var, result )
newResult << var.value<Lob>().toVariant().toString();
return newResult;
}
QVariantList Lob::freeVariables ( Lob context, QVariant boundAbove ) const
{
if ( boundAbove.type() != QVariant::List ) {
// if boundAbove data not yet computed, compute it
boundAbove = boundVariables( context );
} else {
// otherwise, just add to it the variables bound at this one particular node
boundAbove = mergeVariableLists( boundAbove.toList(), boundVariables( *this ) );
}
// if this node is a variable, return it as free iff it's not on the boundAbove list
if ( nodeType() == OmVariableType )
return variableOnList( *this, boundAbove.toList() ) ?
QVariantList() :
QVariantList() << QVariant::fromValue( *this );
// otherwise recur on children and compile the results
QVariantList result;
for ( unsigned int i = 0 ; i < numChildren() ; i++ )
result = mergeVariableLists( result,
child( i ).freeVariables( context, boundAbove ) );
return result;
}
QStringList Lob::freeVariableNames ( Lob context ) const
{
QVariantList result = freeVariables( context );
QStringList newResult;
foreach ( const QVariant& var, result )
newResult << var.value<Lob>().toVariant().toString();
return newResult;
}
bool Lob::occursFree ( Lob var, Lob context ) const
{
return variableOnList( var, freeVariables( context ) );
}
bool Lob::occursFree ( QString var, Lob context ) const
{
return freeVariableNames( context ).contains( var );
}
bool Lob::isFree ( Lob context ) const
{
if ( nodeType() != OmVariableType )
return false;
return !variableOnList( *this, boundVariables( context ) );
}
Lob Lob::binderOf ( Lob context ) const
{
if ( nodeType() != OmVariableType )
return Lob();
Lob walker = *this;
while ( !walker.isEmpty() ) {
if ( walker.binds( *this ) )
return walker;
if ( walker == context )
break;
walker = Lob( walker.omobj->getOwner() );
}
return Lob();
}
QVariantList Lob::freeOccurrences ( Lob var, Lob context ) const
{
if ( variableOnList( var, boundVariables( context ) ) )
return QVariantList();
if ( ( nodeType() == OmVariableType ) && compareVariables( *this, var ) )
return QVariantList() << QVariant::fromValue( *this );
QVariantList result;
for ( unsigned int i = 0 ; i < numChildren() ; i++ ) {
Lob ch = child( i );
// can tell the child that its context equals itself, because we've already
// verified that no higher context binds the variables, so let's be efficient
result << ch.freeOccurrences( var, ch );
}
return result;
}
QVariantList Lob::freeOccurrences ( QString var, Lob context ) const
{
return freeOccurrences( Lob::fromXML( QString( "<OMV name='%1'/>" ).arg( var ) ).child(),
context );
}
void Lob::freeSubstitute ( Lob var, Lob term, Lob context )
{
foreach ( QVariant v, freeOccurrences( var, context ) )
v.value<Lob>().set( term.copy() );
}
void Lob::freeSubstitute ( QString var, Lob term, Lob context )
{
freeSubstitute( Lob::fromXML( QString( "<OMV name='%1'/>" ).arg( var ) ).child(),
term, context );
}
bool Lob::freeToSubstitute ( Lob term, Lob context ) const
{
QVariantList bounds = Lob( omobj->getOwner() ).boundVariables( context );
QVariantList frees = term.freeVariables();
foreach ( const QVariant& b, bounds )
if ( variableOnList( b.value<Lob>(), frees ) )
return false;
return true;
}
void Lob::renameBound ( Lob var, Lob newvar )
{
if ( ( nodeType() == OmVariableType ) && compareVariables( *this, var ) ) {
Lob replacement = newvar.copy();
moveAttributesTo( replacement );
set( replacement );
return;
}
for ( unsigned int i = 0 ; i < numChildren() ; i++ ) {
Lob ch = child( i );
if ( !ch.binds( var ) )
// only recur if it's not a nested quantification of the same variable
ch.renameBound( var, newvar );
else
// otherwise, just recur on the quantifier itself, which may be an expression,
// and which technically isn't bound by itself (what a corner case!)
ch.child().renameBound( var, newvar );
}
}
void Lob::renameBound ( QString var, QString newvar )
{
renameBound( Lob::fromXML( QString( "<OMV name='%1'/>" ).arg( var ) ).child(),
Lob::fromXML( QString( "<OMV name='%1'/>" ).arg( newvar ) ).child() );
}
void Lob::moveAttributesTo ( Lob other )
{
foreach ( Lob key, attributeKeys() )
other.setAttribute( key, attribute( key ) );
}