JOOMLA中国
  • Joomla中国首页
  • 社区
  • 教程
  • 应用市场
  • B计划
Joomla! Framework TM
  • Namespace
  • Class
  • Tree
  • Deprecated

Namespaces

  • Composer
    • Autoload
  • Joomla
    • Application
      • Cli
        • Output
          • Processor
      • Web
    • Data
    • DI
      • Exception
    • Event
    • Filter
    • Input
    • Ldap
    • Registry
      • Format
    • Session
      • Storage
    • String
    • Uri
    • Utilities
  • None
  • PasswordCompat
    • binary
  • PHP
  • Psr
    • Log
  • Symfony
    • Component
      • Yaml
        • Exception
    • Polyfill
      • Util

Classes

  • CallbackFilterIterator
  • ComposerAutoloaderInit205c915b9c7d3e718e7c95793ee67ffe
  • easyparse
  • EasyPeasyICS
  • FOFAutoloaderComponent
  • FOFAutoloaderFof
  • FOFConfigDomainDispatcher
  • FOFConfigDomainTables
  • FOFConfigDomainViews
  • FOFConfigProvider
  • FOFController
  • FOFDatabase
  • FOFDatabaseDriver
  • FOFDatabaseDriverJoomla
  • FOFDatabaseDriverMysql
  • FOFDatabaseDriverMysqli
  • FOFDatabaseDriverOracle
  • FOFDatabaseDriverPdo
  • FOFDatabaseDriverPdomysql
  • FOFDatabaseDriverPostgresql
  • FOFDatabaseDriverSqlazure
  • FOFDatabaseDriverSqlite
  • FOFDatabaseDriverSqlsrv
  • FOFDatabaseFactory
  • FOFDatabaseInstaller
  • FOFDatabaseIterator
  • FOFDatabaseIteratorAzure
  • FOFDatabaseIteratorMysql
  • FOFDatabaseIteratorMysqli
  • FOFDatabaseIteratorOracle
  • FOFDatabaseIteratorPdo
  • FOFDatabaseIteratorPdomysql
  • FOFDatabaseIteratorPostgresql
  • FOFDatabaseIteratorSqlite
  • FOFDatabaseIteratorSqlsrv
  • FOFDatabaseQuery
  • FOFDatabaseQueryElement
  • FOFDatabaseQueryMysql
  • FOFDatabaseQueryMysqli
  • FOFDatabaseQueryOracle
  • FOFDatabaseQueryPdo
  • FOFDatabaseQueryPdomysql
  • FOFDatabaseQueryPostgresql
  • FOFDatabaseQuerySqlazure
  • FOFDatabaseQuerySqlite
  • FOFDatabaseQuerySqlsrv
  • FOFDispatcher
  • FOFDownload
  • FOFDownloadAdapterAbstract
  • FOFDownloadAdapterCurl
  • FOFDownloadAdapterFopen
  • FOFEncryptAes
  • FOFEncryptAesAbstract
  • FOFEncryptAesMcrypt
  • FOFEncryptAesOpenssl
  • FOFEncryptBase32
  • FOFEncryptRandval
  • FOFEncryptTotp
  • FOFForm
  • FOFFormFieldAccesslevel
  • FOFFormFieldActions
  • FOFFormFieldButton
  • FOFFormFieldCachehandler
  • FOFFormFieldCalendar
  • FOFFormFieldCaptcha
  • FOFFormFieldCheckbox
  • FOFFormFieldCheckboxes
  • FOFFormFieldComponents
  • FOFFormFieldEditor
  • FOFFormFieldEmail
  • FOFFormFieldGroupedbutton
  • FOFFormFieldGroupedlist
  • FOFFormFieldHidden
  • FOFFormFieldImage
  • FOFFormFieldImagelist
  • FOFFormFieldInteger
  • FOFFormFieldLanguage
  • FOFFormFieldList
  • FOFFormFieldMedia
  • FOFFormFieldModel
  • FOFFormFieldOrdering
  • FOFFormFieldPassword
  • FOFFormFieldPlugins
  • FOFFormFieldPublished
  • FOFFormFieldRadio
  • FOFFormFieldRelation
  • FOFFormFieldRules
  • FOFFormFieldSelectrow
  • FOFFormFieldSessionhandler
  • FOFFormFieldSpacer
  • FOFFormFieldSql
  • FOFFormFieldTag
  • FOFFormFieldTel
  • FOFFormFieldText
  • FOFFormFieldTextarea
  • FOFFormFieldTimezone
  • FOFFormFieldTitle
  • FOFFormFieldUrl
  • FOFFormFieldUser
  • FOFFormFieldUsergroup
  • FOFFormHeader
  • FOFFormHeaderAccesslevel
  • FOFFormHeaderField
  • FOFFormHeaderFielddate
  • FOFFormHeaderFieldfilterable
  • FOFFormHeaderFieldsearchable
  • FOFFormHeaderFieldselectable
  • FOFFormHeaderFieldsql
  • FOFFormHeaderFilterdate
  • FOFFormHeaderFilterfilterable
  • FOFFormHeaderFiltersearchable
  • FOFFormHeaderFilterselectable
  • FOFFormHeaderFiltersql
  • FOFFormHeaderLanguage
  • FOFFormHeaderModel
  • FOFFormHeaderOrdering
  • FOFFormHeaderPublished
  • FOFFormHeaderRowselect
  • FOFFormHelper
  • FOFHalDocument
  • FOFHalLink
  • FOFHalLinks
  • FOFHalRenderJson
  • FOFInflector
  • FOFInput
  • FOFIntegrationJoomlaFilesystem
  • FOFIntegrationJoomlaPlatform
  • FOFLayoutFile
  • FOFLayoutHelper
  • FOFLess
  • FOFLessFormatterClassic
  • FOFLessFormatterCompressed
  • FOFLessFormatterJoomla
  • FOFLessFormatterLessjs
  • FOFLessParser
  • FOFModel
  • FOFModelBehavior
  • FOFModelBehaviorAccess
  • FOFModelBehaviorEmptynonzero
  • FOFModelBehaviorEnabled
  • FOFModelBehaviorFilters
  • FOFModelBehaviorLanguage
  • FOFModelBehaviorPrivate
  • FOFModelDispatcherBehavior
  • FOFModelField
  • FOFModelFieldBoolean
  • FOFModelFieldDate
  • FOFModelFieldNumber
  • FOFModelFieldText
  • FOFPlatform
  • FOFPlatformFilesystem
  • FOFQueryAbstract
  • FOFRenderAbstract
  • FOFRenderJoomla
  • FOFRenderJoomla3
  • FOFRenderStrapper
  • FOFStringUtils
  • FOFTable
  • FOFTableBehavior
  • FOFTableBehaviorAssets
  • FOFTableBehaviorContenthistory
  • FOFTableBehaviorTags
  • FOFTableDispatcherBehavior
  • FOFTableNested
  • FOFTableRelations
  • FOFTemplateUtils
  • FOFToolbar
  • FOFUtilsArray
  • FOFUtilsCacheCleaner
  • FOFUtilsConfigHelper
  • FOFUtilsFilescheck
  • FOFUtilsIniParser
  • FOFUtilsInstallscript
  • FOFUtilsIp
  • FOFUtilsObject
  • FOFUtilsObservableDispatcher
  • FOFUtilsObservableEvent
  • FOFUtilsPhpfunc
  • FOFUtilsTimer
  • FOFUtilsUpdate
  • FOFUtilsUpdateCollection
  • FOFUtilsUpdateExtension
  • FOFUtilsUpdateJoomla
  • FOFView
  • FOFViewCsv
  • FOFViewForm
  • FOFViewHtml
  • FOFViewJson
  • FOFViewRaw
  • idna_convert
  • JAccess
  • JAccessRule
  • JAccessRules
  • JAccessWrapperAccess
  • JAdapter
  • JAdapterInstance
  • JApplication
  • JApplicationAdministrator
  • JApplicationBase
  • JApplicationCli
  • JApplicationCms
  • JApplicationDaemon
  • JApplicationHelper
  • JApplicationSite
  • JApplicationWeb
  • JApplicationWebRouter
  • JApplicationWebRouterBase
  • JApplicationWebRouterRest
  • JArchive
  • JArchiveBzip2
  • JArchiveGzip
  • JArchiveTar
  • JArchiveWrapperArchive
  • JArchiveZip
  • JArrayHelper
  • JAssociationExtensionHelper
  • JAuthentication
  • JAuthenticationHelper
  • JAuthenticationResponse
  • JBrowser
  • JBuffer
  • JButton
  • JCache
  • JCacheController
  • JCacheControllerCallback
  • JCacheControllerOutput
  • JCacheControllerPage
  • JCacheControllerView
  • JCacheStorage
  • JCacheStorageApc
  • JCacheStorageApcu
  • JCacheStorageCachelite
  • JCacheStorageFile
  • JCacheStorageHelper
  • JCacheStorageMemcache
  • JCacheStorageMemcached
  • JCacheStorageRedis
  • JCacheStorageWincache
  • JCacheStorageXcache
  • JCaptcha
  • JCategories
  • JCategoryNode
  • JClassLoader
  • JCli
  • JClientFtp
  • JClientHelper
  • JClientLdap
  • JClientWrapperHelper
  • JComponentHelper
  • JComponentRecord
  • JComponentRouterBase
  • JComponentRouterLegacy
  • JComponentRouterRulesMenu
  • JComponentRouterRulesNomenu
  • JComponentRouterRulesStandard
  • JComponentRouterView
  • JComponentRouterViewconfiguration
  • JControllerAdmin
  • JControllerBase
  • JControllerForm
  • JControllerLegacy
  • JCrypt
  • JCryptCipher3Des
  • JCryptCipherBlowfish
  • JCryptCipherCrypto
  • JCryptCipherMcrypt
  • JCryptCipherRijndael256
  • JCryptCipherSimple
  • JCryptKey
  • JCryptPasswordSimple
  • JDaemon
  • JDatabase
  • JDatabaseDriver
  • JDatabaseDriverMysql
  • JDatabaseDriverMysqli
  • JDatabaseDriverOracle
  • JDatabaseDriverPdo
  • JDatabaseDriverPdomysql
  • JDatabaseDriverPostgresql
  • JDatabaseDriverSqlazure
  • JDatabaseDriverSqlite
  • JDatabaseDriverSqlsrv
  • JDatabaseExporter
  • JDatabaseExporterMysql
  • JDatabaseExporterMysqli
  • JDatabaseExporterPdomysql
  • JDatabaseExporterPostgresql
  • JDatabaseFactory
  • JDatabaseImporter
  • JDatabaseImporterMysql
  • JDatabaseImporterMysqli
  • JDatabaseImporterPdomysql
  • JDatabaseImporterPostgresql
  • JDatabaseInterface
  • JDatabaseIterator
  • JDatabaseIteratorMysql
  • JDatabaseIteratorMysqli
  • JDatabaseIteratorOracle
  • JDatabaseIteratorPdo
  • JDatabaseIteratorPdomysql
  • JDatabaseIteratorPostgresql
  • JDatabaseIteratorSqlazure
  • JDatabaseIteratorSqlite
  • JDatabaseIteratorSqlsrv
  • JDatabaseMysql
  • JDatabaseMysqli
  • JDatabaseQuery
  • JDatabaseQueryElement
  • JDatabaseQueryLimitable
  • JDatabaseQueryMysql
  • JDatabaseQueryMysqli
  • JDatabaseQueryOracle
  • JDatabaseQueryPdo
  • JDatabaseQueryPdomysql
  • JDatabaseQueryPostgresql
  • JDatabaseQueryPreparable
  • JDatabaseQuerySqlazure
  • JDatabaseQuerySqlite
  • JDatabaseQuerySqlsrv
  • JDatabaseSqlazure
  • JDatabaseSqlsrv
  • JDate
  • JDispatcher
  • JDocument
  • JDocumentError
  • JDocumentFeed
  • JDocumentHtml
  • JDocumentImage
  • JDocumentJson
  • JDocumentOpensearch
  • JDocumentRaw
  • JDocumentRenderer
  • JDocumentRendererAtom
  • JDocumentRendererComponent
  • JDocumentRendererFeedAtom
  • JDocumentRendererFeedRss
  • JDocumentRendererHead
  • JDocumentRendererHtmlComponent
  • JDocumentRendererHtmlHead
  • JDocumentRendererHtmlMessage
  • JDocumentRendererHtmlModule
  • JDocumentRendererHtmlModules
  • JDocumentRendererMessage
  • JDocumentRendererModule
  • JDocumentRendererModules
  • JDocumentRendererRSS
  • JDocumentXml
  • JEditor
  • JError
  • JErrorPage
  • JEvent
  • JEventDispatcher
  • JExtension
  • JFacebook
  • JFacebookAlbum
  • JFacebookCheckin
  • JFacebookComment
  • JFacebookEvent
  • JFacebookGroup
  • JFacebookLink
  • JFacebookNote
  • JFacebookOAuth
  • JFacebookObject
  • JFacebookPhoto
  • JFacebookPost
  • JFacebookStatus
  • JFacebookUser
  • JFacebookVideo
  • JFactory
  • JFeed
  • JFeedEnclosure
  • JFeedEntry
  • JFeedFactory
  • JFeedImage
  • JFeedItem
  • JFeedLink
  • JFeedParser
  • JFeedParserAtom
  • JFeedParserRss
  • JFeedParserRssItunes
  • JFeedParserRssMedia
  • JFeedPerson
  • JFile
  • JFilesystemHelper
  • JFilesystemPatcher
  • JFilesystemWrapperFile
  • JFilesystemWrapperFolder
  • JFilesystemWrapperPath
  • JFilterInput
  • JFilterOutput
  • JFilterWrapperOutput
  • JFolder
  • JForm
  • JFormField
  • JFormFieldAccessLevel
  • JFormFieldAliastag
  • JFormFieldAuthor
  • JFormFieldCacheHandler
  • JFormFieldCalendar
  • JFormFieldCaptcha
  • JFormFieldCategory
  • JFormFieldCheckbox
  • JFormFieldCheckboxes
  • JFormFieldChromeStyle
  • JFormFieldColor
  • JFormFieldCombo
  • JFormFieldComponentlayout
  • JFormFieldComponents
  • JFormFieldContenthistory
  • JFormFieldContentlanguage
  • JFormFieldContenttype
  • JFormFieldDatabaseConnection
  • JFormFieldEditor
  • JFormFieldEMail
  • JFormFieldFile
  • JFormFieldFileList
  • JFormFieldFolderList
  • JFormFieldFrontend_Language
  • JFormFieldGroupedList
  • JFormFieldHeadertag
  • JFormFieldHelpsite
  • JFormFieldHidden
  • JFormFieldImageList
  • JFormFieldInteger
  • JFormFieldLanguage
  • JFormFieldLastvisitDateRange
  • JFormFieldLimitbox
  • JFormFieldList
  • JFormFieldMedia
  • JFormFieldMenu
  • JFormFieldMenuitem
  • JFormFieldMeter
  • JFormFieldModulelayout
  • JFormFieldModuleOrder
  • JFormFieldModulePosition
  • JFormFieldModuletag
  • JFormFieldNote
  • JFormFieldNumber
  • JFormFieldOrdering
  • JFormFieldPassword
  • JFormFieldPlugin_Status
  • JFormFieldPlugins
  • JFormFieldPredefinedList
  • JFormFieldRadio
  • JFormFieldRange
  • JFormFieldRegistrationDateRange
  • JFormFieldRepeatable
  • JFormFieldRules
  • JFormFieldSessionHandler
  • JFormFieldSpacer
  • JFormFieldSQL
  • JFormFieldStatus
  • JFormFieldSubform
  • JFormFieldTag
  • JFormFieldTel
  • JFormFieldTemplatestyle
  • JFormFieldText
  • JFormFieldTextarea
  • JFormFieldTimezone
  • JFormFieldUrl
  • JFormFieldUser
  • JFormFieldUserActive
  • JFormFieldUsergroup
  • JFormFieldUserGroupList
  • JFormFieldUserState
  • JFormHelper
  • JFormRule
  • JFormRuleBoolean
  • JFormRuleCalendar
  • JFormRuleCaptcha
  • JFormRuleColor
  • JFormRuleEmail
  • JFormRuleEquals
  • JFormRuleNotequals
  • JFormRuleNumber
  • JFormRuleOptions
  • JFormRulePassword
  • JFormRuleRules
  • JFormRuleTel
  • JFormRuleUrl
  • JFormRuleUsername
  • JFormWrapperHelper
  • JFTP
  • JGithub
  • JGithubAccount
  • JGithubCommits
  • JGithubForks
  • JGithubHooks
  • JGithubHttp
  • JGithubMeta
  • JGithubMilestones
  • JGithubObject
  • JGithubPackage
  • JGithubPackageActivity
  • JGithubPackageActivityEvents
  • JGithubPackageActivityNotifications
  • JGithubPackageActivityStarring
  • JGithubPackageActivityWatching
  • JGithubPackageAuthorization
  • JGithubPackageData
  • JGithubPackageDataBlobs
  • JGithubPackageDataCommits
  • JGithubPackageDataRefs
  • JGithubPackageDataTags
  • JGithubPackageDataTrees
  • JGithubPackageGists
  • JGithubPackageGistsComments
  • JGithubPackageGitignore
  • JGithubPackageIssues
  • JGithubPackageIssuesAssignees
  • JGithubPackageIssuesComments
  • JGithubPackageIssuesEvents
  • JGithubPackageIssuesLabels
  • JGithubPackageIssuesMilestones
  • JGithubPackageMarkdown
  • JGithubPackageOrgs
  • JGithubPackageOrgsMembers
  • JGithubPackageOrgsTeams
  • JGithubPackagePulls
  • JGithubPackagePullsComments
  • JGithubPackageRepositories
  • JGithubPackageRepositoriesCollaborators
  • JGithubPackageRepositoriesComments
  • JGithubPackageRepositoriesCommits
  • JGithubPackageRepositoriesContents
  • JGithubPackageRepositoriesDownloads
  • JGithubPackageRepositoriesForks
  • JGithubPackageRepositoriesHooks
  • JGithubPackageRepositoriesKeys
  • JGithubPackageRepositoriesMerging
  • JGithubPackageRepositoriesStatistics
  • JGithubPackageRepositoriesStatuses
  • JGithubPackageSearch
  • JGithubPackageUsers
  • JGithubPackageUsersEmails
  • JGithubPackageUsersFollowers
  • JGithubPackageUsersKeys
  • JGithubRefs
  • JGithubStatuses
  • JGoogle
  • JGoogleAuth
  • JGoogleAuthOauth2
  • JGoogleData
  • JGoogleDataAdsense
  • JGoogleDataCalendar
  • JGoogleDataPicasa
  • JGoogleDataPicasaAlbum
  • JGoogleDataPicasaPhoto
  • JGoogleDataPlus
  • JGoogleDataPlusActivities
  • JGoogleDataPlusComments
  • JGoogleDataPlusPeople
  • JGoogleEmbed
  • JGoogleEmbedAnalytics
  • JGoogleEmbedMaps
  • JGrid
  • JHelp
  • JHelper
  • JHelperContent
  • JHelperContenthistory
  • JHelperMedia
  • JHelperRoute
  • JHelperTags
  • JHelperUsergroups
  • JHtml
  • JHtmlAccess
  • JHtmlActionsDropdown
  • JHtmlBatch
  • JHtmlBehavior
  • JHtmlBootstrap
  • JHtmlCategory
  • JHtmlContent
  • JHtmlContentLanguage
  • JHtmlDate
  • JHtmlDebug
  • JHtmlDropdown
  • JHtmlEmail
  • JHtmlForm
  • JHtmlFormbehavior
  • JHtmlGrid
  • JHtmlIcons
  • JHtmlJGrid
  • JHtmlJquery
  • JHtmlLinks
  • JHtmlList
  • JHtmlMenu
  • JHtmlNumber
  • JHtmlRules
  • JHtmlSearchtools
  • JHtmlSelect
  • JHtmlSidebar
  • JHtmlSliders
  • JHtmlSortablelist
  • JHtmlString
  • JHtmlTabs
  • JHtmlTag
  • JHtmlTel
  • JHtmlUser
  • JHttp
  • JHttpFactory
  • JHttpResponse
  • JHttpTransportCurl
  • JHttpTransportSocket
  • JHttpTransportStream
  • JHttpWrapperFactory
  • JImage
  • JImageFilter
  • JImageFilterBackgroundfill
  • JImageFilterBrightness
  • JImageFilterContrast
  • JImageFilterEdgedetect
  • JImageFilterEmboss
  • JImageFilterGrayscale
  • JImageFilterNegate
  • JImageFilterSketchy
  • JImageFilterSmooth
  • JInput
  • JInputCli
  • JInputCookie
  • JInputFiles
  • JInputJSON
  • JInstaller
  • JInstallerAdapter
  • JInstallerAdapterComponent
  • JInstallerAdapterFile
  • JInstallerAdapterLanguage
  • JInstallerAdapterLibrary
  • JInstallerAdapterModule
  • JInstallerAdapterPackage
  • JInstallerAdapterPlugin
  • JInstallerAdapterTemplate
  • JInstallerComponent
  • JInstallerExtension
  • JInstallerFile
  • JInstallerHelper
  • JInstallerLanguage
  • JInstallerLibrary
  • JInstallerManifest
  • JInstallerManifestLibrary
  • JInstallerManifestPackage
  • JInstallerModule
  • JInstallerPackage
  • JInstallerPlugin
  • JInstallerScript
  • JInstallerTemplate
  • JKeychain
  • JLanguage
  • JLanguageAssociations
  • JLanguageHelper
  • JLanguageMultilang
  • JLanguageStemmer
  • JLanguageStemmerPorteren
  • JLanguageTransliterate
  • JLanguageWrapperHelper
  • JLanguageWrapperText
  • JLanguageWrapperTransliterate
  • JLayoutBase
  • JLayoutFile
  • JLayoutHelper
  • JLDAP
  • JLess
  • JLessFormatterJoomla
  • JLibraryHelper
  • JLinkedin
  • JLinkedinCommunications
  • JLinkedinCompanies
  • JLinkedinGroups
  • JLinkedinJobs
  • JLinkedinOauth
  • JLinkedinObject
  • JLinkedinPeople
  • JLinkedinStream
  • JLoader
  • JLog
  • JLogEntry
  • JLogger
  • JLogLogger
  • JLogLoggerCallback
  • JLogLoggerDatabase
  • JLogLoggerEcho
  • JLogLoggerFormattedtext
  • JLogLoggerMessagequeue
  • JLogLoggerSyslog
  • JLogLoggerW3c
  • JMail
  • JMailHelper
  • JMailWrapperHelper
  • JMediawiki
  • JMediawikiCategories
  • JMediawikiHttp
  • JMediawikiImages
  • JMediawikiLinks
  • JMediawikiObject
  • JMediawikiPages
  • JMediawikiSearch
  • JMediawikiSites
  • JMediawikiUsers
  • JMenu
  • JMenuAdministrator
  • JMenuItem
  • JMenuSite
  • JMicrodata
  • JModelAdmin
  • JModelBase
  • JModelDatabase
  • JModelForm
  • JModelItem
  • JModelLegacy
  • JModelList
  • JModuleHelper
  • JNode
  • JOAuth1Client
  • JOAuth2Client
  • JObject
  • JObservable
  • JObserver
  • JObserverMapper
  • JObserverUpdater
  • JObserverWrapperMapper
  • JOpenSearchImage
  • JOpenSearchUrl
  • JOpenstreetmap
  • JOpenstreetmapChangesets
  • JOpenstreetmapElements
  • JOpenstreetmapGps
  • JOpenstreetmapInfo
  • JOpenstreetmapOauth
  • JOpenstreetmapObject
  • JOpenstreetmapUser
  • JPagination
  • JPaginationObject
  • JPath
  • JPathway
  • JPathwaySite
  • JPlatform
  • JPlugin
  • JPluginHelper
  • JProfiler
  • JRequest
  • JResponse
  • JResponseJson
  • JRoute
  • JRouter
  • JRouterAdministrator
  • JRouterSite
  • JRouteWrapperRoute
  • JRule
  • JRules
  • JSchemaChangeitem
  • JSchemaChangeitemMysql
  • JSchemaChangeitemPostgresql
  • JSchemaChangeitemSqlsrv
  • JSchemaChangeset
  • JSearchHelper
  • JSession
  • JSessionHandlerJoomla
  • JSessionHandlerNative
  • JSessionStorage
  • JSessionStorageApc
  • JSessionStorageDatabase
  • JSessionStorageMemcache
  • JSessionStorageMemcached
  • JSessionStorageNone
  • JSessionStorageWincache
  • JSessionStorageXcache
  • JSimplecrypt
  • JSimplepieFactory
  • JStream
  • JStreamString
  • JString
  • JStringController
  • JStringPunycode
  • JStringWrapperNormalise
  • JStringWrapperPunycode
  • JTable
  • JTableAsset
  • JTableCategory
  • JTableContent
  • JTableContenthistory
  • JTableContenttype
  • JTableCorecontent
  • JTableExtension
  • JTableInterface
  • JTableLanguage
  • JTableMenu
  • JTableMenuType
  • JTableModule
  • JTableNested
  • JTableObserver
  • JTableObserverContenthistory
  • JTableObserverTags
  • JTableSession
  • JTableUcm
  • JTableUpdate
  • JTableUpdatesite
  • JTableUser
  • JTableUsergroup
  • JTableViewlevel
  • JText
  • JToolbar
  • JToolbarButton
  • JToolbarButtonConfirm
  • JToolbarButtonCustom
  • JToolbarButtonHelp
  • JToolbarButtonLink
  • JToolbarButtonPopup
  • JToolbarButtonSeparator
  • JToolbarButtonSlider
  • JToolbarButtonStandard
  • JTree
  • JTwitter
  • JTwitterBlock
  • JTwitterDirectmessages
  • JTwitterFavorites
  • JTwitterFriends
  • JTwitterHelp
  • JTwitterLists
  • JTwitterOAuth
  • JTwitterObject
  • JTwitterPlaces
  • JTwitterProfile
  • JTwittersearch
  • JTwitterStatuses
  • JTwitterTrends
  • JTwitterUsers
  • JUcmBase
  • JUcmContent
  • JUcmType
  • JUpdate
  • JUpdateAdapter
  • JUpdater
  • JUpdaterCollection
  • JUpdaterExtension
  • JUri
  • JUser
  • JUserHelper
  • JUserWrapperHelper
  • JUtility
  • JVersion
  • JViewBase
  • JViewCategories
  • JViewCategory
  • JViewCategoryfeed
  • JViewHtml
  • JViewLegacy
  • JWeb
  • JWebClient
  • JXMLElement
  • lessc
  • lessc_formatter_classic
  • lessc_formatter_compressed
  • lessc_formatter_lessjs
  • lessc_parser
  • lessify
  • Net_IDNA_php4
  • nodecounter
  • ntlm_sasl_client_class
  • PHPMailer
  • PHPMailerOAuth
  • PHPMailerOAuthGoogle
  • POP3
  • SimplePie
  • SimplePie_Author
  • SimplePie_Autoloader
  • SimplePie_Cache
  • SimplePie_Cache_DB
  • SimplePie_Cache_File
  • SimplePie_Cache_Memcache
  • SimplePie_Cache_MySQL
  • SimplePie_Caption
  • SimplePie_Category
  • SimplePie_Content_Type_Sniffer
  • SimplePie_Copyright
  • SimplePie_Core
  • SimplePie_Credit
  • SimplePie_Decode_HTML_Entities
  • SimplePie_Enclosure
  • SimplePie_File
  • SimplePie_gzdecode
  • SimplePie_HTTP_Parser
  • SimplePie_IRI
  • SimplePie_Item
  • SimplePie_Locator
  • SimplePie_Misc
  • SimplePie_Net_IPv6
  • SimplePie_Parse_Date
  • SimplePie_Parser
  • SimplePie_Rating
  • SimplePie_Registry
  • SimplePie_Restriction
  • SimplePie_Sanitize
  • SimplePie_Source
  • SimplePie_XML_Declaration_Parser
  • SMTP
  • tagparse
  • TypeError

