<commit_before>module.exports.patronSettingsToKohaSettings = patronSettings => {
  return {
    item_due: {
      transports: patronSettingToArray(patronSettings.alerts.reminderOfDueDate),
      wants_digest: true
    },
    advance_notice: {
      days_in_advance: 2,
      transports: patronSettingToArray(patronSettings.alerts.reminderOfDueDate),
      wants_digest: true
    },
    hold_filled: {
      transports: patronSettingToArray(patronSettings.alerts.reminderOfPickup),
      wants_digest: false
    },
    item_checkout: {
      transports: patronSettingToArray(patronSettings.receipts.loans),
      wants_digest: true
    },
    item_check_in: {
      transports: patronSettingToArray(patronSettings.receipts.returns),
      wants_digest: true
    }
  }
}

module.exports.kohaSettingsToPatronSettings = kohaSettings => {
  return {
    alerts: {
      reminderOfDueDate: kohaSettingToObject(kohaSettings.advance_notice),
      reminderOfPickup: kohaSettingToObject(kohaSettings.hold_filled)
    },
    receipts: {
      loans: kohaSettingToObject(kohaSettings.item_checkout),
      returns: kohaSettingToObject(kohaSettings.item_check_in)
    }
  }
}

function kohaSettingToObject (kohaSetting) {
  return {
    email: kohaSetting && kohaSetting.transports && kohaSetting.transports.email && true,
    sms: kohaSetting && kohaSetting.transports && kohaSetting.transports.sms && true
  }
}

function patronSettingToArray (patronSetting) {
  const out = []
  if (patronSetting.email) {
    out.push('email')
  }
  if (patronSetting.sms) {
    out.push('sms')
  }
  return out.length > 0 ? out : undefined
}
<commit_msg>patron-client: fix message preferences saving<commit_after>module.exports.patronSettingsToKohaSettings = patronSettings => {
  return {
    item_due: {
      transports: patronSettingToArray(patronSettings.alerts.reminderOfDueDate),
      wants_digest: true
    },
    advance_notice: {
      days_in_advance: 2,
      transports: patronSettingToArray(patronSettings.alerts.reminderOfDueDate),
      wants_digest: true
    },
    hold_filled: {
      transports: patronSettingToArray(patronSettings.alerts.reminderOfPickup),
      wants_digest: false
    },
    item_checkout: {
      transports: patronSettingToArray(patronSettings.receipts.loans)
    },
    item_check_in: {
      transports: patronSettingToArray(patronSettings.receipts.returns)
    }
  }
}

module.exports.kohaSettingsToPatronSettings = kohaSettings => {
  return {
    alerts: {
      reminderOfDueDate: kohaSettingToObject(kohaSettings.advance_notice),
      reminderOfPickup: kohaSettingToObject(kohaSettings.hold_filled)
    },
    receipts: {
      loans: kohaSettingToObject(kohaSettings.item_checkout),
      returns: kohaSettingToObject(kohaSettings.item_check_in)
    }
  }
}

function kohaSettingToObject (kohaSetting) {
  return {
    email: kohaSetting && kohaSetting.transports && kohaSetting.transports.email && true,
    sms: kohaSetting && kohaSetting.transports && kohaSetting.transports.sms && true
  }
}

function patronSettingToArray (patronSetting) {
  const out = []
  if (patronSetting.email) {
    out.push('email')
  }
  if (patronSetting.sms) {
    out.push('sms')
  }
  return out.length > 0 ? out : undefined
}
<|endoftext|>


<commit_before>import React, {PropTypes} from 'react'
import NonIETransitionGroup from './NonIETransitionGroup'
import {Link} from 'react-router'
import {push} from 'react-router-redux'
import {defineMessages, FormattedMessage, injectIntl, intlShape} from 'react-intl'
import MediaQuery from 'react-responsive'
import LuceneParser from 'lucene-query-parser'
import {queryFieldTranslations} from '../constants/Constants'

class SearchHeader extends React.Component {
  constructor (props) {
    super(props)
    this.handleSearch = this.handleSearch.bind(this)
    this.handleLoginClick = this.handleLoginClick.bind(this)
    this.toggleMobileNav = this.toggleMobileNav.bind(this)
    this.handleRegistrationClick = this.handleRegistrationClick.bind(this)
    this.checkQuery = this.checkQuery.bind(this)
    this.state = { querySyntaxError: false, unknownFields:{} }
  }

  checkQuery (event) {
    let unknownFields = []

    function traverse (node) {
      if (node.left) {
        traverse(node.left)
      }
      if (node.field && node.field !== '<implicit>') {
        if (!{...queryFieldTranslations, '*':'*'}[ node.field ]) {
          unknownFields[ node.field ] = {}
        }
      }
      if (node.right) {
        traverse(node.right)
      }
    }

    try {
      traverse(LuceneParser.parse(this.searchFieldInput.value))

      this.setState(prevState => {
        return {
          ...prevState,
          querySyntaxError: false,
          unknownFields
        }
      });
    } catch (err) {
      console.log(err)
      this.setState(prevState => ({
        ...prevState,
        querySyntaxError: true,
        unknownFields:{}
      }));
    }
  }

  handleSearch (event) {
    event.preventDefault()
    if (!this.state.querySyntaxError && Object.keys(this.state.unknownFields).length === 0) {

      // Ensure that page-param is deleted on new search
      delete this.props.locationQuery[ 'page' ]
      this.props.mobileNavigationActions.hideMobileNavigation()

      /* Active filters are removed on new query */
      if (this.props.path.includes('/work')) {
        this.props.dispatch(push({ pathname: '/search', query: { query: this.searchFieldInput.value } }))
      } else {
        /* Active filters are NOT removed on new query */
        this.props.locationQuery.query = this.searchFieldInput.value
        this.props.dispatch(push({ pathname: '/search', query: this.props.locationQuery }))
      }
      this.props.searchActions.search()
    }
  }

  handleLoginClick (event) {
    event.preventDefault()
    this.props.mobileNavigationActions.hideMobileNavigation()
    this.props.showLoginDialog(push({ pathname: '/profile/loans' }))
  }

  handleRegistrationClick (event) {
    event.preventDefault()
    this.props.startRegistration()
  }

  toggleMobileNav () {
    this.props.mobileNavigationActions.toggleMobileNavigation()
  }

  /**
   * Links used in the menu and the mobile menu
   */
  loginLink () {
    if (!this.props.isLoggedIn) {
      return [

          <Link to="/" >
            <FormattedMessage {...messages.logIn} /> »
          </Link >

      ]
    }
  }

  logoutLink () {
    if (this.props.isLoggedIn) {
      return [

          <Link to="/" >
            <FormattedMessage {...messages.logout} /> »
          </Link >

      ]
    }
  }

  profileLink () {
    if (this.props.isLoggedIn) {
      return [

          <Link to="/profile/loans" ><FormattedMessage {...messages.myProfile} /> »</Link >

      ]
    }
  }

  registrationLink () {
    if (!this.props.isLoggedIn) {
      return (

          <FormattedMessage {...messages.register} />»

      )
    }
  }

  faqLink () {
    return (

          <FormattedMessage {...messages.faq} />
          »

    )
  }

  loggedInMessage () {
    const { borrowerName } = this.props
    return (

        <FormattedMessage {...messages.loggedInAs} /> {borrowerName}

    )
  }

  /**
   * Renders the menu, and/or the mobile menu
   */
  renderNavigationLinks () {
    const { borrowerName } = this.props
    return (

        {this.faqLink()}
        {this.profileLink()}
        {this.registrationLink()}
        {borrowerName
          ? (
            <MediaQuery query="(min-width: 992px)" values={{ ...this.props.mediaQueryValues }} >

{this.loggedInMessage()}

            </MediaQuery >
          )
          : null}
        {this.loginLink()}
        {this.logoutLink()}

    )
  }

  renderMobileNavigationLinks () {
    return (

        {this.faqLink()}
        {this.profileLink()}
        {this.registrationLink()}
        {this.loginLink()}
        {this.logoutLink()}

    )
  }

  render () {
    const { borrowerName } = this.props
    const mobileNavClass = this.props.showMobileNavigation ? 'primary-mobile-menu' : 'primary-mobile-menu collapsed'
    return (

        <NonIETransitionGroup
          transitionName="fade-in"
          transitionAppear
          transitionAppearTimeout={500}
          transitionEnterTimeout={500}
          transitionLeaveTimeout={500}
          component="header"
          className="wrapper" >

          <MediaQuery query="(max-width: 667px)" values={{ ...this.props.mediaQueryValues }} >

          </MediaQuery >

          <MediaQuery query="(min-width: 668px)" values={{ ...this.props.mediaQueryValues }} >

              {this.renderNavigationLinks()}

          </MediaQuery >

          {borrowerName
            ? (<MediaQuery query="(max-width: 991px)" values={{ ...this.props.mediaQueryValues }} >

                {this.loggedInMessage()}

            </MediaQuery >)
            : null}

        </NonIETransitionGroup >

        <MediaQuery query="(max-width: 667px)" values={{ ...this.props.mediaQueryValues }} >

            {this.renderMobileNavigationLinks()}

        </MediaQuery >

        <NonIETransitionGroup
          transitionName="fade-in"
          transitionAppear
          transitionAppearTimeout={500}
          transitionEnterTimeout={500}
          transitionLeaveTimeout={500}
          component="section"
          className="search-box-wrapper"
          role="search" >

            <form onSubmit={this.handleSearch} >
              {this.props.intl.formatMessage(messages.searchLabel)}:

                  <input placeholder={this.props.intl.formatMessage(messages.searchInputPlaceholder)}
                         id="search"
                         type="text"
                         defaultValue={this.props.locationQuery.query || ''}
                         ref={e => this.searchFieldInput = e}
                         onChange={this.checkQuery}
                         data-automation-id="search_input_field"
                  />

                  <button onClick={this.handleSearch} type="button" className="search-submit"
                          data-automation-id="search_button" disabled={this.state.querySyntaxError || Object.keys(this.state.unknownFields).length > 0} >
                    {!this.props.isSearching
                      ? <FormattedMessage {...messages.search} />
                      :

                    }
                  </button >

            </form >

          {Object.keys(this.state.unknownFields).length > 0
            ?

<FormattedMessage {...Object.assign(messages.unknownFields, {values:{unrecognizedFields:Object.keys(this.state.unknownFields).map(field => `"${field}"`).join(', ')}})}/>

            : ''
          }
          {this.state.querySyntaxError
            ?

<FormattedMessage {...messages.luceneSyntaxError} />

            : ''
          }
        </NonIETransitionGroup >

    )
  }
}

SearchHeader.propTypes = {
  locationQuery: PropTypes.object.isRequired,
  requireLoginBeforeAction: PropTypes.func.isRequired,
  dispatch: PropTypes.func.isRequired,
  locale: PropTypes.string.isRequired,
  showLoginDialog: PropTypes.func.isRequired,
  isLoggedIn: PropTypes.bool.isRequired,
  logout: PropTypes.func.isRequired,
  mediaQueryValues: PropTypes.object,
  intl: intlShape.isRequired,
  startRegistration: PropTypes.func.isRequired,
  showMobileNavigation: PropTypes.bool.isRequired,
  mobileNavigationActions: PropTypes.object.isRequired,
  searchActions: PropTypes.object.isRequired,
  isSearching: PropTypes.bool.isRequired,
  borrowerName: PropTypes.string,
  path: PropTypes.string.isRequired,
  querySyntaxError: PropTypes.boolean,
  unknownFields: PropTypes.object
}

