Задать вопрос
@mediees

Как корректно обрабатывать объединённые ячейки?

Решил сделать бота в тг для более удобного пользования расписанием университета. (Забираю xlsx файл из сайта университета, распаршиваю его в bd.json)

Возникла проблема, которую не могу решить: есть три массива all, sub1, sub2 в расписании присутсвует две подгруппы с разными парами, и общие пары для этих подгрупп, которые находятся в объединении ячеек). И сейчас общие пары кладутся в массив sub1 вместе с парами для первой подгруппы, в массив sub2 кладутся только пары для второй подгруппы.

Как бы хотелось:
  • В all попадут общие пары
    В sub1 попадут только пары для первой подгруппы
    sub2 работает без перебоев
  • all исчезнет
    sub1 не изменится
    В sub2 попадут еще и общие пары

Код
const axios   = require('axios');
const cheerio = require('cheerio');
const XLSX    = require('xlsx');

const PAGE_URL     = 'https://schedule-cloud.cfuv.ru/index.php/s/DKNodfRxgf9c5Xc';
const DAYS         = ['понедельник','вторник','среда','четверг','пятница', 'суббота'];
const PARITY_ORDER = ['odd','even'];

const ROOM_RX    = /(?:аудитория|ауд)\.?\s*([0-9A-Za-zА-Яа-я]+)/i;
const TEACHER_RX = /^(ст\.|доц\.|проф\.|ас\.|вак\.)/i;