Interfaces

  • FOFConfigDomainInterface
  • FOFDatabaseInterface
  • FOFDatabaseQueryLimitable
  • FOFDatabaseQueryPreparable
  • FOFDownloadInterface
  • FOFEncryptAesInterface
  • FOFEncryptRandvalinterface
  • FOFFormField
  • FOFHalRenderInterface
  • FOFPlatformFilesystemInterface
  • FOFPlatformInterface
  • JArchiveExtractable
  • JAssociationExtensionInterface
  • JCacheException
  • JComponentRouterInterface
  • JComponentRouterRulesInterface
  • JController
  • JCryptCipher
  • JCryptPassword
  • JFeedParserNamespace
  • JHttpTransport
  • JLayout
  • JModel
  • JObservableInterface
  • JObserverInterface
  • JObserverUpdaterInterface
  • JSessionHandlerInterface
  • JsonSerializable
  • JUcm
  • JView
  • SimplePie_Cache_Base

Exceptions

  • Error
  • JAccessExceptionNotallowed
  • JCacheExceptionConnecting
  • JCacheExceptionUnsupported
  • JComponentExceptionMissing
  • JDatabaseException
  • JDatabaseExceptionConnecting
  • JDatabaseExceptionExecuting
  • JDatabaseExceptionUnsupported
  • JException
  • JSessionExceptionUnsupported
  • LogException
  • phpmailerException
  • SimplePie_Exception

Constants

  • JERROR_CALLBACK_NOT_CALLABLE
  • JERROR_ILLEGAL_MODE
  • JERROR_ILLEGAL_OPTIONS
  • JREQUEST_ALLOWHTML
  • JREQUEST_ALLOWRAW
  • JREQUEST_NOTRIM
  • JROUTER_MODE_RAW
  • JROUTER_MODE_SEF

Functions

  • __autoload
  • array_column
  • boolval
  • composerRequire205c915b9c7d3e718e7c95793ee67ffe
  • gzopen
  • gzseek
  • gztell
  • hash_equals
  • hash_pbkdf2
  • HTMLFilter
  • jexit
  • jimport
  • json_last_error_msg
  • ldap_escape
  • password_get_info
  • password_hash
  • password_needs_rehash
  • password_verify
  • PHPMailerAutoload
  • random_bytes
  • random_int
  • RandomCompat_intval
  • RandomCompat_strlen
  • RandomCompat_substr
  • tln_body2div
  • tln_casenormalize
  • tln_deent
  • tln_defang
  • tln_findnxreg
  • tln_findnxstr
  • tln_fixatts
  • tln_fixstyle
  • tln_fixurl
  • tln_getnxtag
  • tln_sanitize
  • tln_skipspace
  • tln_tagprint
  • tln_unspace
  • utf8_accents_to_ascii
  • utf8_bad_explain
  • utf8_bad_find
  • utf8_bad_findall
  • utf8_bad_identify
  • utf8_bad_replace
  • utf8_bad_strip
  • utf8_byte_position
  • utf8_compliant
  • utf8_from_unicode
  • utf8_ireplace
  • utf8_is_ascii
  • utf8_is_ascii_ctrl
  • utf8_is_valid
  • utf8_is_word_chars
  • utf8_locate_current_chr
  • utf8_locate_next_chr
  • utf8_ltrim
  • utf8_ord
  • utf8_rtrim
  • utf8_specials_pattern
  • utf8_str_pad
  • utf8_str_split
  • utf8_strcasecmp
  • utf8_strcspn
  • utf8_strip_ascii_ctrl
  • utf8_strip_non_ascii
  • utf8_strip_non_ascii_ctrl
  • utf8_strip_specials
  • utf8_stristr
  • utf8_strlen
  • utf8_strpos
  • utf8_strrev
  • utf8_strrpos
  • utf8_strspn
  • utf8_strtolower
  • utf8_strtoupper
  • utf8_substr
  • utf8_substr_replace
  • utf8_to_unicode
  • utf8_trim
  • utf8_ucfirst
  • utf8_ucwords
  • utf8_ucwords_callback
   1 <?php
   2 
   3 /**
   4  * lessphp v0.5.0
   5  * http://leafo.net/lessphp
   6  *
   7  * LESS CSS compiler, adapted from http://lesscss.org
   8  *
   9  * Copyright 2013, Leaf Corcoran <leafot@gmail.com>
  10  * Licensed under MIT or GPLv3, see LICENSE
  11  */
  12 
  13 
  14 /**
  15  * The LESS compiler and parser.
  16  *
  17  * Converting LESS to CSS is a three stage process. The incoming file is parsed
  18  * by `lessc_parser` into a syntax tree, then it is compiled into another tree
  19  * representing the CSS structure by `lessc`. The CSS tree is fed into a
  20  * formatter, like `lessc_formatter` which then outputs CSS as a string.
  21  *
  22  * During the first compile, all values are *reduced*, which means that their
  23  * types are brought to the lowest form before being dump as strings. This
  24  * handles math equations, variable dereferences, and the like.
  25  *
  26  * The `parse` function of `lessc` is the entry point.
  27  *
  28  * In summary:
  29  *
  30  * The `lessc` class creates an instance of the parser, feeds it LESS code,
  31  * then transforms the resulting tree to a CSS tree. This class also holds the
  32  * evaluation context, such as all available mixins and variables at any given
  33  * time.
  34  *
  35  * The `lessc_parser` class is only concerned with parsing its input.
  36  *
  37  * The `lessc_formatter` takes a CSS tree, and dumps it to a formatted string,
  38  * handling things like indentation.
  39  */
  40 class lessc {
  41     static public $VERSION = "v0.5.0";
  42 
  43     static public $TRUE = array("keyword", "true");
  44     static public $FALSE = array("keyword", "false");
  45 
  46     protected $libFunctions = array();
  47     protected $registeredVars = array();
  48     protected $preserveComments = false;
  49 
  50     public $vPrefix = '@'; // prefix of abstract properties
  51     public $mPrefix = '$'; // prefix of abstract blocks
  52     public $parentSelector = '&';
  53 
  54     public $importDisabled = false;
  55     public $importDir = '';
  56 
  57     protected $numberPrecision = null;
  58 
  59     protected $allParsedFiles = array();
  60 
  61     // set to the parser that generated the current line when compiling
  62     // so we know how to create error messages
  63     protected $sourceParser = null;
  64     protected $sourceLoc = null;
  65 
  66     static protected $nextImportId = 0; // uniquely identify imports
  67 
  68     // attempts to find the path of an import url, returns null for css files
  69     protected function findImport($url) {
  70         foreach ((array)$this->importDir as $dir) {
  71             $full = $dir.(substr($dir, -1) != '/' ? '/' : '').$url;
  72             if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full)) {
  73                 return $file;
  74             }
  75         }
  76 
  77         return null;
  78     }
  79 
  80     protected function fileExists($name) {
  81         return is_file($name);
  82     }
  83 
  84     static public function compressList($items, $delim) {
  85         if (!isset($items[1]) && isset($items[0])) return $items[0];
  86         else return array('list', $delim, $items);
  87     }
  88 
  89     static public function preg_quote($what) {
  90         return preg_quote($what, '/');
  91     }
  92 
  93     protected function tryImport($importPath, $parentBlock, $out) {
  94         if ($importPath[0] == "function" && $importPath[1] == "url") {
  95             $importPath = $this->flattenList($importPath[2]);
  96         }
  97 
  98         $str = $this->coerceString($importPath);
  99         if ($str === null) return false;
 100 
 101         $url = $this->compileValue($this->lib_e($str));
 102 
 103         // don't import if it ends in css
 104         if (substr_compare($url, '.css', -4, 4) === 0) return false;
 105 
 106         $realPath = $this->findImport($url);
 107 
 108         if ($realPath === null) return false;
 109 
 110         if ($this->importDisabled) {
 111             return array(false, "/* import disabled */");
 112         }
 113 
 114         if (isset($this->allParsedFiles[realpath($realPath)])) {
 115             return array(false, null);
 116         }
 117 
 118         $this->addParsedFile($realPath);
 119         $parser = $this->makeParser($realPath);
 120         $root = $parser->parse(file_get_contents($realPath));
 121 
 122         // set the parents of all the block props
 123         foreach ($root->props as $prop) {
 124             if ($prop[0] == "block") {
 125                 $prop[1]->parent = $parentBlock;
 126             }
 127         }
 128 
 129         // copy mixins into scope, set their parents
 130         // bring blocks from import into current block
 131         // TODO: need to mark the source parser these came from this file
 132         foreach ($root->children as $childName => $child) {
 133             if (isset($parentBlock->children[$childName])) {
 134                 $parentBlock->children[$childName] = array_merge(
 135                     $parentBlock->children[$childName],
 136                     $child);
 137             } else {
 138                 $parentBlock->children[$childName] = $child;
 139             }
 140         }
 141 
 142         $pi = pathinfo($realPath);
 143         $dir = $pi["dirname"];
 144 
 145         list($top, $bottom) = $this->sortProps($root->props, true);
 146         $this->compileImportedProps($top, $parentBlock, $out, $parser, $dir);
 147 
 148         return array(true, $bottom, $parser, $dir);
 149     }
 150 
 151     protected function compileImportedProps($props, $block, $out, $sourceParser, $importDir) {
 152         $oldSourceParser = $this->sourceParser;
 153 
 154         $oldImport = $this->importDir;
 155 
 156         // TODO: this is because the importDir api is stupid
 157         $this->importDir = (array)$this->importDir;
 158         array_unshift($this->importDir, $importDir);
 159 
 160         foreach ($props as $prop) {
 161             $this->compileProp($prop, $block, $out);
 162         }
 163 
 164         $this->importDir = $oldImport;
 165         $this->sourceParser = $oldSourceParser;
 166     }
 167 
 168     /**
 169      * Recursively compiles a block.
 170      *
 171      * A block is analogous to a CSS block in most cases. A single LESS document
 172      * is encapsulated in a block when parsed, but it does not have parent tags
 173      * so all of it's children appear on the root level when compiled.
 174      *
 175      * Blocks are made up of props and children.
 176      *
 177      * Props are property instructions, array tuples which describe an action
 178      * to be taken, eg. write a property, set a variable, mixin a block.
 179      *
 180      * The children of a block are just all the blocks that are defined within.
 181      * This is used to look up mixins when performing a mixin.
 182      *
 183      * Compiling the block involves pushing a fresh environment on the stack,
 184      * and iterating through the props, compiling each one.
 185      *
 186      * See lessc::compileProp()
 187      *
 188      */
 189     protected function compileBlock($block) {
 190         switch ($block->type) {
 191         case "root":
 192             $this->compileRoot($block);
 193             break;
 194         case null:
 195             $this->compileCSSBlock($block);
 196             break;
 197         case "media":
 198             $this->compileMedia($block);
 199             break;
 200         case "directive":
 201             $name = "@" . $block->name;
 202             if (!empty($block->value)) {
 203                 $name .= " " . $this->compileValue($this->reduce($block->value));
 204             }
 205 
 206             $this->compileNestedBlock($block, array($name));
 207             break;
 208         default:
 209             $this->throwError("unknown block type: $block->type\n");
 210         }
 211     }
 212 
 213     protected function compileCSSBlock($block) {
 214         $env = $this->pushEnv();
 215 
 216         $selectors = $this->compileSelectors($block->tags);
 217         $env->selectors = $this->multiplySelectors($selectors);
 218         $out = $this->makeOutputBlock(null, $env->selectors);
 219 
 220         $this->scope->children[] = $out;
 221         $this->compileProps($block, $out);
 222 
 223         $block->scope = $env; // mixins carry scope with them!
 224         $this->popEnv();
 225     }
 226 
 227     protected function compileMedia($media) {
 228         $env = $this->pushEnv($media);
 229         $parentScope = $this->mediaParent($this->scope);
 230 
 231         $query = $this->compileMediaQuery($this->multiplyMedia($env));
 232 
 233         $this->scope = $this->makeOutputBlock($media->type, array($query));
 234         $parentScope->children[] = $this->scope;
 235 
 236         $this->compileProps($media, $this->scope);
 237 
 238         if (count($this->scope->lines) > 0) {
 239             $orphanSelelectors = $this->findClosestSelectors();
 240             if (!is_null($orphanSelelectors)) {
 241                 $orphan = $this->makeOutputBlock(null, $orphanSelelectors);
 242                 $orphan->lines = $this->scope->lines;
 243                 array_unshift($this->scope->children, $orphan);
 244                 $this->scope->lines = array();
 245             }
 246         }
 247 
 248         $this->scope = $this->scope->parent;
 249         $this->popEnv();
 250     }
 251 
 252     protected function mediaParent($scope) {
 253         while (!empty($scope->parent)) {
 254             if (!empty($scope->type) && $scope->type != "media") {
 255                 break;
 256             }
 257             $scope = $scope->parent;
 258         }
 259 
 260         return $scope;
 261     }
 262 
 263     protected function compileNestedBlock($block, $selectors) {
 264         $this->pushEnv($block);
 265         $this->scope = $this->makeOutputBlock($block->type, $selectors);
 266         $this->scope->parent->children[] = $this->scope;
 267 
 268         $this->compileProps($block, $this->scope);
 269 
 270         $this->scope = $this->scope->parent;
 271         $this->popEnv();
 272     }
 273 
 274     protected function compileRoot($root) {
 275         $this->pushEnv();
 276         $this->scope = $this->makeOutputBlock($root->type);
 277         $this->compileProps($root, $this->scope);
 278         $this->popEnv();
 279     }
 280 
 281     protected function compileProps($block, $out) {
 282         foreach ($this->sortProps($block->props) as $prop) {
 283             $this->compileProp($prop, $block, $out);
 284         }
 285         $out->lines = $this->deduplicate($out->lines);
 286     }
 287 
 288     /**
 289      * Deduplicate lines in a block. Comments are not deduplicated. If a
 290      * duplicate rule is detected, the comments immediately preceding each
 291      * occurence are consolidated.
 292      */
 293     protected function deduplicate($lines) {
 294         $unique = array();
 295         $comments = array();
 296 
 297         foreach($lines as $line) {
 298             if (strpos($line, '/*') === 0) {
 299                 $comments[] = $line;
 300                 continue;
 301             }
 302             if (!in_array($line, $unique)) {
 303                 $unique[] = $line;
 304             }
 305             array_splice($unique, array_search($line, $unique), 0, $comments);
 306             $comments = array();
 307         }
 308         return array_merge($unique, $comments);
 309     }
 310 
 311     protected function sortProps($props, $split = false) {
 312         $vars = array();
 313         $imports = array();
 314         $other = array();
 315         $stack = array();
 316 
 317         foreach ($props as $prop) {
 318             switch ($prop[0]) {
 319             case "comment":
 320                 $stack[] = $prop;
 321                 break;
 322             case "assign":
 323                 $stack[] = $prop;
 324                 if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix) {
 325                     $vars = array_merge($vars, $stack);
 326                 } else {
 327                     $other = array_merge($other, $stack);
 328                 }
 329                 $stack = array();
 330                 break;
 331             case "import":
 332                 $id = self::$nextImportId++;
 333                 $prop[] = $id;
 334                 $stack[] = $prop;
 335                 $imports = array_merge($imports, $stack);
 336                 $other[] = array("import_mixin", $id);
 337                 $stack = array();
 338                 break;
 339             default:
 340                 $stack[] = $prop;
 341                 $other = array_merge($other, $stack);
 342                 $stack = array();
 343                 break;
 344             }
 345         }
 346         $other = array_merge($other, $stack);
 347 
 348         if ($split) {
 349             return array(array_merge($imports, $vars), $other);
 350         } else {
 351             return array_merge($imports, $vars, $other);
 352         }
 353     }
 354 
 355     protected function compileMediaQuery($queries) {
 356         $compiledQueries = array();
 357         foreach ($queries as $query) {
 358             $parts = array();
 359             foreach ($query as $q) {
 360                 switch ($q[0]) {
 361                 case "mediaType":
 362                     $parts[] = implode(" ", array_slice($q, 1));
 363                     break;
 364                 case "mediaExp":
 365                     if (isset($q[2])) {
 366                         $parts[] = "($q[1]: " .
 367                             $this->compileValue($this->reduce($q[2])) . ")";
 368                     } else {
 369                         $parts[] = "($q[1])";
 370                     }
 371                     break;
 372                 case "variable":
 373                     $parts[] = $this->compileValue($this->reduce($q));
 374                 break;
 375                 }
 376             }
 377 
 378             if (count($parts) > 0) {
 379                 $compiledQueries[] =  implode(" and ", $parts);
 380             }
 381         }
 382 
 383         $out = "@media";
 384         if (!empty($parts)) {
 385             $out .= " " .
 386                 implode($this->formatter->selectorSeparator, $compiledQueries);
 387         }
 388         return $out;
 389     }
 390 
 391     protected function multiplyMedia($env, $childQueries = null) {
 392         if (is_null($env) ||
 393             !empty($env->block->type) && $env->block->type != "media")
 394         {
 395             return $childQueries;
 396         }
 397 
 398         // plain old block, skip
 399         if (empty($env->block->type)) {
 400             return $this->multiplyMedia($env->parent, $childQueries);
 401         }
 402 
 403         $out = array();
 404         $queries = $env->block->queries;
 405         if (is_null($childQueries)) {
 406             $out = $queries;
 407         } else {
 408             foreach ($queries as $parent) {
 409                 foreach ($childQueries as $child) {
 410                     $out[] = array_merge($parent, $child);
 411                 }
 412             }
 413         }
 414 
 415         return $this->multiplyMedia($env->parent, $out);
 416     }
 417 
 418     protected function expandParentSelectors(&$tag, $replace) {
 419         $parts = explode("$&$", $tag);
 420         $count = 0;
 421         foreach ($parts as &$part) {
 422             $part = str_replace($this->parentSelector, $replace, $part, $c);
 423             $count += $c;
 424         }
 425         $tag = implode($this->parentSelector, $parts);
 426         return $count;
 427     }
 428 
 429     protected function findClosestSelectors() {
 430         $env = $this->env;
 431         $selectors = null;
 432         while ($env !== null) {
 433             if (isset($env->selectors)) {
 434                 $selectors = $env->selectors;
 435                 break;
 436             }
 437             $env = $env->parent;
 438         }
 439 
 440         return $selectors;
 441     }
 442 
 443 
 444     // multiply $selectors against the nearest selectors in env
 445     protected function multiplySelectors($selectors) {
 446         // find parent selectors
 447 
 448         $parentSelectors = $this->findClosestSelectors();
 449         if (is_null($parentSelectors)) {
 450             // kill parent reference in top level selector
 451             foreach ($selectors as &$s) {
 452                 $this->expandParentSelectors($s, "");
 453             }
 454 
 455             return $selectors;
 456         }
 457 
 458         $out = array();
 459         foreach ($parentSelectors as $parent) {
 460             foreach ($selectors as $child) {
 461                 $count = $this->expandParentSelectors($child, $parent);
 462 
 463                 // don't prepend the parent tag if & was used
 464                 if ($count > 0) {
 465                     $out[] = trim($child);
 466                 } else {
 467                     $out[] = trim($parent . ' ' . $child);
 468                 }
 469             }
 470         }
 471 
 472         return $out;
 473     }
 474 
 475     // reduces selector expressions
 476     protected function compileSelectors($selectors) {
 477         $out = array();
 478 
 479         foreach ($selectors as $s) {
 480             if (is_array($s)) {
 481                 list(, $value) = $s;
 482                 $out[] = trim($this->compileValue($this->reduce($value)));
 483             } else {
 484                 $out[] = $s;
 485             }
 486         }
 487 
 488         return $out;
 489     }
 490 
 491     protected function eq($left, $right) {
 492         return $left == $right;
 493     }
 494 
 495     protected function patternMatch($block, $orderedArgs, $keywordArgs) {
 496         // match the guards if it has them
 497         // any one of the groups must have all its guards pass for a match
 498         if (!empty($block->guards)) {
 499             $groupPassed = false;
 500             foreach ($block->guards as $guardGroup) {
 501                 foreach ($guardGroup as $guard) {
 502                     $this->pushEnv();
 503                     $this->zipSetArgs($block->args, $orderedArgs, $keywordArgs);
 504 
 505                     $negate = false;
 506                     if ($guard[0] == "negate") {
 507                         $guard = $guard[1];
 508                         $negate = true;
 509                     }
 510 
 511                     $passed = $this->reduce($guard) == self::$TRUE;
 512                     if ($negate) $passed = !$passed;
 513 
 514                     $this->popEnv();
 515 
 516                     if ($passed) {
 517                         $groupPassed = true;
 518                     } else {
 519                         $groupPassed = false;
 520                         break;
 521                     }
 522                 }
 523 
 524                 if ($groupPassed) break;
 525             }
 526 
 527             if (!$groupPassed) {
 528                 return false;
 529             }
 530         }
 531 
 532         if (empty($block->args)) {
 533             return $block->isVararg || empty($orderedArgs) && empty($keywordArgs);
 534         }
 535 
 536         $remainingArgs = $block->args;
 537         if ($keywordArgs) {
 538             $remainingArgs = array();
 539             foreach ($block->args as $arg) {
 540                 if ($arg[0] == "arg" && isset($keywordArgs[$arg[1]])) {
 541                     continue;
 542                 }
 543 
 544                 $remainingArgs[] = $arg;
 545             }
 546         }
 547 
 548         $i = -1; // no args
 549         // try to match by arity or by argument literal
 550         foreach ($remainingArgs as $i => $arg) {
 551             switch ($arg[0]) {
 552             case "lit":
 553                 if (empty($orderedArgs[$i]) || !$this->eq($arg[1], $orderedArgs[$i])) {
 554                     return false;
 555                 }
 556                 break;
 557             case "arg":
 558                 // no arg and no default value
 559                 if (!isset($orderedArgs[$i]) && !isset($arg[2])) {
 560                     return false;
 561                 }
 562                 break;
 563             case "rest":
 564                 $i--; // rest can be empty
 565                 break 2;
 566             }
 567         }
 568 
 569         if ($block->isVararg) {
 570             return true; // not having enough is handled above
 571         } else {
 572             $numMatched = $i + 1;
 573             // greater than becuase default values always match
 574             return $numMatched >= count($orderedArgs);
 575         }
 576     }
 577 
 578     protected function patternMatchAll($blocks, $orderedArgs, $keywordArgs, $skip=array()) {
 579         $matches = null;
 580         foreach ($blocks as $block) {
 581             // skip seen blocks that don't have arguments
 582             if (isset($skip[$block->id]) && !isset($block->args)) {
 583                 continue;
 584             }
 585 
 586             if ($this->patternMatch($block, $orderedArgs, $keywordArgs)) {
 587                 $matches[] = $block;
 588             }
 589         }
 590 
 591         return $matches;
 592     }
 593 
 594     // attempt to find blocks matched by path and args
 595     protected function findBlocks($searchIn, $path, $orderedArgs, $keywordArgs, $seen=array()) {
 596         if ($searchIn == null) return null;
 597         if (isset($seen[$searchIn->id])) return null;
 598         $seen[$searchIn->id] = true;
 599 
 600         $name = $path[0];
 601 
 602         if (isset($searchIn->children[$name])) {
 603             $blocks = $searchIn->children[$name];
 604             if (count($path) == 1) {
 605                 $matches = $this->patternMatchAll($blocks, $orderedArgs, $keywordArgs, $seen);
 606                 if (!empty($matches)) {
 607                     // This will return all blocks that match in the closest
 608                     // scope that has any matching block, like lessjs
 609                     return $matches;
 610                 }
 611             } else {
 612                 $matches = array();
 613                 foreach ($blocks as $subBlock) {
 614                     $subMatches = $this->findBlocks($subBlock,
 615                         array_slice($path, 1), $orderedArgs, $keywordArgs, $seen);
 616 
 617                     if (!is_null($subMatches)) {
 618                         foreach ($subMatches as $sm) {
 619                             $matches[] = $sm;
 620                         }
 621                     }
 622                 }
 623 
 624                 return count($matches) > 0 ? $matches : null;
 625             }
 626         }
 627         if ($searchIn->parent === $searchIn) return null;
 628         return $this->findBlocks($searchIn->parent, $path, $orderedArgs, $keywordArgs, $seen);
 629     }
 630 
 631     // sets all argument names in $args to either the default value
 632     // or the one passed in through $values
 633     protected function zipSetArgs($args, $orderedValues, $keywordValues) {
 634         $assignedValues = array();
 635 
 636         $i = 0;
 637         foreach ($args as  $a) {
 638             if ($a[0] == "arg") {
 639                 if (isset($keywordValues[$a[1]])) {
 640                     // has keyword arg
 641                     $value = $keywordValues[$a[1]];
 642                 } elseif (isset($orderedValues[$i])) {
 643                     // has ordered arg
 644                     $value = $orderedValues[$i];
 645                     $i++;
 646                 } elseif (isset($a[2])) {
 647                     // has default value
 648                     $value = $a[2];
 649                 } else {
 650                     $this->throwError("Failed to assign arg " . $a[1]);
 651                     $value = null; // :(
 652                 }
 653 
 654                 $value = $this->reduce($value);
 655                 $this->set($a[1], $value);
 656                 $assignedValues[] = $value;
 657             } else {
 658                 // a lit
 659                 $i++;
 660             }
 661         }
 662 
 663         // check for a rest
 664         $last = end($args);
 665         if ($last[0] == "rest") {
 666             $rest = array_slice($orderedValues, count($args) - 1);
 667             $this->set($last[1], $this->reduce(array("list", " ", $rest)));
 668         }
 669 
 670         // wow is this the only true use of PHP's + operator for arrays?
 671         $this->env->arguments = $assignedValues + $orderedValues;
 672     }
 673 
 674     // compile a prop and update $lines or $blocks appropriately
 675     protected function compileProp($prop, $block, $out) {
 676         // set error position context
 677         $this->sourceLoc = isset($prop[-1]) ? $prop[-1] : -1;
 678 
 679         switch ($prop[0]) {
 680         case 'assign':
 681             list(, $name, $value) = $prop;
 682             if ($name[0] == $this->vPrefix) {
 683                 $this->set($name, $value);
 684             } else {
 685                 $out->lines[] = $this->formatter->property($name,
 686                         $this->compileValue($this->reduce($value)));
 687             }
 688             break;
 689         case 'block':
 690             list(, $child) = $prop;
 691             $this->compileBlock($child);
 692             break;
 693         case 'mixin':
 694             list(, $path, $args, $suffix) = $prop;
 695 
 696             $orderedArgs = array();
 697             $keywordArgs = array();
 698             foreach ((array)$args as $arg) {
 699                 $argval = null;
 700                 switch ($arg[0]) {
 701                 case "arg":
 702                     if (!isset($arg[2])) {
 703                         $orderedArgs[] = $this->reduce(array("variable", $arg[1]));
 704                     } else {
 705                         $keywordArgs[$arg[1]] = $this->reduce($arg[2]);
 706                     }
 707                     break;
 708 
 709                 case "lit":
 710                     $orderedArgs[] = $this->reduce($arg[1]);
 711                     break;
 712                 default:
 713                     $this->throwError("Unknown arg type: " . $arg[0]);
 714                 }
 715             }
 716 
 717             $mixins = $this->findBlocks($block, $path, $orderedArgs, $keywordArgs);
 718 
 719             if ($mixins === null) {
 720                 $this->throwError("{$prop[1][0]} is undefined");
 721             }
 722 
 723             foreach ($mixins as $mixin) {
 724                 if ($mixin === $block && !$orderedArgs) {
 725                     continue;
 726                 }
 727 
 728                 $haveScope = false;
 729                 if (isset($mixin->parent->scope)) {
 730                     $haveScope = true;
 731                     $mixinParentEnv = $this->pushEnv();
 732                     $mixinParentEnv->storeParent = $mixin->parent->scope;
 733                 }
 734 
 735                 $haveArgs = false;
 736                 if (isset($mixin->args)) {
 737                     $haveArgs = true;
 738                     $this->pushEnv();
 739                     $this->zipSetArgs($mixin->args, $orderedArgs, $keywordArgs);
 740                 }
 741 
 742                 $oldParent = $mixin->parent;
 743                 if ($mixin != $block) $mixin->parent = $block;
 744 
 745                 foreach ($this->sortProps($mixin->props) as $subProp) {
 746                     if ($suffix !== null &&
 747                         $subProp[0] == "assign" &&
 748                         is_string($subProp[1]) &&
 749                         $subProp[1]{0} != $this->vPrefix)
 750                     {
 751                         $subProp[2] = array(
 752                             'list', ' ',
 753                             array($subProp[2], array('keyword', $suffix))
 754                         );
 755                     }
 756 
 757                     $this->compileProp($subProp, $mixin, $out);
 758                 }
 759 
 760                 $mixin->parent = $oldParent;
 761 
 762                 if ($haveArgs) $this->popEnv();
 763                 if ($haveScope) $this->popEnv();
 764             }
 765 
 766             break;
 767         case 'raw':
 768             $out->lines[] = $prop[1];
 769             break;
 770         case "directive":
 771             list(, $name, $value) = $prop;
 772             $out->lines[] = "@$name " . $this->compileValue($this->reduce($value)).';';
 773             break;
 774         case "comment":
 775             $out->lines[] = $prop[1];
 776             break;
 777         case "import";
 778             list(, $importPath, $importId) = $prop;
 779             $importPath = $this->reduce($importPath);
 780 
 781             if (!isset($this->env->imports)) {
 782                 $this->env->imports = array();
 783             }
 784 
 785             $result = $this->tryImport($importPath, $block, $out);
 786 
 787             $this->env->imports[$importId] = $result === false ?
 788                 array(false, "@import " . $this->compileValue($importPath).";") :
 789                 $result;
 790 
 791             break;
 792         case "import_mixin":
 793             list(,$importId) = $prop;
 794             $import = $this->env->imports[$importId];
 795             if ($import[0] === false) {
 796                 if (isset($import[1])) {
 797                     $out->lines[] = $import[1];
 798                 }
 799             } else {
 800                 list(, $bottom, $parser, $importDir) = $import;
 801                 $this->compileImportedProps($bottom, $block, $out, $parser, $importDir);
 802             }
 803 
 804             break;
 805         default:
 806             $this->throwError("unknown op: {$prop[0]}\n");
 807         }
 808     }
 809 
 810 
 811     /**
 812      * Compiles a primitive value into a CSS property value.
 813      *
 814      * Values in lessphp are typed by being wrapped in arrays, their format is
 815      * typically:
 816      *
 817      *     array(type, contents [, additional_contents]*)
 818      *
 819      * The input is expected to be reduced. This function will not work on
 820      * things like expressions and variables.
 821      */
 822     public function compileValue($value) {
 823         switch ($value[0]) {
 824         case 'list':
 825             // [1] - delimiter
 826             // [2] - array of values
 827             return implode($value[1], array_map(array($this, 'compileValue'), $value[2]));
 828         case 'raw_color':
 829             if (!empty($this->formatter->compressColors)) {
 830                 return $this->compileValue($this->coerceColor($value));
 831             }
 832             return $value[1];
 833         case 'keyword':
 834             // [1] - the keyword
 835             return $value[1];
 836         case 'number':
 837             list(, $num, $unit) = $value;
 838             // [1] - the number
 839             // [2] - the unit
 840             if ($this->numberPrecision !== null) {
 841                 $num = round($num, $this->numberPrecision);
 842             }
 843             return $num . $unit;
 844         case 'string':
 845             // [1] - contents of string (includes quotes)
 846             list(, $delim, $content) = $value;
 847             foreach ($content as &$part) {
 848                 if (is_array($part)) {
 849                     $part = $this->compileValue($part);
 850                 }
 851             }
 852             return $delim . implode($content) . $delim;
 853         case 'color':
 854             // [1] - red component (either number or a %)
 855             // [2] - green component
 856             // [3] - blue component
 857             // [4] - optional alpha component
 858             list(, $r, $g, $b) = $value;
 859             $r = round($r);
 860             $g = round($g);
 861             $b = round($b);
 862 
 863             if (count($value) == 5 && $value[4] != 1) { // rgba
 864                 return 'rgba('.$r.','.$g.','.$b.','.$value[4].')';
 865             }
 866 
 867             $h = sprintf("#%02x%02x%02x", $r, $g, $b);
 868 
 869             if (!empty($this->formatter->compressColors)) {
 870                 // Converting hex color to short notation (e.g. #003399 to #039)
 871                 if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
 872                     $h = '#' . $h[1] . $h[3] . $h[5];
 873                 }
 874             }
 875 
 876             return $h;
 877 
 878         case 'function':
 879             list(, $name, $args) = $value;
 880             return $name.'('.$this->compileValue($args).')';
 881         default: // assumed to be unit
 882             $this->throwError("unknown value type: $value[0]");
 883         }
 884     }
 885 
 886     protected function lib_pow($args) {
 887         list($base, $exp) = $this->assertArgs($args, 2, "pow");
 888         return pow($this->assertNumber($base), $this->assertNumber($exp));
 889     }
 890 
 891     protected function lib_pi() {
 892         return pi();
 893     }
 894 
 895     protected function lib_mod($args) {
 896         list($a, $b) = $this->assertArgs($args, 2, "mod");
 897         return $this->assertNumber($a) % $this->assertNumber($b);
 898     }
 899 
 900     protected function lib_tan($num) {
 901         return tan($this->assertNumber($num));
 902     }
 903 
 904     protected function lib_sin($num) {
 905         return sin($this->assertNumber($num));
 906     }
 907 
 908     protected function lib_cos($num) {
 909         return cos($this->assertNumber($num));
 910     }
 911 
 912     protected function lib_atan($num) {
 913         $num = atan($this->assertNumber($num));
 914         return array("number", $num, "rad");
 915     }
 916 
 917     protected function lib_asin($num) {
 918         $num = asin($this->assertNumber($num));
 919         return array("number", $num, "rad");
 920     }
 921 
 922     protected function lib_acos($num) {
 923         $num = acos($this->assertNumber($num));
 924         return array("number", $num, "rad");
 925     }
 926 
 927     protected function lib_sqrt($num) {
 928         return sqrt($this->assertNumber($num));
 929     }
 930 
 931     protected function lib_extract($value) {
 932         list($list, $idx) = $this->assertArgs($value, 2, "extract");
 933         $idx = $this->assertNumber($idx);
 934         // 1 indexed
 935         if ($list[0] == "list" && isset($list[2][$idx - 1])) {
 936             return $list[2][$idx - 1];
 937         }
 938     }
 939 
 940     protected function lib_isnumber($value) {
 941         return $this->toBool($value[0] == "number");
 942     }
 943 
 944     protected function lib_isstring($value) {
 945         return $this->toBool($value[0] == "string");
 946     }
 947 
 948     protected function lib_iscolor($value) {
 949         return $this->toBool($this->coerceColor($value));
 950     }
 951 
 952     protected function lib_iskeyword($value) {
 953         return $this->toBool($value[0] == "keyword");
 954     }
 955 
 956     protected function lib_ispixel($value) {
 957         return $this->toBool($value[0] == "number" && $value[2] == "px");
 958     }
 959 
 960     protected function lib_ispercentage($value) {
 961         return $this->toBool($value[0] == "number" && $value[2] == "%");
 962     }
 963 
 964     protected function lib_isem($value) {
 965         return $this->toBool($value[0] == "number" && $value[2] == "em");
 966     }
 967 
 968     protected function lib_isrem($value) {
 969         return $this->toBool($value[0] == "number" && $value[2] == "rem");
 970     }
 971 
 972     protected function lib_rgbahex($color) {
 973         $color = $this->coerceColor($color);
 974         if (is_null($color))
 975             $this->throwError("color expected for rgbahex");
 976 
 977         return sprintf("#%02x%02x%02x%02x",
 978             isset($color[4]) ? $color[4]*255 : 255,
 979             $color[1],$color[2], $color[3]);
 980     }
 981 
 982     protected function lib_argb($color){
 983         return $this->lib_rgbahex($color);
 984     }
 985 
 986     /**
 987      * Given an url, decide whether to output a regular link or the base64-encoded contents of the file
 988      *
 989      * @param  array  $value either an argument list (two strings) or a single string
 990      * @return string        formatted url(), either as a link or base64-encoded
 991      */
 992     protected function lib_data_uri($value) {
 993         $mime = ($value[0] === 'list') ? $value[2][0][2] : null;
 994         $url = ($value[0] === 'list') ? $value[2][1][2][0] : $value[2][0];
 995 
 996         $fullpath = $this->findImport($url);
 997 
 998         if($fullpath && ($fsize = filesize($fullpath)) !== false) {
 999             // IE8 can't handle data uris larger than 32KB
1000             if($fsize/1024 < 32) {
1001                 if(is_null($mime)) {
1002                     if(class_exists('finfo')) { // php 5.3+
1003                         $finfo = new finfo(FILEINFO_MIME);
1004                         $mime = explode('; ', $finfo->file($fullpath));
1005                         $mime = $mime[0];
1006                     } elseif(function_exists('mime_content_type')) { // PHP 5.2
1007                         $mime = mime_content_type($fullpath);
1008                     }
1009                 }
1010 
1011                 if(!is_null($mime)) // fallback if the mime type is still unknown
1012                     $url = sprintf('data:%s;base64,%s', $mime, base64_encode(file_get_contents($fullpath)));
1013             }
1014         }
1015 
1016         return 'url("'.$url.'")';
1017     }
1018 
1019     // utility func to unquote a string
1020     protected function lib_e($arg) {
1021         switch ($arg[0]) {
1022             case "list":
1023                 $items = $arg[2];
1024                 if (isset($items[0])) {
1025                     return $this->lib_e($items[0]);
1026                 }
1027                 $this->throwError("unrecognised input");
1028             case "string":
1029                 $arg[1] = "";
1030                 return $arg;
1031             case "keyword":
1032                 return $arg;
1033             default:
1034                 return array("keyword", $this->compileValue($arg));
1035         }
1036     }
1037 
1038     protected function lib__sprintf($args) {
1039         if ($args[0] != "list") return $args;
1040         $values = $args[2];
1041         $string = array_shift($values);
1042         $template = $this->compileValue($this->lib_e($string));
1043 
1044         $i = 0;
1045         if (preg_match_all('/%[dsa]/', $template, $m)) {
1046             foreach ($m[0] as $match) {
1047                 $val = isset($values[$i]) ?
1048                     $this->reduce($values[$i]) : array('keyword', '');
1049 
1050                 // lessjs compat, renders fully expanded color, not raw color
1051                 if ($color = $this->coerceColor($val)) {
1052                     $val = $color;
1053                 }
1054 
1055                 $i++;
1056                 $rep = $this->compileValue($this->lib_e($val));
1057                 $template = preg_replace('/'.self::preg_quote($match).'/',
1058                     $rep, $template, 1);
1059             }
1060         }
1061 
1062         $d = $string[0] == "string" ? $string[1] : '"';
1063         return array("string", $d, array($template));
1064     }
1065 
1066     protected function lib_floor($arg) {
1067         $value = $this->assertNumber($arg);
1068         return array("number", floor($value), $arg[2]);
1069     }
1070 
1071     protected function lib_ceil($arg) {
1072         $value = $this->assertNumber($arg);
1073         return array("number", ceil($value), $arg[2]);
1074     }
1075 
1076     protected function lib_round($arg) {
1077         if($arg[0] != "list") {
1078             $value = $this->assertNumber($arg);
1079             return array("number", round($value), $arg[2]);
1080         } else {
1081             $value = $this->assertNumber($arg[2][0]);
1082             $precision = $this->assertNumber($arg[2][1]);
1083             return array("number", round($value, $precision), $arg[2][0][2]);
1084         }
1085     }
1086 
1087     protected function lib_unit($arg) {
1088         if ($arg[0] == "list") {
1089             list($number, $newUnit) = $arg[2];
1090             return array("number", $this->assertNumber($number),
1091                 $this->compileValue($this->lib_e($newUnit)));
1092         } else {
1093             return array("number", $this->assertNumber($arg), "");
1094         }
1095     }
1096 
1097     /**
1098      * Helper function to get arguments for color manipulation functions.
1099      * takes a list that contains a color like thing and a percentage
1100      */
1101     public function colorArgs($args) {
1102         if ($args[0] != 'list' || count($args[2]) < 2) {
1103             return array(array('color', 0, 0, 0), 0);
1104         }
1105         list($color, $delta) = $args[2];
1106         $color = $this->assertColor($color);
1107         $delta = floatval($delta[1]);
1108 
1109         return array($color, $delta);
1110     }
1111 
1112     protected function lib_darken($args) {
1113         list($color, $delta) = $this->colorArgs($args);
1114 
1115         $hsl = $this->toHSL($color);
1116         $hsl[3] = $this->clamp($hsl[3] - $delta, 100);
1117         return $this->toRGB($hsl);
1118     }
1119 
1120     protected function lib_lighten($args) {
1121         list($color, $delta) = $this->colorArgs($args);
1122 
1123         $hsl = $this->toHSL($color);
1124         $hsl[3] = $this->clamp($hsl[3] + $delta, 100);
1125         return $this->toRGB($hsl);
1126     }
1127 
1128     protected function lib_saturate($args) {
1129         list($color, $delta) = $this->colorArgs($args);
1130 
1131         $hsl = $this->toHSL($color);
1132         $hsl[2] = $this->clamp($hsl[2] + $delta, 100);
1133         return $this->toRGB($hsl);
1134     }
1135 
1136     protected function lib_desaturate($args) {
1137         list($color, $delta) = $this->colorArgs($args);
1138 
1139         $hsl = $this->toHSL($color);
1140         $hsl[2] = $this->clamp($hsl[2] - $delta, 100);
1141         return $this->toRGB($hsl);
1142     }
1143 
1144     protected function lib_spin($args) {
1145         list($color, $delta) = $this->colorArgs($args);
1146 
1147         $hsl = $this->toHSL($color);
1148 
1149         $hsl[1] = $hsl[1] + $delta % 360;
1150         if ($hsl[1] < 0) $hsl[1] += 360;
1151 
1152         return $this->toRGB($hsl);
1153     }
1154 
1155     protected function lib_fadeout($args) {
1156         list($color, $delta) = $this->colorArgs($args);
1157         $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta/100);
1158         return $color;
1159     }
1160 
1161     protected function lib_fadein($args) {
1162         list($color, $delta) = $this->colorArgs($args);
1163         $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta/100);
1164         return $color;
1165     }
1166 
1167     protected function lib_hue($color) {
1168         $hsl = $this->toHSL($this->assertColor($color));
1169         return round($hsl[1]);
1170     }
1171 
1172     protected function lib_saturation($color) {
1173         $hsl = $this->toHSL($this->assertColor($color));
1174         return round($hsl[2]);
1175     }
1176 
1177     protected function lib_lightness($color) {
1178         $hsl = $this->toHSL($this->assertColor($color));
1179         return round($hsl[3]);
1180     }
1181 
1182     // get the alpha of a color
1183     // defaults to 1 for non-colors or colors without an alpha
1184     protected function lib_alpha($value) {
1185         if (!is_null($color = $this->coerceColor($value))) {
1186             return isset($color[4]) ? $color[4] : 1;
1187         }
1188     }
1189 
1190     // set the alpha of the color
1191     protected function lib_fade($args) {
1192         list($color, $alpha) = $this->colorArgs($args);
1193         $color[4] = $this->clamp($alpha / 100.0);
1194         return $color;
1195     }
1196 
1197     protected function lib_percentage($arg) {
1198         $num = $this->assertNumber($arg);
1199         return array("number", $num*100, "%");
1200     }
1201 
1202     // mixes two colors by weight
1203     // mix(@color1, @color2, [@weight: 50%]);
1204     // http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method
1205     protected function lib_mix($args) {
1206         if ($args[0] != "list" || count($args[2]) < 2)
1207             $this->throwError("mix expects (color1, color2, weight)");
1208 
1209         list($first, $second) = $args[2];
1210         $first = $this->assertColor($first);
1211         $second = $this->assertColor($second);
1212 
1213         $first_a = $this->lib_alpha($first);
1214         $second_a = $this->lib_alpha($second);
1215 
1216         if (isset($args[2][2])) {
1217             $weight = $args[2][2][1] / 100.0;
1218         } else {
1219             $weight = 0.5;
1220         }
1221 
1222         $w = $weight * 2 - 1;
1223         $a = $first_a - $second_a;
1224 
1225         $w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0;
1226         $w2 = 1.0 - $w1;
1227 
1228         $new = array('color',
1229             $w1 * $first[1] + $w2 * $second[1],
1230             $w1 * $first[2] + $w2 * $second[2],
1231             $w1 * $first[3] + $w2 * $second[3],
1232         );
1233 
1234         if ($first_a != 1.0 || $second_a != 1.0) {
1235             $new[] = $first_a * $weight + $second_a * ($weight - 1);
1236         }
1237 
1238         return $this->fixColor($new);
1239     }
1240 
1241     protected function lib_contrast($args) {
1242         $darkColor  = array('color', 0, 0, 0);
1243         $lightColor = array('color', 255, 255, 255);
1244         $threshold  = 0.43;
1245 
1246         if ( $args[0] == 'list' ) {
1247             $inputColor = ( isset($args[2][0]) ) ? $this->assertColor($args[2][0])  : $lightColor;
1248             $darkColor  = ( isset($args[2][1]) ) ? $this->assertColor($args[2][1])  : $darkColor;
1249             $lightColor = ( isset($args[2][2]) ) ? $this->assertColor($args[2][2])  : $lightColor;
1250             $threshold  = ( isset($args[2][3]) ) ? $this->assertNumber($args[2][3]) : $threshold;
1251         }
1252         else {
1253             $inputColor  = $this->assertColor($args);
1254         }
1255 
1256         $inputColor = $this->coerceColor($inputColor);
1257         $darkColor  = $this->coerceColor($darkColor);
1258         $lightColor = $this->coerceColor($lightColor);
1259 
1260         //Figure out which is actually light and dark!
1261         if ( $this->lib_luma($darkColor) > $this->lib_luma($lightColor) ) {
1262             $t  = $lightColor;
1263             $lightColor = $darkColor;
1264             $darkColor  = $t;
1265         }
1266 
1267         $inputColor_alpha = $this->lib_alpha($inputColor);
1268         if ( ( $this->lib_luma($inputColor) * $inputColor_alpha) < $threshold) {
1269             return $lightColor;
1270         }
1271         return $darkColor;
1272     }
1273 
1274     protected function lib_luma($color) {
1275         $color = $this->coerceColor($color);
1276         return (0.2126 * $color[0] / 255) + (0.7152 * $color[1] / 255) + (0.0722 * $color[2] / 255);
1277     }
1278 
1279 
1280     public function assertColor($value, $error = "expected color value") {
1281         $color = $this->coerceColor($value);
1282         if (is_null($color)) $this->throwError($error);
1283         return $color;
1284     }
1285 
1286     public function assertNumber($value, $error = "expecting number") {
1287         if ($value[0] == "number") return $value[1];
1288         $this->throwError($error);
1289     }
1290 
1291     public function assertArgs($value, $expectedArgs, $name="") {
1292         if ($expectedArgs == 1) {
1293             return $value;
1294         } else {
1295             if ($value[0] !== "list" || $value[1] != ",") $this->throwError("expecting list");
1296             $values = $value[2];
1297             $numValues = count($values);
1298             if ($expectedArgs != $numValues) {
1299                 if ($name) {
1300                     $name = $name . ": ";
1301                 }
1302 
1303                 $this->throwError("${name}expecting $expectedArgs arguments, got $numValues");
1304             }
1305 
1306             return $values;
1307         }
1308     }
1309 
1310     protected function toHSL($color) {
1311         if ($color[0] == 'hsl') return $color;
1312 
1313         $r = $color[1] / 255;
1314         $g = $color[2] / 255;
1315         $b = $color[3] / 255;
1316 
1317         $min = min($r, $g, $b);
1318         $max = max($r, $g, $b);
1319 
1320         $L = ($min + $max) / 2;
1321         if ($min == $max) {
1322             $S = $H = 0;
1323         } else {
1324             if ($L < 0.5)
1325                 $S = ($max - $min)/($max + $min);
1326             else
1327                 $S = ($max - $min)/(2.0 - $max - $min);
1328 
1329             if ($r == $max) $H = ($g - $b)/($max - $min);
1330             elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min);
1331             elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min);
1332 
1333         }
1334 
1335         $out = array('hsl',
1336             ($H < 0 ? $H + 6 : $H)*60,
1337             $S*100,
1338             $L*100,
1339         );
1340 
1341         if (count($color) > 4) $out[] = $color[4]; // copy alpha
1342         return $out;
1343     }
1344 
1345     protected function toRGB_helper($comp, $temp1, $temp2) {
1346         if ($comp < 0) $comp += 1.0;
1347         elseif ($comp > 1) $comp -= 1.0;
1348 
1349         if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp;
1350         if (2 * $comp < 1) return $temp2;
1351         if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6;
1352 
1353         return $temp1;
1354     }
1355 
1356     /**
1357      * Converts a hsl array into a color value in rgb.
1358      * Expects H to be in range of 0 to 360, S and L in 0 to 100
1359      */
1360     protected function toRGB($color) {
1361         if ($color[0] == 'color') return $color;
1362 
1363         $H = $color[1] / 360;
1364         $S = $color[2] / 100;
1365         $L = $color[3] / 100;
1366 
1367         if ($S == 0) {
1368             $r = $g = $b = $L;
1369         } else {
1370             $temp2 = $L < 0.5 ?
1371                 $L*(1.0 + $S) :
1372                 $L + $S - $L * $S;
1373 
1374             $temp1 = 2.0 * $L - $temp2;
1375 
1376             $r = $this->toRGB_helper($H + 1/3, $temp1, $temp2);
1377             $g = $this->toRGB_helper($H, $temp1, $temp2);
1378             $b = $this->toRGB_helper($H - 1/3, $temp1, $temp2);
1379         }
1380 
1381         // $out = array('color', round($r*255), round($g*255), round($b*255));
1382         $out = array('color', $r*255, $g*255, $b*255);
1383         if (count($color) > 4) $out[] = $color[4]; // copy alpha
1384         return $out;
1385     }
1386 
1387     protected function clamp($v, $max = 1, $min = 0) {
1388         return min($max, max($min, $v));
1389     }
1390 
1391     /**
1392      * Convert the rgb, rgba, hsl color literals of function type
1393      * as returned by the parser into values of color type.
1394      */
1395     protected function funcToColor($func) {
1396         $fname = $func[1];
1397         if ($func[2][0] != 'list') return false; // need a list of arguments
1398         $rawComponents = $func[2][2];
1399 
1400         if ($fname == 'hsl' || $fname == 'hsla') {
1401             $hsl = array('hsl');
1402             $i = 0;
1403             foreach ($rawComponents as $c) {
1404                 $val = $this->reduce($c);
1405                 $val = isset($val[1]) ? floatval($val[1]) : 0;
1406 
1407                 if ($i == 0) $clamp = 360;
1408                 elseif ($i < 3) $clamp = 100;
1409                 else $clamp = 1;
1410 
1411                 $hsl[] = $this->clamp($val, $clamp);
1412                 $i++;
1413             }
1414 
1415             while (count($hsl) < 4) $hsl[] = 0;
1416             return $this->toRGB($hsl);
1417 
1418         } elseif ($fname == 'rgb' || $fname == 'rgba') {
1419             $components = array();
1420             $i = 1;
1421             foreach ($rawComponents as $c) {
1422                 $c = $this->reduce($c);
1423                 if ($i < 4) {
1424                     if ($c[0] == "number" && $c[2] == "%") {
1425                         $components[] = 255 * ($c[1] / 100);
1426                     } else {
1427                         $components[] = floatval($c[1]);
1428                     }
1429                 } elseif ($i == 4) {
1430                     if ($c[0] == "number" && $c[2] == "%") {
1431                         $components[] = 1.0 * ($c[1] / 100);
1432                     } else {
1433                         $components[] = floatval($c[1]);
1434                     }
1435                 } else break;
1436 
1437                 $i++;
1438             }
1439             while (count($components) < 3) $components[] = 0;
1440             array_unshift($components, 'color');
1441             return $this->fixColor($components);
1442         }
1443 
1444         return false;
1445     }
1446 
1447     protected function reduce($value, $forExpression = false) {
1448         switch ($value[0]) {
1449         case "interpolate":
1450             $reduced = $this->reduce($value[1]);
1451             $var = $this->compileValue($reduced);
1452             $res = $this->reduce(array("variable", $this->vPrefix . $var));
1453 
1454             if ($res[0] == "raw_color") {
1455                 $res = $this->coerceColor($res);
1456             }
1457 
1458             if (empty($value[2])) $res = $this->lib_e($res);
1459 
1460             return $res;
1461         case "variable":
1462             $key = $value[1];
1463             if (is_array($key)) {
1464                 $key = $this->reduce($key);
1465                 $key = $this->vPrefix . $this->compileValue($this->lib_e($key));
1466             }
1467 
1468             $seen =& $this->env->seenNames;
1469 
1470             if (!empty($seen[$key])) {
1471                 $this->throwError("infinite loop detected: $key");
1472             }
1473 
1474             $seen[$key] = true;
1475             $out = $this->reduce($this->get($key));
1476             $seen[$key] = false;
1477             return $out;
1478         case "list":
1479             foreach ($value[2] as &$item) {
1480                 $item = $this->reduce($item, $forExpression);
1481             }
1482             return $value;
1483         case "expression":
1484             return $this->evaluate($value);
1485         case "string":
1486             foreach ($value[2] as &$part) {
1487                 if (is_array($part)) {
1488                     $strip = $part[0] == "variable";
1489                     $part = $this->reduce($part);
1490                     if ($strip) $part = $this->lib_e($part);
1491                 }
1492             }
1493             return $value;
1494         case "escape":
1495             list(,$inner) = $value;
1496             return $this->lib_e($this->reduce($inner));
1497         case "function":
1498             $color = $this->funcToColor($value);
1499             if ($color) return $color;
1500 
1501             list(, $name, $args) = $value;
1502             if ($name == "%") $name = "_sprintf";
1503 
1504             $f = isset($this->libFunctions[$name]) ?
1505                 $this->libFunctions[$name] : array($this, 'lib_'.str_replace('-', '_', $name));
1506 
1507             if (is_callable($f)) {
1508                 if ($args[0] == 'list')
1509                     $args = self::compressList($args[2], $args[1]);
1510 
1511                 $ret = call_user_func($f, $this->reduce($args, true), $this);
1512 
1513                 if (is_null($ret)) {
1514                     return array("string", "", array(
1515                         $name, "(", $args, ")"
1516                     ));
1517                 }
1518 
1519                 // convert to a typed value if the result is a php primitive
1520                 if (is_numeric($ret)) $ret = array('number', $ret, "");
1521                 elseif (!is_array($ret)) $ret = array('keyword', $ret);
1522 
1523                 return $ret;
1524             }
1525 
1526             // plain function, reduce args
1527             $value[2] = $this->reduce($value[2]);
1528             return $value;
1529         case "unary":
1530             list(, $op, $exp) = $value;
1531             $exp = $this->reduce($exp);
1532 
1533             if ($exp[0] == "number") {
1534                 switch ($op) {
1535                 case "+":
1536                     return $exp;
1537                 case "-":
1538                     $exp[1] *= -1;
1539                     return $exp;
1540                 }
1541             }
1542             return array("string", "", array($op, $exp));
1543         }
1544 
1545         if ($forExpression) {
1546             switch ($value[0]) {
1547             case "keyword":
1548                 if ($color = $this->coerceColor($value)) {
1549                     return $color;
1550                 }
1551                 break;
1552             case "raw_color":
1553                 return $this->coerceColor($value);
1554             }
1555         }
1556 
1557         return $value;
1558     }
1559 
1560 
1561     // coerce a value for use in color operation
1562     protected function coerceColor($value) {
1563         switch($value[0]) {
1564             case 'color': return $value;
1565             case 'raw_color':
1566                 $c = array("color", 0, 0, 0);
1567                 $colorStr = substr($value[1], 1);
1568                 $num = hexdec($colorStr);
1569                 $width = strlen($colorStr) == 3 ? 16 : 256;
1570 
1571                 for ($i = 3; $i > 0; $i--) { // 3 2 1
1572                     $t = $num % $width;
1573                     $num /= $width;
1574 
1575                     $c[$i] = $t * (256/$width) + $t * floor(16/$width);
1576                 }
1577 
1578                 return $c;
1579             case 'keyword':
1580                 $name = $value[1];
1581                 if (isset(self::$cssColors[$name])) {
1582                     $rgba = explode(',', self::$cssColors[$name]);
1583 
1584                     if(isset($rgba[3]))
1585                         return array('color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]);
1586 
1587                     return array('color', $rgba[0], $rgba[1], $rgba[2]);
1588                 }
1589                 return null;
1590         }
1591     }
1592 
1593     // make something string like into a string
1594     protected function coerceString($value) {
1595         switch ($value[0]) {
1596         case "string":
1597             return $value;
1598         case "keyword":
1599             return array("string", "", array($value[1]));
1600         }
1601         return null;
1602     }
1603 
1604     // turn list of length 1 into value type
1605     protected function flattenList($value) {
1606         if ($value[0] == "list" && count($value[2]) == 1) {
1607             return $this->flattenList($value[2][0]);
1608         }
1609         return $value;
1610     }
1611 
1612     public function toBool($a) {
1613         if ($a) return self::$TRUE;
1614         else return self::$FALSE;
1615     }
1616 
1617     // evaluate an expression
1618     protected function evaluate($exp) {
1619         list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp;
1620 
1621         $left = $this->reduce($left, true);
1622         $right = $this->reduce($right, true);
1623 
1624         if ($leftColor = $this->coerceColor($left)) {
1625             $left = $leftColor;
1626         }
1627 
1628         if ($rightColor = $this->coerceColor($right)) {
1629             $right = $rightColor;
1630         }
1631 
1632         $ltype = $left[0];
1633         $rtype = $right[0];
1634 
1635         // operators that work on all types
1636         if ($op == "and") {
1637             return $this->toBool($left == self::$TRUE && $right == self::$TRUE);
1638         }
1639 
1640         if ($op == "=") {
1641             return $this->toBool($this->eq($left, $right) );
1642         }
1643 
1644         if ($op == "+" && !is_null($str = $this->stringConcatenate($left, $right))) {
1645             return $str;
1646         }
1647 
1648         // type based operators
1649         $fname = "op_${ltype}_${rtype}";
1650         if (is_callable(array($this, $fname))) {
1651             $out = $this->$fname($op, $left, $right);
1652             if (!is_null($out)) return $out;
1653         }
1654 
1655         // make the expression look it did before being parsed
1656         $paddedOp = $op;
1657         if ($whiteBefore) $paddedOp = " " . $paddedOp;
1658         if ($whiteAfter) $paddedOp .= " ";
1659 
1660         return array("string", "", array($left, $paddedOp, $right));
1661     }
1662 
1663     protected function stringConcatenate($left, $right) {
1664         if ($strLeft = $this->coerceString($left)) {
1665             if ($right[0] == "string") {
1666                 $right[1] = "";
1667             }
1668             $strLeft[2][] = $right;
1669             return $strLeft;
1670         }
1671 
1672         if ($strRight = $this->coerceString($right)) {
1673             array_unshift($strRight[2], $left);
1674             return $strRight;
1675         }
1676     }
1677 
1678 
1679     // make sure a color's components don't go out of bounds
1680     protected function fixColor($c) {
1681         foreach (range(1, 3) as $i) {
1682             if ($c[$i] < 0) $c[$i] = 0;
1683             if ($c[$i] > 255) $c[$i] = 255;
1684         }
1685 
1686         return $c;
1687     }
1688 
1689     protected function op_number_color($op, $lft, $rgt) {
1690         if ($op == '+' || $op == '*') {
1691             return $this->op_color_number($op, $rgt, $lft);
1692         }
1693     }
1694 
1695     protected function op_color_number($op, $lft, $rgt) {
1696         if ($rgt[0] == '%') $rgt[1] /= 100;
1697 
1698         return $this->op_color_color($op, $lft,
1699             array_fill(1, count($lft) - 1, $rgt[1]));
1700     }
1701 
1702     protected function op_color_color($op, $left, $right) {
1703         $out = array('color');
1704         $max = count($left) > count($right) ? count($left) : count($right);
1705         foreach (range(1, $max - 1) as $i) {
1706             $lval = isset($left[$i]) ? $left[$i] : 0;
1707             $rval = isset($right[$i]) ? $right[$i] : 0;
1708             switch ($op) {
1709             case '+':
1710                 $out[] = $lval + $rval;
1711                 break;
1712             case '-':
1713                 $out[] = $lval - $rval;
1714                 break;
1715             case '*':
1716                 $out[] = $lval * $rval;
1717                 break;
1718             case '%':
1719                 $out[] = $lval % $rval;
1720                 break;
1721             case '/':
1722                 if ($rval == 0) $this->throwError("evaluate error: can't divide by zero");
1723                 $out[] = $lval / $rval;
1724                 break;
1725             default:
1726                 $this->throwError('evaluate error: color op number failed on op '.$op);
1727             }
1728         }
1729         return $this->fixColor($out);
1730     }
1731 
1732     function lib_red($color){
1733         $color = $this->coerceColor($color);
1734         if (is_null($color)) {
1735             $this->throwError('color expected for red()');
1736         }
1737 
1738         return $color[1];
1739     }
1740 
1741     function lib_green($color){
1742         $color = $this->coerceColor($color);
1743         if (is_null($color)) {
1744             $this->throwError('color expected for green()');
1745         }
1746 
1747         return $color[2];
1748     }
1749 
1750     function lib_blue($color){
1751         $color = $this->coerceColor($color);
1752         if (is_null($color)) {
1753             $this->throwError('color expected for blue()');
1754         }
1755 
1756         return $color[3];
1757     }
1758 
1759 
1760     // operator on two numbers
1761     protected function op_number_number($op, $left, $right) {
1762         $unit = empty($left[2]) ? $right[2] : $left[2];
1763 
1764         $value = 0;
1765         switch ($op) {
1766         case '+':
1767             $value = $left[1] + $right[1];
1768             break;
1769         case '*':
1770             $value = $left[1] * $right[1];
1771             break;
1772         case '-':
1773             $value = $left[1] - $right[1];
1774             break;
1775         case '%':
1776             $value = $left[1] % $right[1];
1777             break;
1778         case '/':
1779             if ($right[1] == 0) $this->throwError('parse error: divide by zero');
1780             $value = $left[1] / $right[1];
1781             break;
1782         case '<':
1783             return $this->toBool($left[1] < $right[1]);
1784         case '>':
1785             return $this->toBool($left[1] > $right[1]);
1786         case '>=':
1787             return $this->toBool($left[1] >= $right[1]);
1788         case '=<':
1789             return $this->toBool($left[1] <= $right[1]);
1790         default:
1791             $this->throwError('parse error: unknown number operator: '.$op);
1792         }
1793 
1794         return array("number", $value, $unit);
1795     }
1796 
1797 
1798     /* environment functions */
1799 
1800     protected function makeOutputBlock($type, $selectors = null) {
1801         $b = new stdclass;
1802         $b->lines = array();
1803         $b->children = array();
1804         $b->selectors = $selectors;
1805         $b->type = $type;
1806         $b->parent = $this->scope;
1807         return $b;
1808     }
1809 
1810     // the state of execution
1811     protected function pushEnv($block = null) {
1812         $e = new stdclass;
1813         $e->parent = $this->env;
1814         $e->store = array();
1815         $e->block = $block;
1816 
1817         $this->env = $e;
1818         return $e;
1819     }
1820 
1821     // pop something off the stack
1822     protected function popEnv() {
1823         $old = $this->env;
1824         $this->env = $this->env->parent;
1825         return $old;
1826     }
1827 
1828     // set something in the current env
1829     protected function set($name, $value) {
1830         $this->env->store[$name] = $value;
1831     }
1832 
1833 
1834     // get the highest occurrence entry for a name
1835     protected function get($name) {
1836         $current = $this->env;
1837 
1838         $isArguments = $name == $this->vPrefix . 'arguments';
1839         while ($current) {
1840             if ($isArguments && isset($current->arguments)) {
1841                 return array('list', ' ', $current->arguments);
1842             }
1843 
1844             if (isset($current->store[$name]))
1845                 return $current->store[$name];
1846             else {
1847                 $current = isset($current->storeParent) ?
1848                     $current->storeParent : $current->parent;
1849             }
1850         }
1851 
1852         $this->throwError("variable $name is undefined");
1853     }
1854 
1855     // inject array of unparsed strings into environment as variables
1856     protected function injectVariables($args) {
1857         $this->pushEnv();
1858         $parser = new lessc_parser($this, __METHOD__);
1859         foreach ($args as $name => $strValue) {
1860             if ($name{0} != '@') $name = '@'.$name;
1861             $parser->count = 0;
1862             $parser->buffer = (string)$strValue;
1863             if (!$parser->propertyValue($value)) {
1864                 throw new Exception("failed to parse passed in variable $name: $strValue");
1865             }
1866 
1867             $this->set($name, $value);
1868         }
1869     }
1870 
1871     /**
1872      * Initialize any static state, can initialize parser for a file
1873      * $opts isn't used yet
1874      */
1875     public function __construct($fname = null) {
1876         if ($fname !== null) {
1877             // used for deprecated parse method
1878             $this->_parseFile = $fname;
1879         }
1880     }
1881 
1882     public function compile($string, $name = null) {
1883         $locale = setlocale(LC_NUMERIC, 0);
1884         setlocale(LC_NUMERIC, "C");
1885 
1886         $this->parser = $this->makeParser($name);
1887         $root = $this->parser->parse($string);
1888 
1889         $this->env = null;
1890         $this->scope = null;
1891 
1892         $this->formatter = $this->newFormatter();
1893 
1894         if (!empty($this->registeredVars)) {
1895             $this->injectVariables($this->registeredVars);
1896         }
1897 
1898         $this->sourceParser = $this->parser; // used for error messages
1899         $this->compileBlock($root);
1900 
1901         ob_start();
1902         $this->formatter->block($this->scope);
1903         $out = ob_get_clean();
1904         setlocale(LC_NUMERIC, $locale);
1905         return $out;
1906     }
1907 
1908     public function compileFile($fname, $outFname = null) {
1909         if (!is_readable($fname)) {
1910             throw new Exception('load error: failed to find '.$fname);
1911         }
1912 
1913         $pi = pathinfo($fname);
1914 
1915         $oldImport = $this->importDir;
1916 
1917         $this->importDir = (array)$this->importDir;
1918         $this->importDir[] = $pi['dirname'].'/';
1919 
1920         $this->addParsedFile($fname);
1921 
1922         $out = $this->compile(file_get_contents($fname), $fname);
1923 
1924         $this->importDir = $oldImport;
1925 
1926         if ($outFname !== null) {
1927             return file_put_contents($outFname, $out);
1928         }
1929 
1930         return $out;
1931     }
1932 
1933     // compile only if changed input has changed or output doesn't exist
1934     public function checkedCompile($in, $out) {
1935         if (!is_file($out) || filemtime($in) > filemtime($out)) {
1936             $this->compileFile($in, $out);
1937             return true;
1938         }
1939         return false;
1940     }
1941 
1942     /**
1943      * Execute lessphp on a .less file or a lessphp cache structure
1944      *
1945      * The lessphp cache structure contains information about a specific
1946      * less file having been parsed. It can be used as a hint for future
1947      * calls to determine whether or not a rebuild is required.
1948      *
1949      * The cache structure contains two important keys that may be used
1950      * externally:
1951      *
1952      * compiled: The final compiled CSS
1953      * updated: The time (in seconds) the CSS was last compiled
1954      *
1955      * The cache structure is a plain-ol' PHP associative array and can
1956      * be serialized and unserialized without a hitch.
1957      *
1958      * @param mixed $in Input
1959      * @param bool $force Force rebuild?
1960      * @return array lessphp cache structure
1961      */
1962     public function cachedCompile($in, $force = false) {
1963         // assume no root
1964         $root = null;
1965 
1966         if (is_string($in)) {
1967             $root = $in;
1968         } elseif (is_array($in) and isset($in['root'])) {
1969             if ($force or ! isset($in['files'])) {
1970                 // If we are forcing a recompile or if for some reason the
1971                 // structure does not contain any file information we should
1972                 // specify the root to trigger a rebuild.
1973                 $root = $in['root'];
1974             } elseif (isset($in['files']) and is_array($in['files'])) {
1975                 foreach ($in['files'] as $fname => $ftime ) {
1976                     if (!file_exists($fname) or filemtime($fname) > $ftime) {
1977                         // One of the files we knew about previously has changed
1978                         // so we should look at our incoming root again.
1979                         $root = $in['root'];
1980                         break;
1981                     }
1982                 }
1983             }
1984         } else {
1985             // TODO: Throw an exception? We got neither a string nor something
1986             // that looks like a compatible lessphp cache structure.
1987             return null;
1988         }
1989 
1990         if ($root !== null) {
1991             // If we have a root value which means we should rebuild.
1992             $out = array();
1993             $out['root'] = $root;
1994             $out['compiled'] = $this->compileFile($root);
1995             $out['files'] = $this->allParsedFiles();
1996             $out['updated'] = time();
1997             return $out;
1998         } else {
1999             // No changes, pass back the structure
2000             // we were given initially.
2001             return $in;
2002         }
2003 
2004     }
2005 
2006     // parse and compile buffer
2007     // This is deprecated
2008     public function parse($str = null, $initialVariables = null) {
2009         if (is_array($str)) {
2010             $initialVariables = $str;
2011             $str = null;
2012         }
2013 
2014         $oldVars = $this->registeredVars;
2015         if ($initialVariables !== null) {
2016             $this->setVariables($initialVariables);
2017         }
2018 
2019         if ($str == null) {
2020             if (empty($this->_parseFile)) {
2021                 throw new exception("nothing to parse");
2022             }
2023 
2024             $out = $this->compileFile($this->_parseFile);
2025         } else {
2026             $out = $this->compile($str);
2027         }
2028 
2029         $this->registeredVars = $oldVars;
2030         return $out;
2031     }
2032 
2033     protected function makeParser($name) {
2034         $parser = new lessc_parser($this, $name);
2035         $parser->writeComments = $this->preserveComments;
2036 
2037         return $parser;
2038     }
2039 
2040     public function setFormatter($name) {
2041         $this->formatterName = $name;
2042     }
2043 
2044     protected function newFormatter() {
2045         $className = "lessc_formatter_lessjs";
2046         if (!empty($this->formatterName)) {
2047             if (!is_string($this->formatterName))
2048                 return $this->formatterName;
2049             $className = "lessc_formatter_$this->formatterName";
2050         }
2051 
2052         return new $className;
2053     }
2054 
2055     public function setPreserveComments($preserve) {
2056         $this->preserveComments = $preserve;
2057     }
2058 
2059     public function registerFunction($name, $func) {
2060         $this->libFunctions[$name] = $func;
2061     }
2062 
2063     public function unregisterFunction($name) {
2064         unset($this->libFunctions[$name]);
2065     }
2066 
2067     public function setVariables($variables) {
2068         $this->registeredVars = array_merge($this->registeredVars, $variables);
2069     }
2070 
2071     public function unsetVariable($name) {
2072         unset($this->registeredVars[$name]);
2073     }
2074 
2075     public function setImportDir($dirs) {
2076         $this->importDir = (array)$dirs;
2077     }
2078 
2079     public function addImportDir($dir) {
2080         $this->importDir = (array)$this->importDir;
2081         $this->importDir[] = $dir;
2082     }
2083 
2084     public function allParsedFiles() {
2085         return $this->allParsedFiles;
2086     }
2087 
2088     public function addParsedFile($file) {
2089         $this->allParsedFiles[realpath($file)] = filemtime($file);
2090     }
2091 
2092     /**
2093      * Uses the current value of $this->count to show line and line number
2094      */
2095     public function throwError($msg = null) {
2096         if ($this->sourceLoc >= 0) {
2097             $this->sourceParser->throwError($msg, $this->sourceLoc);
2098         }
2099         throw new exception($msg);
2100     }
2101 
2102     // compile file $in to file $out if $in is newer than $out
2103     // returns true when it compiles, false otherwise
2104     public static function ccompile($in, $out, $less = null) {
2105         if ($less === null) {
2106             $less = new self;
2107         }
2108         return $less->checkedCompile($in, $out);
2109     }
2110 
2111     public static function cexecute($in, $force = false, $less = null) {
2112         if ($less === null) {
2113             $less = new self;
2114         }
2115         return $less->cachedCompile($in, $force);
2116     }
2117 
2118     static protected $cssColors = array(
2119         'aliceblue' => '240,248,255',
2120         'antiquewhite' => '250,235,215',
2121         'aqua' => '0,255,255',
2122         'aquamarine' => '127,255,212',
2123         'azure' => '240,255,255',
2124         'beige' => '245,245,220',
2125         'bisque' => '255,228,196',
2126         'black' => '0,0,0',
2127         'blanchedalmond' => '255,235,205',
2128         'blue' => '0,0,255',
2129         'blueviolet' => '138,43,226',
2130         'brown' => '165,42,42',
2131         'burlywood' => '222,184,135',
2132         'cadetblue' => '95,158,160',
2133         'chartreuse' => '127,255,0',
2134         'chocolate' => '210,105,30',
2135         'coral' => '255,127,80',
2136         'cornflowerblue' => '100,149,237',
2137         'cornsilk' => '255,248,220',
2138         'crimson' => '220,20,60',
2139         'cyan' => '0,255,255',
2140         'darkblue' => '0,0,139',
2141         'darkcyan' => '0,139,139',
2142         'darkgoldenrod' => '184,134,11',
2143         'darkgray' => '169,169,169',
2144         'darkgreen' => '0,100,0',
2145         'darkgrey' => '169,169,169',
2146         'darkkhaki' => '189,183,107',
2147         'darkmagenta' => '139,0,139',
2148         'darkolivegreen' => '85,107,47',
2149         'darkorange' => '255,140,0',
2150         'darkorchid' => '153,50,204',
2151         'darkred' => '139,0,0',
2152         'darksalmon' => '233,150,122',
2153         'darkseagreen' => '143,188,143',
2154         'darkslateblue' => '72,61,139',
2155         'darkslategray' => '47,79,79',
2156         'darkslategrey' => '47,79,79',
2157         'darkturquoise' => '0,206,209',
2158         'darkviolet' => '148,0,211',
2159         'deeppink' => '255,20,147',
2160         'deepskyblue' => '0,191,255',
2161         'dimgray' => '105,105,105',
2162         'dimgrey' => '105,105,105',
2163         'dodgerblue' => '30,144,255',
2164         'firebrick' => '178,34,34',
2165         'floralwhite' => '255,250,240',
2166         'forestgreen' => '34,139,34',
2167         'fuchsia' => '255,0,255',
2168         'gainsboro' => '220,220,220',
2169         'ghostwhite' => '248,248,255',
2170         'gold' => '255,215,0',
2171         'goldenrod' => '218,165,32',
2172         'gray' => '128,128,128',
2173         'green' => '0,128,0',
2174         'greenyellow' => '173,255,47',
2175         'grey' => '128,128,128',
2176         'honeydew' => '240,255,240',
2177         'hotpink' => '255,105,180',
2178         'indianred' => '205,92,92',
2179         'indigo' => '75,0,130',
2180         'ivory' => '255,255,240',
2181         'khaki' => '240,230,140',
2182         'lavender' => '230,230,250',
2183         'lavenderblush' => '255,240,245',
2184         'lawngreen' => '124,252,0',
2185         'lemonchiffon' => '255,250,205',
2186         'lightblue' => '173,216,230',
2187         'lightcoral' => '240,128,128',
2188         'lightcyan' => '224,255,255',
2189         'lightgoldenrodyellow' => '250,250,210',
2190         'lightgray' => '211,211,211',
2191         'lightgreen' => '144,238,144',
2192         'lightgrey' => '211,211,211',
2193         'lightpink' => '255,182,193',
2194         'lightsalmon' => '255,160,122',
2195         'lightseagreen' => '32,178,170',
2196         'lightskyblue' => '135,206,250',
2197         'lightslategray' => '119,136,153',
2198         'lightslategrey' => '119,136,153',
2199         'lightsteelblue' => '176,196,222',
2200         'lightyellow' => '255,255,224',
2201         'lime' => '0,255,0',
2202         'limegreen' => '50,205,50',
2203         'linen' => '250,240,230',
2204         'magenta' => '255,0,255',
2205         'maroon' => '128,0,0',
2206         'mediumaquamarine' => '102,205,170',
2207         'mediumblue' => '0,0,205',
2208         'mediumorchid' => '186,85,211',
2209         'mediumpurple' => '147,112,219',
2210         'mediumseagreen' => '60,179,113',
2211         'mediumslateblue' => '123,104,238',
2212         'mediumspringgreen' => '0,250,154',
2213         'mediumturquoise' => '72,209,204',
2214         'mediumvioletred' => '199,21,133',
2215         'midnightblue' => '25,25,112',
2216         'mintcream' => '245,255,250',
2217         'mistyrose' => '255,228,225',
2218         'moccasin' => '255,228,181',
2219         'navajowhite' => '255,222,173',
2220         'navy' => '0,0,128',
2221         'oldlace' => '253,245,230',
2222         'olive' => '128,128,0',
2223         'olivedrab' => '107,142,35',
2224         'orange' => '255,165,0',
2225         'orangered' => '255,69,0',
2226         'orchid' => '218,112,214',
2227         'palegoldenrod' => '238,232,170',
2228         'palegreen' => '152,251,152',
2229         'paleturquoise' => '175,238,238',
2230         'palevioletred' => '219,112,147',
2231         'papayawhip' => '255,239,213',
2232         'peachpuff' => '255,218,185',
2233         'peru' => '205,133,63',
2234         'pink' => '255,192,203',
2235         'plum' => '221,160,221',
2236         'powderblue' => '176,224,230',
2237         'purple' => '128,0,128',
2238         'red' => '255,0,0',
2239         'rosybrown' => '188,143,143',
2240         'royalblue' => '65,105,225',
2241         'saddlebrown' => '139,69,19',
2242         'salmon' => '250,128,114',
2243         'sandybrown' => '244,164,96',
2244         'seagreen' => '46,139,87',
2245         'seashell' => '255,245,238',
2246         'sienna' => '160,82,45',
2247         'silver' => '192,192,192',
2248         'skyblue' => '135,206,235',
2249         'slateblue' => '106,90,205',
2250         'slategray' => '112,128,144',
2251         'slategrey' => '112,128,144',
2252         'snow' => '255,250,250',
2253         'springgreen' => '0,255,127',
2254         'steelblue' => '70,130,180',
2255         'tan' => '210,180,140',
2256         'teal' => '0,128,128',
2257         'thistle' => '216,191,216',
2258         'tomato' => '255,99,71',
2259         'transparent' => '0,0,0,0',
2260         'turquoise' => '64,224,208',
2261         'violet' => '238,130,238',
2262         'wheat' => '245,222,179',
2263         'white' => '255,255,255',
2264         'whitesmoke' => '245,245,245',
2265         'yellow' => '255,255,0',
2266         'yellowgreen' => '154,205,50'
2267     );
2268 }
2269 
2270 // responsible for taking a string of LESS code and converting it into a
2271 // syntax tree
2272 class lessc_parser {
2273     static protected $nextBlockId = 0; // used to uniquely identify blocks
2274 
2275     static protected $precedence = array(
2276         '=<' => 0,
2277         '>=' => 0,
2278         '=' => 0,
2279         '<' => 0,
2280         '>' => 0,
2281 
2282         '+' => 1,
2283         '-' => 1,
2284         '*' => 2,
2285         '/' => 2,
2286         '%' => 2,
2287     );
2288 
2289     static protected $whitePattern;
2290     static protected $commentMulti;
2291 
2292     static protected $commentSingle = "//";
2293     static protected $commentMultiLeft = "/*";
2294     static protected $commentMultiRight = "*/";
2295 
2296     // regex string to match any of the operators
2297     static protected $operatorString;
2298 
2299     // these properties will supress division unless it's inside parenthases
2300     static protected $supressDivisionProps =
2301         array('/border-radius$/i', '/^font$/i');
2302 
2303     protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document", "viewport", "-moz-viewport", "-o-viewport", "-ms-viewport");
2304     protected $lineDirectives = array("charset");
2305 
2306     /**
2307      * if we are in parens we can be more liberal with whitespace around
2308      * operators because it must evaluate to a single value and thus is less
2309      * ambiguous.
2310      *
2311      * Consider:
2312      *     property1: 10 -5; // is two numbers, 10 and -5
2313      *     property2: (10 -5); // should evaluate to 5
2314      */
2315     protected $inParens = false;
2316 
2317     // caches preg escaped literals
2318     static protected $literalCache = array();
2319 
2320     public function __construct($lessc, $sourceName = null) {
2321         $this->eatWhiteDefault = true;
2322         // reference to less needed for vPrefix, mPrefix, and parentSelector
2323         $this->lessc = $lessc;
2324 
2325         $this->sourceName = $sourceName; // name used for error messages
2326 
2327         $this->writeComments = false;
2328 
2329         if (!self::$operatorString) {
2330             self::$operatorString =
2331                 '('.implode('|', array_map(array('lessc', 'preg_quote'),
2332                     array_keys(self::$precedence))).')';
2333 
2334             $commentSingle = lessc::preg_quote(self::$commentSingle);
2335             $commentMultiLeft = lessc::preg_quote(self::$commentMultiLeft);
2336             $commentMultiRight = lessc::preg_quote(self::$commentMultiRight);
2337 
2338             self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight;
2339             self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais';
2340         }
2341     }
2342 
2343     public function parse($buffer) {
2344         $this->count = 0;
2345         $this->line = 1;
2346 
2347         $this->env = null; // block stack
2348         $this->buffer = $this->writeComments ? $buffer : $this->removeComments($buffer);
2349         $this->pushSpecialBlock("root");
2350         $this->eatWhiteDefault = true;
2351         $this->seenComments = array();
2352 
2353         // trim whitespace on head
2354         // if (preg_match('/^\s+/', $this->buffer, $m)) {
2355         //  $this->line += substr_count($m[0], "\n");
2356         //  $this->buffer = ltrim($this->buffer);
2357         // }
2358         $this->whitespace();
2359 
2360         // parse the entire file
2361         while (false !== $this->parseChunk());
2362 
2363         if ($this->count != strlen($this->buffer))
2364             $this->throwError();
2365 
2366         // TODO report where the block was opened
2367         if ( !property_exists($this->env, 'parent') || !is_null($this->env->parent) )
2368             throw new exception('parse error: unclosed block');
2369 
2370         return $this->env;
2371     }
2372 
2373     /**
2374      * Parse a single chunk off the head of the buffer and append it to the
2375      * current parse environment.
2376      * Returns false when the buffer is empty, or when there is an error.
2377      *
2378      * This function is called repeatedly until the entire document is
2379      * parsed.
2380      *
2381      * This parser is most similar to a recursive descent parser. Single
2382      * functions represent discrete grammatical rules for the language, and
2383      * they are able to capture the text that represents those rules.
2384      *
2385      * Consider the function lessc::keyword(). (all parse functions are
2386      * structured the same)
2387      *
2388      * The function takes a single reference argument. When calling the
2389      * function it will attempt to match a keyword on the head of the buffer.
2390      * If it is successful, it will place the keyword in the referenced
2391      * argument, advance the position in the buffer, and return true. If it
2392      * fails then it won't advance the buffer and it will return false.
2393      *
2394      * All of these parse functions are powered by lessc::match(), which behaves
2395      * the same way, but takes a literal regular expression. Sometimes it is
2396      * more convenient to use match instead of creating a new function.
2397      *
2398      * Because of the format of the functions, to parse an entire string of
2399      * grammatical rules, you can chain them together using &&.
2400      *
2401      * But, if some of the rules in the chain succeed before one fails, then
2402      * the buffer position will be left at an invalid state. In order to
2403      * avoid this, lessc::seek() is used to remember and set buffer positions.
2404      *
2405      * Before parsing a chain, use $s = $this->seek() to remember the current
2406      * position into $s. Then if a chain fails, use $this->seek($s) to
2407      * go back where we started.
2408      */
2409     protected function parseChunk() {
2410         if (empty($this->buffer)) return false;
2411         $s = $this->seek();
2412 
2413         if ($this->whitespace()) {
2414             return true;
2415         }
2416 
2417         // setting a property
2418         if ($this->keyword($key) && $this->assign() &&
2419             $this->propertyValue($value, $key) && $this->end())
2420         {
2421             $this->append(array('assign', $key, $value), $s);
2422             return true;
2423         } else {
2424             $this->seek($s);
2425         }
2426 
2427 
2428         // look for special css blocks
2429         if ($this->literal('@', false)) {
2430             $this->count--;
2431 
2432             // media
2433             if ($this->literal('@media')) {
2434                 if (($this->mediaQueryList($mediaQueries) || true)
2435                     && $this->literal('{'))
2436                 {
2437                     $media = $this->pushSpecialBlock("media");
2438                     $media->queries = is_null($mediaQueries) ? array() : $mediaQueries;
2439                     return true;
2440                 } else {
2441                     $this->seek($s);
2442                     return false;
2443                 }
2444             }
2445 
2446             if ($this->literal("@", false) && $this->keyword($dirName)) {
2447                 if ($this->isDirective($dirName, $this->blockDirectives)) {
2448                     if (($this->openString("{", $dirValue, null, array(";")) || true) &&
2449                         $this->literal("{"))
2450                     {
2451                         $dir = $this->pushSpecialBlock("directive");
2452                         $dir->name = $dirName;
2453                         if (isset($dirValue)) $dir->value = $dirValue;
2454                         return true;
2455                     }
2456                 } elseif ($this->isDirective($dirName, $this->lineDirectives)) {
2457                     if ($this->propertyValue($dirValue) && $this->end()) {
2458                         $this->append(array("directive", $dirName, $dirValue));
2459                         return true;
2460                     }
2461                 }
2462             }
2463 
2464             $this->seek($s);
2465         }
2466 
2467         // setting a variable
2468         if ($this->variable($var) && $this->assign() &&
2469             $this->propertyValue($value) && $this->end())
2470         {
2471             $this->append(array('assign', $var, $value), $s);
2472             return true;
2473         } else {
2474             $this->seek($s);
2475         }
2476 
2477         if ($this->import($importValue)) {
2478             $this->append($importValue, $s);
2479             return true;
2480         }
2481 
2482         // opening parametric mixin
2483         if ($this->tag($tag, true) && $this->argumentDef($args, $isVararg) &&
2484             ($this->guards($guards) || true) &&
2485             $this->literal('{'))
2486         {
2487             $block = $this->pushBlock($this->fixTags(array($tag)));
2488             $block->args = $args;
2489             $block->isVararg = $isVararg;
2490             if (!empty($guards)) $block->guards = $guards;
2491             return true;
2492         } else {
2493             $this->seek($s);
2494         }
2495 
2496         // opening a simple block
2497         if ($this->tags($tags) && $this->literal('{', false)) {
2498             $tags = $this->fixTags($tags);
2499             $this->pushBlock($tags);
2500             return true;
2501         } else {
2502             $this->seek($s);
2503         }
2504 
2505         // closing a block
2506         if ($this->literal('}', false)) {
2507             try {
2508                 $block = $this->pop();
2509             } catch (exception $e) {
2510                 $this->seek($s);
2511                 $this->throwError($e->getMessage());
2512             }
2513 
2514             $hidden = false;
2515             if (is_null($block->type)) {
2516                 $hidden = true;
2517                 if (!isset($block->args)) {
2518                     foreach ($block->tags as $tag) {
2519                         if (!is_string($tag) || $tag{0} != $this->lessc->mPrefix) {
2520                             $hidden = false;
2521                             break;
2522                         }
2523                     }
2524                 }
2525 
2526                 foreach ($block->tags as $tag) {
2527                     if (is_string($tag)) {
2528                         $this->env->children[$tag][] = $block;
2529                     }
2530                 }
2531             }
2532 
2533             if (!$hidden) {
2534                 $this->append(array('block', $block), $s);
2535             }
2536 
2537             // this is done here so comments aren't bundled into he block that
2538             // was just closed
2539             $this->whitespace();
2540             return true;
2541         }
2542 
2543         // mixin
2544         if ($this->mixinTags($tags) &&
2545             ($this->argumentDef($argv, $isVararg) || true) &&
2546             ($this->keyword($suffix) || true) && $this->end())
2547         {
2548             $tags = $this->fixTags($tags);
2549             $this->append(array('mixin', $tags, $argv, $suffix), $s);
2550             return true;
2551         } else {
2552             $this->seek($s);
2553         }
2554 
2555         // spare ;
2556         if ($this->literal(';')) return true;
2557 
2558         return false; // got nothing, throw error
2559     }
2560 
2561     protected function isDirective($dirname, $directives) {
2562         // TODO: cache pattern in parser
2563         $pattern = implode("|",
2564             array_map(array("lessc", "preg_quote"), $directives));
2565         $pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i';
2566 
2567         return preg_match($pattern, $dirname);
2568     }
2569 
2570     protected function fixTags($tags) {
2571         // move @ tags out of variable namespace
2572         foreach ($tags as &$tag) {
2573             if ($tag{0} == $this->lessc->vPrefix)
2574                 $tag[0] = $this->lessc->mPrefix;
2575         }
2576         return $tags;
2577     }
2578 
2579     // a list of expressions
2580     protected function expressionList(&$exps) {
2581         $values = array();
2582 
2583         while ($this->expression($exp)) {
2584             $values[] = $exp;
2585         }
2586 
2587         if (count($values) == 0) return false;
2588 
2589         $exps = lessc::compressList($values, ' ');
2590         return true;
2591     }
2592 
2593     /**
2594      * Attempt to consume an expression.
2595      * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code
2596      */
2597     protected function expression(&$out) {
2598         if ($this->value($lhs)) {
2599             $out = $this->expHelper($lhs, 0);
2600 
2601             // look for / shorthand
2602             if (!empty($this->env->supressedDivision)) {
2603                 unset($this->env->supressedDivision);
2604                 $s = $this->seek();
2605                 if ($this->literal("/") && $this->value($rhs)) {
2606                     $out = array("list", "",
2607                         array($out, array("keyword", "/"), $rhs));
2608                 } else {
2609                     $this->seek($s);
2610                 }
2611             }
2612 
2613             return true;
2614         }
2615         return false;
2616     }
2617 
2618     /**
2619      * recursively parse infix equation with $lhs at precedence $minP
2620      */
2621     protected function expHelper($lhs, $minP) {
2622         $this->inExp = true;
2623         $ss = $this->seek();
2624 
2625         while (true) {
2626             $whiteBefore = isset($this->buffer[$this->count - 1]) &&
2627                 ctype_space($this->buffer[$this->count - 1]);
2628 
2629             // If there is whitespace before the operator, then we require
2630             // whitespace after the operator for it to be an expression
2631             $needWhite = $whiteBefore && !$this->inParens;
2632 
2633             if ($this->match(self::$operatorString.($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) {
2634                 if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/" && empty($this->env->supressedDivision)) {
2635                     foreach (self::$supressDivisionProps as $pattern) {
2636                         if (preg_match($pattern, $this->env->currentProperty)) {
2637                             $this->env->supressedDivision = true;
2638                             break 2;
2639                         }
2640                     }
2641                 }
2642 
2643 
2644                 $whiteAfter = isset($this->buffer[$this->count - 1]) &&
2645                     ctype_space($this->buffer[$this->count - 1]);
2646 
2647                 if (!$this->value($rhs)) break;
2648 
2649                 // peek for next operator to see what to do with rhs
2650                 if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) {
2651                     $rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
2652                 }
2653 
2654                 $lhs = array('expression', $m[1], $lhs, $rhs, $whiteBefore, $whiteAfter);
2655                 $ss = $this->seek();
2656 
2657                 continue;
2658             }
2659 
2660             break;
2661         }
2662 
2663         $this->seek($ss);
2664 
2665         return $lhs;
2666     }
2667 
2668     // consume a list of values for a property
2669     public function propertyValue(&$value, $keyName = null) {
2670         $values = array();
2671 
2672         if ($keyName !== null) $this->env->currentProperty = $keyName;
2673 
2674         $s = null;
2675         while ($this->expressionList($v)) {
2676             $values[] = $v;
2677             $s = $this->seek();
2678             if (!$this->literal(',')) break;
2679         }
2680 
2681         if ($s) $this->seek($s);
2682 
2683         if ($keyName !== null) unset($this->env->currentProperty);
2684 
2685         if (count($values) == 0) return false;
2686 
2687         $value = lessc::compressList($values, ', ');
2688         return true;
2689     }
2690 
2691     protected function parenValue(&$out) {
2692         $s = $this->seek();
2693 
2694         // speed shortcut
2695         if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "(") {
2696             return false;
2697         }
2698 
2699         $inParens = $this->inParens;
2700         if ($this->literal("(") &&
2701             ($this->inParens = true) && $this->expression($exp) &&
2702             $this->literal(")"))
2703         {
2704             $out = $exp;
2705             $this->inParens = $inParens;
2706             return true;
2707         } else {
2708             $this->inParens = $inParens;
2709             $this->seek($s);
2710         }
2711 
2712         return false;
2713     }
2714 
2715     // a single value
2716     protected function value(&$value) {
2717         $s = $this->seek();
2718 
2719         // speed shortcut
2720         if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "-") {
2721             // negation
2722             if ($this->literal("-", false) &&
2723                 (($this->variable($inner) && $inner = array("variable", $inner)) ||
2724                 $this->unit($inner) ||
2725                 $this->parenValue($inner)))
2726             {
2727                 $value = array("unary", "-", $inner);
2728                 return true;
2729             } else {
2730                 $this->seek($s);
2731             }
2732         }
2733 
2734         if ($this->parenValue($value)) return true;
2735         if ($this->unit($value)) return true;
2736         if ($this->color($value)) return true;
2737         if ($this->func($value)) return true;
2738         if ($this->string($value)) return true;
2739 
2740         if ($this->keyword($word)) {
2741             $value = array('keyword', $word);
2742             return true;
2743         }
2744 
2745         // try a variable
2746         if ($this->variable($var)) {
2747             $value = array('variable', $var);
2748             return true;
2749         }
2750 
2751         // unquote string (should this work on any type?
2752         if ($this->literal("~") && $this->string($str)) {
2753             $value = array("escape", $str);
2754             return true;
2755         } else {
2756             $this->seek($s);
2757         }
2758 
2759         // css hack: \0
2760         if ($this->literal('\\') && $this->match('([0-9]+)', $m)) {
2761             $value = array('keyword', '\\'.$m[1]);
2762             return true;
2763         } else {
2764             $this->seek($s);
2765         }
2766 
2767         return false;
2768     }
2769 
2770     // an import statement
2771     protected function import(&$out) {
2772         if (!$this->literal('@import')) return false;
2773 
2774         // @import "something.css" media;
2775         // @import url("something.css") media;
2776         // @import url(something.css) media;
2777 
2778         if ($this->propertyValue($value)) {
2779             $out = array("import", $value);
2780             return true;
2781         }
2782     }
2783 
2784     protected function mediaQueryList(&$out) {
2785         if ($this->genericList($list, "mediaQuery", ",", false)) {
2786             $out = $list[2];
2787             return true;
2788         }
2789         return false;
2790     }
2791 
2792     protected function mediaQuery(&$out) {
2793         $s = $this->seek();
2794 
2795         $expressions = null;
2796         $parts = array();
2797 
2798         if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->keyword($mediaType)) {
2799             $prop = array("mediaType");
2800             if (isset($only)) $prop[] = "only";
2801             if (isset($not)) $prop[] = "not";
2802             $prop[] = $mediaType;
2803             $parts[] = $prop;
2804         } else {
2805             $this->seek($s);
2806         }
2807 
2808 
2809         if (!empty($mediaType) && !$this->literal("and")) {
2810             // ~
2811         } else {
2812             $this->genericList($expressions, "mediaExpression", "and", false);
2813             if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]);
2814         }
2815 
2816         if (count($parts) == 0) {
2817             $this->seek($s);
2818             return false;
2819         }
2820 
2821         $out = $parts;
2822         return true;
2823     }
2824 
2825     protected function mediaExpression(&$out) {
2826         $s = $this->seek();
2827         $value = null;
2828         if ($this->literal("(") &&
2829             $this->keyword($feature) &&
2830             ($this->literal(":") && $this->expression($value) || true) &&
2831             $this->literal(")"))
2832         {
2833             $out = array("mediaExp", $feature);
2834             if ($value) $out[] = $value;
2835             return true;
2836         } elseif ($this->variable($variable)) {
2837             $out = array('variable', $variable);
2838             return true;
2839         }
2840 
2841         $this->seek($s);
2842         return false;
2843     }
2844 
2845     // an unbounded string stopped by $end
2846     protected function openString($end, &$out, $nestingOpen=null, $rejectStrs = null) {
2847         $oldWhite = $this->eatWhiteDefault;
2848         $this->eatWhiteDefault = false;
2849 
2850         $stop = array("'", '"', "@{", $end);
2851         $stop = array_map(array("lessc", "preg_quote"), $stop);
2852         // $stop[] = self::$commentMulti;
2853 
2854         if (!is_null($rejectStrs)) {
2855             $stop = array_merge($stop, $rejectStrs);
2856         }
2857 
2858         $patt = '(.*?)('.implode("|", $stop).')';
2859 
2860         $nestingLevel = 0;
2861 
2862         $content = array();
2863         while ($this->match($patt, $m, false)) {
2864             if (!empty($m[1])) {
2865                 $content[] = $m[1];
2866                 if ($nestingOpen) {
2867                     $nestingLevel += substr_count($m[1], $nestingOpen);
2868                 }
2869             }
2870 
2871             $tok = $m[2];
2872 
2873             $this->count-= strlen($tok);
2874             if ($tok == $end) {
2875                 if ($nestingLevel == 0) {
2876                     break;
2877                 } else {
2878                     $nestingLevel--;
2879                 }
2880             }
2881 
2882             if (($tok == "'" || $tok == '"') && $this->string($str)) {
2883                 $content[] = $str;
2884                 continue;
2885             }
2886 
2887             if ($tok == "@{" && $this->interpolation($inter)) {
2888                 $content[] = $inter;
2889                 continue;
2890             }
2891 
2892             if (!empty($rejectStrs) && in_array($tok, $rejectStrs)) {
2893                 break;
2894             }
2895 
2896             $content[] = $tok;
2897             $this->count+= strlen($tok);
2898         }
2899 
2900         $this->eatWhiteDefault = $oldWhite;
2901 
2902         if (count($content) == 0) return false;
2903 
2904         // trim the end
2905         if (is_string(end($content))) {
2906             $content[count($content) - 1] = rtrim(end($content));
2907         }
2908 
2909         $out = array("string", "", $content);
2910         return true;
2911     }
2912 
2913     protected function string(&$out) {
2914         $s = $this->seek();
2915         if ($this->literal('"', false)) {
2916             $delim = '"';
2917         } elseif ($this->literal("'", false)) {
2918             $delim = "'";
2919         } else {
2920             return false;
2921         }
2922 
2923         $content = array();
2924 
2925         // look for either ending delim , escape, or string interpolation
2926         $patt = '([^\n]*?)(@\{|\\\\|' .
2927             lessc::preg_quote($delim).')';
2928 
2929         $oldWhite = $this->eatWhiteDefault;
2930         $this->eatWhiteDefault = false;
2931 
2932         while ($this->match($patt, $m, false)) {
2933             $content[] = $m[1];
2934             if ($m[2] == "@{") {
2935                 $this->count -= strlen($m[2]);
2936                 if ($this->interpolation($inter, false)) {
2937                     $content[] = $inter;
2938                 } else {
2939                     $this->count += strlen($m[2]);
2940                     $content[] = "@{"; // ignore it
2941                 }
2942             } elseif ($m[2] == '\\') {
2943                 $content[] = $m[2];
2944                 if ($this->literal($delim, false)) {
2945                     $content[] = $delim;
2946                 }
2947             } else {
2948                 $this->count -= strlen($delim);
2949                 break; // delim
2950             }
2951         }
2952 
2953         $this->eatWhiteDefault = $oldWhite;
2954 
2955         if ($this->literal($delim)) {
2956             $out = array("string", $delim, $content);
2957             return true;
2958         }
2959 
2960         $this->seek($s);
2961         return false;
2962     }
2963 
2964     protected function interpolation(&$out) {
2965         $oldWhite = $this->eatWhiteDefault;
2966         $this->eatWhiteDefault = true;
2967 
2968         $s = $this->seek();
2969         if ($this->literal("@{") &&
2970             $this->openString("}", $interp, null, array("'", '"', ";")) &&
2971             $this->literal("}", false))
2972         {
2973             $out = array("interpolate", $interp);
2974             $this->eatWhiteDefault = $oldWhite;
2975             if ($this->eatWhiteDefault) $this->whitespace();
2976             return true;
2977         }
2978 
2979         $this->eatWhiteDefault = $oldWhite;
2980         $this->seek($s);
2981         return false;
2982     }
2983 
2984     protected function unit(&$unit) {
2985         // speed shortcut
2986         if (isset($this->buffer[$this->count])) {
2987             $char = $this->buffer[$this->count];
2988             if (!ctype_digit($char) && $char != ".") return false;
2989         }
2990 
2991         if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m)) {
2992             $unit = array("number", $m[1], empty($m[2]) ? "" : $m[2]);
2993             return true;
2994         }
2995         return false;
2996     }
2997 
2998     // a # color
2999     protected function color(&$out) {
3000         if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m)) {
3001             if (strlen($m[1]) > 7) {
3002                 $out = array("string", "", array($m[1]));
3003             } else {
3004                 $out = array("raw_color", $m[1]);
3005             }
3006             return true;
3007         }
3008 
3009         return false;
3010     }
3011 
3012     // consume an argument definition list surrounded by ()
3013     // each argument is a variable name with optional value
3014     // or at the end a ... or a variable named followed by ...
3015     // arguments are separated by , unless a ; is in the list, then ; is the
3016     // delimiter.
3017     protected function argumentDef(&$args, &$isVararg) {
3018         $s = $this->seek();
3019         if (!$this->literal('(')) return false;
3020 
3021         $values = array();
3022         $delim = ",";
3023         $method = "expressionList";
3024 
3025         $isVararg = false;
3026         while (true) {
3027             if ($this->literal("...")) {
3028                 $isVararg = true;
3029                 break;
3030             }
3031 
3032             if ($this->$method($value)) {
3033                 if ($value[0] == "variable") {
3034                     $arg = array("arg", $value[1]);
3035                     $ss = $this->seek();
3036 
3037                     if ($this->assign() && $this->$method($rhs)) {
3038                         $arg[] = $rhs;
3039                     } else {
3040                         $this->seek($ss);
3041                         if ($this->literal("...")) {
3042                             $arg[0] = "rest";
3043                             $isVararg = true;
3044                         }
3045                     }
3046 
3047                     $values[] = $arg;
3048                     if ($isVararg) break;
3049                     continue;
3050                 } else {
3051                     $values[] = array("lit", $value);
3052                 }
3053             }
3054 
3055 
3056             if (!$this->literal($delim)) {
3057                 if ($delim == "," && $this->literal(";")) {
3058                     // found new delim, convert existing args
3059                     $delim = ";";
3060                     $method = "propertyValue";
3061 
3062                     // transform arg list
3063                     if (isset($values[1])) { // 2 items
3064                         $newList = array();
3065                         foreach ($values as $i => $arg) {
3066                             switch($arg[0]) {
3067                             case "arg":
3068                                 if ($i) {
3069                                     $this->throwError("Cannot mix ; and , as delimiter types");
3070                                 }
3071                                 $newList[] = $arg[2];
3072                                 break;
3073                             case "lit":
3074                                 $newList[] = $arg[1];
3075                                 break;
3076                             case "rest":
3077                                 $this->throwError("Unexpected rest before semicolon");
3078                             }
3079                         }
3080 
3081                         $newList = array("list", ", ", $newList);
3082 
3083                         switch ($values[0][0]) {
3084                         case "arg":
3085                             $newArg = array("arg", $values[0][1], $newList);
3086                             break;
3087                         case "lit":
3088                             $newArg = array("lit", $newList);
3089                             break;
3090                         }
3091 
3092                     } elseif ($values) { // 1 item
3093                         $newArg = $values[0];
3094                     }
3095 
3096                     if ($newArg) {
3097                         $values = array($newArg);
3098                     }
3099                 } else {
3100                     break;
3101                 }
3102             }
3103         }
3104 
3105         if (!$this->literal(')')) {
3106             $this->seek($s);
3107             return false;
3108         }
3109 
3110         $args = $values;
3111 
3112         return true;
3113     }
3114 
3115     // consume a list of tags
3116     // this accepts a hanging delimiter
3117     protected function tags(&$tags, $simple = false, $delim = ',') {
3118         $tags = array();
3119         while ($this->tag($tt, $simple)) {
3120             $tags[] = $tt;
3121             if (!$this->literal($delim)) break;
3122         }
3123         if (count($tags) == 0) return false;
3124 
3125         return true;
3126     }
3127 
3128     // list of tags of specifying mixin path
3129     // optionally separated by > (lazy, accepts extra >)
3130     protected function mixinTags(&$tags) {
3131         $tags = array();
3132         while ($this->tag($tt, true)) {
3133             $tags[] = $tt;
3134             $this->literal(">");
3135         }
3136 
3137         if (count($tags) == 0) return false;
3138 
3139         return true;
3140     }
3141 
3142     // a bracketed value (contained within in a tag definition)
3143     protected function tagBracket(&$parts, &$hasExpression) {
3144         // speed shortcut
3145         if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[") {
3146             return false;
3147         }
3148 
3149         $s = $this->seek();
3150 
3151         $hasInterpolation = false;
3152 
3153         if ($this->literal("[", false)) {
3154             $attrParts = array("[");
3155             // keyword, string, operator
3156             while (true) {
3157                 if ($this->literal("]", false)) {
3158                     $this->count--;
3159                     break; // get out early
3160                 }
3161 
3162                 if ($this->match('\s+', $m)) {
3163                     $attrParts[] = " ";
3164                     continue;
3165                 }
3166                 if ($this->string($str)) {
3167                     // escape parent selector, (yuck)
3168                     foreach ($str[2] as &$chunk) {
3169                         $chunk = str_replace($this->lessc->parentSelector, "$&$", $chunk);
3170                     }
3171 
3172                     $attrParts[] = $str;
3173                     $hasInterpolation = true;
3174                     continue;
3175                 }
3176 
3177                 if ($this->keyword($word)) {
3178                     $attrParts[] = $word;
3179                     continue;
3180                 }
3181 
3182                 if ($this->interpolation($inter, false)) {
3183                     $attrParts[] = $inter;
3184                     $hasInterpolation = true;
3185                     continue;
3186                 }
3187 
3188                 // operator, handles attr namespace too
3189                 if ($this->match('[|-~\$\*\^=]+', $m)) {
3190                     $attrParts[] = $m[0];
3191                     continue;
3192                 }
3193 
3194                 break;
3195             }
3196 
3197             if ($this->literal("]", false)) {
3198                 $attrParts[] = "]";
3199                 foreach ($attrParts as $part) {
3200                     $parts[] = $part;
3201                 }
3202                 $hasExpression = $hasExpression || $hasInterpolation;
3203                 return true;
3204             }
3205             $this->seek($s);
3206         }
3207 
3208         $this->seek($s);
3209         return false;
3210     }
3211 
3212     // a space separated list of selectors
3213     protected function tag(&$tag, $simple = false) {
3214         if ($simple)
3215             $chars = '^@,:;{}\][>\(\) "\'';
3216         else
3217             $chars = '^@,;{}["\'';
3218 
3219         $s = $this->seek();
3220 
3221         $hasExpression = false;
3222         $parts = array();
3223         while ($this->tagBracket($parts, $hasExpression));
3224 
3225         $oldWhite = $this->eatWhiteDefault;
3226         $this->eatWhiteDefault = false;
3227 
3228         while (true) {
3229             if ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) {
3230                 $parts[] = $m[1];
3231                 if ($simple) break;
3232 
3233                 while ($this->tagBracket($parts, $hasExpression));
3234                 continue;
3235             }
3236 
3237             if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") {
3238                 if ($this->interpolation($interp)) {
3239                     $hasExpression = true;
3240                     $interp[2] = true; // don't unescape
3241                     $parts[] = $interp;
3242                     continue;
3243                 }
3244 
3245                 if ($this->literal("@")) {
3246                     $parts[] = "@";
3247                     continue;
3248                 }
3249             }
3250 
3251             if ($this->unit($unit)) { // for keyframes
3252                 $parts[] = $unit[1];
3253                 $parts[] = $unit[2];
3254                 continue;
3255             }
3256 
3257             break;
3258         }
3259 
3260         $this->eatWhiteDefault = $oldWhite;
3261         if (!$parts) {
3262             $this->seek($s);
3263             return false;
3264         }
3265 
3266         if ($hasExpression) {
3267             $tag = array("exp", array("string", "", $parts));
3268         } else {
3269             $tag = trim(implode($parts));
3270         }
3271 
3272         $this->whitespace();
3273         return true;
3274     }
3275 
3276     // a css function
3277     protected function func(&$func) {
3278         $s = $this->seek();
3279 
3280         if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('(')) {
3281             $fname = $m[1];
3282 
3283             $sPreArgs = $this->seek();
3284 
3285             $args = array();
3286             while (true) {
3287                 $ss = $this->seek();
3288                 // this ugly nonsense is for ie filter properties
3289                 if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) {
3290                     $args[] = array("string", "", array($name, "=", $value));
3291                 } else {
3292                     $this->seek($ss);
3293                     if ($this->expressionList($value)) {
3294                         $args[] = $value;
3295                     }
3296                 }
3297 
3298                 if (!$this->literal(',')) break;
3299             }
3300             $args = array('list', ',', $args);
3301 
3302             if ($this->literal(')')) {
3303                 $func = array('function', $fname, $args);
3304                 return true;
3305             } elseif ($fname == 'url') {
3306                 // couldn't parse and in url? treat as string
3307                 $this->seek($sPreArgs);
3308                 if ($this->openString(")", $string) && $this->literal(")")) {
3309                     $func = array('function', $fname, $string);
3310                     return true;
3311                 }
3312             }
3313         }
3314 
3315         $this->seek($s);
3316         return false;
3317     }
3318 
3319     // consume a less variable
3320     protected function variable(&$name) {
3321         $s = $this->seek();
3322         if ($this->literal($this->lessc->vPrefix, false) &&
3323             ($this->variable($sub) || $this->keyword($name)))
3324         {
3325             if (!empty($sub)) {
3326                 $name = array('variable', $sub);
3327             } else {
3328                 $name = $this->lessc->vPrefix.$name;
3329             }
3330             return true;
3331         }
3332 
3333         $name = null;
3334         $this->seek($s);
3335         return false;
3336     }
3337 
3338     /**
3339      * Consume an assignment operator
3340      * Can optionally take a name that will be set to the current property name
3341      */
3342     protected function assign($name = null) {
3343         if ($name) $this->currentProperty = $name;
3344         return $this->literal(':') || $this->literal('=');
3345     }
3346 
3347     // consume a keyword
3348     protected function keyword(&$word) {
3349         if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) {
3350             $word = $m[1];
3351             return true;
3352         }
3353         return false;
3354     }
3355 
3356     // consume an end of statement delimiter
3357     protected function end() {
3358         if ($this->literal(';', false)) {
3359             return true;
3360         } elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') {
3361             // if there is end of file or a closing block next then we don't need a ;
3362             return true;
3363         }
3364         return false;
3365     }
3366 
3367     protected function guards(&$guards) {
3368         $s = $this->seek();
3369 
3370         if (!$this->literal("when")) {
3371             $this->seek($s);
3372             return false;
3373         }
3374 
3375         $guards = array();
3376 
3377         while ($this->guardGroup($g)) {
3378             $guards[] = $g;
3379             if (!$this->literal(",")) break;
3380         }
3381 
3382         if (count($guards) == 0) {
3383             $guards = null;
3384             $this->seek($s);
3385             return false;
3386         }
3387 
3388         return true;
3389     }
3390 
3391     // a bunch of guards that are and'd together
3392     // TODO rename to guardGroup
3393     protected function guardGroup(&$guardGroup) {
3394         $s = $this->seek();
3395         $guardGroup = array();
3396         while ($this->guard($guard)) {
3397             $guardGroup[] = $guard;
3398             if (!$this->literal("and")) break;
3399         }
3400 
3401         if (count($guardGroup) == 0) {
3402             $guardGroup = null;
3403             $this->seek($s);
3404             return false;
3405         }
3406 
3407         return true;
3408     }
3409 
3410     protected function guard(&$guard) {
3411         $s = $this->seek();
3412         $negate = $this->literal("not");
3413 
3414         if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) {
3415             $guard = $exp;
3416             if ($negate) $guard = array("negate", $guard);
3417             return true;
3418         }
3419 
3420         $this->seek($s);
3421         return false;
3422     }
3423 
3424     /* raw parsing functions */
3425 
3426     protected function literal($what, $eatWhitespace = null) {
3427         if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
3428 
3429         // shortcut on single letter
3430         if (!isset($what[1]) && isset($this->buffer[$this->count])) {
3431             if ($this->buffer[$this->count] == $what) {
3432                 if (!$eatWhitespace) {
3433                     $this->count++;
3434                     return true;
3435                 }
3436                 // goes below...
3437             } else {
3438                 return false;
3439             }
3440         }
3441 
3442         if (!isset(self::$literalCache[$what])) {
3443             self::$literalCache[$what] = lessc::preg_quote($what);
3444         }
3445 
3446         return $this->match(self::$literalCache[$what], $m, $eatWhitespace);
3447     }
3448 
3449     protected function genericList(&$out, $parseItem, $delim="", $flatten=true) {
3450         $s = $this->seek();
3451         $items = array();
3452         while ($this->$parseItem($value)) {
3453             $items[] = $value;
3454             if ($delim) {
3455                 if (!$this->literal($delim)) break;
3456             }
3457         }
3458 
3459         if (count($items) == 0) {
3460             $this->seek($s);
3461             return false;
3462         }
3463 
3464         if ($flatten && count($items) == 1) {
3465             $out = $items[0];
3466         } else {
3467             $out = array("list", $delim, $items);
3468         }
3469 
3470         return true;
3471     }
3472 
3473 
3474     // advance counter to next occurrence of $what
3475     // $until - don't include $what in advance
3476     // $allowNewline, if string, will be used as valid char set
3477     protected function to($what, &$out, $until = false, $allowNewline = false) {
3478         if (is_string($allowNewline)) {
3479             $validChars = $allowNewline;
3480         } else {
3481             $validChars = $allowNewline ? "." : "[^\n]";
3482         }
3483         if (!$this->match('('.$validChars.'*?)'.lessc::preg_quote($what), $m, !$until)) return false;
3484         if ($until) $this->count -= strlen($what); // give back $what
3485         $out = $m[1];
3486         return true;
3487     }
3488 
3489     // try to match something on head of buffer
3490     protected function match($regex, &$out, $eatWhitespace = null) {
3491         if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
3492 
3493         $r = '/'.$regex.($eatWhitespace && !$this->writeComments ? '\s*' : '').'/Ais';
3494         if (preg_match($r, $this->buffer, $out, null, $this->count)) {
3495             $this->count += strlen($out[0]);
3496             if ($eatWhitespace && $this->writeComments) $this->whitespace();
3497             return true;
3498         }
3499         return false;
3500     }
3501 
3502     // match some whitespace
3503     protected function whitespace() {
3504         if ($this->writeComments) {
3505             $gotWhite = false;
3506             while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) {
3507                 if (isset($m[1]) && empty($this->seenComments[$this->count])) {
3508                     $this->append(array("comment", $m[1]));
3509                     $this->seenComments[$this->count] = true;
3510                 }
3511                 $this->count += strlen($m[0]);
3512                 $gotWhite = true;
3513             }
3514             return $gotWhite;
3515         } else {
3516             $this->match("", $m);
3517             return strlen($m[0]) > 0;
3518         }
3519     }
3520 
3521     // match something without consuming it
3522     protected function peek($regex, &$out = null, $from=null) {
3523         if (is_null($from)) $from = $this->count;
3524         $r = '/'.$regex.'/Ais';
3525         $result = preg_match($r, $this->buffer, $out, null, $from);
3526 
3527         return $result;
3528     }
3529 
3530     // seek to a spot in the buffer or return where we are on no argument
3531     protected function seek($where = null) {
3532         if ($where === null) return $this->count;
3533         else $this->count = $where;
3534         return true;
3535     }
3536 
3537     /* misc functions */
3538 
3539     public function throwError($msg = "parse error", $count = null) {
3540         $count = is_null($count) ? $this->count : $count;
3541 
3542         $line = $this->line +
3543             substr_count(substr($this->buffer, 0, $count), "\n");
3544 
3545         if (!empty($this->sourceName)) {
3546             $loc = "$this->sourceName on line $line";
3547         } else {
3548             $loc = "line: $line";
3549         }
3550 
3551         // TODO this depends on $this->count
3552         if ($this->peek("(.*?)(\n|$)", $m, $count)) {
3553             throw new exception("$msg: failed at `$m[1]` $loc");
3554         } else {
3555             throw new exception("$msg: $loc");
3556         }
3557     }
3558 
3559     protected function pushBlock($selectors=null, $type=null) {
3560         $b = new stdclass;
3561         $b->parent = $this->env;
3562 
3563         $b->type = $type;
3564         $b->id = self::$nextBlockId++;
3565 
3566         $b->isVararg = false; // TODO: kill me from here
3567         $b->tags = $selectors;
3568 
3569         $b->props = array();
3570         $b->children = array();
3571 
3572         $this->env = $b;
3573         return $b;
3574     }
3575 
3576     // push a block that doesn't multiply tags
3577     protected function pushSpecialBlock($type) {
3578         return $this->pushBlock(null, $type);
3579     }
3580 
3581     // append a property to the current block
3582     protected function append($prop, $pos = null) {
3583         if ($pos !== null) $prop[-1] = $pos;
3584         $this->env->props[] = $prop;
3585     }
3586 
3587     // pop something off the stack
3588     protected function pop() {
3589         $old = $this->env;
3590         $this->env = $this->env->parent;
3591         return $old;
3592     }
3593 
3594     // remove comments from $text
3595     // todo: make it work for all functions, not just url
3596     protected function removeComments($text) {
3597         $look = array(
3598             'url(', '//', '/*', '"', "'"
3599         );
3600 
3601         $out = '';
3602         $min = null;
3603         while (true) {
3604             // find the next item
3605             foreach ($look as $token) {
3606                 $pos = strpos($text, $token);
3607                 if ($pos !== false) {
3608                     if (!isset($min) || $pos < $min[1]) $min = array($token, $pos);
3609                 }
3610             }
3611 
3612             if (is_null($min)) break;
3613 
3614             $count = $min[1];
3615             $skip = 0;
3616             $newlines = 0;
3617             switch ($min[0]) {
3618             case 'url(':
3619                 if (preg_match('/url\(.*?\)/', $text, $m, 0, $count))
3620                     $count += strlen($m[0]) - strlen($min[0]);
3621                 break;
3622             case '"':
3623             case "'":
3624                 if (preg_match('/'.$min[0].'.*?(?<!\\\\)'.$min[0].'/', $text, $m, 0, $count))
3625                     $count += strlen($m[0]) - 1;
3626                 break;
3627             case '//':
3628                 $skip = strpos($text, "\n", $count);
3629                 if ($skip === false) $skip = strlen($text) - $count;
3630                 else $skip -= $count;
3631                 break;
3632             case '/*':
3633                 if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) {
3634                     $skip = strlen($m[0]);
3635                     $newlines = substr_count($m[0], "\n");
3636                 }
3637                 break;
3638             }
3639 
3640             if ($skip == 0) $count += strlen($min[0]);
3641 
3642             $out .= substr($text, 0, $count).str_repeat("\n", $newlines);
3643             $text = substr($text, $count + $skip);
3644 
3645             $min = null;
3646         }
3647 
3648         return $out.$text;
3649     }
3650 
3651 }
3652 
3653 class lessc_formatter_classic {
3654     public $indentChar = "  ";
3655 
3656     public $break = "\n";
3657     public $open = " {";
3658     public $close = "}";
3659     public $selectorSeparator = ", ";
3660     public $assignSeparator = ":";
3661 
3662     public $openSingle = " { ";
3663     public $closeSingle = " }";
3664 
3665     public $disableSingle = false;
3666     public $breakSelectors = false;
3667 
3668     public $compressColors = false;
3669 
3670     public function __construct() {
3671         $this->indentLevel = 0;
3672     }
3673 
3674     public function indentStr($n = 0) {
3675         return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
3676     }
3677 
3678     public function property($name, $value) {
3679         return $name . $this->assignSeparator . $value . ";";
3680     }
3681 
3682     protected function isEmpty($block) {
3683         if (empty($block->lines)) {
3684             foreach ($block->children as $child) {
3685                 if (!$this->isEmpty($child)) return false;
3686             }
3687 
3688             return true;
3689         }
3690         return false;
3691     }
3692 
3693     public function block($block) {
3694         if ($this->isEmpty($block)) return;
3695 
3696         $inner = $pre = $this->indentStr();
3697 
3698         $isSingle = !$this->disableSingle &&
3699             is_null($block->type) && count($block->lines) == 1;
3700 
3701         if (!empty($block->selectors)) {
3702             $this->indentLevel++;
3703 
3704             if ($this->breakSelectors) {
3705                 $selectorSeparator = $this->selectorSeparator . $this->break . $pre;
3706             } else {
3707                 $selectorSeparator = $this->selectorSeparator;
3708             }
3709 
3710             echo $pre .
3711                 implode($selectorSeparator, $block->selectors);
3712             if ($isSingle) {
3713                 echo $this->openSingle;
3714                 $inner = "";
3715             } else {
3716                 echo $this->open . $this->break;
3717                 $inner = $this->indentStr();
3718             }
3719 
3720         }
3721 
3722         if (!empty($block->lines)) {
3723             $glue = $this->break.$inner;
3724             echo $inner . implode($glue, $block->lines);
3725             if (!$isSingle && !empty($block->children)) {
3726                 echo $this->break;
3727             }
3728         }
3729 
3730         foreach ($block->children as $child) {
3731             $this->block($child);
3732         }
3733 
3734         if (!empty($block->selectors)) {
3735             if (!$isSingle && empty($block->children)) echo $this->break;
3736 
3737             if ($isSingle) {
3738                 echo $this->closeSingle . $this->break;
3739             } else {
3740                 echo $pre . $this->close . $this->break;
3741             }
3742 
3743             $this->indentLevel--;
3744         }
3745     }
3746 }
3747 
3748 class lessc_formatter_compressed extends lessc_formatter_classic {
3749     public $disableSingle = true;
3750     public $open = "{";
3751     public $selectorSeparator = ",";
3752     public $assignSeparator = ":";
3753     public $break = "";
3754     public $compressColors = true;
3755 
3756     public function indentStr($n = 0) {
3757         return "";
3758     }
3759 }
3760 
3761 class lessc_formatter_lessjs extends lessc_formatter_classic {
3762     public $disableSingle = true;
3763     public $breakSelectors = true;
3764     public $assignSeparator = ": ";
3765     public $selectorSeparator = ",";
3766 }
3767 
3768 
3769 
Joomla! Framework TM API documentation generated by ApiGen 2.8.0
Joomla!® and Joomla! Framework™ are trademarks of Open Source Matters, Inc. in the United States and other countries.