export const messages = defineMessages({
  logoAlt: {
    id: 'SearchHeader.logoAlt',
    description: 'Alt text for the logo',
    defaultMessage: 'Black logo with text'
  },
  myProfile: {
    id: 'SearchHeader.myProfile',
    description: 'Label for the link to go to the user profile',
    defaultMessage: 'My profile'
  },
  myLoans: {
    id: 'SearchHeader.myLoans',
    description: 'Label for the link to go to the users loans',
    defaultMessage: 'My loans'
  },
  more: {
    id: 'SearchHeader.more',
    description: 'Label for the link to show more',
    defaultMessage: 'More'
  },
  contactUs: {
    id: 'SearchHeader.contactUs',
    description: 'Label for the link to go to the contact page',
    defaultMessage: 'Contact us'
  },
  searchInputPlaceholder: {
    id: 'SearchHeader.searchInputPlaceholder',
    description: 'Placeholder for the search field',
    defaultMessage: 'Search by title, author, subject, or other keywords'
  },
  searchLabel: {
    id: 'SearchHeader.searchLabel',
    description: 'Label for the main search bar',
    defaultMessage: 'Search the collections'
  },
  search: {
    id: 'SearchHeader.search',
    description: 'Label on search button',
    defaultMessage: 'Search'
  },
  logout: {
    id: 'Navigation.logout',
    description: 'Shown when logged in',
    defaultMessage: 'Log out'
  },
  logIn: {
    id: 'Navigation.logIn',
    description: 'Shown when logged out',
    defaultMessage: 'Log in'
  },
  register: {
    id: 'Navigation.register',
    description: 'Register link in main menu',
    defaultMessage: 'Register'
  },
  faq: {
    id: 'Navigation.faq',
    description: 'Faq link in main menu',
    defaultMessage: 'FAQ'
  },
  loggedInAs: {
    id: 'Navigation.loggedInAs',
    description: 'Shown then logged',
    defaultMessage: 'Logged in as:'
  },
  luceneSyntaxError: {
    id: 'Search.querySyntaxError',
    description: 'Shown when search query does not conform to lucene syntax',
    defaultMessage: `It looks like you want to perform an advanced search with field codes, but the query is not quite right.
    Check that any parentheses and quotes match and note that colon (":") is meant to be used to specify a field.
    If you want to search for anything with a colon or any other special symbols, try enclosing it in quotes (")`
  },
  unknownFields: {
    id: 'Search.unknownField',
    description: 'Shown when an unknown field i specified',
    defaultMessage:`It looks like you want to perform an advanced search with field codes, but you have specified one or more fields that are we don't recognize:{unrecognizedFields}
    We suggest you replace these with recognized field codes, otherwise the search may turn up empty`
  }
})

export default injectIntl(SearchHeader)
<commit_msg>patron-client: experimental allow search even when some field codes may be unknown DEICH-880<commit_after>import React, {PropTypes} from 'react'
import NonIETransitionGroup from './NonIETransitionGroup'
import {Link} from 'react-router'
import {push} from 'react-router-redux'
import {defineMessages, FormattedMessage, injectIntl, intlShape} from 'react-intl'
import MediaQuery from 'react-responsive'
import LuceneParser from 'lucene-query-parser'
import {queryFieldTranslations} from '../constants/Constants'

class SearchHeader extends React.Component {
  constructor (props) {
    super(props)
    this.handleSearch = this.handleSearch.bind(this)
    this.handleLoginClick = this.handleLoginClick.bind(this)
    this.toggleMobileNav = this.toggleMobileNav.bind(this)
    this.handleRegistrationClick = this.handleRegistrationClick.bind(this)
    this.checkQuery = this.checkQuery.bind(this)
    this.state = { querySyntaxError: false, unknownFields:{} }
  }

  checkQuery (event) {
    let unknownFields = []

    function traverse (node) {
      if (node.left) {
        traverse(node.left)
      }
      if (node.field && node.field !== '<implicit>') {
        if (!{...queryFieldTranslations, '*':'*'}[ node.field ]) {
          unknownFields[ node.field ] = {}
        }
      }
      if (node.right) {
        traverse(node.right)
      }
    }

    try {
      traverse(LuceneParser.parse(this.searchFieldInput.value))

      this.setState(prevState => {
        return {
          ...prevState,
          querySyntaxError: false,
          unknownFields
        }
      });
    } catch (err) {
      console.log(err)
      this.setState(prevState => ({
        ...prevState,
        querySyntaxError: true,
        unknownFields:{}
      }));
    }
  }

  handleSearch (event) {
    event.preventDefault()
    if (!this.state.querySyntaxError) {

      this.state.unknownFields = {}

      // Ensure that page-param is deleted on new search
      delete this.props.locationQuery[ 'page' ]
      this.props.mobileNavigationActions.hideMobileNavigation()

      /* Active filters are removed on new query */
      if (this.props.path.includes('/work')) {
        this.props.dispatch(push({ pathname: '/search', query: { query: this.searchFieldInput.value } }))
      } else {
        /* Active filters are NOT removed on new query */
        this.props.locationQuery.query = this.searchFieldInput.value
        this.props.dispatch(push({ pathname: '/search', query: this.props.locationQuery }))
      }
      this.props.searchActions.search()
    }
  }

  handleLoginClick (event) {
    event.preventDefault()
    this.props.mobileNavigationActions.hideMobileNavigation()
    this.props.showLoginDialog(push({ pathname: '/profile/loans' }))
  }

  handleRegistrationClick (event) {
    event.preventDefault()
    this.props.startRegistration()
  }

  toggleMobileNav () {
    this.props.mobileNavigationActions.toggleMobileNavigation()
  }

  /**
   * Links used in the menu and the mobile menu
   */
  loginLink () {
    if (!this.props.isLoggedIn) {
      return [

          <Link to="/" >
            <FormattedMessage {...messages.logIn} /> »
          </Link >

      ]
    }
  }

  logoutLink () {
    if (this.props.isLoggedIn) {
      return [

          <Link to="/" >
            <FormattedMessage {...messages.logout} /> »
          </Link >

      ]
    }
  }

  profileLink () {
    if (this.props.isLoggedIn) {
      return [

          <Link to="/profile/loans" ><FormattedMessage {...messages.myProfile} /> »</Link >

      ]
    }
  }

  registrationLink () {
    if (!this.props.isLoggedIn) {
      return (

          <FormattedMessage {...messages.register} />»

      )
    }
  }

  faqLink () {
    return (

          <FormattedMessage {...messages.faq} />
          »

    )
  }

  loggedInMessage () {
    const { borrowerName } = this.props
    return (

        <FormattedMessage {...messages.loggedInAs} /> {borrowerName}

    )
  }

  /**
   * Renders the menu, and/or the mobile menu
   */
  renderNavigationLinks () {
    const { borrowerName } = this.props
    return (

        {this.faqLink()}
        {this.profileLink()}
        {this.registrationLink()}
        {borrowerName
          ? (
            <MediaQuery query="(min-width: 992px)" values={{ ...this.props.mediaQueryValues }} >

{this.loggedInMessage()}

            </MediaQuery >
          )
          : null}
        {this.loginLink()}
        {this.logoutLink()}

    )
  }

  renderMobileNavigationLinks () {
    return (

        {this.faqLink()}
        {this.profileLink()}
        {this.registrationLink()}
        {this.loginLink()}
        {this.logoutLink()}

    )
  }

  render () {
    const { borrowerName } = this.props
    const mobileNavClass = this.props.showMobileNavigation ? 'primary-mobile-menu' : 'primary-mobile-menu collapsed'
    return (

        <NonIETransitionGroup
          transitionName="fade-in"
          transitionAppear
          transitionAppearTimeout={500}
          transitionEnterTimeout={500}
          transitionLeaveTimeout={500}
          component="header"
          className="wrapper" >

          <MediaQuery query="(max-width: 667px)" values={{ ...this.props.mediaQueryValues }} >

          </MediaQuery >

          <MediaQuery query="(min-width: 668px)" values={{ ...this.props.mediaQueryValues }} >

              {this.renderNavigationLinks()}

          </MediaQuery >

          {borrowerName
            ? (<MediaQuery query="(max-width: 991px)" values={{ ...this.props.mediaQueryValues }} >

                {this.loggedInMessage()}

            </MediaQuery >)
            : null}

        </NonIETransitionGroup >

        <MediaQuery query="(max-width: 667px)" values={{ ...this.props.mediaQueryValues }} >

            {this.renderMobileNavigationLinks()}

        </MediaQuery >

        <NonIETransitionGroup
          transitionName="fade-in"
          transitionAppear
          transitionAppearTimeout={500}
          transitionEnterTimeout={500}
          transitionLeaveTimeout={500}
          component="section"
          className="search-box-wrapper"
          role="search" >

            <form onSubmit={this.handleSearch} >
              {this.props.intl.formatMessage(messages.searchLabel)}:

                  <input placeholder={this.props.intl.formatMessage(messages.searchInputPlaceholder)}
                         id="search"
                         type="text"
                         defaultValue={this.props.locationQuery.query || ''}
                         ref={e => this.searchFieldInput = e}
                         onChange={this.checkQuery}
                         data-automation-id="search_input_field"
                  />

                  <button onClick={this.handleSearch} type="button" className="search-submit"
                          data-automation-id="search_button" disabled={this.state.querySyntaxError} >
                    {!this.props.isSearching
                      ? <FormattedMessage {...messages.search} />
                      :

                    }
                  </button >

            </form >

          {Object.keys(this.state.unknownFields).length > 0
            ?

<FormattedMessage {...Object.assign(messages.unknownFields, {values:{unrecognizedFields:Object.keys(this.state.unknownFields).map(field => `"${field}"`).join(', ')}})}/>

            : ''
          }
          {this.state.querySyntaxError
            ?

<FormattedMessage {...messages.luceneSyntaxError} />

            : ''
          }
        </NonIETransitionGroup >

    )
  }
}

SearchHeader.propTypes = {
  locationQuery: PropTypes.object.isRequired,
  requireLoginBeforeAction: PropTypes.func.isRequired,
  dispatch: PropTypes.func.isRequired,
  locale: PropTypes.string.isRequired,
  showLoginDialog: PropTypes.func.isRequired,
  isLoggedIn: PropTypes.bool.isRequired,
  logout: PropTypes.func.isRequired,
  mediaQueryValues: PropTypes.object,
  intl: intlShape.isRequired,
  startRegistration: PropTypes.func.isRequired,
  showMobileNavigation: PropTypes.bool.isRequired,
  mobileNavigationActions: PropTypes.object.isRequired,
  searchActions: PropTypes.object.isRequired,
  isSearching: PropTypes.bool.isRequired,
  borrowerName: PropTypes.string,
  path: PropTypes.string.isRequired
}

export const messages = defineMessages({
  logoAlt: {
    id: 'SearchHeader.logoAlt',
    description: 'Alt text for the logo',
    defaultMessage: 'Black logo with text'
  },
  myProfile: {
    id: 'SearchHeader.myProfile',
    description: 'Label for the link to go to the user profile',
    defaultMessage: 'My profile'
  },
  myLoans: {
    id: 'SearchHeader.myLoans',
    description: 'Label for the link to go to the users loans',
    defaultMessage: 'My loans'
  },
  more: {
    id: 'SearchHeader.more',
    description: 'Label for the link to show more',
    defaultMessage: 'More'
  },
  contactUs: {
    id: 'SearchHeader.contactUs',
    description: 'Label for the link to go to the contact page',
    defaultMessage: 'Contact us'
  },
  searchInputPlaceholder: {
    id: 'SearchHeader.searchInputPlaceholder',
    description: 'Placeholder for the search field',
    defaultMessage: 'Search by title, author, subject, or other keywords'
  },
  searchLabel: {
    id: 'SearchHeader.searchLabel',
    description: 'Label for the main search bar',
    defaultMessage: 'Search the collections'
  },
  search: {
    id: 'SearchHeader.search',
    description: 'Label on search button',
    defaultMessage: 'Search'
  },
  logout: {
    id: 'Navigation.logout',
    description: 'Shown when logged in',
    defaultMessage: 'Log out'
  },
  logIn: {
    id: 'Navigation.logIn',
    description: 'Shown when logged out',
    defaultMessage: 'Log in'
  },
  register: {
    id: 'Navigation.register',
    description: 'Register link in main menu',
    defaultMessage: 'Register'
  },
  faq: {
    id: 'Navigation.faq',
    description: 'Faq link in main menu',
    defaultMessage: 'FAQ'
  },
  loggedInAs: {
    id: 'Navigation.loggedInAs',
    description: 'Shown then logged',
    defaultMessage: 'Logged in as:'
  },
  luceneSyntaxError: {
    id: 'Search.querySyntaxError',
    description: 'Shown when search query does not conform to lucene syntax',
    defaultMessage: `It looks like you want to perform an advanced search with field codes, but the query is not quite right.
    Check that any parentheses and quotes match and note that colon (":") is meant to be used to specify a field.
    If you want to search for anything with a colon or any other special symbols, try enclosing it in quotes (")`
  },
  unknownFields: {
    id: 'Search.unknownField',
    description: 'Shown when an unknown field i specified',
    defaultMessage:`It looks like you want to perform an advanced search with field codes, but you have specified one or more fields that are we don't recognize:{unrecognizedFields}
    We suggest you replace these with recognized field codes, otherwise the search may turn up empty`
  }
})