async function fetchSchedule() {
  //  скачать XLSX
  const { data: html } = await axios.get(PAGE_URL);
  const $ = cheerio.load(html);
  const rel = $('a[href$=".xlsx"]').first().attr('href');
  if (!rel) throw new Error('xlsx не найден');
  const xlsxUrl = new URL(rel, PAGE_URL).href;
  const { data: buf } = await axios.get(xlsxUrl, { responseType: 'arraybuffer' });

  //  прочитать первый лист(т.к листы разделены по курсам)
  const wb   = XLSX.read(buf, { type: 'buffer' });
  const ws   = wb.Sheets[wb.SheetNames[0]];
  const rows = XLSX.utils.sheet_to_json(ws, { header: 1, defval: '' });

  //  найти блоки odd/even
  let hdr = -1, blocks = [];
  for (let i = 0; i < rows.length; i++) {
    const lc = rows[i].map(c => String(c).toLowerCase().trim());
    const idxsDay = lc.reduce((a,v,i) => v === 'дни недели' ? a.concat(i) : a, []);
    if (idxsDay.length >= 2 && lc.includes('пара') && lc.includes('вид занятий')) {
      const found = [];
      for (let col of idxsDay) {
        const ci = lc.indexOf('пара', col+1);
        const ti = lc.indexOf('вид занятий', col+1);
        if (ci > col && ti > ci) found.push({ colDay: col, colPair: ci, colType: ti });
      }
      if (found.length >= 2) {
        hdr = i;
        found.forEach((f, b) => {
          const start = f.colDay;
          const end   = found[b+1]?.colDay ?? lc.length;
          blocks.push({ ...f, startCol:start, endColExcl:end, parity: b===0?'odd':'even' });
        });
        break;
      }
    }
  }
  if (hdr < 0) throw new Error('Не найдены блоки недель');

  // строка с подгруппами
  let subg = hdr + 1;
  while (subg < rows.length && !rows[subg].some(c=>/\([12]\)$/.test(String(c).trim()))) subg++;
  if (subg >= rows.length) throw new Error('Не найдена строка подгрупп');

  blocks.forEach(b => {
    b.subjCols = [];
    for (let i = b.startCol; i < b.endColExcl; i++) {
      const m = String(rows[subg][i]).trim().match(/\((\d)\)$/);
      if (m) b.subjCols.push({ idx:i, subgroup:m[1] });
    }
    b.col1 = b.subjCols.find(x=>x.subgroup==='1').idx;
    b.col2 = b.subjCols.find(x=>x.subgroup==='2').idx;
  });

  const slots = {};
  const state = blocks.map(() => ({ day:null, pair:null }));

  for (let r = hdr+1; r < rows.length; r++) {
    const row = rows[r];
    blocks.forEach((b,i) => {
      const d = String(row[b.colDay]||'').trim().toLowerCase();
      const p = String(row[b.colPair]||'').trim();
      if (DAYS.includes(d)) { state[i].day = d; state[i].pair = null; }
      if (/^[1-7]$/.test(p)) state[i].pair = +p;
    });
    blocks.forEach((b,i) => {
      const { day, pair } = state[i];
      if (!day || !pair) return;
      const rawType = String(row[b.colType]||'').trim().toUpperCase();
      const type    = ['ЛК','ПЗ'].includes(rawType) ? rawType : '';
      [ {idx:b.col1, sub:'1'}, {idx:b.col2, sub:'2'} ].forEach(({idx,sub}) => {
        const raw = String(row[idx]||'').trim();
        if (!raw || /^\d+$/.test(raw)) return;
        const key = [b.parity, day, pair, sub].join('|');
        if (!slots[key]) slots[key] = { parity:b.parity, day, pair, subgroup:sub, type, subject:'', teacher:'', room:'' };
        const parts = raw.split(/\r?\n| {2,}/).map(s=>s.trim()).filter(Boolean);
        parts.forEach(p => {
          const slot = slots[key];
          if (!slot.room && ROOM_RX.test(p)) slot.room = ROOM_RX.exec(p)[1];
          else if (!slot.teacher && TEACHER_RX.test(p)) slot.teacher = p;
          else if (!slot.subject) slot.subject = p;
        });
      });
    });
  }

  // сортировка
  const raw = Object.values(slots).sort((a,b) => {
    const pa = PARITY_ORDER.indexOf(a.parity), pb = PARITY_ORDER.indexOf(b.parity);
    if (pa !== pb) return pa - pb;
    const dayOrder = { понедельник:1, вторник:2, среда:3, четверг:4, пятница:5 };
    if (dayOrder[a.day] !== dayOrder[b.day]) return dayOrder[a.day] - dayOrder[b.day];
    return a.pair - b.pair;
  });

  // группировка по времени
  const byTime = {};
  raw.forEach(slot => {
    const key = [slot.parity, slot.day, slot.pair].join('|');
    if (!byTime[key]) byTime[key] = {};
    byTime[key][slot.subgroup] = slot;
  });

  // распределение по массивам
  const all  = [];
  const sub1 = [];
  const sub2 = [];

  Object.values(byTime).forEach(group => {
    const s1 = group['1'];
    const s2 = group['2'];

    if (s1 && s2 && s1.subject === s2.subject) {
      // общая пара
      all.push({ ...s1, subgroup: 'all' });
      sub1.push({ ...s1 });
      sub2.push({ ...s2 });
    } else {
      if (s1) sub1.push(s1);
      if (s2) sub2.push(s2);
    }
  });

  // фильтр
  function finalize(arr) {
    const dayOrder = { понедельник:1, вторник:2, среда:3, четверг:4, пятница:5 };
    return arr
      .filter(s => s.subject)
      .sort((a,b) => {
        const pa = PARITY_ORDER.indexOf(a.parity),
              pb = PARITY_ORDER.indexOf(b.parity);
        if (pa !== pb) return pa - pb;
        if (dayOrder[a.day] !== dayOrder[b.day]) {
          return dayOrder[a.day] - dayOrder[b.day];
        }
        return a.pair - b.pair;
      })
      .map((s,i) => ({ id: i + 1, ...s }));
  }

  return {
    all:  finalize(all),
    sub1: finalize(sub1),
    sub2: finalize(sub2),
  };
}

const fs = require('fs');

if (require.main === module) {
  fetchSchedule()
    .then(data => {
      fs.writeFileSync('bd.json', JSON.stringify(data, null, 2), 'utf-8');
      console.log('Расписание сохранено');
    })
    .catch(err => {
      console.error('Ошибка', err.message);
    });
}

module.exports = fetchSchedule;
  • Вопрос задан
  • 593 просмотра
Подписаться 2 Простой 3 комментария
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы