<commit_before>'use strict';
var site = require( './site' );
var charmap = require( './charmap' );
var error = require( './error' );

/*
    lexer token types
*/

// lexer token type enumeration
const type_ids = {
    trm : 1,
    tag : 2,
    lpr : 3,
    rpr : 5,
    neg : 7,
    oro : 11,
    wts : 13,
    quo : 17
};

// base, abstract token type
class tok {
    constructor ( tok_site ) {
        if ( !( tok_site instanceof site ) )
            throw new Error( "tok accepts only one site" );
        this._site = tok_site;
    }

    getType () {
        return this._type;
    }

    getSite () {
        return this._site;
    }

    getText () {
        return this.getSite().getText();
    }

    error ( msg ) {
        return new error( this, msg );
    }
};

// term
class trm extends tok {
    constructor ( trm_site ) {
        super( trm_site );
        this._type = type_ids.trm;
    }
};

// quote
class quo extends tok {
    constructor ( site ) {
        super( site );
        this._type = type_ids.quo;
    }
}

// tag identifier (':')
class tag extends tok {
    constructor( tag_site ) {
        super( tag_site );
        this._type = type_ids.tag;
    }
};

// left parenthesis
class lpr extends tok {
    constructor ( lpr_site ) {
        super( lpr_site );
        this._type = type_ids.lpr;
    }
};

// right parenthesis
class rpr extends tok {
    constructor ( rpr_site ) {
        super( rpr_site );
        this._type = type_ids.rpr;
    }
};

// negation operator ('!', '-')
class neg extends tok {
    constructor ( neg_site ) {
        super( neg_site );
        this._type = type_ids.neg;
    }
};

// disjunctive operator ('|')
class oro extends tok {
    constructor ( oro_site ) {
        super( oro_site );
        this._type = type_ids.oro;
    }
};

// whitespace
class wts extends tok {
    constructor ( wts_site ) {
        super( wts_site );
        this._type = type_ids.wts;
    }
};

module.exports = {
    tok : tok,
    trm : trm,
    quo : quo,
    tag : tag,
    lpr : lpr,
    rpr : rpr,
    neg : neg,
    oro : oro,
    wts : wts,
    type_ids : type_ids
};
<commit_msg>Fix: token passes itself to error instead of site<commit_after>'use strict';
var site = require( './site' );
var charmap = require( './charmap' );
var error = require( './error' );

/*
    lexer token types
*/

// lexer token type enumeration
const type_ids = {
    trm : 1,
    tag : 2,
    lpr : 3,
    rpr : 5,
    neg : 7,
    oro : 11,
    wts : 13,
    quo : 17
};

// base, abstract token type
class tok {
    constructor ( tok_site ) {
        if ( !( tok_site instanceof site ) )
            throw new Error( "tok accepts only one site" );
        this._site = tok_site;
    }

    getType () {
        return this._type;
    }

    getSite () {
        return this._site;
    }

    getText () {
        return this.getSite().getText();
    }

    error ( msg ) {
        return new error( this.getSite(), msg );
    }
};

// term
class trm extends tok {
    constructor ( trm_site ) {
        super( trm_site );
        this._type = type_ids.trm;
    }
};

// quote
class quo extends tok {
    constructor ( site ) {
        super( site );
        this._type = type_ids.quo;
    }
}

// tag identifier (':')
class tag extends tok {
    constructor( tag_site ) {
        super( tag_site );
        this._type = type_ids.tag;
    }
};

// left parenthesis
class lpr extends tok {
    constructor ( lpr_site ) {
        super( lpr_site );
        this._type = type_ids.lpr;
    }
};

// right parenthesis
class rpr extends tok {
    constructor ( rpr_site ) {
        super( rpr_site );
        this._type = type_ids.rpr;
    }
};

// negation operator ('!', '-')
class neg extends tok {
    constructor ( neg_site ) {
        super( neg_site );
        this._type = type_ids.neg;
    }
};

// disjunctive operator ('|')
class oro extends tok {
    constructor ( oro_site ) {
        super( oro_site );
        this._type = type_ids.oro;
    }
};

// whitespace
class wts extends tok {
    constructor ( wts_site ) {
        super( wts_site );
        this._type = type_ids.wts;
    }
};

module.exports = {
    tok : tok,
    trm : trm,
    quo : quo,
    tag : tag,
    lpr : lpr,
    rpr : rpr,
    neg : neg,
    oro : oro,
    wts : wts,
    type_ids : type_ids
};
<|endoftext|>


<commit_before>let TYPES = [
  { title: "Dokument",
    color: (type, i) => `hsl(30, ${80 + 5 * i}%, ${60 + 15 * i}%)`,
    types: [
      { id: 'doc.originator',
        title: "Autor/-in",
        hint: "Von wem kommts?",
        metadata: ['person']
      },
      { id: 'doc.recipient',
        title: "Empfänger/-in",
        hint: "An jemanden gerichtet?",
        metadata: ['person']
      }
    ]
  },
  { title: "Sitzung",
    color: (type, i) => `hsl(225, ${50 + 10 * i}%, ${70 + 10 * i}%)`,
    types: [
      { id: 'meeting.participant',
        title: "Teilnehmer/-in",
        hint: "Wer war da?",
        metadata: ['person']
      },
      { id: 'meeting.absent',
        title: "Abwesend",
        hint: "Wer fehlte?",
        metadata: ['person']
      }
    ]
  },
  { title: "Vorlage/Anfrage",
    color: (type, i) => `hsl(120, 60%, ${65 + 15 * i}%)`,
    types: [
      { id: 'paper.reference',
        title: "Aktenzeichen",
        hint: "Daran werden Dateiabschnitte zu Vorlagen/Anfragen erkannt.",
        metadata: ['paper']
      },
      { id: 'paper.name',
        title: "Titel",
        metadata: ['paper']
      }
    ]
  },
  { title: "Anfrage",
    color: (type, i) => `hsl(60, ${75 + 10 * i}%, ${60 + 15 * i}%)`,
    types: [
      { id: 'paper.intro',
        title: "Einleitung",
      },
      { id: 'paper.inquiry',
        title: "Fragestellung",
      },
      { id: 'paper.response',
        title: "Antwort",
      }
    ]
  },
  { title: "Vorlage",
    color: (type, i) => `hsl(180, ${60 + 10 * i}%, ${60 + 8 * i}%)`,
    types: [
      { id: 'paper.proposition',
        title: "Beschlussvorschlag",
      },
      { id: 'paper.reason',
        title: "Begründung",
      },
      { id: 'paper.resolution',
        title: "Beschluss",
      },
      { id: 'paper.report',
        title: "Beschlusskontrolle",
      },
      { id: 'paper.info',
        title: "Allgemeine Information",
      }
    ]
  },
  { title: "Wortprotokoll",
    color: (type, i) => `hsl(225, 100%, ${85 + 5 * i}%)`,
    types: [
      { id: 'record.speaker',
        title: "Sprecher/-in",
        hint: "Wer hat es gesagt?",
        metadata: ['person']
      },
      { id: 'record.protocol',
        title: "Niederschrift",
        hint: "Zusammengefasster Inhalt",
      },
      { id: 'record.transcript',
        title: "Wortlaut",
        hint: "Wortwörtliches Transkript",
      }
    ]
  },
  { title: "Abstimmung",
    color: (type, i) => `hsl(300, 50%, ${70 + 5 * i}%)`,
    types: [
      { id: 'vote.yes',
        title: "Ja-Stimmen",
        hint: "Stimmenanzahl oder Personenname",
      },
      { id: 'vote.no',
        title: "Nein-Stimmen",
        hint: "Stimmenanzahl oder Personenname",
      },
      { id: 'vote.neutral',
        title: "Enthaltungen",
        hint: "Stimmenanzahl oder Personenname",
      },
      { id: 'vote.biased',
        title: "Befangen",
        hint: "Stimmenanzahl oder Personenname",
      },
      { id: 'vote.result',
        title: "Ergebnis",
        hint: "\"Zustimmung mit Änderung\"",
      }
    ]
  },
  { title: "Verweis",
    color: (type, i) => `hsl(0, 80%, ${65 + 5 * i}%)`,
    types: [
      { id: 'ref.person',
        title: "Person",
        metadata: ['person']
      },
      { id: 'ref.organization',
        title: "Gremium",
        metadata: ['organization']
      },
      { id: 'ref.meeting',
        title: "Sitzung",
        metadata: ['meeting']
      },
      { id: 'ref.paper',
        title: "Vorlage/Anfrage",
        metadata: ['paper']
      },
      { id: 'ref.file',
        title: "Datei",
        metadata: ['file']
      },
      // { id: 'ref.location',
      //   title: "Ort",
      //   rgb: "255,207,207",
      //   metadata: ['geolocation']
      // }
    ]
  },
  // Autor: Gremium, Person
  // Anwesenheit: Anwesend, Abwesend, Verlassen, Erscheinen
  // Gelder: Einnahmen, Einsparungen, Ausgaben
  // Links? Termine?
/*  { title: "",
    types: [
      { title: "",
        rgb: ""
      },
      { title: "",
        rgb: ""
      }
    ]
  } */
]

for(let category of TYPES) {
  let i = 0
  for(let type of category.types) {
    type.color = category.color(type, i)
    i += 1
  }
}

export default TYPES

let byIdCache = null

export function getTypeById(id) {
  if (!byIdCache) {
    byIdCache = {}
    for(let category of TYPES) {
      for(let type of category.types) {
        byIdCache[type.id] = type
      }
    }
  }

  return byIdCache[id] || {
    id: 'unknown',
    title: "Unbekannt",
    rgb: "31,31,31"
  }
}
<commit_msg>types: rm meeting for now<commit_after>let TYPES = [
  { title: "Dokument",
    color: (type, i) => `hsl(30, ${80 + 5 * i}%, ${60 + 15 * i}%)`,
    types: [
      { id: 'doc.date',
        title: "Datum",
        hint: "Zeitpunkt der Herausgabe/Veröffentlichung"
      },
      { id: 'doc.originator',
        title: "Autor/-in",
        hint: "Von wem kommts?",
        metadata: ['person']
      },
      { id: 'doc.recipient',
        title: "Empfänger/-in",
        hint: "An jemanden gerichtet?",
        metadata: ['person']
      }
    ]
  },
  // { title: "Sitzung",
  //   color: (type, i) => `hsl(225, ${50 + 10 * i}%, ${70 + 10 * i}%)`,
  //   types: [
  //     { id: 'meeting.participant',
  //       title: "Teilnehmer/-in",
  //       hint: "Wer war da?",
  //       metadata: ['person']
  //     },
  //     { id: 'meeting.absent',
  //       title: "Abwesend",
  //       hint: "Wer fehlte?",
  //       metadata: ['person']
  //     }
  //   ]
  // },
  { title: "Vorlage/Anfrage",
    color: (type, i) => `hsl(120, 60%, ${65 + 15 * i}%)`,
    types: [
      { id: 'paper.reference',
        title: "Aktenzeichen",
        hint: "Daran werden Dateiabschnitte zu Vorlagen/Anfragen erkannt.",
        metadata: ['paper']
      },
      { id: 'paper.name',
        title: "Titel",
        metadata: ['paper']
      }
    ]
  },
  { title: "Anfrage",
    color: (type, i) => `hsl(60, ${75 + 10 * i}%, ${60 + 15 * i}%)`,
    types: [
      { id: 'paper.intro',
        title: "Einleitung",
      },
      { id: 'paper.inquiry',
        title: "Fragestellung",
      },
      { id: 'paper.response',
        title: "Antwort",
      }
    ]
  },
  { title: "Vorlage",
    color: (type, i) => `hsl(180, ${60 + 10 * i}%, ${60 + 8 * i}%)`,
    types: [
      { id: 'paper.proposition',
        title: "Beschlussvorschlag",
      },
      { id: 'paper.reason',
        title: "Begründung",
      },
      { id: 'paper.resolution',
        title: "Beschluss",
      },
      { id: 'paper.report',
        title: "Beschlusskontrolle",
      },
      { id: 'paper.info',
        title: "Allgemeine Information",
      }
    ]
  },
  { title: "Wortprotokoll",
    color: (type, i) => `hsl(225, 100%, ${85 + 5 * i}%)`,
    types: [
      { id: 'record.speaker',
        title: "Sprecher/-in",
        hint: "Wer hat es gesagt?",
        metadata: ['person']
      },
      { id: 'record.protocol',
        title: "Niederschrift",
        hint: "Zusammengefasster Inhalt",
      },
      { id: 'record.transcript',
        title: "Wortlaut",
        hint: "Wortwörtliches Transkript",
      }
    ]
  },
  { title: "Abstimmung",
    color: (type, i) => `hsl(300, 50%, ${70 + 5 * i}%)`,
    types: [
      { id: 'vote.yes',
        title: "Ja-Stimmen",
        hint: "Stimmenanzahl oder Personenname",
      },
      { id: 'vote.no',
        title: "Nein-Stimmen",
        hint: "Stimmenanzahl oder Personenname",
      },
      { id: 'vote.neutral',
        title: "Enthaltungen",
        hint: "Stimmenanzahl oder Personenname",
      },
      { id: 'vote.biased',
        title: "Befangen",
        hint: "Stimmenanzahl oder Personenname",
      },
      { id: 'vote.result',
        title: "Ergebnis",
        hint: "\"Zustimmung mit Änderung\"",
      }
    ]
  },
  { title: "Verweis",
    color: (type, i) => `hsl(0, 80%, ${65 + 5 * i}%)`,
    types: [
      { id: 'ref.person',
        title: "Person",
        metadata: ['person']
      },
      { id: 'ref.organization',
        title: "Gremium",
        metadata: ['organization']
      },
      { id: 'ref.meeting',
        title: "Sitzung",
        metadata: ['meeting']
      },
      { id: 'ref.paper',
        title: "Vorlage/Anfrage",
        metadata: ['paper']
      },
      { id: 'ref.file',
        title: "Datei",
        metadata: ['file']
      },
      // { id: 'ref.location',
      //   title: "Ort",
      //   rgb: "255,207,207",
      //   metadata: ['geolocation']
      // }
    ]
  },
  // Autor: Gremium, Person
  // Anwesenheit: Anwesend, Abwesend, Verlassen, Erscheinen
  // Gelder: Einnahmen, Einsparungen, Ausgaben
  // Links? Termine?
/*  { title: "",
    types: [
      { title: "",
        rgb: ""
      },
      { title: "",
        rgb: ""
      }
    ]
  } */
]

for(let category of TYPES) {
  let i = 0
  for(let type of category.types) {
    type.color = category.color(type, i)
    i += 1
  }
}

export default TYPES

let byIdCache = null

export function getTypeById(id) {
  if (!byIdCache) {
    byIdCache = {}
    for(let category of TYPES) {
      for(let type of category.types) {
        byIdCache[type.id] = type
      }
    }
  }

  return byIdCache[id] || {
    id: 'unknown',
    title: "Unbekannt",
    rgb: "31,31,31"
  }
}
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2017 The Web Activities Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {ActivityRequestDef} from './activity-types';

/** Only allows http/https URLs. */
const HTTP_S_ONLY_RE = /^https?\:/i;

/** DOMException.ABORT_ERR name */
const ABORT_ERR_NAME = 'AbortError';

/** DOMException.ABORT_ERR = 20 */
const ABORT_ERR_CODE = 20;

/** @type {?HTMLAnchorElement} */
let aResolver;

export function setParserForTesting(resolver) {
  aResolver = resolver;
}

/**
 * @param {string} urlString
 * @return {!HTMLAnchorElement}
 */
function parseUrl(urlString) {
  if (!aResolver) {
    aResolver = /** @type {!HTMLAnchorElement} */ (document.createElement('a'));
  }
  aResolver.href = urlString;
  return /** @type {!HTMLAnchorElement} */ (aResolver);
}

/**
 * @param {!Location|!URL|!HTMLAnchorElement} loc
 * @return {string}
 */
function getOrigin(loc) {
  if (loc.origin) {
    return loc.origin;
  }
  // Make sure that the origin is normalized. Specifically on IE, host sometimes
  // includes the default port, which is not per standard.
  const protocol = loc.protocol;
  let host = loc.host;
  if (protocol == 'https:' && host.indexOf(':443') == host.length - 4) {
    host = host.replace(':443', '');
  } else if (protocol == 'http:' && host.indexOf(':80') == host.length - 3) {
    host = host.replace(':80', '');
  }
  return protocol + '//' + host;
}

/**
 * @param {string} urlString
 * @return {string}
 */
export function getOriginFromUrl(urlString) {
  return getOrigin(parseUrl(urlString));
}

/**
 * @param {!Window} win
 * @return {string}
 */
export function getWindowOrigin(win) {
  return (win.origin || getOrigin(win.location));
}

/**
 * @param {string} urlString
 * @return {string}
 */
export function removeFragment(urlString) {
  const index = urlString.indexOf('#');
  if (index == -1) {
    return urlString;
  }
  return urlString.substring(0, index);
}

/**
 * Parses and builds Object of URL query string.
 * @param {string} query The URL query string.
 * @return {!Object<string, string>}
 */
function parseQueryString(query) {
  if (!query) {
    return {};
  }
  return (/^[?#]/.test(query) ? query.slice(1) : query)
      .split('&')
      .reduce((params, param) => {
        const item = param.split('=');
        const key = decodeURIComponent(item[0] || '');
        const value = decodeURIComponent(item[1] || '');
        if (key) {
          params[key] = value;
        }
        return params;
      }, {});
}

/**
 * @param {string} queryString  A query string in the form of "a=b&c=d". Could
 *   be optionally prefixed with "?" or "#".
 * @param {string} param The param to get from the query string.
 * @return {?string}
 */
export function getQueryParam(queryString, param) {
  return parseQueryString(queryString)[param];
}

/**
 * Add a query-like parameter to the fragment string.
 * @param {string} url
 * @param {string} param
 * @param {string} value
 * @return {string}
 */
export function addFragmentParam(url, param, value) {
  return url +
      (url.indexOf('#') == -1 ? '#' : '&') +
      encodeURIComponent(param) + '=' + encodeURIComponent(value);
}

/**
 * @param {string} queryString  A query string in the form of "a=b&c=d". Could
 *   be optionally prefixed with "?" or "#".
 * @param {string} param The param to remove from the query string.
 * @return {?string}
 */
export function removeQueryParam(queryString, param) {
  if (!queryString) {
    return queryString;
  }
  const search = encodeURIComponent(param) + '=';
  let index = -1;
  do {
    index = queryString.indexOf(search, index);
    if (index != -1) {
      const prev = index > 0 ? queryString.substring(index - 1, index) : '';
      if (prev == '' || prev == '?' || prev == '#' || prev == '&') {
        let end = queryString.indexOf('&', index + 1);
        if (end == -1) {
          end = queryString.length;
        }
        queryString =
            queryString.substring(0, index) +
            queryString.substring(end + 1);
      } else {
        index++;
      }
    }
  } while (index != -1 && index < queryString.length);
  return queryString;
}

/**
 * Asserts that a given url is an absolute HTTP or HTTPS URL.
 * @param {?string} urlString
 * @return {?string}
 */
export function assertAbsoluteHttpOrHttpsUrl(urlString) {
  if (!HTTP_S_ONLY_RE.test(urlString)) {
    throw new Error('must be http(s)');
  }
  return urlString;
}

/**
 * Asserts that a given url is not an obvious script URL. This is not intended
 * as a complete verification. The complete verification is left up to the
 * host before `accept()` method is called.
 * @param {?string} urlString
 * @return {?string}
 */
export function assertObviousUnsafeUrl(urlString) {
  if (urlString) {
    const protocol = parseUrl(urlString).protocol;
    if (protocol.indexOf('script') != -1) {
      throw new Error('unsafe');
    }
  }
  return urlString;
}

/**
 * @param {?string} requestString
 * @param {boolean=} trusted
 * @return {?ActivityRequestDef}
 */
export function parseRequest(requestString, trusted = false) {
  if (!requestString) {
    return null;
  }
  const parsed = /** @type {!Object} */ (JSON.parse(requestString));
  const request = {
    requestId: /** @type {string} */ (parsed['requestId']),
    returnUrl: /** @type {string} */ (parsed['returnUrl']),
    args: /** @type {?Object} */ (parsed['args'] || null),
  };
  if (trusted) {
    request.origin = /** @type {string|undefined} */ (
        parsed['origin'] || undefined);
    request.originVerified = /** @type {boolean|undefined} */ (
        parsed['originVerified'] || undefined);
  }
  return request;
}

/**
 * @param {!ActivityRequestDef} request
 * @return {string}
 */
export function serializeRequest(request) {
  const map = {
    'requestId': request.requestId,
    'returnUrl': request.returnUrl,
    'args': request.args,
  };
  if (request.origin !== undefined) {
    map['origin'] = request.origin;
  }
  if (request.originVerified !== undefined) {
    map['originVerified'] = request.originVerified;
  }
  return JSON.stringify(map);
}

/**
 * @param {*} error
 * @return {boolean}
 */
export function isAbortError(error) {
  if (!error || typeof error != 'object') {
    return false;
  }
  return (error['name'] === ABORT_ERR_NAME);
}

/**
 * Creates or emulates a DOMException of AbortError type.
 * See https://heycam.github.io/webidl/#aborterror.
 * @param {!Window} win
 * @param {string=} opt_message
 * @return {!DOMException}
 */
export function createAbortError(win, opt_message) {
  const message = 'AbortError' + (opt_message ? ': ' + opt_message : '');
  let error = null;
  if (typeof win['DOMException'] == 'function') {
    // TODO(dvoytenko): remove typecast once externs are fixed.
    const constr = /** @type {function(new:DOMException, string, string)} */ (
        win['DOMException']);
    try {
      error = new constr(message, ABORT_ERR_NAME);
    } catch (e) {
      // Ignore. In particular, `new DOMException()` fails in Edge.
    }
  }
  if (!error) {
    // TODO(dvoytenko): remove typecast once externs are fixed.
    const constr = /** @type {function(new:DOMException, string)} */ (
        Error);
    error = new constr(message);
    error.name = ABORT_ERR_NAME;
    error.code = ABORT_ERR_CODE;
  }
  return error;
}

/**
 * Resolves the activity result as a promise:
 *  - `OK` result is yielded as the promise's payload;
 *  - `CANCEL` result is rejected with the `AbortError`;
 *  - `FAILED` result is rejected with the embedded error.
 *
 * @param {!Window} win
 * @param {!./activity-types.ActivityResult} result
 * @param {function((!./activity-types.ActivityResult|!Promise))} resolver
 */
export function resolveResult(win, result, resolver) {
  if (result.ok) {
    resolver(result);
  } else {
    const error = result.error || createAbortError(win);
    error.activityResult = result;
    resolver(Promise.reject(error));
  }
}

/**
 * @param {!Window} win
 * @return {boolean}
 */
export function isIeBrowser(win) {
  // MSIE and Trident are typical user agents for IE browsers.
  const nav = win.navigator;
  return /Trident|MSIE|IEMobile/i.test(nav && nav.userAgent);
}

/**
 * @param {!Window} win
 * @return {boolean}
 */
export function isEdgeBrowser(win) {
  const nav = win.navigator;
  return /Edge/i.test(nav && nav.userAgent);
}

/**
 * @param {!Error} e
 */
export function throwAsync(e) {
  setTimeout(() => {throw e;});
}

/**
 * Polyfill of the `Node.isConnected` API. See
 * https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected.
 * @param {!Node} node
 * @return {boolean}
 */
export function isNodeConnected(node) {
  // Ensure that node is attached if specified. This check uses a new and
  // fast `isConnected` API and thus only checked on platforms that have it.
  // See https://www.chromestatus.com/feature/5676110549352448.
  if ('isConnected' in node) {
    return node['isConnected'];
  }
  // Polyfill.
  const root = node.ownerDocument && node.ownerDocument.documentElement;
  return (root && root.contains(node)) || false;
}
<commit_msg>cleanpu<commit_after>/**
 * @license
 * Copyright 2017 The Web Activities Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {ActivityRequestDef} from './activity-types';

/** Only allows http/https URLs. */
const HTTP_S_ONLY_RE = /^https?\:/i;

/** DOMException.ABORT_ERR name */
const ABORT_ERR_NAME = 'AbortError';

/** DOMException.ABORT_ERR = 20 */
const ABORT_ERR_CODE = 20;

/** @type {?HTMLAnchorElement} */
let aResolver;

export function setParserForTesting(resolver) {
  aResolver = resolver;
}

/**
 * @param {string} urlString
 * @return {!HTMLAnchorElement}
 */
function parseUrl(urlString) {
  if (!aResolver) {
    aResolver = /** @type {!HTMLAnchorElement} */ (document.createElement('a'));
  }
  aResolver.href = urlString;
  return /** @type {!HTMLAnchorElement} */ (aResolver);
}

/**
 * @param {!Location|!URL|!HTMLAnchorElement} loc
 * @return {string}
 */
function getOrigin(loc) {
  if (loc.origin) {
    return loc.origin;
  }
  // Make sure that the origin is normalized. Specifically on IE, host sometimes
  // includes the default port, which is not per standard.
  const protocol = loc.protocol;
  let host = loc.host;
  if (protocol == 'https:' && host.indexOf(':443') == host.length - 4) {
    host = host.replace(':443', '');
  } else if (protocol == 'http:' && host.indexOf(':80') == host.length - 3) {
    host = host.replace(':80', '');
  }
  return protocol + '//' + host;
}

/**
 * @param {string} urlString
 * @return {string}
 */
export function getOriginFromUrl(urlString) {
  return getOrigin(parseUrl(urlString));
}

/**
 * @param {!Window} win
 * @return {string}
 */
export function getWindowOrigin(win) {
  return (win.origin || getOrigin(win.location));
}

/**
 * @param {string} urlString
 * @return {string}
 */
export function removeFragment(urlString) {
  const index = urlString.indexOf('#');
  if (index == -1) {
    return urlString;
  }
  return urlString.substring(0, index);
}

/**
 * Parses and builds Object of URL query string.
 * @param {string} query The URL query string.
 * @return {!Object<string, string>}
 */
function parseQueryString(query) {
  if (!query) {
    return {};
  }
  return (/^[?#]/.test(query) ? query.slice(1) : query)
      .split('&')
      .reduce((params, param) => {
        const item = param.split('=');
        const key = decodeURIComponent(item[0] || '');
        const value = decodeURIComponent(item[1] || '');
        if (key) {
          params[key] = value;
        }
        return params;
      }, {});
}

/**
 * @param {string} queryString  A query string in the form of "a=b&c=d". Could
 *   be optionally prefixed with "?" or "#".
 * @param {string} param The param to get from the query string.
 * @return {?string}
 */
export function getQueryParam(queryString, param) {
  return parseQueryString(queryString)[param];
}

/**
 * Add a query-like parameter to the fragment string.
 * @param {string} url
 * @param {string} param
 * @param {string} value
 * @return {string}
 */
export function addFragmentParam(url, param, value) {
  return url +
      (url.indexOf('#') == -1 ? '#' : '&') +
      encodeURIComponent(param) + '=' + encodeURIComponent(value);
}

/**
 * @param {string} queryString  A query string in the form of "a=b&c=d". Could
 *   be optionally prefixed with "?" or "#".
 * @param {string} param The param to remove from the query string.
 * @return {?string}
 */
export function removeQueryParam(queryString, param) {
  if (!queryString) {
    return queryString;
  }
  const search = encodeURIComponent(param) + '=';
  let index = -1;
  do {
    index = queryString.indexOf(search, index);
    if (index != -1) {
      const prev = index > 0 ? queryString.substring(index - 1, index) : '';
      if (prev == '' || prev == '?' || prev == '#' || prev == '&') {
        let end = queryString.indexOf('&', index + 1);
        if (end == -1) {
          end = queryString.length;
        }
        queryString =
            queryString.substring(0, index) +
            queryString.substring(end + 1);
      } else {
        index++;
      }
    }
  } while (index != -1 && index < queryString.length);
  return queryString;
}

/**
 * Asserts that a given url is an absolute HTTP or HTTPS URL.
 * @param {?string} urlString
 * @return {?string}
 */
export function assertAbsoluteHttpOrHttpsUrl(urlString) {
  if (!HTTP_S_ONLY_RE.test(urlString)) {
    throw new Error('must be http(s)');
  }
  return urlString;
}

/**
 * Asserts that a given url is not an obvious script URL. This is not intended
 * as a complete verification. The complete verification is left up to the
 * host before `accept()` method is called.
 * @param {?string} urlString
 * @return {?string}
 */
export function assertObviousUnsafeUrl(urlString) {
  if (urlString) {
    const protocol = parseUrl(urlString).protocol;
    if (protocol.indexOf('script') != -1) {
      throw new Error('unsafe "' + urlString + '"');
    }
  }
  return urlString;
}

/**
 * @param {?string} requestString
 * @param {boolean=} trusted
 * @return {?ActivityRequestDef}
 */
export function parseRequest(requestString, trusted = false) {
  if (!requestString) {
    return null;
  }
  const parsed = /** @type {!Object} */ (JSON.parse(requestString));
  const request = {
    requestId: /** @type {string} */ (parsed['requestId']),
    returnUrl: /** @type {string} */ (parsed['returnUrl']),
    args: /** @type {?Object} */ (parsed['args'] || null),
  };
  if (trusted) {
    request.origin = /** @type {string|undefined} */ (
        parsed['origin'] || undefined);
    request.originVerified = /** @type {boolean|undefined} */ (
        parsed['originVerified'] || undefined);
  }
  return request;
}

/**
 * @param {!ActivityRequestDef} request
 * @return {string}
 */
export function serializeRequest(request) {
  const map = {
    'requestId': request.requestId,
    'returnUrl': request.returnUrl,
    'args': request.args,
  };
  if (request.origin !== undefined) {
    map['origin'] = request.origin;
  }
  if (request.originVerified !== undefined) {
    map['originVerified'] = request.originVerified;
  }
  return JSON.stringify(map);
}

/**
 * @param {*} error
 * @return {boolean}
 */
export function isAbortError(error) {
  if (!error || typeof error != 'object') {
    return false;
  }
  return (error['name'] === ABORT_ERR_NAME);
}

/**
 * Creates or emulates a DOMException of AbortError type.
 * See https://heycam.github.io/webidl/#aborterror.
 * @param {!Window} win
 * @param {string=} opt_message
 * @return {!DOMException}
 */
export function createAbortError(win, opt_message) {
  const message = 'AbortError' + (opt_message ? ': ' + opt_message : '');
  let error = null;
  if (typeof win['DOMException'] == 'function') {
    // TODO(dvoytenko): remove typecast once externs are fixed.
    const constr = /** @type {function(new:DOMException, string, string)} */ (
        win['DOMException']);
    try {
      error = new constr(message, ABORT_ERR_NAME);
    } catch (e) {
      // Ignore. In particular, `new DOMException()` fails in Edge.
    }
  }
  if (!error) {
    // TODO(dvoytenko): remove typecast once externs are fixed.
    const constr = /** @type {function(new:DOMException, string)} */ (
        Error);
    error = new constr(message);
    error.name = ABORT_ERR_NAME;
    error.code = ABORT_ERR_CODE;
  }
  return error;
}

/**
 * Resolves the activity result as a promise:
 *  - `OK` result is yielded as the promise's payload;
 *  - `CANCEL` result is rejected with the `AbortError`;
 *  - `FAILED` result is rejected with the embedded error.
 *
 * @param {!Window} win
 * @param {!./activity-types.ActivityResult} result
 * @param {function((!./activity-types.ActivityResult|!Promise))} resolver
 */
export function resolveResult(win, result, resolver) {
  if (result.ok) {
    resolver(result);
  } else {
    const error = result.error || createAbortError(win);
    error.activityResult = result;
    resolver(Promise.reject(error));
  }
}

/**
 * @param {!Window} win
 * @return {boolean}
 */
export function isIeBrowser(win) {
  // MSIE and Trident are typical user agents for IE browsers.
  const nav = win.navigator;
  return /Trident|MSIE|IEMobile/i.test(nav && nav.userAgent);
}

/**
 * @param {!Window} win
 * @return {boolean}
 */
export function isEdgeBrowser(win) {
  const nav = win.navigator;
  return /Edge/i.test(nav && nav.userAgent);
}

/**
 * @param {!Error} e
 */
export function throwAsync(e) {
  setTimeout(() => {throw e;});
}

/**
 * Polyfill of the `Node.isConnected` API. See
 * https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected.
 * @param {!Node} node
 * @return {boolean}
 */
export function isNodeConnected(node) {
  // Ensure that node is attached if specified. This check uses a new and
  // fast `isConnected` API and thus only checked on platforms that have it.
  // See https://www.chromestatus.com/feature/5676110549352448.
  if ('isConnected' in node) {
    return node['isConnected'];
  }
  // Polyfill.
  const root = node.ownerDocument && node.ownerDocument.documentElement;
  return (root && root.contains(node)) || false;
}
<|endoftext|>


<commit_before>/**
 * Finds DOM element
 * @param {HTMLElement, String} el
 * @param {Document|HTMLElement} [context=document]
 */
import consts from './consts';

export function getEl (el, context=document) {
    return typeof el === 'string'
        ? context['querySelector'](el)
        : el;
}

/**
 * Creates HTML DOM element
 * @param {String} [tagName] - element's tag name
 * @param {String} [className]
 * @param {String} [innerHtml]
 * @param {String} [id]
 * @param {Object} [attrs]
 * @returns {HTMLElement}
 */
export function createElement({tagName='div', className='', innerHtml='', id='', attrs={}} = {}) {
    let $element = document.createElement(tagName);
    if (className) $element.classList.add(...className.split(' '));
    if (id) $element.id = id;

    if (innerHtml) {
        $element.innerHTML = innerHtml;
    }

    if (attrs) {
        for (let attr in attrs) {
            $element.setAttribute(attr, attrs[attr]);
        }
    }

    return $element;
}

/**
 * Sets multiple attributes of element
 * @param {HTMLElement} el
 * @param {Object} attrs - attributes object
 * @returns {HTMLElement}
 */
export function setAttribute(el, attrs){
    for (let [name, value] of Object.entries(attrs)) {
        el.setAttribute(name, value);
    }
    return el;
}

/**
 * Inserts newElement after targetElement
 * @param {HTMLElement} newElement - element to be inserted
 * @param {HTMLElement} targetElement - after which must be inserted
 * @return {HTMLElement} newElement
 */
export function insertAfter(newElement, targetElement) {
    targetElement.parentNode
        .insertBefore(newElement, targetElement.nextSibling);
    return newElement;
}

/**
 * Makes object deep copy
 * @param {Object} obj
 * @return {Object}
 */
export function deepCopy(obj) {
    return JSON.parse(JSON.stringify(obj));
}

/**
 * Calculates amount of days in passed date
 * @param {Date} date
 * @return {number}
 */
export function getDaysCount(date) {
    return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
}

/**
 * Get detailed date object
 * @param {Date} date
 * @return {{
 *  date: number,
 *  hours: number,
 *  fullDate: (string|*),
 *  month: number,
 *  fullHours: (string|*),
 *  year: number,
 *  minutes: number,
 *  fullMonth: string,
 *  day: number,
 *  fullMinutes: (string|*)
 * }}
 */
export function getParsedDate(date) {
    let hours = date.getHours(),
        hours12 = hours % 12 === 0 ? 12 : hours % 12;

    return {
        year: date.getFullYear(),
        month: date.getMonth(),
        fullMonth: (date.getMonth() + 1) < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1, // One based
        date: date.getDate(),
        fullDate: date.getDate() < 10 ? '0' + date.getDate() : date.getDate(),
        day: date.getDay(),
        hours,
        fullHours: getLeadingZeroNum(hours),
        hours12,
        fullHours12: getLeadingZeroNum(hours12),
        minutes: date.getMinutes(),
        fullMinutes:  date.getMinutes() < 10 ? '0' + date.getMinutes() :  date.getMinutes()
    };
}

/**
 * Converts 1 -> 01
 * @param {Number} num
 * @return {String|Number}
 */
export function getLeadingZeroNum(num) {
    return num < 10 ? '0' + num : num;
}

/**
 * Calculates current decade
 * @param {Date} date
 * @return {number[]} - array of two years, decade start - decade end
 */
export function getDecade(date) {
    let firstYear = Math.floor(date.getFullYear() / 10) * 10;
    return [firstYear, firstYear + 9];
}

/**
 * Subtract days from date
 * @param {Date} date
 * @param {Number} days
 * @return {Date}
 */
export function subDays(date, days) {
    let {year, month, date: _date} = getParsedDate(date);
    return new Date(year, month, _date - days);
}

/**
 * Add days to date
 * @param {Date} date
 * @param {Number} days
 * @return {Date}
 */
export function addDays(date, days) {
    let {year, month, date: _date} = getParsedDate(date);
    return new Date(year, month, _date + days);
}

/**
 * Class names handler, inspired by https://github.com/JedWatson/classnames but very simplified
 * @param {String|Object} classes - class names, could contain strings or object
 */
export function classNames(...classes) {
    let classNames = [];

    classes.forEach((c) => {
        if (typeof c === 'object') {
            for(let cName in c) {
                if (c[cName]) {
                    classNames.push(cName);
                }
            }
        } else if (c) {
            classNames.push(c);
        }
    });
    return classNames.join(' ');
}

export function toggleClass(el, classes) {
    for (let className in classes) {
        if (classes[className]) {
            el.classList.add(className);
        } else {
            el.classList.remove(className);
        }
    }
}

export function addClass(el, ...classes) {
    if (el.length) {
        el.forEach((node)=>{
            node.classList.add(...classes);
        });
    } else {
        el.classList.add(...classes);
    }
}

export function removeClass(el, ...classes) {
    if (el.length) {
        el.forEach((node) => {
            node.classList.remove(...classes);
        });
    } else {
        el.classList.remove(...classes);
    }
}

/**
 * Checks if passed dates are the same
 * @param {Date} date1
 * @param {Date} date2
 * @param {String} cellType - one of days|months|years
 * @return {boolean}
 */
export function isSameDate(date1, date2, cellType=consts.days) {
    if (!date1 || !date2) return false;
    let d1 = getParsedDate(date1),
        d2 = getParsedDate(date2),

        conditions = {
            [consts.days]: d1.date === d2.date && d1.month === d2.month && d1.year === d2.year,
            [consts.months]: d1.month === d2.month && d1.year === d2.year,
            [consts.years]: d1.year === d2.year
        };

    return conditions[cellType];
}

export function isDateBigger(date, comparedDate, loose) {
    let d1 = copyDate(date, false).getTime(),
        d2 = copyDate(comparedDate, false).getTime();

    return loose
        ? d1 >= d2
        : d1 > d2;
}

export function isDateSmaller(date, comparedDate) {
    return !isDateBigger(date, comparedDate, true);
}

/**
 * Copies date
 * @param {Date} date
 * @param {Boolean} [keepTime] - should keep the time in a new date or not
 * @return {Date}
 */
export function copyDate(date, keepTime=true) {
    let newDate = new Date(date.getTime());

    if (typeof keepTime === 'boolean' && !keepTime) {
        resetTime(newDate);
    }

    return newDate;
}

export function resetTime(date) {
    date.setHours(0,0,0,0);
    return date;
}

export function isDateBetween(date, dateFrom, dateTo) {
    return isDateBigger(date, dateFrom) && isDateSmaller(date, dateTo);
}

/**
 * Adds event listener to DOM element
 * @param {HTMLElement|HTMLCollection} el
 * @param {String} type
 * @param {Function} listener
 */
export function addEventListener(el, type, listener) {
    if (el.length) {
        el.forEach((e) => {
            e.addEventListener(type, listener);
        });
    } else {
        el.addEventListener(type, listener);
    }
}

/**
 * Finds closest DOM element to passed target. Similar to jQuery.closest()
 * @param {HTMLElement} target
 * @param {String} selector
 * @return {HTMLElement|Boolean}
 */
export function closest(target, selector) {
    if (!target || target === document) return false;

    if (target.matches(selector)) {
        return target;
    }

    return closest(target.parentNode, selector);
}

/**
 * Clamps number between min and max
 * @param {Number} val
 * @param {Number} min
 * @param {Number} max
 * @return {Number}
 */
export function clamp(val, min, max,) {
    return val > max
        ? max
        : val < min
            ? min
            : val;
}

/**
 * Deep merge of objects or arrays, used to merge options
 * @param {object|array} target - target object or array
 * @param {object|array} objects - source objects
 * @return {object|array}
 */
export function deepMerge(target, ...objects) {
    objects.filter(o => o).forEach((obj) => {
        for (let [key, value] of Object.entries(obj)) {
            let arrayOrObject = value !== undefined ? value.toString() === ('[object Object]' || '[object Array]') : false;

            if (arrayOrObject) {
                let targetType = target[key] !== undefined ? target[key].toString() : undefined,
                    sourceType = value.toString(),
                    initialValue = Array.isArray(value) ? [] : {};

                // If target and source types are different, e.g. we try to merge number with object,
                // then take source type
                target[key] = target[key]
                    ? targetType !== sourceType
                        ? initialValue
                        : target[key]
                    : initialValue;

                deepMerge(target[key], value);
            } else {
                target[key] = value;
            }
        }
    });

    return target;
}

/**
 * Creates Date object from string or number. If passed param is instance of Date, then just returns it.
 * @param {number|string|Date} date
 * @return {Date | boolean}
 */
export function createDate(date) {
    let resultDate = date;

    if (!(date instanceof Date)) {
        resultDate = new Date(date);
    }

    if (isNaN(resultDate.getTime())) {
        console.log(`Unable to convert value "${date}" to Date object`);
        resultDate = false;
    }

    return resultDate;
}

export function getWordBoundaryRegExp(sign){
    let symbols = '\\s|\\.|-|/|\\\\|,|\\$|\\!|\\?|:|;';

    return new RegExp('(^|>|' + symbols + ')(' + sign + ')($|<|' + symbols + ')', 'g');
}
<commit_msg>Prevent crash when use lib inside webcomponent<commit_after>/**
 * Finds DOM element
 * @param {HTMLElement, String} el
 * @param {Document|HTMLElement} [context=document]
 */
import consts from './consts';

export function getEl (el, context=document) {
    return typeof el === 'string'
        ? context['querySelector'](el)
        : el;
}

/**
 * Creates HTML DOM element
 * @param {String} [tagName] - element's tag name
 * @param {String} [className]
 * @param {String} [innerHtml]
 * @param {String} [id]
 * @param {Object} [attrs]
 * @returns {HTMLElement}
 */
export function createElement({tagName='div', className='', innerHtml='', id='', attrs={}} = {}) {
    let $element = document.createElement(tagName);
    if (className) $element.classList.add(...className.split(' '));
    if (id) $element.id = id;

    if (innerHtml) {
        $element.innerHTML = innerHtml;
    }

    if (attrs) {
        for (let attr in attrs) {
            $element.setAttribute(attr, attrs[attr]);
        }
    }

    return $element;
}

/**
 * Sets multiple attributes of element
 * @param {HTMLElement} el
 * @param {Object} attrs - attributes object
 * @returns {HTMLElement}
 */
export function setAttribute(el, attrs){
    for (let [name, value] of Object.entries(attrs)) {
        el.setAttribute(name, value);
    }
    return el;
}

/**
 * Inserts newElement after targetElement
 * @param {HTMLElement} newElement - element to be inserted
 * @param {HTMLElement} targetElement - after which must be inserted
 * @return {HTMLElement} newElement
 */
export function insertAfter(newElement, targetElement) {
    targetElement.parentNode
        .insertBefore(newElement, targetElement.nextSibling);
    return newElement;
}

/**
 * Makes object deep copy
 * @param {Object} obj
 * @return {Object}
 */
export function deepCopy(obj) {
    return JSON.parse(JSON.stringify(obj));
}

/**
 * Calculates amount of days in passed date
 * @param {Date} date
 * @return {number}
 */
export function getDaysCount(date) {
    return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
}

/**
 * Get detailed date object
 * @param {Date} date
 * @return {{
 *  date: number,
 *  hours: number,
 *  fullDate: (string|*),
 *  month: number,
 *  fullHours: (string|*),
 *  year: number,
 *  minutes: number,
 *  fullMonth: string,
 *  day: number,
 *  fullMinutes: (string|*)
 * }}
 */
export function getParsedDate(date) {
    let hours = date.getHours(),
        hours12 = hours % 12 === 0 ? 12 : hours % 12;

    return {
        year: date.getFullYear(),
        month: date.getMonth(),
        fullMonth: (date.getMonth() + 1) < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1, // One based
        date: date.getDate(),
        fullDate: date.getDate() < 10 ? '0' + date.getDate() : date.getDate(),
        day: date.getDay(),
        hours,
        fullHours: getLeadingZeroNum(hours),
        hours12,
        fullHours12: getLeadingZeroNum(hours12),
        minutes: date.getMinutes(),
        fullMinutes:  date.getMinutes() < 10 ? '0' + date.getMinutes() :  date.getMinutes()
    };
}

/**
 * Converts 1 -> 01
 * @param {Number} num
 * @return {String|Number}
 */
export function getLeadingZeroNum(num) {
    return num < 10 ? '0' + num : num;
}

/**
 * Calculates current decade
 * @param {Date} date
 * @return {number[]} - array of two years, decade start - decade end
 */
export function getDecade(date) {
    let firstYear = Math.floor(date.getFullYear() / 10) * 10;
    return [firstYear, firstYear + 9];
}

/**
 * Subtract days from date
 * @param {Date} date
 * @param {Number} days
 * @return {Date}
 */
export function subDays(date, days) {
    let {year, month, date: _date} = getParsedDate(date);
    return new Date(year, month, _date - days);
}

/**
 * Add days to date
 * @param {Date} date
 * @param {Number} days
 * @return {Date}
 */
export function addDays(date, days) {
    let {year, month, date: _date} = getParsedDate(date);
    return new Date(year, month, _date + days);
}

/**
 * Class names handler, inspired by https://github.com/JedWatson/classnames but very simplified
 * @param {String|Object} classes - class names, could contain strings or object
 */
export function classNames(...classes) {
    let classNames = [];

    classes.forEach((c) => {
        if (typeof c === 'object') {
            for(let cName in c) {
                if (c[cName]) {
                    classNames.push(cName);
                }
            }
        } else if (c) {
            classNames.push(c);
        }
    });
    return classNames.join(' ');
}

export function toggleClass(el, classes) {
    for (let className in classes) {
        if (classes[className]) {
            el.classList.add(className);
        } else {
            el.classList.remove(className);
        }
    }
}

export function addClass(el, ...classes) {
    if (el.length) {
        el.forEach((node)=>{
            node.classList.add(...classes);
        });
    } else {
        el.classList.add(...classes);
    }
}

export function removeClass(el, ...classes) {
    if (el.length) {
        el.forEach((node) => {
            node.classList.remove(...classes);
        });
    } else {
        el.classList.remove(...classes);
    }
}

/**
 * Checks if passed dates are the same
 * @param {Date} date1
 * @param {Date} date2
 * @param {String} cellType - one of days|months|years
 * @return {boolean}
 */
export function isSameDate(date1, date2, cellType=consts.days) {
    if (!date1 || !date2) return false;
    let d1 = getParsedDate(date1),
        d2 = getParsedDate(date2),

        conditions = {
            [consts.days]: d1.date === d2.date && d1.month === d2.month && d1.year === d2.year,
            [consts.months]: d1.month === d2.month && d1.year === d2.year,
            [consts.years]: d1.year === d2.year
        };

    return conditions[cellType];
}

export function isDateBigger(date, comparedDate, loose) {
    let d1 = copyDate(date, false).getTime(),
        d2 = copyDate(comparedDate, false).getTime();

    return loose
        ? d1 >= d2
        : d1 > d2;
}

export function isDateSmaller(date, comparedDate) {
    return !isDateBigger(date, comparedDate, true);
}

/**
 * Copies date
 * @param {Date} date
 * @param {Boolean} [keepTime] - should keep the time in a new date or not
 * @return {Date}
 */
export function copyDate(date, keepTime=true) {
    let newDate = new Date(date.getTime());

    if (typeof keepTime === 'boolean' && !keepTime) {
        resetTime(newDate);
    }

    return newDate;
}

export function resetTime(date) {
    date.setHours(0,0,0,0);
    return date;
}

export function isDateBetween(date, dateFrom, dateTo) {
    return isDateBigger(date, dateFrom) && isDateSmaller(date, dateTo);
}

/**
 * Adds event listener to DOM element
 * @param {HTMLElement|HTMLCollection} el
 * @param {String} type
 * @param {Function} listener
 */
export function addEventListener(el, type, listener) {
    if (el.length) {
        el.forEach((e) => {
            e.addEventListener(type, listener);
        });
    } else {
        el.addEventListener(type, listener);
    }
}

/**
 * Finds closest DOM element to passed target. Similar to jQuery.closest()
 * @param {HTMLElement} target
 * @param {String} selector
 * @return {HTMLElement|Boolean}
 */
export function closest(target, selector) {
    if (!target || target === document) return false;

    if (typeof target.matches !== 'function') return false;

    if (target.matches(selector)) {
        return target;
    }

    return closest(target.parentNode, selector);
}

/**
 * Clamps number between min and max
 * @param {Number} val
 * @param {Number} min
 * @param {Number} max
 * @return {Number}
 */
export function clamp(val, min, max,) {
    return val > max
        ? max
        : val < min
            ? min
            : val;
}

/**
 * Deep merge of objects or arrays, used to merge options
 * @param {object|array} target - target object or array
 * @param {object|array} objects - source objects
 * @return {object|array}
 */
export function deepMerge(target, ...objects) {
    objects.filter(o => o).forEach((obj) => {
        for (let [key, value] of Object.entries(obj)) {
            let arrayOrObject = value !== undefined ? value.toString() === ('[object Object]' || '[object Array]') : false;

            if (arrayOrObject) {
                let targetType = target[key] !== undefined ? target[key].toString() : undefined,
                    sourceType = value.toString(),
                    initialValue = Array.isArray(value) ? [] : {};

                // If target and source types are different, e.g. we try to merge number with object,
                // then take source type
                target[key] = target[key]
                    ? targetType !== sourceType
                        ? initialValue
                        : target[key]
                    : initialValue;

                deepMerge(target[key], value);
            } else {
                target[key] = value;
            }
        }
    });

    return target;
}

/**
 * Creates Date object from string or number. If passed param is instance of Date, then just returns it.
 * @param {number|string|Date} date
 * @return {Date | boolean}
 */
export function createDate(date) {
    let resultDate = date;

    if (!(date instanceof Date)) {
        resultDate = new Date(date);
    }

    if (isNaN(resultDate.getTime())) {
        console.log(`Unable to convert value "${date}" to Date object`);
        resultDate = false;
    }

    return resultDate;
}

export function getWordBoundaryRegExp(sign){
    let symbols = '\\s|\\.|-|/|\\\\|,|\\$|\\!|\\?|:|;';

    return new RegExp('(^|>|' + symbols + ')(' + sign + ')($|<|' + symbols + ')', 'g');
}
<|endoftext|>


<commit_before>
//Fix raw text to parse correctly in crazy XML
export function xmlencode(string) {
    return string.replace(/\&/g,'&'+'amp;')
        .replace(/</g,'&'+'lt;')
        .replace(/>/g,'&'+'gt;')
        .replace(/\'/g,'&'+'apos;')
        .replace(/\"/g,'&'+'quot;')
        .replace(/`/g,'&'+'#96;');
}

export function _process_numbers(attrs, numbers) {
    //Map from terminal commands to CSS classes
    var ansi_colormap = {
        "01":"ansibold",

        "30":"ansiblack",
        "31":"ansired",
        "32":"ansigreen",
        "33":"ansiyellow",
        "34":"ansiblue",
        "35":"ansipurple",
        "36":"ansicyan",
        "37":"ansigray",

        "40":"ansibgblack",
        "41":"ansibgred",
        "42":"ansibggreen",
        "43":"ansibgyellow",
        "44":"ansibgblue",
        "45":"ansibgpurple",
        "46":"ansibgcyan",
        "47":"ansibggray"
    };

    // process ansi escapes
    var n = numbers.shift();
    if (ansi_colormap[n]) {
        if ( ! attrs["class"] ) {
            attrs["class"] = ansi_colormap[n];
        } else {
            attrs["class"] += " " + ansi_colormap[n];
        }
    } else if (n == "38" || n == "48") {
        // VT100 256 color or 24 bit RGB
        if (numbers.length < 2) {
            console.log("Not enough fields for VT100 color", numbers);
            return;
        }

        var index_or_rgb = numbers.shift();
        var r,g,b;
        if (index_or_rgb == "5") {
            // 256 color
            var idx = parseInt(numbers.shift(), 10);
            if (idx < 16) {
                // indexed ANSI
                // ignore bright / non-bright distinction
                idx = idx % 8;
                var ansiclass = ansi_colormap[n[0] + (idx % 8).toString()];
                if ( ! attrs["class"] ) {
                    attrs["class"] = ansiclass;
                } else {
                    attrs["class"] += " " + ansiclass;
                }
                return;
            } else if (idx < 232) {
                // 216 color 6x6x6 RGB
                idx = idx - 16;
                b = idx % 6;
                g = Math.floor(idx / 6) % 6;
                r = Math.floor(idx / 36) % 6;
                // convert to rgb
                r = (r * 51);
                g = (g * 51);
                b = (b * 51);
            } else {
                // grayscale
                idx = idx - 231;
                // it's 1-24 and should *not* include black or white,
                // so a 26 point scale
                r = g = b = Math.floor(idx * 256 / 26);
            }
        } else if (index_or_rgb == "2") {
            // Simple 24 bit RGB
            if (numbers.length > 3) {
                console.log("Not enough fields for RGB", numbers);
                return;
            }
            r = numbers.shift();
            g = numbers.shift();
            b = numbers.shift();
        } else {
            console.log("unrecognized control", numbers);
            return;
        }
        if (r !== undefined) {
            // apply the rgb color
            var line;
            if (n == "38") {
                line = "color: ";
            } else {
                line = "background-color: ";
            }
            line = line + "rgb(" + r + "," + g + "," + b + ");";
            if ( !attrs.style ) {
                attrs.style = line;
            } else {
                attrs.style += " " + line;
            }
        }
    }
}

export function ansispan(str) {
    // ansispan export function adapted from github.com/mmalecki/ansispan (MIT License)
    // regular ansi escapes (using the table above)
    var is_open = false;
    return str.replace(/\033\[(0?[01]|22|39)?([;\d]+)?m/g, function(match, prefix, pattern) {
        if (!pattern) {
            // [(01|22|39|)m close spans
            if (is_open) {
                is_open = false;
                return "";
            } else {
                return "";
            }
        } else {
            is_open = true;

            // consume sequence of color escapes
            var numbers = pattern.match(/\d+/g);
            var attrs = {};
            while (numbers.length > 0) {
                _process_numbers(attrs, numbers);
            }

            var span = "";
        }
    });
}
// Transform ANSI color escape codes into HTML  tags with css
// classes listed in the above ansi_colormap object. The actual color used
// are set in the css file.
export function fixConsole(txt) {
    txt = xmlencode(txt);

    // Strip all ANSI codes that are not color related.  Matches
    // all ANSI codes that do not end with "m".
    var ignored_re = /(?=(\033\[[\d;=]*[a-ln-zA-Z]{1}))\1(?!m)/g;
    txt = txt.replace(ignored_re, "");

    // color ansi codes
    txt = ansispan(txt);
    return txt;
}

// Remove chunks that should be overridden by the effect of
// carriage return characters
export function fixCarriageReturn(txt) {
    var tmp = txt;
    do {
        txt = tmp;
        tmp = txt.replace(/\r+\n/gm, '\n'); // \r followed by \n --> newline
        tmp = tmp.replace(/^.*\r+/gm, '');  // Other \r --> clear line
    } while (tmp.length < txt.length);
    return txt;
}

// Locate any URLs and convert them to a anchor tag
export function autoLinkUrls(txt) {
    return txt.replace(/(^|\s)(https?|ftp)(:[^'">\s]+)/gi,
        "$1$2$3");
}
<commit_msg>In with ansi-to-html, out with utils.js<commit_after><|endoftext|>


<commit_before>// Utility functions

var PREFIXES = 'webkit moz o ms'.split(' ');

// Copy all attributes from source object to destination object.
// destination object is mutated.
function extend(destination, source, recursive) {
    destination = destination || {};
    source = source || {};
    recursive = recursive || false;

    for (var attrName in source) {
        if (source.hasOwnProperty(attrName)) {
            var destVal = destination[attrName];
            var sourceVal = source[attrName];
            if (recursive && isObject(destVal) && isObject(sourceVal)) {
                destination[attrName] = extend(destVal, sourceVal, recursive);
            } else {
                destination[attrName] = sourceVal;
            }
        }
    }

    return destination;
}

// Renders templates with given variables. Variables must be surrounded with
// braces without any spaces, e.g. {variable}
// All instances of variable placeholders will be replaced with given content
// Example:
// render('Hello, {message}!', {message: 'world'})
function render(template, vars) {
    var rendered = template;

    for (var key in vars) {
        if (vars.hasOwnProperty(key)) {
            var val = vars[key];
            var regExpString = '\\{' + key + '\\}';
            var regExp = new RegExp(regExpString, 'g');

            rendered = rendered.replace(regExp, val);
        }
    }

    return rendered;
}

function setStyle(element, style, value) {
    for (var i = 0; i < PREFIXES.length; ++i) {
        var prefix = capitalize(PREFIXES[i]);
        element.style[prefix + capitalize(style)] = value;
    }

    element.style[style] = value;
}

function capitalize(text) {
    return text.charAt(0).toUpperCase() + text.slice(1);
}

function isString(obj) {
    return typeof obj === 'string' || obj instanceof String;
}

function isFunction(obj) {
    return typeof obj === 'function';
}

function isArray(obj) {
    return toString.call(obj) === '[object Array]';
}

// Returns true if `obj` is object as in {a: 1, b: 2}, not if it's function or
// array
function isObject(obj) {
    if (isArray(obj)) return false;

    var type = typeof obj;
    return type === 'object' && !!obj;
}

module.exports = {
    extend: extend,
    render: render,
    setStyle: setStyle,
    capitalize: capitalize,
    isString: isString,
    isFunction: isFunction,
    isObject: isObject
};
<commit_msg>Fix to use toString from Object.prototype<commit_after>// Utility functions

var PREFIXES = 'webkit moz o ms'.split(' ');

// Copy all attributes from source object to destination object.
// destination object is mutated.
function extend(destination, source, recursive) {
    destination = destination || {};
    source = source || {};
    recursive = recursive || false;

    for (var attrName in source) {
        if (source.hasOwnProperty(attrName)) {
            var destVal = destination[attrName];
            var sourceVal = source[attrName];
            if (recursive && isObject(destVal) && isObject(sourceVal)) {
                destination[attrName] = extend(destVal, sourceVal, recursive);
            } else {
                destination[attrName] = sourceVal;
            }
        }
    }

    return destination;
}

// Renders templates with given variables. Variables must be surrounded with
// braces without any spaces, e.g. {variable}
// All instances of variable placeholders will be replaced with given content
// Example:
// render('Hello, {message}!', {message: 'world'})
function render(template, vars) {
    var rendered = template;

    for (var key in vars) {
        if (vars.hasOwnProperty(key)) {
            var val = vars[key];
            var regExpString = '\\{' + key + '\\}';
            var regExp = new RegExp(regExpString, 'g');

            rendered = rendered.replace(regExp, val);
        }
    }

    return rendered;
}

function setStyle(element, style, value) {
    for (var i = 0; i < PREFIXES.length; ++i) {
        var prefix = capitalize(PREFIXES[i]);
        element.style[prefix + capitalize(style)] = value;
    }

    element.style[style] = value;
}

function capitalize(text) {
    return text.charAt(0).toUpperCase() + text.slice(1);
}

function isString(obj) {
    return typeof obj === 'string' || obj instanceof String;
}

function isFunction(obj) {
    return typeof obj === 'function';
}

function isArray(obj) {
    return Object.prototype.toString.call(obj) === '[object Array]';
}

// Returns true if `obj` is object as in {a: 1, b: 2}, not if it's function or
// array
function isObject(obj) {
    if (isArray(obj)) return false;

    var type = typeof obj;
    return type === 'object' && !!obj;
}

module.exports = {
    extend: extend,
    render: render,
    setStyle: setStyle,
    capitalize: capitalize,
    isString: isString,
    isFunction: isFunction,
    isObject: isObject
};
<|endoftext|>


<commit_before>const assign = require('object-assign');
const chalk = require('chalk');
const deasync = require('deasync');
const fs = require('fs');
const htmllint = require('htmllint');
const stripAnsi = require('strip-ansi');
const table = require('text-table');

const htmlAttributes = require('./html-attributes');
const severities = require('./html-severities');

const getRandomInt = (min, max) => {
  const minCeil = Math.ceil(min);
  const maxFloor = Math.floor(max);

  return Math.floor(Math.random() * (maxFloor - minCeil)) + minCeil;
};

const randomKey = key => `${key}-${getRandomInt(1, 10000)}`;

const matchReplace = match => {
  let output = match;

  while (output.search(/{{{.*?}}}/) >= 0) {
    output = output.replace(/{{{.*?}}}/, randomKey('handlebars'));
  }

  while (output.search(/{{.*?}}/) >= 0) {
    output = output.replace(/{{.*?}}/, randomKey('handlebars'));
  }

  while (output.search(/<%=.*?%>/) >= 0) {
    output = output.replace(/<%=.*?%>/, randomKey('ejs'));
  }

  while (output.search(/<%-.*?%>/) >= 0) {
    output = output.replace(/<%-.*?%>/, randomKey('ejs'));
  }

  while (output.search(/<%.*?%>/) >= 0) {
    output = output.replace(/<%.*?%>/, randomKey('ejs'));
  }

  while (output.search(/<\?php.*?\?>/) >= 0) {
    output = output.replace(/<\?php.*?\?>/, randomKey('php'));
  }

  while (output.search(/<\?=.*?\?>/) >= 0) {
    output = output.replace(/<\?=.*?\?>/, randomKey('php'));
  }

  while (output.search(/<\?.*?\?>/) >= 0) {
    output = output.replace(/<\?.*?\?>/, randomKey('php'));
  }

  return output;
};

const cleanAttributes = content => {
  let output = content;

  for (const key in htmlAttributes) {
    if (Object.prototype.hasOwnProperty.call(htmlAttributes, key)) {
      const attr = htmlAttributes[key];

      // if the content has the attribute within it then go ahead and clean it for handlebars and php
      if (output.indexOf(attr) > -1) {
        // regex to catch attributes with quotes
        const regex = new RegExp(`${attr}=".*?"`, 'g');

        // every valid html attr that is not quoted
        const hbsRegex = new RegExp(`${attr}={{{.*?}}}`);
        const hbsRegex2 = new RegExp(`${attr}={{.*?}}`);
        const ejsRegex = new RegExp(`${attr}=<%=.*?%>`);
        const ejsRegex2 = new RegExp(`${attr}=<%-.*?%>`);
        const ejsRegex3 = new RegExp(`${attr}=<%.*?%>`);
        const phpRegex = new RegExp(`${attr}=<\\?php.*?\\?>`);
        const phpRegex2 = new RegExp(`${attr}=<\\?=.*?\\?>`);
        const phpRegex3 = new RegExp(`${attr}=<\\?.*?\\?>`);

        output = output.replace(regex, matchReplace);
        output = output.replace(hbsRegex, `${attr}=${randomKey('handlebars')}`);
        output = output.replace(hbsRegex2, `${attr}=${randomKey('handlebars')}`);
        output = output.replace(ejsRegex, `${attr}=${randomKey('ejs')}`);
        output = output.replace(ejsRegex2, `${attr}=${randomKey('ejs')}`);
        output = output.replace(ejsRegex3, `${attr}=${randomKey('ejs')}`);
        output = output.replace(phpRegex, `${attr}=${randomKey('php')}`);
        output = output.replace(phpRegex2, `${attr}=${randomKey('php')}`);
        output = output.replace(phpRegex3, `${attr}=${randomKey('php')}`);
      }
    }
  }

  return output;
};

const cleanHandlebars = content => {
  let output = content;

  // clean multiple intances of handlebars text in 1 line
  while (output.search(/{{{.*?}}}/) >= 0) {
    output = output.replace(/{{{.*?}}}/, '');
  }

  // clean multiple intances of handlebars text in 1 line
  while (output.search(/{{.*?}}/) >= 0) {
    output = output.replace(/{{.*?}}/, '');
  }

  return output;
};

const cleanEjs = content => {
  let output = content;

  // clean multiple intances of EJS escaped text in 1 line
  while (output.search(/<%=.*?%>/) >= 0) {
    output = output.replace(/<%=.*?%>/, '');
  }

  // clean multiple intances of EJS unescaped text in 1 line
  while (output.search(/<%-.*?%>/) >= 0) {
    output = output.replace(/<%-.*?%>/, '');
  }

  // clean multiple intances of EJS all other text in 1 line
  while (output.search(/<%.*?%>/) >= 0) {
    output = output.replace(/<%.*?%>/, '');
  }

  return output;
};

const cleanPHP = content => {
  let output = content;

  // clean multiple intances of php tag text in 1 line
  while (output.search(/<\?php.*?\?>/) >= 0) {
    output = output.replace(/<\?php.*?\?>/, '');
  }

  // clean multiple intances of php short tag text in 1 line
  while (output.search(/<\?=.*?\?>/) >= 0) {
    output = output.replace(/<\?=.*?\?>/, '');
  }

  // clean multiple intances of php shortest tag text in 1 line
  while (output.search(/<\?.*?\?>/) >= 0) {
    output = output.replace(/<\?.*?\?>/, '');
  }

  return output;
};

const cleanContent = content => {
  const lines = content.split('\n');
  let disabled = false;

  /* eslint-disable no-div-regex */
  for (let [i, line] of lines.entries()) { // eslint-disable-line prefer-const
    if (line.indexOf('htmllint:disable') > -1) {
      disabled = true;
    }

    if (line.indexOf('htmllint:enable') > -1) {
      disabled = false;
    }

    if (disabled || line.indexOf('htmllint:disable-line') > -1) {
      line = '';
    } else {
      line = cleanAttributes(line);
      line = cleanHandlebars(line);
      line = cleanEjs(line);
      line = cleanPHP(line);
      line = line
        .replace(/="\s\s\s/, '="')
        .replace(/="\s\s/, '="')
        .replace(/="\s/, '="')
        .replace(/\s\s\s"/, '"')
        .replace(/\s\s"/, '"')
        .replace(/\s"/, '"')
        .replace(/<>/, '')
        .replace(/<\/>/, '')
        .replace('<?php', '')
        .replace('<?=', '')
        .replace('<?', '')
        .replace('<%', '')
        .replace('%>', '')
        .replace('?>', '');
    }
    /* eslint-enable no-div-regex */

    lines[i] = line;
  }

  return lines.join('\n');
};

const pluralize = (_word, count) => {
  let word = _word;

  if (count > 1) {
    word = `${word}s`;
  }

  return word;
};

const stylish = results => {
  let errors = 0;
  let warnings = 0;
  let output = '';
  let total = 0;
  let summaryColor = 'yellow';
  let messages = null;
  let filename = null;
  let tableOptions = null;
  let fileOutput = null;
  let styledOutput = null;
  let tableLayout = null;
  let format = null;

  results.forEach(file => {
    messages = file.messages;
    filename = chalk.underline(file.filePath);
    tableOptions = {
      align: ['', '  ', 'r', 'l'],
      stringLength: function stringLength(str) {
        return stripAnsi(str).length;
      },
    };

    fileOutput = '\n';

    if (messages.length === 0) {
      return;
    }

    total = total + messages.length;
    styledOutput = messages.map(message => {
      let messageType = 'unknown';

      if (message.severity === 'error') {
        messageType = chalk.red('error');
        errors = errors + 1;
      } else {
        messageType = chalk.yellow('warning');
        warnings = warnings + 1;
      }

      return [
        '',
        chalk.dim(`${message.line}:${message.column}`),
        messageType,
        chalk.dim(message.linter || ''),
        message.reason.replace(/\.$/, ''),
      ];
    });
    tableLayout = table(styledOutput, tableOptions);
    format = tableLayout.split('\n');
    format = `${format.join('\n')}\n\n`;
    fileOutput = `${fileOutput}${filename}\n`;
    fileOutput = fileOutput + format;
    output = output + fileOutput;
  });

  if (total > 0) {
    const bold = [
      '\u2716 ',
      total,
      pluralize(' problem', total),
      ' (',
      errors,
      pluralize(' error', errors),
      ', ',
      warnings,
      pluralize(' warning', warnings),
      ')\n',
    ];

    if (errors > 0) {
      summaryColor = 'red';
    }

    output = output + chalk[summaryColor].bold(bold.join(''));
  }

  const message = total > 0 ? new Error(output) : '';
  delete message.stack;
  return {
    message,
    warnings,
    errors,
  };
};

const lint = (source, options, webpack) => {
  const messages = [];
  let done = false;
  let content = fs.readFileSync(options.resourcePath, 'utf8'); // eslint-disable-line no-sync

  // take care of fragmented title tags
  content = content.replace(/<title>[\s\S]*?<\/title>/, match => {
    const parts = match.split('\n');
    const last = parts.length - 1;

    if (last === 0) {
      return '<title>has title</title>';
    }

    for (const [i, part] of parts.entries()) { // eslint-disable-line no-unused-vars
      if (i === 0) {
        parts[i] = '<title>';
      } else if (i === last) {
        parts[i] = '</title>';
      } else {
        parts[i] = `${i}`;
      }
    }

    return parts.join('\n');
  });

  content = cleanContent(content);

  // fragmented handlebars
  while (content.search(/{{{[\s\S]*?}}}/) >= 0) {
    content = content.replace(/{{{[\s\S]*?}}}/, match => '\n'.repeat(match.split('\n').length - 1));
  }

  // fragmented handlebars
  while (content.search(/{{[\s\S]*?}}/) >= 0) {
    content = content.replace(/{{[\s\S]*?}}/, match => '\n'.repeat(match.split('\n').length - 1));
  }

  htmllint(content, options.lintOptions).then(issues => {
    for (const issue of issues) {
      let rule = issue.rule;

      switch (issue.code) { // eslint-disable-line default-case
      case 'E018':
        rule = 'tag-self-close';
        break;
      case 'E025':
        rule = 'html-req-lang';
        break;
      }

      messages.push({
        line: issue.line,
        column: issue.column,
        length: 0,
        severity: severities[rule] || 'warning',
        reason: htmllint.messages.renderIssue(issue),
        linter: rule,
      });
    }

    done = true;
  }, err => {
    messages.push({
      line: 0,
      column: 0,
      length: 0,
      severity: 'warning',
      reason: err.message,
      linter: 'unknown',
    });

    done = true;
  });

  deasync.loopWhile(() => !done);

  if (messages.length > 0) {
    // formatted.message is returned from stylish();
    const formatted = stylish([{
      filePath: options.resourcePath,
      messages,
    }]);

    if (formatted.errors > 0) {
      if (options.failOnError) {
        webpack.emitError(formatted.message);

        if (process.env.NODE_ENV === 'production') {
          throw new Error(`Module failed because of a htmllint errors.\n ${formatted.message}`);
        }
      } else {
        webpack.emitWarning(formatted.message);
      }
    } else if (formatted.warnings > 0) {
      if (options.failOnWarning) {
        webpack.emitError(formatted.message);

        if (process.env.NODE_ENV === 'production') {
          throw new Error(`Module failed because of a htmllint warnings.\n ${formatted.message}`);
        }
      } else {
        webpack.emitWarning(formatted.message);
      }
    }
  }
};

module.exports = function htmlLint(source) {
  const cwd = process.cwd();
  const options = assign(
    {
      config: 'node_modules/htmllint-loader/.htmllintrc',
      failOnError: true,
      failWarning: false,
    },
    this.query
  );

  options.lintOptions = JSON.parse(fs.readFileSync(options.config)); // eslint-disable-line no-sync

  if (this.resourcePath.indexOf(cwd) === 0) {
    options.resourcePath = this.resourcePath.substr(cwd.length + 1);
  }

  lint(source, options, this);

  return source;
};

<commit_msg>Fixed disable comment bug with 'disable-line'<commit_after>const assign = require('object-assign');
const chalk = require('chalk');
const deasync = require('deasync');
const fs = require('fs');
const htmllint = require('htmllint');
const stripAnsi = require('strip-ansi');
const table = require('text-table');

const htmlAttributes = require('./html-attributes');
const severities = require('./html-severities');

const getRandomInt = (min, max) => {
  const minCeil = Math.ceil(min);
  const maxFloor = Math.floor(max);

  return Math.floor(Math.random() * (maxFloor - minCeil)) + minCeil;
};

const randomKey = key => `${key}-${getRandomInt(1, 10000)}`;

const matchReplace = match => {
  let output = match;

  while (output.search(/{{{.*?}}}/) >= 0) {
    output = output.replace(/{{{.*?}}}/, randomKey('handlebars'));
  }

  while (output.search(/{{.*?}}/) >= 0) {
    output = output.replace(/{{.*?}}/, randomKey('handlebars'));
  }

  while (output.search(/<%=.*?%>/) >= 0) {
    output = output.replace(/<%=.*?%>/, randomKey('ejs'));
  }

  while (output.search(/<%-.*?%>/) >= 0) {
    output = output.replace(/<%-.*?%>/, randomKey('ejs'));
  }

  while (output.search(/<%.*?%>/) >= 0) {
    output = output.replace(/<%.*?%>/, randomKey('ejs'));
  }

  while (output.search(/<\?php.*?\?>/) >= 0) {
    output = output.replace(/<\?php.*?\?>/, randomKey('php'));
  }

  while (output.search(/<\?=.*?\?>/) >= 0) {
    output = output.replace(/<\?=.*?\?>/, randomKey('php'));
  }

  while (output.search(/<\?.*?\?>/) >= 0) {
    output = output.replace(/<\?.*?\?>/, randomKey('php'));
  }

  return output;
};

const cleanAttributes = content => {
  let output = content;

  for (const key in htmlAttributes) {
    if (Object.prototype.hasOwnProperty.call(htmlAttributes, key)) {
      const attr = htmlAttributes[key];

      // if the content has the attribute within it then go ahead and clean it for handlebars and php
      if (output.indexOf(attr) > -1) {
        // regex to catch attributes with quotes
        const regex = new RegExp(`${attr}=".*?"`, 'g');

        // every valid html attr that is not quoted
        const hbsRegex = new RegExp(`${attr}={{{.*?}}}`);
        const hbsRegex2 = new RegExp(`${attr}={{.*?}}`);
        const ejsRegex = new RegExp(`${attr}=<%=.*?%>`);
        const ejsRegex2 = new RegExp(`${attr}=<%-.*?%>`);
        const ejsRegex3 = new RegExp(`${attr}=<%.*?%>`);
        const phpRegex = new RegExp(`${attr}=<\\?php.*?\\?>`);
        const phpRegex2 = new RegExp(`${attr}=<\\?=.*?\\?>`);
        const phpRegex3 = new RegExp(`${attr}=<\\?.*?\\?>`);

        output = output.replace(regex, matchReplace);
        output = output.replace(hbsRegex, `${attr}=${randomKey('handlebars')}`);
        output = output.replace(hbsRegex2, `${attr}=${randomKey('handlebars')}`);
        output = output.replace(ejsRegex, `${attr}=${randomKey('ejs')}`);
        output = output.replace(ejsRegex2, `${attr}=${randomKey('ejs')}`);
        output = output.replace(ejsRegex3, `${attr}=${randomKey('ejs')}`);
        output = output.replace(phpRegex, `${attr}=${randomKey('php')}`);
        output = output.replace(phpRegex2, `${attr}=${randomKey('php')}`);
        output = output.replace(phpRegex3, `${attr}=${randomKey('php')}`);
      }
    }
  }

  return output;
};

const cleanHandlebars = content => {
  let output = content;

  // clean multiple intances of handlebars text in 1 line
  while (output.search(/{{{.*?}}}/) >= 0) {
    output = output.replace(/{{{.*?}}}/, '');
  }

  // clean multiple intances of handlebars text in 1 line
  while (output.search(/{{.*?}}/) >= 0) {
    output = output.replace(/{{.*?}}/, '');
  }

  return output;
};

const cleanEjs = content => {
  let output = content;

  // clean multiple intances of EJS escaped text in 1 line
  while (output.search(/<%=.*?%>/) >= 0) {
    output = output.replace(/<%=.*?%>/, '');
  }

  // clean multiple intances of EJS unescaped text in 1 line
  while (output.search(/<%-.*?%>/) >= 0) {
    output = output.replace(/<%-.*?%>/, '');
  }

  // clean multiple intances of EJS all other text in 1 line
  while (output.search(/<%.*?%>/) >= 0) {
    output = output.replace(/<%.*?%>/, '');
  }

  return output;
};

const cleanPHP = content => {
  let output = content;

  // clean multiple intances of php tag text in 1 line
  while (output.search(/<\?php.*?\?>/) >= 0) {
    output = output.replace(/<\?php.*?\?>/, '');
  }

  // clean multiple intances of php short tag text in 1 line
  while (output.search(/<\?=.*?\?>/) >= 0) {
    output = output.replace(/<\?=.*?\?>/, '');
  }

  // clean multiple intances of php shortest tag text in 1 line
  while (output.search(/<\?.*?\?>/) >= 0) {
    output = output.replace(/<\?.*?\?>/, '');
  }

  return output;
};

const cleanContent = content => {
  const lines = content.split('\n');
  let disabled = false;

  /* eslint-disable no-div-regex */
  for (let [i, line] of lines.entries()) { // eslint-disable-line prefer-const
    if (line.indexOf('htmllint:disable-line') > -1) {
      line = '';
      continue; // eslint-disable-line no-continue
    }

    if (line.indexOf('htmllint:disable') > -1) {
      disabled = true;
    }

    if (line.indexOf('htmllint:enable') > -1) {
      disabled = false;
    }

    if (disabled) {
      line = '';
    } else {
      line = cleanAttributes(line);
      line = cleanHandlebars(line);
      line = cleanEjs(line);
      line = cleanPHP(line);
      line = line
        .replace(/="\s\s\s/, '="')
        .replace(/="\s\s/, '="')
        .replace(/="\s/, '="')
        .replace(/\s\s\s"/, '"')
        .replace(/\s\s"/, '"')
        .replace(/\s"/, '"')
        .replace(/<>/, '')
        .replace(/<\/>/, '')
        .replace('<?php', '')
        .replace('<?=', '')
        .replace('<?', '')
        .replace('<%', '')
        .replace('%>', '')
        .replace('?>', '');
    }
    /* eslint-enable no-div-regex */

    lines[i] = line;
  }

  return lines.join('\n');
};

const pluralize = (_word, count) => {
  let word = _word;

  if (count > 1) {
    word = `${word}s`;
  }

  return word;
};

const stylish = results => {
  let errors = 0;
  let warnings = 0;
  let output = '';
  let total = 0;
  let summaryColor = 'yellow';
  let messages = null;
  let filename = null;
  let tableOptions = null;
  let fileOutput = null;
  let styledOutput = null;
  let tableLayout = null;
  let format = null;

  results.forEach(file => {
    messages = file.messages;
    filename = chalk.underline(file.filePath);
    tableOptions = {
      align: ['', '  ', 'r', 'l'],
      stringLength: function stringLength(str) {
        return stripAnsi(str).length;
      },
    };

    fileOutput = '\n';

    if (messages.length === 0) {
      return;
    }

    total = total + messages.length;
    styledOutput = messages.map(message => {
      let messageType = 'unknown';

      if (message.severity === 'error') {
        messageType = chalk.red('error');
        errors = errors + 1;
      } else {
        messageType = chalk.yellow('warning');
        warnings = warnings + 1;
      }

      return [
        '',
        chalk.dim(`${message.line}:${message.column}`),
        messageType,
        chalk.dim(message.linter || ''),
        message.reason.replace(/\.$/, ''),
      ];
    });
    tableLayout = table(styledOutput, tableOptions);
    format = tableLayout.split('\n');
    format = `${format.join('\n')}\n\n`;
    fileOutput = `${fileOutput}${filename}\n`;
    fileOutput = fileOutput + format;
    output = output + fileOutput;
  });

  if (total > 0) {
    const bold = [
      '\u2716 ',
      total,
      pluralize(' problem', total),
      ' (',
      errors,
      pluralize(' error', errors),
      ', ',
      warnings,
      pluralize(' warning', warnings),
      ')\n',
    ];

    if (errors > 0) {
      summaryColor = 'red';
    }

    output = output + chalk[summaryColor].bold(bold.join(''));
  }

  const message = total > 0 ? new Error(output) : '';
  delete message.stack;
  return {
    message,
    warnings,
    errors,
  };
};

const lint = (source, options, webpack) => {
  const messages = [];
  let done = false;
  let content = fs.readFileSync(options.resourcePath, 'utf8'); // eslint-disable-line no-sync

  // take care of fragmented title tags
  content = content.replace(/<title>[\s\S]*?<\/title>/, match => {
    const parts = match.split('\n');
    const last = parts.length - 1;

    if (last === 0) {
      return '<title>has title</title>';
    }

    for (const [i, part] of parts.entries()) { // eslint-disable-line no-unused-vars
      if (i === 0) {
        parts[i] = '<title>';
      } else if (i === last) {
        parts[i] = '</title>';
      } else {
        parts[i] = `${i}`;
      }
    }

    return parts.join('\n');
  });

  content = cleanContent(content);

  // fragmented handlebars
  while (content.search(/{{{[\s\S]*?}}}/) >= 0) {
    content = content.replace(/{{{[\s\S]*?}}}/, match => '\n'.repeat(match.split('\n').length - 1));
  }

  // fragmented handlebars
  while (content.search(/{{[\s\S]*?}}/) >= 0) {
    content = content.replace(/{{[\s\S]*?}}/, match => '\n'.repeat(match.split('\n').length - 1));
  }

  htmllint(content, options.lintOptions).then(issues => {
    for (const issue of issues) {
      let rule = issue.rule;

      switch (issue.code) { // eslint-disable-line default-case
      case 'E018':
        rule = 'tag-self-close';
        break;
      case 'E025':
        rule = 'html-req-lang';
        break;
      }

      messages.push({
        line: issue.line,
        column: issue.column,
        length: 0,
        severity: severities[rule] || 'warning',
        reason: htmllint.messages.renderIssue(issue),
        linter: rule,
      });
    }

    done = true;
  }, err => {
    messages.push({
      line: 0,
      column: 0,
      length: 0,
      severity: 'warning',
      reason: err.message,
      linter: 'unknown',
    });

    done = true;
  });

  deasync.loopWhile(() => !done);

  if (messages.length > 0) {
    // formatted.message is returned from stylish();
    const formatted = stylish([{
      filePath: options.resourcePath,
      messages,
    }]);

    if (formatted.errors > 0) {
      if (options.failOnError) {
        webpack.emitError(formatted.message);

        if (process.env.NODE_ENV === 'production') {
          throw new Error(`Module failed because of a htmllint errors.\n ${formatted.message}`);
        }
      } else {
        webpack.emitWarning(formatted.message);
      }
    } else if (formatted.warnings > 0) {
      if (options.failOnWarning) {
        webpack.emitError(formatted.message);

        if (process.env.NODE_ENV === 'production') {
          throw new Error(`Module failed because of a htmllint warnings.\n ${formatted.message}`);
        }
      } else {
        webpack.emitWarning(formatted.message);
      }
    }
  }
};

module.exports = function htmlLint(source) {
  const cwd = process.cwd();
  const options = assign(
    {
      config: 'node_modules/htmllint-loader/.htmllintrc',
      failOnError: true,
      failWarning: false,
    },
    this.query
  );

  options.lintOptions = JSON.parse(fs.readFileSync(options.config)); // eslint-disable-line no-sync

  if (this.resourcePath.indexOf(cwd) === 0) {
    options.resourcePath = this.resourcePath.substr(cwd.length + 1);
  }

  lint(source, options, this);

  return source;
};

<|endoftext|>