export default injectIntl(SearchHeader)
<|endoftext|>


<commit_before>/****************************************************************************
 Copyright (c) 2008-2010 Ricardo Quesada
 Copyright (c) 2011-2012 cocos2d-x.org
 Copyright (c) 2013-2014 Chukong Technologies Inc.

 http://www.cocos2d-x.org

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 ****************************************************************************/

var audioEngine = cc.audioEngine;

var MUSIC_FILE = "res/Sound/background.mp3";
var EFFECT_FILE = "res/Sound/effect2.mp3";

var _DenshionTests = [
    'Music Test'
];
var DenshionTests = [
    {
        title:"Play Music",
        playFunc:function () {
            return new playMusic();
        }
    },
    {
        title:"Stop Music",
        playFunc:function () {
            return new stopMusic();
        }
    },
    {
        title:"Pause Music",
        playFunc:function () {
            return new pauseMusic();
        }
    },
    {
        title:"Resume Music",
        playFunc:function () {
            return new resumeMusic();
        }
    },
    {
        title:"Rewind Music",
        playFunc:function () {
            return new rewindMusic();
        }
    },
    {
        title:"is Music Playing",
        playFunc:function () {
            return new isMusicPlaying();
        }
    },
    {
        title:"Increase Music Volume",
        playFunc:function () {
            return new addMusicVolume();
        }
    },
    {
        title:"Decrease Music Volume",
        playFunc:function () {
            return new subMusicVolume();
        }
    },
    {
        title:"Play Sound Effect",
        playFunc:function () {
            return new playEffect();
        }
    },
    {
        title:"Repeat Sound Effect",
        playFunc:function () {
            return new playEffectRepeatly();
        }
    },
    {
        title:"Stop Sound Effect",
        playFunc:function () {
            return new stopEffect();
        }
    },
    {
        title:"Unload Sound Effect",
        playFunc:function () {
            return new unloadEffect();
        }
    },
    {
        title:"Increase Sound Effect Volume",
        playFunc:function () {
            return new addEffectsVolume();
        }
    },
    {
        title:"Decrease Sound Effect Volume",
        playFunc:function () {
            return new subEffectsVolume();
        }
    },
    {
        title:"Pause Sound Effect",
        playFunc:function () {
            return new pauseEffect();
        }
    },
    {
        title:"Resume Sound Effect",
        playFunc:function () {
            return new resumeEffect();
        }
    },
    {
        title:"Pause All Sound Effects",
        playFunc:function () {
            return new pauseAllEffects();
        }
    },
    {
        title:"Resume All Sound Effects",
        playFunc:function () {
            return new resumeAllEffects();
        }
    },
    {
        title:"Stop All Sound Effects",
        playFunc:function () {
            return new stopAllEffects();
        }
    }
];

var CocosDenshionTest = cc.LayerGradient.extend({
    _itemMenu:null,
    _beginPos:cc.p(0, 0),
    _testCount:0,
    ctor:function () {
        this._super(cc.color(0, 0, 0, 255), cc.color(148, 80, 120, 255));

        this._itemMenu = cc.Menu.create();
        var winSize = director.getWinSize();
        for (var i = 0; i < DenshionTests.length; i++) {
            var label = cc.LabelTTF.create(DenshionTests[i].title, "Arial", 24);
            var menuItem = cc.MenuItemLabel.create(label, this.onMenuCallback, this);
            this._itemMenu.addChild(menuItem, i + 10000);
            menuItem.x = winSize.width / 2;
            menuItem.y = winSize.height - (i + 1) * LINE_SPACE;
        }
        this._testCount = i;
        this._itemMenu.width = winSize.width;
	    this._itemMenu.height = (this._testCount + 1) * LINE_SPACE;
        this._itemMenu.x = 0;
        this._itemMenu.y = 0;
        this.addChild(this._itemMenu);

        if( 'touches' in cc.sys.capabilities ) {
            cc.eventManager.addListener({
                event: cc.EventListener.TOUCH_ALL_AT_ONCE,
                onTouchesMoved: function (touches, event) {
                    event.getCurrentTarget().moveMenu(touches[0].getDelta());
                }
            }, this);
        } else if ('mouse' in cc.sys.capabilities )
             cc.eventManager.addListener({
                event: cc.EventListener.MOUSE,
                 onMouseMove: function(event){
                     if(event.getButton() != undefined)
                        event.getCurrentTarget().moveMenu(event.getDelta());
                 }
             }, this);

        // set default volume
        audioEngine.setEffectsVolume(0.5);
        audioEngine.setMusicVolume(0.5);
    },
    onExit:function () {
        this._super();
        this.stopMusic();
        this.stopAllEffects();
    },

    onMenuCallback:function (sender) {
        var idx = sender.zIndex - 10000;
        // create the test scene and run it
        var scene = DenshionTests[idx].playFunc();
    },

    moveMenu:function (delta) {
        var newY = this._itemMenu.y + delta.y;

        if (newY < 0)
            newY = 0;

        if (newY > ((DenshionTests.length + 1) * LINE_SPACE - winSize.height))
            newY = ((DenshionTests.length + 1) * LINE_SPACE - winSize.height);

        this._itemMenu.y = newY;
    }
});

var CocosDenshionTestScene = TestScene.extend({
    runThisTest:function () {

        audioEngine = cc.audioEngine;
        var layer = new CocosDenshionTest();
        this.addChild(layer);
        director.runScene(this);
    }
});

var soundId = null;

var playMusic = function () {
    cc.log("play background music");
    audioEngine.playMusic(MUSIC_FILE, false);
};

var stopMusic = function () {
    cc.log("stop background music");
    audioEngine.stopMusic();
};

var pauseMusic = function () {
    cc.log("pause background music");
    audioEngine.pauseMusic();
};

var resumeMusic = function () {
    cc.log("resume background music");
    audioEngine.resumeMusic();
};

var rewindMusic = function () {
    cc.log("rewind background music");
    audioEngine.rewindMusic();
};

// is background music playing
var isMusicPlaying = function () {
    if (audioEngine.isMusicPlaying()) {
        cc.log("background music is playing");
    }
    else {
        cc.log("background music is not playing");
    }
};

var playEffect = function () {
    cc.log("play effect");
    soundId = audioEngine.playEffect(EFFECT_FILE);
};

var playEffectRepeatly = function () {
    cc.log("play effect repeatly");
    soundId = audioEngine.playEffect(EFFECT_FILE, true);
};

var stopEffect = function () {
    cc.log("stop effect");
    audioEngine.stopEffect(soundId);
};

var unloadEffect = function () {
    cc.log("unload effect");
    audioEngine.unloadEffect(EFFECT_FILE);
};

var addMusicVolume = function () {
    cc.log("add bakcground music volume");
    audioEngine.setMusicVolume(audioEngine.getMusicVolume() + 0.1);
};

var subMusicVolume = function () {
    cc.log("sub backgroud music volume");
    audioEngine.setMusicVolume(audioEngine.getMusicVolume() - 0.1);
};

var addEffectsVolume = function () {
    cc.log("add effects volume");
    audioEngine.setEffectsVolume(audioEngine.getEffectsVolume() + 0.1);
};

var subEffectsVolume = function () {
    cc.log("sub effects volume");
    audioEngine.setEffectsVolume(audioEngine.getEffectsVolume() - 0.1);
};

var pauseEffect = function () {
    cc.log("pause effect");
    audioEngine.pauseEffect(soundId);
};

var resumeEffect = function () {
    cc.log("resume effect");
    audioEngine.resumeEffect(soundId);
};

var pauseAllEffects = function () {
    cc.log("pause all effects");
    audioEngine.pauseAllEffects();
};
var resumeAllEffects = function () {
    cc.log("resume all effects");
    audioEngine.resumeAllEffects();
};
var stopAllEffects = function () {
    cc.log("stop all effects");
    audioEngine.stopAllEffects();
};<commit_msg>Fixed #5620: Change logic of cc.audioEngine.end() in JSB<commit_after>/****************************************************************************
 Copyright (c) 2008-2010 Ricardo Quesada
 Copyright (c) 2011-2012 cocos2d-x.org
 Copyright (c) 2013-2014 Chukong Technologies Inc.

 http://www.cocos2d-x.org

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 ****************************************************************************/

var audioEngine = cc.audioEngine;

var MUSIC_FILE = "res/Sound/background.mp3";
var EFFECT_FILE = "res/Sound/effect2.mp3";

var _DenshionTests = [
    'Music Test'
];
var DenshionTests = [
    {
        title:"Play Music",
        playFunc:function () {
            return new playMusic();
        }
    },
    {
        title:"Stop Music",
        playFunc:function () {
            return new stopMusic();
        }
    },
    {
        title:"Pause Music",
        playFunc:function () {
            return new pauseMusic();
        }
    },
    {
        title:"Resume Music",
        playFunc:function () {
            return new resumeMusic();
        }
    },
    {
        title:"Rewind Music",
        playFunc:function () {
            return new rewindMusic();
        }
    },
    {
        title:"is Music Playing",
        playFunc:function () {
            return new isMusicPlaying();
        }
    },
    {
        title:"Increase Music Volume",
        playFunc:function () {
            return new addMusicVolume();
        }
    },
    {
        title:"Decrease Music Volume",
        playFunc:function () {
            return new subMusicVolume();
        }
    },
    {
        title:"Play Sound Effect",
        playFunc:function () {
            return new playEffect();
        }
    },
    {
        title:"Repeat Sound Effect",
        playFunc:function () {
            return new playEffectRepeatly();
        }
    },
    {
        title:"Stop Sound Effect",
        playFunc:function () {
            return new stopEffect();
        }
    },
    {
        title:"Unload Sound Effect",
        playFunc:function () {
            return new unloadEffect();
        }
    },
    {
        title:"Increase Sound Effect Volume",
        playFunc:function () {
            return new addEffectsVolume();
        }
    },
    {
        title:"Decrease Sound Effect Volume",
        playFunc:function () {
            return new subEffectsVolume();
        }
    },
    {
        title:"Pause Sound Effect",
        playFunc:function () {
            return new pauseEffect();
        }
    },
    {
        title:"Resume Sound Effect",
        playFunc:function () {
            return new resumeEffect();
        }
    },
    {
        title:"Pause All Sound Effects",
        playFunc:function () {
            return new pauseAllEffects();
        }
    },
    {
        title:"Resume All Sound Effects",
        playFunc:function () {
            return new resumeAllEffects();
        }
    },
    {
        title:"Stop All Sound Effects",
        playFunc:function () {
            return new stopAllEffects();
        }
    }
];

var CocosDenshionTest = cc.LayerGradient.extend({
    _itemMenu:null,
    _beginPos:cc.p(0, 0),
    _testCount:0,
    ctor:function () {
        this._super(cc.color(0, 0, 0, 255), cc.color(148, 80, 120, 255));

        this._itemMenu = cc.Menu.create();
        var winSize = director.getWinSize();
        for (var i = 0; i < DenshionTests.length; i++) {
            var label = cc.LabelTTF.create(DenshionTests[i].title, "Arial", 24);
            var menuItem = cc.MenuItemLabel.create(label, this.onMenuCallback, this);
            this._itemMenu.addChild(menuItem, i + 10000);
            menuItem.x = winSize.width / 2;
            menuItem.y = winSize.height - (i + 1) * LINE_SPACE;
        }
        this._testCount = i;
        this._itemMenu.width = winSize.width;
	    this._itemMenu.height = (this._testCount + 1) * LINE_SPACE;
        this._itemMenu.x = 0;
        this._itemMenu.y = 0;
        this.addChild(this._itemMenu);

        if( 'touches' in cc.sys.capabilities ) {
            cc.eventManager.addListener({
                event: cc.EventListener.TOUCH_ALL_AT_ONCE,
                onTouchesMoved: function (touches, event) {
                    event.getCurrentTarget().moveMenu(touches[0].getDelta());
                }
            }, this);
        } else if ('mouse' in cc.sys.capabilities )
             cc.eventManager.addListener({
                event: cc.EventListener.MOUSE,
                 onMouseMove: function(event){
                     if(event.getButton() != undefined)
                        event.getCurrentTarget().moveMenu(event.getDelta());
                 }
             }, this);

        // set default volume
        audioEngine.setEffectsVolume(0.5);
        audioEngine.setMusicVolume(0.5);
    },
    onExit:function () {
        this._super();
        audioEngine.stopMusic();
        audioEngine.stopAllEffects();
    },

    onMenuCallback:function (sender) {
        var idx = sender.zIndex - 10000;
        // create the test scene and run it
        var scene = DenshionTests[idx].playFunc();
    },

    moveMenu:function (delta) {
        var newY = this._itemMenu.y + delta.y;

        if (newY < 0)
            newY = 0;

        if (newY > ((DenshionTests.length + 1) * LINE_SPACE - winSize.height))
            newY = ((DenshionTests.length + 1) * LINE_SPACE - winSize.height);

        this._itemMenu.y = newY;
    }
});

var CocosDenshionTestScene = TestScene.extend({
    runThisTest:function () {

        audioEngine = cc.audioEngine;
        var layer = new CocosDenshionTest();
        this.addChild(layer);
        director.runScene(this);
    }
});

var soundId = null;

var playMusic = function () {
    cc.log("play background music");
    audioEngine.playMusic(MUSIC_FILE, false);
};

var stopMusic = function () {
    cc.log("stop background music");
    audioEngine.stopMusic();
};

var pauseMusic = function () {
    cc.log("pause background music");
    audioEngine.pauseMusic();
};

var resumeMusic = function () {
    cc.log("resume background music");
    audioEngine.resumeMusic();
};

var rewindMusic = function () {
    cc.log("rewind background music");
    audioEngine.rewindMusic();
};

// is background music playing
var isMusicPlaying = function () {
    if (audioEngine.isMusicPlaying()) {
        cc.log("background music is playing");
    }
    else {
        cc.log("background music is not playing");
    }
};

var playEffect = function () {
    cc.log("play effect");
    soundId = audioEngine.playEffect(EFFECT_FILE);
};

var playEffectRepeatly = function () {
    cc.log("play effect repeatly");
    soundId = audioEngine.playEffect(EFFECT_FILE, true);
};

var stopEffect = function () {
    cc.log("stop effect");
    audioEngine.stopEffect(soundId);
};

var unloadEffect = function () {
    cc.log("unload effect");
    audioEngine.unloadEffect(EFFECT_FILE);
};

var addMusicVolume = function () {
    cc.log("add bakcground music volume");
    audioEngine.setMusicVolume(audioEngine.getMusicVolume() + 0.1);
};

var subMusicVolume = function () {
    cc.log("sub backgroud music volume");
    audioEngine.setMusicVolume(audioEngine.getMusicVolume() - 0.1);
};

var addEffectsVolume = function () {
    cc.log("add effects volume");
    audioEngine.setEffectsVolume(audioEngine.getEffectsVolume() + 0.1);
};

var subEffectsVolume = function () {
    cc.log("sub effects volume");
    audioEngine.setEffectsVolume(audioEngine.getEffectsVolume() - 0.1);
};

var pauseEffect = function () {
    cc.log("pause effect");
    audioEngine.pauseEffect(soundId);
};

var resumeEffect = function () {
    cc.log("resume effect");
    audioEngine.resumeEffect(soundId);
};

var pauseAllEffects = function () {
    cc.log("pause all effects");
    audioEngine.pauseAllEffects();
};
var resumeAllEffects = function () {
    cc.log("resume all effects");
    audioEngine.resumeAllEffects();
};
var stopAllEffects = function () {
    cc.log("stop all effects");
    audioEngine.stopAllEffects();
};<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var moment = require('moment');
var request = require('request');
var nconf = require('nconf');
nconf.argv();

var token = nconf.get('token') || process.env.OANDA_TOKEN;
var time = nconf.get('time')
	? nconf.get('time') + 'T22:00:00Z'
	: '';
if(time && moment().isAfter(moment(time))) {
	time = moment(time).utc().format();
} else {
	time = '';
}
var margin = parseFloat(nconf.get('margin')) || 1.0;
var target = parseFloat(nconf.get('target')) || 0.0075;

var data = nconf.get('csv') || './report.csv';

// Create the parser
var parser = require('csv-parse')();

var results = {};
var wagers = [];
var rankBase = {};
var rankTotal = {usd:0, gbp:0, aud:0, eur:0, nzd:0, chf:0, cad:0, jpy:0};

// Use the writable stream api
parser.on('readable', function(){
	var cursor;
	var record;
	var chance;
	var odds;
	var meetsCriterion;
	var currency;
	var position;

  while(record = parser.read()){
	  currency = record[0].toLowerCase();
	  position = record[1];
		chance = parseFloat(record[2]) || 0.0;
		odds = parseFloat(record[3]) || 0.0;
		meetsCriterion = record[4] === 'true';

		if(!results[currency]) {
			results[currency] = {
				hold: {
					wager: 0.0,
					total: 0
				},
				long: {
					wager: 0.0,
					total: 0
				},
				short: {
					wager: 0.0,
					total: 0
				}
			};
		}

		if(chance > 0.0 && odds > 0.0) {
			if(meetsCriterion) {
				results[currency][position]['chance'] = (results[currency][position]['chance'] || 0.0) + chance;
				results[currency][position]['odds'] = (results[currency][position]['odds'] || 0.0) + odds;

				results[currency][position]['total'] = (results[currency][position]['total'] || 0) + 1.0;
			}
		}
  }
});
// Catch any error
parser.on('error', function(err){
  console.error(err.message);
});
// When we are done, test that the parsed output matched what expected
parser.on('finish', function(){
	var currencies = _.keys(results);
	var positions = ['hold', 'long', 'short'];
	var currency, position;
	var chance = 0.0;
	var odds = 0.0;
	var wager;
	var netWager;
	var total;
	var pair;
	var multiplier;

	// console.error('results:', JSON.stringify(results, null, '\t'));

	for(var i = 0; i< currencies.length; i++) {
		currency = currencies[i];
		// console.error('-------', currency,'-------');

		total = 0.0;
		for(var j=0; j < positions.length; j++) {
			position = positions[j];
			odds = 0.0;

			if(results[currency][position]['total']) {
				chance = results[currency][position]['chance'] / results[currency][position]['total'];
				odds = (results[currency][position]['odds'] / results[currency][position]['total']) - 1.0;
			}

			if(odds > 0.0) {
				wager = chance - ((1.0 - chance) / odds);
				results[currency][position]['wager'] = wager;

				// console.error(position, ':', (wager * 100.0) + '%');
			} else {
				results[currency][position]['wager'] = 0.0;
			}

			total = total + results[currency][position]['wager'];
		}

		netWager =  results[currency]['long']['wager'] - results[currency]['short']['wager'];
		multiplier = netWager > 0.0 ? 1.0 : -1.0;

		pair = getPair(currency);

		rankBase[pair.base] = (rankBase[pair.base] || 0.0) + netWager;
		rankBase[pair.other] = (rankBase[pair.other] || 0.0) - netWager;
		rankTotal[pair.base]++;
		rankTotal[pair.other]++;

		// if(results[currency]['hold']['wager'] < results[currency][netWager > 0.0 ? 'long' : 'short']['wager'])
		wagers.push({currency: currency, wager: netWager});
	}

	var rankings = {};
	_.each(_.keys(rankBase), function(key) {
		rankings[key] = 100.0 * (rankBase[key] || 0.0) / rankTotal[key];
	});

	_.each(_.keys(results), function (currencyPair) {
		var newPair = getPair(currencyPair);

		var sum = rankings[newPair.base] - rankings[newPair.other];

		switch(currencyPair) {
			case 'audjpy':
			case 'audusd':
			case 'euraud':
			case 'eurchf':
			case 'eurgbp':
			case 'eurjpy':
			case 'eurusd':
			case 'gbpchf':
			case 'gbpjpy':
			case 'gbpusd':
			case 'nzdusd':
			case 'usdcad':
			case 'usdchf':
			case 'usdjpy':
				fetchEntryOrders(currencyPair, time, function(orderEntryResult) {
					fetchStopOrders(currencyPair, time, orderEntryResult, function(orderStopResult) {
						fetchOpenPositions(currencyPair, time, function(openPositionsResult) {
							console.error('open positions:', openPositionsResult);
							console.error('orders (default):', orderEntryResult);
							console.error('orders (alt):', orderStopResult);

							var bullish = openPositionsResult.bullish && orderEntryResult.bullish
								? 'yes'
								: openPositionsResult.bullish || orderEntryResult.bullish
									? 'neutral'
									: 'no';
							var risk = sum > 0.0
								? Math.abs(orderEntryResult.bid - orderStopResult.bid)
								: Math.abs(orderEntryResult.ask - orderStopResult.ask);
							var el = sum > 0.0
								? orderEntryResult.bid
								: orderEntryResult.ask;
							var sl = sum > 0.0
								? el - risk
								: el + risk;
							var tp1 = sum > 0.0
								? el + risk
								: el - risk;
							var tp2 = sum > 0.0
								? orderEntryResult.ask
								: orderEntryResult.bid;

							console.log(
								sum,
								currencyPair,
								'bullish:'+bullish,
								'risk:'+(risk * 100.0 / openPositionsResult.rate),
								'sl:'+sl, 'el:'+el, 'tp1:'+tp1, 'tp2:'+tp2
							);
						});
					});
				});
				break;
			default:
				console.log(sum, currencyPair);
		};
	});
});

var lineReader = require('readline').createInterface({
  input: require('fs').createReadStream(data)
});

var lineCount = 0;
lineReader.on('line', function (line) {
	if(lineCount) {
		parser.write(line + '\n');
	}

	lineCount++;
});

lineReader.on('close', function() {
	parser.end();
})

function getPair(currency) {
	var base = currency.substring(0, 3);
	var other = currency.substring(3, 6);

	var pair = {
		base: base,
		other: other
	};

	return pair;
}

var currencyPairMapping = {
	audjpy: 'AUD_JPY',
	audusd: 'AUD_USD',
	euraud: 'EUR_AUD',
	eurchf: 'EUR_CHF',
	eurgbp: 'EUR_GBP',
	eurjpy: 'EUR_JPY',
	eurusd: 'EUR_USD',
	gbpchf: 'GBP_CHF',
	gbpjpy: 'GBP_JPY',
	gbpusd: 'GBP_USD',
	nzdusd: 'NZD_USD',
	usdcad: 'USD_CAD',
	usdchf: 'USD_CHF',
	usdjpy: 'USD_JPY'
};

function fetchEntryOrders(currencyPair, time, done) {
	var instrument = currencyPairMapping[currencyPair];
	var requestUrl = 'https://api-fxpractice.oanda.com/v3/instruments/' + instrument + '/orderBook';

	if(time) {
		requestUrl = requestUrl + '?time=' + time;
	}

	var requestOpts = {
		url: requestUrl,
		gzip: true,
		headers: {
			'Authorization': 'Bearer ' + token
		}
	};

	var results = {};

	request(requestOpts, function(error, response, body) {
		var data = JSON.parse(body);
		if(error || _.has(data, 'errorMessage')) {
			return console.error('error:', error || data.errorMessage, requestUrl);
		}

		var rate = parseFloat(data.orderBook.price);
		var sum = { bid: 0.0, ask: 0.0 };
		var total = { bid: 0, ask: 0 };

		_.each(_.sortBy(data.orderBook.buckets, 'price'), function(pricePoint) {
			pricePoint = _.reduce(_.keys(pricePoint), function(acc, key) {
				acc[key] = parseFloat(pricePoint[key]);

				return acc;
			}, {});

			var distOrig = pricePoint.price - rate;
			var dist = Math.abs(distOrig);
			var net = pricePoint.longCountPercent - pricePoint.shortCountPercent;

			if(dist < margin * rate) {
				if(distOrig < 0.0) {
					sum.bid = sum.bid + (net * dist);
					total.bid = total.bid + Math.abs(net);
				} else {
					sum.ask = sum.ask + (net * dist);
					total.ask = total.ask + Math.abs(net);
				}
			}
		});

		var bid = rate - Math.abs(sum.bid / total.bid);
		var ask = rate + Math.abs(sum.ask / total.ask);
		var average = (bid + ask) / 2.0;
		var bullish = (sum.bid / (Math.abs(sum.bid) + Math.abs(sum.ask))) > 0.5;

		done({
			instrument: instrument,
			ask: _.isNaN(ask) ? 0.0 : ask,
			rate: rate,
			bid: _.isNaN(bid) ? 0.0 : bid,
			average: average,
			bullish: bullish
		});
	});
}

function fetchStopOrders(currencyPair, time, entryOrders, done) {
	var instrument = currencyPairMapping[currencyPair];
	var requestUrl = 'https://api-fxpractice.oanda.com/v3/instruments/' + instrument + '/orderBook';

	if(time) {
		requestUrl = requestUrl + '?time=' + time;
	}

	var requestOpts = {
		url: requestUrl,
		gzip: true,
		headers: {
			'Authorization': 'Bearer ' + token
		}
	};

	var results = {};

	request(requestOpts, function(error, response, body) {
		var data = JSON.parse(body);
		if(error || _.has(data, 'errorMessage')) {
			return console.error('error:', error || data.errorMessage, requestUrl);
		}

		var rate = parseFloat(data.orderBook.price);
		var sum = { bid: 0.0, ask: 0.0 };
		var total = { bid: 0, ask: 0 };

		_.each(_.sortBy(data.orderBook.buckets, 'price'), function(pricePoint) {
			pricePoint = _.reduce(_.keys(pricePoint), function(acc, key) {
				acc[key] = parseFloat(pricePoint[key]);

				return acc;
			}, {});

			var distOrig = pricePoint.price - rate;
			var dist = Math.abs(distOrig);
			var net = pricePoint.longCountPercent - pricePoint.shortCountPercent;

			if(dist < margin * rate && (pricePoint.price > entryOrders.ask || pricePoint.price < entryOrders.bid)) {
				if(distOrig < 0.0) {
					sum.bid = sum.bid + (net * dist);
					total.bid = total.bid + Math.abs(net);
				} else {
					sum.ask = sum.ask + (net * dist);
					total.ask = total.ask + Math.abs(net);
				}
			}
		});

		var bid = rate - Math.abs(sum.bid / total.bid);
		var ask = rate + Math.abs(sum.ask / total.ask);
		var average = (bid + ask) / 2.0;
		var bullish = (sum.bid / (Math.abs(sum.bid) + Math.abs(sum.ask))) > 0.5;

		done({
			instrument: instrument,
			ask: _.isNaN(ask) ? 0.0 : ask,
			rate: rate,
			bid: _.isNaN(bid) ? 0.0 : bid,
			average: average,
			bullish: bullish
		});
	});
}

function fetchOpenPositions(currencyPair, time, done) {
	var instrument = currencyPairMapping[currencyPair];
	var requestUrl = 'https://api-fxpractice.oanda.com/v3/instruments/' + instrument + '/positionBook';

	if(time) {
		requestUrl = requestUrl + '?time=' + time;
	}

	var requestOpts = {
		url: requestUrl,
		gzip: true,
		headers: {
			'Authorization': 'Bearer ' + token
		}
	};

	var results = {};

	request(requestOpts, function(error, response, body) {
		var data = JSON.parse(body);
		if(error || _.has(data, 'errorMessage')) {
			return console.error('error:', error || data.errorMessage, requestUrl);
		}

		var rate = parseFloat(data.positionBook.price);
		var sum = { bid: 0.0, ask: 0.0 };
		var total = { bid: 0, ask: 0 };

		_.each(_.sortBy(data.positionBook.buckets, 'price'), function(pricePoint) {
			pricePoint = _.reduce(_.keys(pricePoint), function(acc, key) {
				acc[key] = parseFloat(pricePoint[key]);

				return acc;
			}, {});

			var d = Math.abs(pricePoint.price - rate);

			if(d/rate <= margin) {
				if(pricePoint.longCountPercent) {
					sum.ask = sum.ask + (pricePoint.longCountPercent * pricePoint.price);
					total.ask = total.ask + pricePoint.longCountPercent;
				}

				if(pricePoint.shortCountPercent) {
					//console.error(pricePoint);
					sum.bid = sum.bid + (pricePoint.shortCountPercent * pricePoint.price);
					total.bid = total.bid + pricePoint.shortCountPercent;
				}
			}
		});

		var bid = sum.bid / total.bid;
		var ask = sum.ask / total.ask;
		var average = (bid + ask) / 2.0;
		var bullish = average < rate;

		done({
			instrument: instrument,
			ask: _.isNaN(ask) ? 0.0 : ask,
			rate: rate,
			bid: _.isNaN(bid) ? 0.0 : bid,
			average: average,
			bullish: bullish
		});
	});
}
<commit_msg>fix: improve entry/exit precision<commit_after>'use strict';

var _ = require('lodash');
var moment = require('moment');
var request = require('request');
var nconf = require('nconf');
nconf.argv();

var token = nconf.get('token') || process.env.OANDA_TOKEN;
var time = nconf.get('time')
	? nconf.get('time') + 'T22:00:00Z'
	: '';
if(time && moment().isAfter(moment(time))) {
	time = moment(time).utc().format();
} else {
	time = '';
}
var margin = parseFloat(nconf.get('margin')) || 1.0;
var target = parseFloat(nconf.get('target')) || 0.0075;

var data = nconf.get('csv') || './report.csv';

// Create the parser
var parser = require('csv-parse')();

var results = {};
var wagers = [];
var rankBase = {};
var rankTotal = {usd:0, gbp:0, aud:0, eur:0, nzd:0, chf:0, cad:0, jpy:0};

// Use the writable stream api
parser.on('readable', function(){
	var cursor;
	var record;
	var chance;
	var odds;
	var meetsCriterion;
	var currency;
	var position;

  while(record = parser.read()){
	  currency = record[0].toLowerCase();
	  position = record[1];
		chance = parseFloat(record[2]) || 0.0;
		odds = parseFloat(record[3]) || 0.0;
		meetsCriterion = record[4] === 'true';

		if(!results[currency]) {
			results[currency] = {
				hold: {
					wager: 0.0,
					total: 0
				},
				long: {
					wager: 0.0,
					total: 0
				},
				short: {
					wager: 0.0,
					total: 0
				}
			};
		}

		if(chance > 0.0 && odds > 0.0) {
			if(meetsCriterion) {
				results[currency][position]['chance'] = (results[currency][position]['chance'] || 0.0) + chance;
				results[currency][position]['odds'] = (results[currency][position]['odds'] || 0.0) + odds;

				results[currency][position]['total'] = (results[currency][position]['total'] || 0) + 1.0;
			}
		}
  }
});
// Catch any error
parser.on('error', function(err){
  console.error(err.message);
});
// When we are done, test that the parsed output matched what expected
parser.on('finish', function(){
	var currencies = _.keys(results);
	var positions = ['hold', 'long', 'short'];
	var currency, position;
	var chance = 0.0;
	var odds = 0.0;
	var wager;
	var netWager;
	var total;
	var pair;
	var multiplier;

	// console.error('results:', JSON.stringify(results, null, '\t'));

	for(var i = 0; i< currencies.length; i++) {
		currency = currencies[i];
		// console.error('-------', currency,'-------');

		total = 0.0;
		for(var j=0; j < positions.length; j++) {
			position = positions[j];
			odds = 0.0;

			if(results[currency][position]['total']) {
				chance = results[currency][position]['chance'] / results[currency][position]['total'];
				odds = (results[currency][position]['odds'] / results[currency][position]['total']) - 1.0;
			}

			if(odds > 0.0) {
				wager = chance - ((1.0 - chance) / odds);
				results[currency][position]['wager'] = wager;

				// console.error(position, ':', (wager * 100.0) + '%');
			} else {
				results[currency][position]['wager'] = 0.0;
			}

			total = total + results[currency][position]['wager'];
		}

		netWager =  results[currency]['long']['wager'] - results[currency]['short']['wager'];
		multiplier = netWager > 0.0 ? 1.0 : -1.0;

		pair = getPair(currency);

		rankBase[pair.base] = (rankBase[pair.base] || 0.0) + netWager;
		rankBase[pair.other] = (rankBase[pair.other] || 0.0) - netWager;
		rankTotal[pair.base]++;
		rankTotal[pair.other]++;

		// if(results[currency]['hold']['wager'] < results[currency][netWager > 0.0 ? 'long' : 'short']['wager'])
		wagers.push({currency: currency, wager: netWager});
	}

	var rankings = {};
	_.each(_.keys(rankBase), function(key) {
		rankings[key] = 100.0 * (rankBase[key] || 0.0) / rankTotal[key];
	});

	_.each(_.keys(results), function (currencyPair) {
		var newPair = getPair(currencyPair);

		var sum = rankings[newPair.base] - rankings[newPair.other];

		switch(currencyPair) {
			case 'audjpy':
			case 'audusd':
			case 'euraud':
			case 'eurchf':
			case 'eurgbp':
			case 'eurjpy':
			case 'eurusd':
			case 'gbpchf':
			case 'gbpjpy':
			case 'gbpusd':
			case 'nzdusd':
			case 'usdcad':
			case 'usdchf':
			case 'usdjpy':
				fetchEntryOrders(currencyPair, time, function(orderEntryResult) {
					fetchStopOrders(currencyPair, time, orderEntryResult, function(orderStopResult) {
						fetchOpenPositions(currencyPair, time, function(openPositionsResult) {
							console.error('open positions:', openPositionsResult);
							console.error('orders (default):', orderEntryResult);
							console.error('orders (alt):', orderStopResult);

							var bullish = openPositionsResult.bullish && orderEntryResult.bullish
								? 'yes'
								: openPositionsResult.bullish || orderEntryResult.bullish
									? 'neutral'
									: 'no';
							var sl = sum > 0.0
								? orderStopResult.bid
								: orderStopResult.ask;
							var el = sum > 0.0
								? (orderEntryResult.bid + openPositionsResult.bid) / 2.0
								: (orderEntryResult.ask + openPositionsResult.ask) / 2.0;
							var risk = el - sl;
							var tp1 = el + risk;
							var tp2 = sum < 0.0
								? (orderEntryResult.bid + openPositionsResult.bid) / 2.0
								: (orderEntryResult.ask + openPositionsResult.ask) / 2.0;
							var reward = tp2 - el;
							var ratio = reward / risk;

							console.log(
								sum,
								currencyPair,
								'bullish:'+bullish,
								'risk:'+(risk * 100.0 / openPositionsResult.rate),
								'sl:'+sl, 'el:'+el, 'tp1:'+tp1, 'tp2:'+tp2,
								'ratio:'+ratio
							);
						});
					});
				});
				break;
			default:
		};
	});
});

var lineReader = require('readline').createInterface({
  input: require('fs').createReadStream(data)
});

var lineCount = 0;
lineReader.on('line', function (line) {
	if(lineCount) {
		parser.write(line + '\n');
	}

	lineCount++;
});

lineReader.on('close', function() {
	parser.end();
})

function getPair(currency) {
	var base = currency.substring(0, 3);
	var other = currency.substring(3, 6);

	var pair = {
		base: base,
		other: other
	};

	return pair;
}

var currencyPairMapping = {
	audjpy: 'AUD_JPY',
	audusd: 'AUD_USD',
	euraud: 'EUR_AUD',
	eurchf: 'EUR_CHF',
	eurgbp: 'EUR_GBP',
	eurjpy: 'EUR_JPY',
	eurusd: 'EUR_USD',
	gbpchf: 'GBP_CHF',
	gbpjpy: 'GBP_JPY',
	gbpusd: 'GBP_USD',
	nzdusd: 'NZD_USD',
	usdcad: 'USD_CAD',
	usdchf: 'USD_CHF',
	usdjpy: 'USD_JPY'
};

function fetchEntryOrders(currencyPair, time, done) {
	var instrument = currencyPairMapping[currencyPair];
	var requestUrl = 'https://api-fxpractice.oanda.com/v3/instruments/' + instrument + '/orderBook';

	if(time) {
		requestUrl = requestUrl + '?time=' + time;
	}

	var requestOpts = {
		url: requestUrl,
		gzip: true,
		headers: {
			'Authorization': 'Bearer ' + token
		}
	};

	var results = {};

	request(requestOpts, function(error, response, body) {
		var data = JSON.parse(body);
		if(error || _.has(data, 'errorMessage')) {
			return console.error('error:', error || data.errorMessage, requestUrl);
		}

		var rate = parseFloat(data.orderBook.price);
		var sum = { bid: 0.0, ask: 0.0 };
		var total = { bid: 0, ask: 0 };

		_.each(_.sortBy(data.orderBook.buckets, 'price'), function(pricePoint) {
			pricePoint = _.reduce(_.keys(pricePoint), function(acc, key) {
				acc[key] = parseFloat(pricePoint[key]);

				return acc;
			}, {});

			var distOrig = pricePoint.price - rate;
			var dist = Math.abs(distOrig);
			var net = pricePoint.price > rate
				? pricePoint.longCountPercent
				: pricePoint.shortCountPercent;

			if(dist < margin * rate) {
				if(distOrig < 0.0) {
					sum.bid = sum.bid + (net * dist);
					total.bid = total.bid + Math.abs(net);
				} else {
					sum.ask = sum.ask + (net * dist);
					total.ask = total.ask + Math.abs(net);
				}
			}
		});

		var bid = rate - Math.abs(sum.bid / total.bid);
		var ask = rate + Math.abs(sum.ask / total.ask);
		var average = (bid + ask) / 2.0;
		var bullish = (sum.bid / (Math.abs(sum.bid) + Math.abs(sum.ask))) > 0.5;

		done({
			instrument: instrument,
			ask: _.isNaN(ask) ? 0.0 : ask,
			rate: rate,
			bid: _.isNaN(bid) ? 0.0 : bid,
			average: average,
			bullish: bullish
		});
	});
}

function fetchStopOrders(currencyPair, time, entryOrders, done) {
	var instrument = currencyPairMapping[currencyPair];
	var requestUrl = 'https://api-fxpractice.oanda.com/v3/instruments/' + instrument + '/orderBook';

	if(time) {
		requestUrl = requestUrl + '?time=' + time;
	}

	var requestOpts = {
		url: requestUrl,
		gzip: true,
		headers: {
			'Authorization': 'Bearer ' + token
		}
	};

	var results = {};

	request(requestOpts, function(error, response, body) {
		var data = JSON.parse(body);
		if(error || _.has(data, 'errorMessage')) {
			return console.error('error:', error || data.errorMessage, requestUrl);
		}

		var rate = parseFloat(data.orderBook.price);
		var sum = { bid: 0.0, ask: 0.0 };
		var total = { bid: 0, ask: 0 };

		_.each(_.sortBy(data.orderBook.buckets, 'price'), function(pricePoint) {
			pricePoint = _.reduce(_.keys(pricePoint), function(acc, key) {
				acc[key] = parseFloat(pricePoint[key]);

				return acc;
			}, {});

			var distOrig = pricePoint.price - rate;
			var dist = Math.abs(distOrig);
			var net = pricePoint.price > rate
				? pricePoint.shortCountPercent
				: pricePoint.longCountPercent;

			if(dist < margin * rate && (pricePoint.price > entryOrders.ask || pricePoint.price < entryOrders.bid)) {
				if(distOrig < 0.0) {
					sum.bid = sum.bid + (net * dist);
					total.bid = total.bid + Math.abs(net);
				} else {
					sum.ask = sum.ask + (net * dist);
					total.ask = total.ask + Math.abs(net);
				}
			}
		});

		var bid = rate - Math.abs(sum.bid / total.bid);
		var ask = rate + Math.abs(sum.ask / total.ask);
		var average = (bid + ask) / 2.0;
		var bullish = (sum.bid / (Math.abs(sum.bid) + Math.abs(sum.ask))) > 0.5;

		done({
			instrument: instrument,
			ask: _.isNaN(ask) ? 0.0 : ask,
			rate: rate,
			bid: _.isNaN(bid) ? 0.0 : bid,
			average: average,
			bullish: bullish
		});
	});
}

function fetchOpenPositions(currencyPair, time, done) {
	var instrument = currencyPairMapping[currencyPair];
	var requestUrl = 'https://api-fxpractice.oanda.com/v3/instruments/' + instrument + '/positionBook';

	if(time) {
		requestUrl = requestUrl + '?time=' + time;
	}

	var requestOpts = {
		url: requestUrl,
		gzip: true,
		headers: {
			'Authorization': 'Bearer ' + token
		}
	};

	var results = {};

	request(requestOpts, function(error, response, body) {
		var data = JSON.parse(body);
		if(error || _.has(data, 'errorMessage')) {
			return console.error('error:', error || data.errorMessage, requestUrl);
		}

		var rate = parseFloat(data.positionBook.price);
		var sum = { bid: 0.0, ask: 0.0 };
		var total = { bid: 0, ask: 0 };

		_.each(_.sortBy(data.positionBook.buckets, 'price'), function(pricePoint) {
			pricePoint = _.reduce(_.keys(pricePoint), function(acc, key) {
				acc[key] = parseFloat(pricePoint[key]);

				return acc;
			}, {});

			var d = Math.abs(pricePoint.price - rate);

			if(d/rate <= margin) {
				if(pricePoint.longCountPercent) {
					sum.ask = sum.ask + (pricePoint.longCountPercent * pricePoint.price);
					total.ask = total.ask + pricePoint.longCountPercent;
				}

				if(pricePoint.shortCountPercent) {
					//console.error(pricePoint);
					sum.bid = sum.bid + (pricePoint.shortCountPercent * pricePoint.price);
					total.bid = total.bid + pricePoint.shortCountPercent;
				}
			}
		});

		var bid = sum.bid / total.bid;
		var ask = sum.ask / total.ask;
		var average = (bid + ask) / 2.0;
		var bullish = average < rate;

		done({
			instrument: instrument,
			ask: _.isNaN(ask) ? 0.0 : ask,
			rate: rate,
			bid: _.isNaN(bid) ? 0.0 : bid,
			average: average,
			bullish: bullish
		});
	});
}
<|endoftext|>


<commit_before>var EXPORTED_SYMBOLS = ['GM_xmlhttpRequester'];

Components.utils.import("resource://greasemonkey/util.js");

function GM_xmlhttpRequester(wrappedContentWin, chromeWindow, originUrl) {
  this.wrappedContentWin = wrappedContentWin;
  this.chromeWindow = chromeWindow;
  this.originUrl = originUrl;
}

// this function gets called by user scripts in content security scope to
// start a cross-domain xmlhttp request.
//
// details should look like:
// {method,url,onload,onerror,onreadystatechange,headers,data}
// headers should be in the form {name:value,name:value,etc}
// can't support mimetype because i think it's only used for forcing
// text/xml and we can't support that
GM_xmlhttpRequester.prototype.contentStartRequest = function(details) {
  if (!GM_util.apiLeakCheck("GM_xmlhttpRequest", arguments)) {
    return;
  }

  try {
    // Validate and parse the (possibly relative) given URL.
    var uri = GM_util.uriFromUrl(details.url, this.originUrl);
    var url = uri.spec;
  } catch (e) {
    // A malformed URL won't be parsed properly.
    throw new Error(
        this.stringBundle.GetStringFromName('error.invalidUrl')
            .replace('%1', name)
        );
  }

  // This is important - without it, GM_xmlhttpRequest can be used to get
  // access to things like files and chrome. Careful.
  switch (uri.scheme) {
    case "http":
    case "https":
    case "ftp":
        var req = new this.chromeWindow.XMLHttpRequest();
        GM_util.hitch(this, "chromeStartRequest", url, details, req)();
      break;
    default:
      throw new Error(
          this.stringBundle.GetStringFromName('error.disallowedScheme')
              .replace('%1', details.url)
          );
  }

  var rv = {
    __exposedProps__: {
        finalUrl: "r",
        readyState: "r",
        responseHeaders: "r",
        responseText: "r",
        status: "r",
        statusText: "r",
        abort: "r"
        },
    abort: function () { return req.abort(); }
  };
  if (!!details.synchronous) {
    rv.finalUrl = req.finalUrl;
    rv.readyState = req.readyState;
    rv.responseHeaders = req.getAllResponseHeaders();
    rv.responseText = req.responseText;
    rv.status = req.status;
    rv.statusText = req.statusText;
  }
  return rv;
};

// this function is intended to be called in chrome's security context, so
// that it can access other domains without security warning
GM_xmlhttpRequester.prototype.chromeStartRequest =
function(safeUrl, details, req) {
  this.setupReferer(details, req);

  var setupRequestEvent = GM_util.hitch(
      this, 'setupRequestEvent', this.wrappedContentWin);

  setupRequestEvent(req, "abort", details);
  setupRequestEvent(req, "error", details);
  setupRequestEvent(req, "load", details);
  setupRequestEvent(req, "progress", details);
  setupRequestEvent(req, "readystatechange", details);
  setupRequestEvent(req, "timeout", details);
  if (details.upload) {
    setupRequestEvent(req.upload, "abort", details.upload);
    setupRequestEvent(req.upload, "error", details.upload);
    setupRequestEvent(req.upload, "load", details.upload);
    setupRequestEvent(req.upload, "progress", details.upload);
  }

  req.mozBackgroundRequest = !!details.mozBackgroundRequest;

  req.open(details.method, safeUrl,
      !details.synchronous, details.user || "", details.password || "");

  if (details.overrideMimeType) {
    req.overrideMimeType(details.overrideMimeType);
  }
  if (details.responseType) {
    req.responseType = details.responseType;
  }
  if (details.timeout) {
    req.timeout = details.timeout;
  }

  if ('redirectionLimit' in details) {
    try {
      var httpChannel = req.channel.QueryInterface(
          Components.interfaces.nsIHttpChannel);
      httpChannel.redirectionLimit = details.redirectionLimit;
    } catch (e) {
      // Ignore.
    }
  }

  if (details.headers) {
    var headers = details.headers;

    for (var prop in headers) {
      if (Object.prototype.hasOwnProperty.call(headers, prop)) {
        req.setRequestHeader(prop, headers[prop]);
      }
    }
  }

  var body = details.data ? details.data : null;
  if (details.binary) {
    req.sendAsBinary(body);
  } else {
    req.send(body);
  }
};

// sets the "Referer" HTTP header for this GM_XHR request.
// Firefox does not let chrome JS set the "Referer" HTTP heade via XHR
// directly. However, we can still set it indirectly via an
// http-on-modify-request observer.
GM_xmlhttpRequester.prototype.setupReferer =
function(details, req) {
  if (!details.headers || !details.headers.Referer) return;

  var observerService = Components.classes["@mozilla.org/observer-service;1"]
      .getService(Components.interfaces.nsIObserverService);
  var requestObserver = {};
  requestObserver.observe = function(subject, topic, data) {
      observerService.removeObserver(requestObserver, "http-on-modify-request");

      var channel = subject.QueryInterface(Components.interfaces.nsIChannel);
      if (channel == req.channel) {
        var httpChannel = subject.QueryInterface(
            Components.interfaces.nsIHttpChannel);
        httpChannel.setRequestHeader("Referer", details.headers.Referer, false);
      }
    };
  observerService.addObserver(requestObserver, "http-on-modify-request", false);
};

// arranges for the specified 'event' on xmlhttprequest 'req' to call the
// method by the same name which is a property of 'details' in the content
// window's security context.
GM_xmlhttpRequester.prototype.setupRequestEvent =
function(wrappedContentWin, req, event, details) {
  if (!details["on" + event]) return;

  req.addEventListener(event, function(evt) {
    var responseState = {
      __exposedProps__: {
          context: "r",
          finalUrl: "r",
          lengthComputable: "r",
          loaded: "r",
          readyState: "r",
          response: "r",
          responseHeaders: "r",
          responseText: "r",
          status: "r",
          statusText: "r",
          total: "r",
          },
      context: details.context || null,
      // Can't support responseXML because security won't
      // let the browser call properties on it.
      readyState: req.readyState,
      response: req.response,
      responseHeaders: null,
      responseText: null,
      status: null,
      statusText: null,
      finalUrl: null
    };

    try {
      responseState.responseText = req.responseText;
    } catch (e) {
      // Some response types don't have .responseText (but do have e.g. blob
      // .response).  Ignore.
    }

    switch (event) {
      case "progress":
        responseState.lengthComputable = evt.lengthComputable;
        responseState.loaded = evt.loaded;
        responseState.total = evt.total;
        break;
      case "error":
        break;
      default:
        if (4 != req.readyState) break;
        responseState.responseHeaders = req.getAllResponseHeaders();
        responseState.status = req.status;
        responseState.statusText = req.statusText;
        responseState.finalUrl = req.channel.URI.spec;
        break;
    }

    if (GM_util.windowIsClosed(wrappedContentWin)) {
      return;
    }

    // Pop back onto browser thread and call event handler.
    // Have to use nested function here instead of GM_util.hitch because
    // otherwise details[event].apply can point to window.setTimeout, which
    // can be abused to get increased privileges.
    new XPCNativeWrapper(wrappedContentWin, "setTimeout()")
      .setTimeout(function(){ details["on" + event](responseState); }, 0);
  }, false);
};
<commit_msg>Support .responseXML in GM_xmlhttpRequest().<commit_after>var EXPORTED_SYMBOLS = ['GM_xmlhttpRequester'];

Components.utils.import("resource://greasemonkey/util.js");

function GM_xmlhttpRequester(wrappedContentWin, chromeWindow, originUrl) {
  this.wrappedContentWin = wrappedContentWin;
  this.chromeWindow = chromeWindow;
  this.originUrl = originUrl;
}

// this function gets called by user scripts in content security scope to
// start a cross-domain xmlhttp request.
//
// details should look like:
// {method,url,onload,onerror,onreadystatechange,headers,data}
// headers should be in the form {name:value,name:value,etc}
// can't support mimetype because i think it's only used for forcing
// text/xml and we can't support that
GM_xmlhttpRequester.prototype.contentStartRequest = function(details) {
  if (!GM_util.apiLeakCheck("GM_xmlhttpRequest", arguments)) {
    return;
  }

  try {
    // Validate and parse the (possibly relative) given URL.
    var uri = GM_util.uriFromUrl(details.url, this.originUrl);
    var url = uri.spec;
  } catch (e) {
    // A malformed URL won't be parsed properly.
    throw new Error(
        this.stringBundle.GetStringFromName('error.invalidUrl')
            .replace('%1', name)
        );
  }

  // This is important - without it, GM_xmlhttpRequest can be used to get
  // access to things like files and chrome. Careful.
  switch (uri.scheme) {
    case "http":
    case "https":
    case "ftp":
        var req = new this.chromeWindow.XMLHttpRequest();
        GM_util.hitch(this, "chromeStartRequest", url, details, req)();
      break;
    default:
      throw new Error(
          this.stringBundle.GetStringFromName('error.disallowedScheme')
              .replace('%1', details.url)
          );
  }

  var rv = {
    __exposedProps__: {
        finalUrl: "r",
        readyState: "r",
        responseHeaders: "r",
        responseText: "r",
        status: "r",
        statusText: "r",
        abort: "r"
        },
    abort: function () { return req.abort(); }
  };
  if (!!details.synchronous) {
    rv.finalUrl = req.finalUrl;
    rv.readyState = req.readyState;
    rv.responseHeaders = req.getAllResponseHeaders();
    rv.responseText = req.responseText;
    rv.status = req.status;
    rv.statusText = req.statusText;
  }
  return rv;
};

// this function is intended to be called in chrome's security context, so
// that it can access other domains without security warning
GM_xmlhttpRequester.prototype.chromeStartRequest =
function(safeUrl, details, req) {
  this.setupReferer(details, req);

  var setupRequestEvent = GM_util.hitch(
      this, 'setupRequestEvent', this.wrappedContentWin);

  setupRequestEvent(req, "abort", details);
  setupRequestEvent(req, "error", details);
  setupRequestEvent(req, "load", details);
  setupRequestEvent(req, "progress", details);
  setupRequestEvent(req, "readystatechange", details);
  setupRequestEvent(req, "timeout", details);
  if (details.upload) {
    setupRequestEvent(req.upload, "abort", details.upload);
    setupRequestEvent(req.upload, "error", details.upload);
    setupRequestEvent(req.upload, "load", details.upload);
    setupRequestEvent(req.upload, "progress", details.upload);
  }

  req.mozBackgroundRequest = !!details.mozBackgroundRequest;

  req.open(details.method, safeUrl,
      !details.synchronous, details.user || "", details.password || "");

  if (details.overrideMimeType) {
    req.overrideMimeType(details.overrideMimeType);
  }

  if (details.timeout) {
    req.timeout = details.timeout;
  }

  if ('redirectionLimit' in details) {
    try {
      var httpChannel = req.channel.QueryInterface(
          Components.interfaces.nsIHttpChannel);
      httpChannel.redirectionLimit = details.redirectionLimit;
    } catch (e) {
      // Ignore.
    }
  }

  if (details.headers) {
    var headers = details.headers;

    for (var prop in headers) {
      if (Object.prototype.hasOwnProperty.call(headers, prop)) {
        req.setRequestHeader(prop, headers[prop]);
      }
    }
  }

  var body = details.data ? details.data : null;
  if (details.binary) {
    req.sendAsBinary(body);
  } else {
    req.send(body);
  }
};

// sets the "Referer" HTTP header for this GM_XHR request.
// Firefox does not let chrome JS set the "Referer" HTTP heade via XHR
// directly. However, we can still set it indirectly via an
// http-on-modify-request observer.
GM_xmlhttpRequester.prototype.setupReferer =
function(details, req) {
  if (!details.headers || !details.headers.Referer) return;

  var observerService = Components.classes["@mozilla.org/observer-service;1"]
      .getService(Components.interfaces.nsIObserverService);
  var requestObserver = {};
  requestObserver.observe = function(subject, topic, data) {
      observerService.removeObserver(requestObserver, "http-on-modify-request");

      var channel = subject.QueryInterface(Components.interfaces.nsIChannel);
      if (channel == req.channel) {
        var httpChannel = subject.QueryInterface(
            Components.interfaces.nsIHttpChannel);
        httpChannel.setRequestHeader("Referer", details.headers.Referer, false);
      }
    };
  observerService.addObserver(requestObserver, "http-on-modify-request", false);
};

// arranges for the specified 'event' on xmlhttprequest 'req' to call the
// method by the same name which is a property of 'details' in the content
// window's security context.
GM_xmlhttpRequester.prototype.setupRequestEvent =
function(wrappedContentWin, req, event, details) {
  if (!details["on" + event]) return;

  req.addEventListener(event, function(evt) {
    var responseState = {
      __exposedProps__: {
          context: "r",
          finalUrl: "r",
          lengthComputable: "r",
          loaded: "r",
          readyState: "r",
          response: "r",
          responseHeaders: "r",
          responseText: "r",
          responseXML: "r",
          status: "r",
          statusText: "r",
          total: "r",
          },
      context: details.context || null,
      readyState: req.readyState,
      response: req.response,
      responseHeaders: null,
      responseText: null,
      responseXML: null,
      status: null,
      statusText: null,
      finalUrl: null
    };

    try {
      responseState.responseText = req.responseText;
    } catch (e) {
      // Some response types don't have .responseText (but do have e.g. blob
      // .response).  Ignore.
    }

    if (req.responseXML) {
      // Adopt the XML object into a content-window-scoped document.
      var xmlDoc = wrappedContentWin.Document();
      xmlDoc.appendChild(xmlDoc.adoptNode(req.responseXML.documentElement));
      responseState.responseXML = xmlDoc;
    }

    switch (event) {
      case "progress":
        responseState.lengthComputable = evt.lengthComputable;
        responseState.loaded = evt.loaded;
        responseState.total = evt.total;
        break;
      case "error":
        break;
      default:
        if (4 != req.readyState) break;
        responseState.responseHeaders = req.getAllResponseHeaders();
        responseState.status = req.status;
        responseState.statusText = req.statusText;
        responseState.finalUrl = req.channel.URI.spec;
        break;
    }

    if (GM_util.windowIsClosed(wrappedContentWin)) {
      return;
    }

    // Pop back onto browser thread and call event handler.
    // Have to use nested function here instead of GM_util.hitch because
    // otherwise details[event].apply can point to window.setTimeout, which
    // can be abused to get increased privileges.
    new XPCNativeWrapper(wrappedContentWin, "setTimeout()")
      .setTimeout(function(){ details["on" + event](responseState); }, 0);
  }, false);
};
<|endoftext|>


<commit_before>// svg.filter.js 0.4 - Copyright (c) 2013-2014 Wout Fierens - Licensed under the MIT license
;(function() {

  // Main filter class
  SVG.Filter = function() {
    this.constructor.call(this, SVG.create('filter'))
  }

  // Inherit from SVG.Container
  SVG.Filter.prototype = new SVG.Parent

  //
  SVG.extend(SVG.Filter, {
    // Static strings
    source:           'SourceGraphic'
  , sourceAlpha:      'SourceAlpha'
  , background:       'BackgroundImage'
  , backgroundAlpha:  'BackgroundAlpha'
  , fill:             'FillPaint'
  , stroke:           'StrokePaint'
    // Custom put method for leaner code
  , put: function(element, i) {
      this.add(element, i)

      return element.attr({
        in:     this.source
      , result: element
      })
    }
    // Blend effect
  , blend: function(in1, in2, mode) {
      return this.put(new SVG.BlendEffect).attr({ in: in1, in2: in2, mode: mode || 'normal' })
    }
    // ColorMatrix effect
  , colorMatrix: function(type, values) {
      if (type == 'matrix')
        values = normaliseMatrix(values)

      return this.put(new SVG.ColorMatrixEffect).attr({
        type:   type
      , values: typeof values == 'undefined' ? null : values
      })
    }
    // ConvolveMatrix effect
  , convolveMatrix: function(matrix) {
      matrix = normaliseMatrix(matrix)

      return this.put(new SVG.ConvolveMatrixEffect).attr({
        order:        Math.sqrt(matrix.split(' ').length)
      , kernelMatrix: matrix
      })
    }
    // ComponentTransfer effect
  , componentTransfer: function(compontents) {
      var transfer = new SVG.ComponentTransferEffect

      /* create rgb set */
      transfer.rgb = new SVG.Set

      /* create components */
      ;(['r', 'g', 'b', 'a']).forEach(function(c) {
        /* create component */
        transfer[c] = new SVG['Func' + c.toUpperCase()]().attr('type', 'identity')

        /* store component in set */
        transfer.rgb.add(transfer[c])

        /* add component node */
        transfer.node.appendChild(transfer[c].node)
      })

      /* set components */
      if (compontents) {
        if (compontents.rgb) {
          /* set bundled components */
          ;(['r', 'g', 'b']).forEach(function(c) {
            transfer[c].attr(compontents.rgb)
          })

          delete compontents.rgb
        }

        /* set individual components */
        for (var c in compontents)
          transfer[c].attr(compontents[c])
      }

      return this.put(transfer)
    }
    // Composite effect
  , composite: function(in1, in2, operator) {
      return this.put(new SVG.CompositeEffect).attr({ in: in1, in2: in2, operator: operator })
    }
    // Flood effect
  , flood: function(color) {
      return this.put(new SVG.FloodEffect).attr({ 'flood-color': color })
    }
    // Offset effect
  , offset: function(x, y) {
      return this.put(new SVG.OffsetEffect).attr({ dx: x, dy: y })
    }
    // Image effect
  , image: function(src) {
      return this.put(new SVG.ImageEffect).attr('href', src, SVG.xlink)
    }
    // Merge effect
  , merge: function() {
      // to be implemented
    }
    // Gaussian Blur effect
  , gaussianBlur: function() {
      return this.put(new SVG.GaussianBlurEffect).attr('stdDeviation', listString(Array.prototype.slice.call(arguments)))
    }
    // Default string value
  , toString: function() {
      return 'url(#' + this.attr('id') + ')'
    }

  })

  //
  SVG.extend(SVG.Defs, {
    // Define filter
    filter: function(block) {
      var filter = this.put(new SVG.Filter)

      /* invoke passed block */
      if (typeof block === 'function')
        block.call(filter, filter)

      return filter
    }

  })

  //
  SVG.extend(SVG.Container, {
    // Define filter on defs
    filter: function(block) {
      return this.defs().filter(block)
    }

  })

  //
  SVG.extend(SVG.Element, SVG.G, SVG.Nested, {
    // Create filter element in defs and store reference
    filter: function(block) {
      this.filterer = block instanceof SVG.Element ?
        block : this.doc().filter(block)

      this.attr('filter', this.filterer)

      return this.filterer
    }
    // Remove filter
  , unfilter: function(remove) {
      /* also remove the filter node */
      if (this.filterer && remove === true)
        this.filterer.remove()

      /* delete reference to filterer */
      delete this.filterer

      /* remove filter attribute */
      return this.attr('filter', null)
    }

  })

  // Create wrapping SVG.Effect class
  SVG.Effect = function() {}

  // Inherit from SVG.Element
  SVG.Effect.prototype = new SVG.Element

  SVG.extend(SVG.Effect, {
    // Set in attribute
    in: function(effect) {
      return this.attr('in', effect)
    }
    // Named result
  , result: function() {
      return this.attr('id') + 'Out'
    }
    // Stringification
  , toString: function() {
      return this.result()
    }

  })

  // Create all different effects
  var effects = [
      'blend'
    , 'colorMatrix'
    , 'componentTransfer'
    , 'composite'
    , 'convolveMatrix'
    , 'diffuseLighting'
    , 'displacementMap'
    , 'flood'
    , 'gaussianBlur'
    , 'image'
    , 'merge'
    , 'morphology'
    , 'offset'
    , 'specularLighting'
    , 'tile'
    , 'turbulence'
    , 'distantLight'
    , 'pointLight'
    , 'spotLight'
  ]

  effects.forEach(function(effect) {
    /* capitalize name */
    var name = effect.charAt(0).toUpperCase() + effect.slice(1)

    /* create class */
    SVG[name + 'Effect'] = function() {
      this.constructor.call(this, SVG.create('fe' + name))
    }

    /* inherit from SVG.Effect */
    SVG[name + 'Effect'].prototype = ['componentTransfer'].indexOf(name) > -1 ?
      new SVG.Parent : new SVG.Effect

    /* make all effects interchainable */
    effects.forEach(function(e) {

      SVG[name + 'Effect'].prototype[e] = function() {
        return this.parent[e].apply(this.parent, arguments).in(this)
      }

    })

  })

  // Create compontent functions
  ;(['r', 'g', 'b', 'a']).forEach(function(c) {
    /* create class */
    SVG['Func' + c.toUpperCase()] = function() {
      this.constructor.call(this, SVG.create('feFunc' + c.toUpperCase()))
    }

    /* inherit from SVG.Element */
    SVG['Func' + c.toUpperCase()].prototype = new SVG.Element

  })

  // Effect-specific extensions
  SVG.extend(SVG.FloodEffect, {
    // implement flood-color and flood-opacity
  })

  // Presets
  SVG.filter = {
    sepiatone:  [ .343, .669, .119, 0, 0
                , .249, .626, .130, 0, 0
                , .172, .334, .111, 0, 0
                , .000, .000, .000, 1, 0 ]
  }

  // Helpers
  function normaliseMatrix(matrix) {
    /* convert possible array value to string */
    if (Array.isArray(matrix))
      matrix = new SVG.Array(matrix)

    /* ensure there are no leading, tailing or double spaces */
    return matrix.toString().replace(/^\s+/, '').replace(/\s+$/, '').replace(/\s+/g, ' ')
  }

  function listString(list) {
    if (!Array.isArray(list))
      return list

    for (var i = 0, l = list.length, s = []; i < l; i++)
      s.push(list[i])

    return s.join(' ')
  }

}).call(this);<commit_msg>added constructors for effects<commit_after>// svg.filter.js 0.4 - Copyright (c) 2013-2014 Wout Fierens - Licensed under the MIT license
;(function() {

  // Main filter class
  SVG.Filter = function() {
    this.constructor.call(this, SVG.create('filter'))
  }

  // Inherit from SVG.Container
  SVG.Filter.prototype = new SVG.Parent

  //
  SVG.extend(SVG.Filter, {
    // Static strings
    source:           'SourceGraphic'
  , sourceAlpha:      'SourceAlpha'
  , background:       'BackgroundImage'
  , backgroundAlpha:  'BackgroundAlpha'
  , fill:             'FillPaint'
  , stroke:           'StrokePaint'
    // Custom put method for leaner code
  , put: function(element, i) {
      this.add(element, i)

      //dont set "in" if the element is parent
      if(!(element instanceof SVG.Parent) && !element.attr('in')){
        element.attr('in',this.source);
      }
      if(!element.attr('result')){
        element.attr('result',element);
      }

      return element;
    }
    // Blend effect
  , blend: function(in1, in2, mode, attrs) {
      return this.put(new SVG.BlendEffect(in1,in2,mode,attrs));
    }
    // ColorMatrix effect
  , colorMatrix: function(type, values, attrs) {
      return this.put(new SVG.ColorMatrixEffect(type,values,attrs));
    }
    // ConvolveMatrix effect
  , convolveMatrix: function(matrix, attrs) {
      return this.put(new SVG.ConvolveMatrixEffect(matrix,attrs));
    }
    // ComponentTransfer effect
  , componentTransfer: function(compontents,attrs) {
      return this.put(new SVG.ComponentTransferEffect(compontents,attrs));
    }
    // Composite effect
  , composite: function(in1, in2, operator, attrs) {
      return this.put(new SVG.CompositeEffect(in1,in2,operator));;
    }
    // Flood effect
  , flood: function(color, attrs) {
      return this.put(new SVG.FloodEffect(color,attrs));
    }
    // Offset effect
  , offset: function(x, y, attrs) {
      return this.put(new SVG.OffsetEffect(x,y,attrs));
    }
    // Image effect
  , image: function(src, attrs) {
      return this.put(new SVG.ImageEffect(src,attrs));
    }
    // Merge effect
  , merge: function() {
      return this.put(new (bindConstructor(SVG.MergeEffect,arguments)));
    }
    // Gaussian Blur effect
  , gaussianBlur: function() {
      return this.put(new (bindConstructor(SVG.GaussianBlurEffect,arguments)));
    }
  , custom: function(type,attrs,children){
      return this.put(new SVG.CustomEffect(type,attrs,children));
    }
    // Default string value
  , toString: function() {
      return 'url(#' + this.attr('id') + ')'
    }

  })

  //
  SVG.extend(SVG.Defs, {
    // Define filter
    filter: function(block) {
      var filter = this.put(new SVG.Filter)

      /* invoke passed block */
      if (typeof block === 'function')
        block.call(filter, filter)

      return filter
    }

  })

  //
  SVG.extend(SVG.Container, {
    // Define filter on defs
    filter: function(block) {
      return this.defs().filter(block)
    }

  })

  //
  SVG.extend(SVG.Element, SVG.G, SVG.Nested, {
    // Create filter element in defs and store reference
    filter: function(block) {
      this.filterer = block instanceof SVG.Element ?
        block : this.doc().filter(block)

      if(this.doc() && this.filterer.doc() !== this.doc()){
        this.doc().defs().add(this.filterer);
      }

      this.attr('filter', this.filterer)

      return this.filterer
    }
    // Remove filter
  , unfilter: function(remove) {
      /* also remove the filter node */
      if (this.filterer && remove === true)
        this.filterer.remove()

      /* delete reference to filterer */
      delete this.filterer

      /* remove filter attribute */
      return this.attr('filter', null)
    }

  })

  // Create wrapping SVG.Effect class
  SVG.Effect = function() {}

  // Inherit from SVG.Element
  SVG.Effect.prototype = new SVG.Element

  SVG.extend(SVG.Effect, {
    // Set in attribute
    in: function(effect) {
      return this.attr('in', effect)
    }
    // Named result
  , result: function() {
      return this.attr('id') + 'Out'
    }
    // Stringification
  , toString: function() {
      return this.result()
    }

  })

  // Create all different effects
  var effects = {
    blend: function(in1,in2,mode){
      this.attr({
        in: in1,
        in2: in2,
        mode: mode || 'normal'
      })
    },
    colorMatrix: function(type,values){
      if (type == 'matrix')
        values = normaliseMatrix(values)

      this.attr({
        type:   type
      , values: typeof values == 'undefined' ? null : values
      });
    },
    convolveMatrix: function(matrix){
      matrix = normaliseMatrix(matrix)

      this.attr({
        order:        Math.sqrt(matrix.split(' ').length)
      , kernelMatrix: matrix
      });
    },
    componentTransfer: function(compontents){
      /* create rgb set */
      this.rgb = new SVG.Set;

      /* create components */
      ;(['r', 'g', 'b', 'a']).forEach(function(c) {
        /* create component */
        this[c] = new SVG['Func' + c.toUpperCase()]().attr('type', 'identity')

        /* store component in set */
        this.rgb.add(this[c])

        /* add component node */
        this.node.appendChild(this[c].node)
      })

      /* set components */
      if (compontents) {
        if (compontents.rgb) {
          /* set bundled components */
          ;(['r', 'g', 'b']).forEach(function(c) {
            this[c].attr(compontents.rgb)
          })

          delete compontents.rgb
        }

        /* set individual components */
        for (var c in compontents)
          this[c].attr(compontents[c])
      }
    },
    composite: function(in1, in2, operator){
      this.attr({
        in: in1,
        in2: in2,
        operator: operator
      })
    },
    flood: function(color){
      this.attr('flood-color',color);
    },
    offset: function(x,y){
      this.attr({
        dx: x,
        dy: y
      })
    },
    image: function(src){
      this.attr('href', src, SVG.xlink)
    },

    merge: function(){
      //add nodes
      for(var i = 0; i < arguments.length; i++){
        var node = arguments[i];
        if(node instanceof SVG.Element){
          this.put(new SVG.CustomEffect('MergeNode',{in: node.attr('result')}));
        }
        else if(typeof node == 'string'){
          this.put(new SVG.CustomEffect('MergeNode',{in: node}));
        }
      }
    },
    diffuseLighting: function(){

    },
    displacementMap: function(){

    },
    gaussianBlur: function(){
      this.attr('stdDeviation', listString(Array.prototype.slice.call(arguments)))
    },
    morphology: function(){

    },
    specularLighting: function(){

    },
    tile: function(){

    },
    turbulence: function(){

    },
    distantLight: function(){

    },
    pointLight: function(){

    },
    spotLight: function(){

    },
  }

  foreach(effects,function(effect,i){

    /* capitalize name */
    var name = i.charAt(0).toUpperCase() + i.slice(1)

    /* create class */
    SVG[name + 'Effect'] = function() {
      var attrs = arguments[arguments.length - 1];
      if(typeof attrs == 'object' && attrs.__proto__ === Object.prototype){ //make sure its a object and its prototype is Object
        Array.prototype.splice.call(arguments,arguments.length - 1, 1);
      }
      else attrs = {};

      //call super
      this.constructor.call(this, SVG.create('fe' + name))

      //call constructor for this effect, not sure about relaying on the scope, but i dont know of anything else to do
      effect.apply(this,arguments);

      this.attr(attrs);
    }

    /* inherit from SVG.Effect */
    SVG[name + 'Effect'].prototype = ['componenttransfer','merge'].indexOf(name.toLowerCase()) > -1 ?
      new SVG.Parent : new SVG.Effect

    /* make all effects interchainable */
    for(var i in effects){
      var e = effects[i];

      SVG[name + 'Effect'].prototype[e] = function() {
        return this.parent[e].apply(this.parent, arguments).in(this)
      }

    }

  });

  //custom
  SVG.CustomEffect = function(nodeType,attrs,children){
    //set prototype based of type
    this.__proto__ = ['componenttransfer','merge'].indexOf(nodeType.toLowerCase()) > -1 ?
      new SVG.Parent : new SVG.Effect

    this.constructor.call(this, SVG.create('fe' + nodeType))
    if(attrs) this.attr(attrs);

    if(this instanceof SVG.Parent){
      children = children || [];
      for(var i = 0; i < children.length; i++){
        if(children[i] instanceof SVG.Element){
          this.put(children[i]);
        }
      }
    }
  }

  // Create compontent functions
  ;(['r', 'g', 'b', 'a']).forEach(function(c) {
    /* create class */
    SVG['Func' + c.toUpperCase()] = function() {
      this.constructor.call(this, SVG.create('feFunc' + c.toUpperCase()))
    }

    /* inherit from SVG.Element */
    SVG['Func' + c.toUpperCase()].prototype = new SVG.Element

  })

  // Effect-specific extensions
  SVG.extend(SVG.FloodEffect, {
    // implement flood-color and flood-opacity
  })

  // Presets
  SVG.filter = {
    sepiatone:  [ .343, .669, .119, 0, 0
                , .249, .626, .130, 0, 0
                , .172, .334, .111, 0, 0
                , .000, .000, .000, 1, 0 ]
  }

  // Helpers
  function normaliseMatrix(matrix) {
    /* convert possible array value to string */
    if (Array.isArray(matrix))
      matrix = new SVG.Array(matrix)

    /* ensure there are no leading, tailing or double spaces */
    return matrix.toString().replace(/^\s+/, '').replace(/\s+$/, '').replace(/\s+/g, ' ')
  }

  function listString(list) {
    if (!Array.isArray(list))
      return list

    for (var i = 0, l = list.length, s = []; i < l; i++)
      s.push(list[i])

    return s.join(' ')
  }

  function foreach(obj,fn){
    for(var i in obj){
      fn(obj[i],i);
    }
  }

  function bindConstructor(fn,args){
    Array.prototype.splice.call(args,0,0,null);
    return fn.bind.apply( fn, args );
  }

}).call(this);<|endoftext|>